Release notes & version history for Freelance Studio
Version 9.15.4
17 changes
▼
- Fixed: Google Calendar events never blocked booking times. The booking page built each busy time as "2026-10-20 2:00 PM:00", which PHP can't read, so every Google event was skipped and clients could book on top of them. Busy times now come from each event's exact start and end, converted to your booking time zone.
- Fixed: Google event times showed in UTC. Times on the Freelance Studio calendar and dashboard were converted with
gmdate(), so a 10am Eastern event showed as 2:00 PM. They now show in your site's time zone. - Fixed: bookings drifted when synced back from Google. The inbound sync converted Google's time to UTC and compared it with the booking's local time, so every sync looked like a change and moved the booking by your UTC offset. It now converts to the booking's own time zone and only changes the booking when the time really changed.
- Fixed: calendar invites in booking emails were at the wrong time. The .ics file stamped the local meeting time as UTC, so a 2pm Eastern meeting showed at 10am in the client's calendar.
- Fixed: booking times now use the booking time zone (Settings → Booking) throughout. Bookings are stored as local time in that zone, but "now" came from three different clocks:
- Fixed: Google pushes with a manual UTC offset. Sites set to a manual offset like UTC-5 sent Google a time zone it doesn't accept. Bookings, events you add on the Freelance Studio calendar, and the Freelance Studio Google calendar itself now go to Google with an explicit offset, plus the zone name only when it's a real one like America/New_York. The booking page gets a name browsers accept (UTC-5 becomes
Etc/GMT+5), so the visitor time conversion no longer fails. - Fixed: "Free" Google events, declined invitations and working location entries. Now that Google events block booking times, these are skipped, since they don't make you busy.
- Fixed: editing a timed Google event on the Freelance Studio calendar reset it to 9:00 to 10:00. The edit form got "2:00 PM", which a time field can't show, so saving fell back to the default. It now gets "14:00".
- Fixed: external calendar times with their own zone. Events like
DTSTART;TZID=America/Chicagowere read as if they were in your time zone. They're now converted from their own zone. Calendars pick this up on their next sync. - Fixed: the booking time zone setting showed the wrong zone. The plugin saves your site's time zone as the booking zone on first activation, in a form like "+00:00" or "-05:00". WordPress's time zone dropdown can't match that, so Settings → Booking showed the first zone in the list (Africa/Abidjan) instead, and saving the page for any other reason quietly switched bookings to it. The dropdown now shows the zone that's really saved.
- Fixed: changing the booking time zone moved upcoming bookings. Bookings are stored as local time, so switching zones made every upcoming booking read at the wrong hour for conflict checks, reminders and the upcoming list. Saving a new zone now rewrites upcoming bookings in that zone so each one keeps its real time (14:00 at UTC+0 becomes 10:00 in New York). Google Calendar doesn't change, since the moment is the same.
- Improved: the Availability tab warns when the booking time zone differs from your site's. A site that changed time zones after activating the plugin kept booking in the old one with no sign of it. The notice now shows both zones when they don't match. Its styles moved from inline to the main stylesheet, with high contrast support.
- Fixed: the Availability tab could crash with a manual offset. Choosing a manual offset like UTC-5 as the booking zone threw a fatal error on that tab.
- Security: bookings are checked before saving. Booking creation now re-checks that the time is still free (existing bookings, Google and external calendars, minimum notice, booking window). A stale page or a hand-crafted request could book any time before. Two people sending the same time at once now wait their turn, so only one gets it. The public slots endpoint also stopped sending calendar troubleshooting details (event titles and times) to visitors; admins still get them.
- Improved: booking times say which zone they're in. Confirmation, notification, reminder and cancellation emails, the confirmation and cancel pages, and the booking detail screen now show a label like "2:00 PM EDT". Zones with no short name show their offset, like "UTC-3".
- How it works:
FS_Dategainedzone()(reads IANA names, "-05:00" and WordPress's "UTC-5"/"UTC+5.5"),convert(),named_zone(),zone_label(),zone_display(),choice_value(),google_time()andgoogle_zone_name().FS_Booking::move_to_timezone()handles the zone change. Google events carrystart_24,end_24andblocks_time.FS_Booking::now()returns the current time in the booking zone. Slot logic lives in one place (FS_Frontend_Router::get_bookable_slots()), used by both the slot list and booking creation. - Fixed: "1 days overdue" on the dashboard now reads "1 day overdue".
Version 9.15.3
3 changes
▼
- Fixed: "today" was UTC almost everywhere. WordPress sets PHP's clock to UTC whatever time zone you pick under Settings → General, and the plugin asked PHP for the date (
gmdate(),time(),strtotime('today')) instead of asking WordPress. From 7pm to midnight on the US East Coast that's already tomorrow. 9.15.2 fixed invoice dates; this release fixes the rest: - Fixed: things showed as overdue on their due date. The checks compared the due date to the current time, so anything due today was overdue from the moment the day started in UTC (7pm the night before, Eastern). An invoice due today now shows as due, and turns overdue the next day. That matches how the invoice status itself already worked.
- How it works: new
FS_Datehelper (includes/helpers/class-fs-date.php) withtoday(),relative(),timestamp(),is_past(),is_today_or_past(),days_since()andbooking_timezone(), all in the site's time zone. Its header explains whengmdate()is still right: formatting a date that's already stored, and timestamps kept in UTC on purpose (time entries, Google Calendar sync times, file link expiry). Those were left alone. Browser code getswindow.fsLocalDate().
Version 9.15.2
4 changes
▼
- Fixed: invoice dates used UTC instead of your site's time zone. The Invoice Deposit button, the invoice wizard, invoices from time entries, recurring and milestone invoices, invoicing a whole project, Duplicate, bulk Mark as Paid, and the default due and valid-until dates on the invoice form all took "today" from UTC. In the evening in the US that's already tomorrow, so a deposit invoice could be dated after the payment recorded against it. They now use your WordPress time zone (
current_time()andwp_date()). - Improved: the Deposit card says when a deposit was marked as already credited. After Already credited, the card used to show "Credit available $0.00" with no reason. It now adds a line like "$2,100.00 marked as already credited on an earlier invoice."
FS_Invoice::get_deposit_credit()returns the amount assettled. - Improved: you're told when a deposit credit is set back. Saving an invoice with a deposit credit bigger than what's left on the deposit used to quietly reset it. The invoice page now says so and points to the Discount field for anything extra.
- Changed: no "1 × -$1,600.00" on the client's invoice. Deposit credit lines on the client-facing invoice show just the description and amount.
Version 9.15.1
3 changes
▼
- Fixed: deposits paid before 9.15.0 showed as unused credit. Before 9.15.0 a deposit was usually accounted for by billing only what was left, or with a note on the final invoice, so there was nothing on that invoice for 9.15.0 to find. Finished projects showed the whole deposit as "Credit available" with an Invoice Balance button, and invoicing more work would have offered to take the deposit off again.
- What happens now: the upgrade looks for deposit invoices that already have a later invoice on the same project or contract and puts them up for review. The project's Deposit card (and the contract's Summary card) names the later invoice and asks whether the deposit was already taken off, with Already credited and Keep the credit buttons. Until you answer, Invoice Balance is hidden and the invoice wizard shows the deposit unticked with the same warning. Deposits with no later invoice, and anything created on 9.15.0 or later, aren't affected.
- How it works:
FS_Invoice::flag_legacy_deposits_for_review()runs once from the upgrade and storesfs_deposit_credit_review(deposit ID to later invoice IDs). Already credited records the remaining credit infs_deposit_credit_settled, which counts as used. Keep the credit adds the deposit tofs_deposit_credit_kept, which also clears the warning for hand-typed minus lines. Both go through the newfs_resolve_deposit_reviewAJAX action. The notice is one partial,includes/views/partials/deposit-review-notice.php, used by both pages.
Version 9.15.0
9 changes
▼
- Added: deduct a paid deposit from the next invoice. When a project has a paid deposit invoice, the invoice wizard's Select Items step now shows a Deposit Credit section with Deduct paid deposit ticked. It adds a line like "Less: deposit paid (INV-0042)" to the invoice, so the client can see which payment it's for. Untick it to leave the deposit off. The live preview and Review Invoice show the credit line before you create anything.
- How the credit is worked out: it's based on what the client actually paid on the deposit invoice, not on the deposit terms. An unpaid deposit gives no credit (the wizard says so and names the invoice), and a partly paid one only credits what came in. If the deposit invoice was taxed, only the pre-tax part is credited, so the tax on the new invoice comes out right. The credit never takes an invoice below zero. Whatever's left over stays available for the next invoice.
- Can't be used twice: each credit line records which deposit invoice(s) it drew from (
deposit_allocationson the line item).FS_Invoice::get_deposit_credit()subtracts every credit already applied on a non-cancelled invoice, so the same deposit can't be deducted again. Delete or cancel the invoice that used a credit and it becomes available again. Editing an invoice keeps the credit line linked to its deposit (it's marked Deposit credit on the edit form), and changing its amount by hand updates the record to match. Duplicating an invoice leaves the credit line off the copy. When two projects made from the same contract are on one invoice, they share its deposit, so it's only taken off once. - Older deposit lines are recognized: balance invoices made before this version had an untracked "Less: Deposit already paid" line. The upgrade links those lines to their deposits (against what each deposit was billed, since the old line took it off whether or not it had been paid) so the same deposit isn't offered again. If an earlier invoice has some other minus line (one you typed in yourself, say), the wizard leaves Deduct paid deposit unticked and names that invoice, the project and contract pages show a warning, and Create Balance Invoice on a contract stops and asks you to check it first. A credit line edited by hand can't take off more than the deposit it's tied to.
- Added: Invoice Balance button on projects. Once a project's deposit has been paid and there's credit left, Invoice Balance replaces Invoice Deposit at the top of the project. It opens the invoice wizard with the deposit credit ticked. The project's Deposit card also shows the credit still available and links to the invoice(s) it was credited on.
- Added: Create Balance Invoice and Invoice Deposit on contracts. Signed and completed contracts get Create Balance Invoice once their deposit has been paid. If the contract has a project, it opens the invoice wizard for that project so services aren't billed twice. If not, it creates a draft with the contract's services and the deposit credit. Contracts that require a deposit and don't have a project or a deposit invoice yet get an Invoice Deposit button. Both used to exist in code with nothing linking to them. The balance invoice used to subtract the deposit from the contract terms whether or not it had been paid, and the contract invoice action now checks a nonce.
- Fixed: deposits paid with Mark Paid or Record Payment still showed as unpaid. Only changing the status to Paid set the project's deposit flag. Mark Paid and Record Payment now set it too, and an upgrade routine (
FS_Activator::upgrade_to_9_15_0()) flags existing projects whose deposit invoice is already paid. - Fixed: marking invoices paid from the invoice list wiped their line items. Bulk Mark as Paid, matching a bank transaction to an invoice with mark-as-paid, and due dates synced back from Google Calendar all sent
FS_Invoice::update()just a few fields, and it rebuilt the whole invoice from them. That cleared the line items and set the subtotal and total to 0. They now use the newFS_Invoice::mark_paid()andFS_Invoice::set_due_date(), which only change those fields. Invoices that were already emptied this way can't be restored automatically. - Fixed: adding a line item after removing one could overwrite another line. The new row's index was the row count, so after a removal it could match an existing row and replace it when saved. New rows now take the next unused index.
Version 9.14.3
1 change
▼
- Updated: Freemius SDK 2.13.1 to 2.13.4. Picks up Freemius's fixes for thousands separators and default currency on the pricing page (Pricing App 1.4.1), adds ILS, CAD, AUD and PLN to the payment history on the Account page, fixes a PHP warning under WP-CLI and a deprecation notice from the contact form, and tightens how the SDK handles incoming query parameters. Freemius API calls now time out after 30 seconds instead of a hardcoded 60. Define
WP_FS__API_TIMEOUTin wp-config.php to change it. Freemius pulled 2.13.3 because it broke add-on checkout, so we skipped it; 2.13.4 includes the fix. Only the 8 SDK files Freemius changed were replaced, and no Freelance Studio code changed.
Version 9.14.2
1 change
▼
- Fixed: dashboard tasks without a project showed no timer and no context. The dashboard Tasks widget only drew the timer button and the project name when a task belonged to a project, so client-level tasks and tasks added without picking a project showed just a title and a status. Kanban and the Tasks list already offered the timer for any open task (the popup timer works without a project), so the dashboard now does too, and it shows the client's name when there's no project. The mobile Tasks list had the same project-only check on its timer and now matches.
Version 9.14.1
4 changes
▼
- Fixed: Update Project did nothing on projects with a tracked-time hourly service. Since 9.10.1 the form save stores the hour-cap early warning as
0when it's off. The edit form printed that0into the hidden warning field, which only accepts 1 to 99, so the browser refused to submit and couldn't show why because the field was hidden. Off now renders as blank on the project form and the project page's services editor. As a safety net, any Freelance Studio form that's blocked by a hidden field now shows an error notice naming the field instead of failing silently (FS_Admin.initHiddenFieldValidation()). - Fixed: Bill Percentage and Custom Amount were ignored when creating an invoice. The wizard stores billing options per service, but the preview and create requests read them as if they were per project, so the server never received them and every service billed in full. The live preview was right; Review Invoice and the saved invoice weren't. Options are now sent per service (
service_options[index]) andFS_Invoice::build_service_items()applies them per service. Both requests share one payload builder (buildWizardProjectsPayload()), and the live preview usesapplyServiceBilling(), which mirrors the PHP, so preview and invoice match line for line. - Fixed: wizard billing fields only registered on blur. Percentage, amount and label are now read as you type, choosing Bill Percentage uses the 50% already in the box, and going Back from Review keeps your choices instead of resetting the dropdowns to Full.
- Changed: partial-billing line descriptions. A label now reads "Label - Service name" instead of replacing the service name, so the client can see which service it's for. Percentages keep decimals (33.5% instead of 33%).
Version 9.14.0
2 changes
▼
- Added: Description suggestions on the expense form. Click into Description on Add or Edit Expense and your most-used past descriptions drop down. Start typing and the list narrows, with names that start with what you typed listed first and the matching text in bold. Each suggestion shows the category and amount it had last time. Picking one (click, or arrow keys and Enter) fills in the description, sets the category, and fills in the amount only if the Amount field is still empty, so an amount you've already typed is never overwritten. Escape closes the list.
- How it works: new
FS_Expense::get_name_suggestions()returns one row per distinct description, most-used first (up to 300), with the category and amount from the most recent expense using it. The controller passes them to the form as JSON on the field, so the list opens instantly with no AJAX round trip. The dropdown is a genericdata-fs-suggestionshandler infreelance-studio-admin.js(combobox and listbox roles,aria-activedescendant, keyboard support) and is styled in the main stylesheet under.fs-suggest, with high-contrast overrides. If there are no past expenses yet, the field renders as a plain input.
Version 9.13.0
8 changes
▼
- Added: clients can view and edit tasks in the portal. Clicking (or tabbing to and pressing Enter on) any task in the client portal opens a Task Details window with the title, project, status, priority, due date, who it's assigned to and the full details. An Edit button switches it to a form where the client can change the title, details, status, priority and due date of any task visible to them. This works on the portal dashboard and on every task list in the project portal. Saving posts to the new public
fs_client_update_taskaction, which only writes the fields that changed. - Added: clients can approve tasks that are With Client. Tasks in With Client show an Approve button on the portal dashboard, in the project's task list, in a new Ready for Your Review card on the project overview, and in a banner at the top of the Task Details window. Approving moves the task to Done and stamps the new
client_approved_atcolumn onfs_tasks. Approved tasks show an Approved badge in the portal, on the Kanban card and in the task list, with the date on hover. Moving a task out of Done clears the stamp, so a reopened task starts a fresh review. Approving a recurring task creates its next occurrence, the same as completing it from the admin. - Added: you're emailed about every change a client makes. Edits, approvals and the existing "mark my task done" checkbox all send you an email listing each field that changed with its old and new value (
FS_Email_Service::send_task_client_change_notification()). Approvals get their own subject line so they stand out. - Added: optional "ready for your review" email when a task moves to With Client. Whenever you move a task into With Client (Kanban drag, the status dropdown in the task list, the task window, a project or client page, or the dashboard), the Notify client? window opens so you can pick contacts and send it, or click Skip. Most status changes reload the page, so the prompt is queued per user and opens on the next page load if it wasn't already handled. With Client tasks also get the paper-airplane send button on Kanban cards and list rows, like Done tasks. The email asks the client to review the task and links to their portal. The notify window, its JavaScript and
fs_notify_client_task_completenow take anotify_typeofcompletedorwith_client, and the window is rendered once from the admin footer on every Freelance Studio page instead of being included separately by the Kanban and list views. - Changed: one task request form for the whole portal. The portal dashboard and the project portal each had their own copy of the Request a Task form and its script. Both now include
includes/views/public/portal/task-modals.phpand loadadmin/js/fs-portal-tasks.js, which also runs the new details, edit and approve features. - Fixed: task requests made without a project disappeared from the portal. The portal dashboard only listed tasks that belong to a project, so a request filed without picking one never showed up for the client again. Client-level tasks are now included.
- Fixed: the client portal was wider than the screen on phones. The single-column layout below 900px could grow to fit its widest unbreakable content, which pushed the page past the edge on a phone and made it scroll sideways. Task rows also wrap their status and buttons onto a second line on small screens.
- Security: portal task actions share one access check.
FS_Task::client_can_access()confirms a task is client-visible and belongs to the client (directly or through one of their projects) before any portal action touches it. The existing checkbox action used to only accept project tasks, and the request action used to return the full database row; it now returns only the fields the portal needs.
Version 9.12.3
1 change
▼
- Fixed: Time Tracking pages 2+ dropped the "Not invoiced" filter. The page links skipped any filter whose value was
0, which is what "Not invoiced" sends, so page 2 showed every entry. The Task filter was also missing from the page links. Both now carry through to every page.
Version 9.12.2
1 change
▼
- Changed: 1Password skips Freelance Studio admin screens. The 1Password extension scans every field on a page, including the hidden ones in our modals, which made screens noticeably slow with it switched on (most visibly in Safari). None of these screens hold logins, so
FS_Admin::mark_password_manager_ignore()now marks the page withdata-1p-ignoreon, which 1Password's developer docs describe for skipping a whole page. WordPress printsitself, so a one-line script onin_admin_headersets the attribute before any Freelance Studio markup loads. Settings is left out because its email-delivery API keys and Google client secret are things people keep in a password manager. Developers can change which screens are skipped with thefs_password_manager_ignorefilter. The client portal and public booking and form pages aren't affected, so clients can still autofill their details.
Version 9.12.1
5 changes
▼
- Fixed: every page on the site paid for a database check.
FS_Migrator::run()is hooked toplugins_loaded, and its schema checks (SHOW COLUMNS,SHOW TABLESandinformation_schemalookups, about 130 queries) ran on every request, front end included. They now run once per plugin version, or when activation or the repair tool asks for a full check. On the test site this took a normal page from about 170 queries to about 40. - Fixed: the dashboard waited on Google Calendar on every load.
get_events_in_range()called the Google Calendar API each time the dashboard or calendar loaded. Results are now cached for 10 minutes per calendar and date range. Any change sent to Google from Freelance Studio, an inbound sync, or disconnecting clears the cache right away. A failed fetch is remembered for 60 seconds so an outage doesn't slow every page. Events added directly in Google show up within 10 minutes. - Fixed: Time Tracking and Projects ran hundreds of queries.
FS_Project::get_all()fetched totals one project at a time (about 9 queries each), and Time Tracking calls it twice. NewFS_Project::get_totals_for_ids()gets the same figures for the whole list in 4 queries.get_totals()uses it too, so there's one copy of the SQL. The Projects list's own duplicate invoiced-amount lookup was removed in favour of these totals. On the test site, with 40 projects and 4,000 time entries, Time Tracking went from 940 queries to 57 and Projects from 549 to 44. - Fixed: Upcoming Events missed next month near the end of a month. It only looked at the current month's calendar grid. It now asks for exactly the next 7 days.
- Changed: quieter Google Calendar logging. Routine trace lines (token checks, every API request and response code, empty syncs) are off unless
FS_GCAL_DEBUGis defined astruein wp-config.php. Errors are still logged.
Version 9.12.0
8 changes
▼
- Added: Base Currency setting. Settings → Documents → Default Rates & Terms has a new Base Currency dropdown (USD until changed). It's the currency your reports, dashboard, expenses and other income are kept in, and the default for new clients. Clients can still be invoiced in their own currency, which converts into the base currency for reporting. The setting locks once the first invoice, estimate, expense or other income entry exists, because those amounts are stored in the base currency and would be wrong if it changed. When it's locked the dropdown is disabled with a note saying why, and the save handler refuses a change even if the form is tampered with. Changing it before then also moves clients that were on the old default to the new one.
FS_Currency::BASEis replaced byFS_Currency::base(), which reads thefs_base_currencyoption. The stored*_usdinvoice columns keep their names for schema compatibility but now hold base-currency amounts. - Changed: money shows in the base currency everywhere. About 100 places hard-coded a "$" sign: reports, the Tax tab, quarterly tax, expenses, other income, transactions, imports, project and proposal and contract forms, booking prices, PDFs, contract placeholders, tax reminder emails and chart axes. Each was changed by hand to use the base currency, or the record's own currency where that's the right one. The "≈ USD" hints on foreign invoices now name the base currency. Scripts get the base currency as
fs_ajax.currencyand can format withfsFormatBaseMoney(). - Fixed: invoice amounts showed in "$" on several screens. The dashboard Invoices card, the client and project invoice tabs, recurring invoices, reminder emails and the client project portal printed foreign invoices with a dollar sign. They now use the invoice's own currency. Recurring invoices follow their client's currency, including the live total on the recurring invoice form when you switch clients.
- Fixed: Time Tracking's Uninvoiced total added different currencies together. Time is billed in the currency of the client it's invoiced to. Time Tracking, Time Reports and the project Timer tab now total it per currency: one currency shows as-is (so a single client's view matches the invoice it'll become), and several are converted to the base currency, with a "Converted from client currencies" note. Report rows grouped by project or client show their client's currency. The report breakdown now uses the same rate and rounding rules as everything else, instead of its own copy.
- Added: clickable Financial Overview cards. Each dashboard card opens the page behind its number. Gross Income and Net Profit open Reports, Pending Income opens unpaid invoices, Overdue Invoices opens overdue invoices, Expenses YTD opens this year's expenses, Unbilled Expenses opens unbilled billable expenses, Uninvoiced Time and Hours This Month open Time Tracking filtered to match, and Estimated Tax opens the Tax tab. Cards lift on hover and have a visible keyboard focus ring.
- Added: "Unpaid (all)" invoice filter and "Unbilled" expense filter. The invoices list can show every invoice not yet paid or cancelled (what Pending Income counts), and the expenses list can show billable expenses not yet on an invoice.
- Changed: Overdue Invoices matches the Overdue filter. The card now counts sent, viewed and partially paid invoices past their due date, the same rule that marks invoices overdue, so drafts with an old due date no longer count.
- Fixed: saving a client without a currency field reset it to USD. Partial client updates now keep the client's existing currency.
Version 9.11.0
5 changes
▼
- Added: choose what the Financial Overview cards show. The dashboard's Financial Overview has a new gear icon. Click it and each of the 4 cards gets a dropdown: Gross Income, Pending Income, Expenses YTD, Net Profit YTD, Uninvoiced Time, Overdue Invoices, Unbilled Expenses, Hours This Month, and Estimated Tax Owed on Pro. A metric can only be used once, choices are saved per user, and Reset to default brings back the original set. The cards update in place without a page reload. Card colour now follows the card's position rather than the metric, so the row stays balanced whatever you pick. Free installs, which can't calculate tax, default the fourth card to Uninvoiced Time instead of showing a tax figure of $0.
- Added: Uninvoiced Time converts currencies. The new Uninvoiced Time card uses the same rate and rounding rules as the Time Tracking summary and the invoice builder (they now share one
FS_Time_Entry::get_billable_sql_expressions()helper). Time for a client invoiced in another currency is valued in that currency, then converted to the reporting currency with the exchange-rate service reporting already uses. If no rate is available for a currency, its time is left out rather than counted as if it were already in dollars. - Added: Duplicate button on expense and other income edit pages. Expenses get a Duplicate button next to Update Expense; other income gets one under Update Income in the Actions card. Duplicating opens the new copy's edit page, which has its own Duplicate button, so you can make several copies in a row. Duplicate copies the saved entry, so if you've changed the form without saving you're asked before continuing. The expense edit page now also confirms "You are now editing the copy" after duplicating, like other income already did.
- Changed: no more "(copy)" on duplicated expenses and other income. The copy keeps the original expense name or income source. It still takes today's date and opens on its own edit page. Projects, proposals, contracts, forms and templates still add "(Copy)", since their names are how you tell them apart.
- Security: duplicate links for expenses and other income now carry a nonce. They were plain GET links, so another site could trigger a duplicate for a logged-in user. The list menus and the new edit-page buttons all build the link from one helper per type, and the controllers reject requests without a valid nonce.
Version 9.10.4
2 changes
▼
- Added: reorder contract sections. Settings → Contract Sections now has a drag handle and up/down arrow buttons on every row. Drag a section by its handle, or use the arrows (they work from the keyboard and on touch screens, where dragging table rows doesn't), and the new order saves automatically with a short "Order saved" confirmation. This sets the order new contracts and the Preview PDF use.
FS_Contract_Section::reorder()already existed but nothing called it; it's now wired to a newfs_reorder_contract_sectionsAJAX action, limited to administrators. - Changed: existing contracts keep their own section order. Each contract stores its sections in
enabled_sections, in the order they were in when it was saved, and the web contract already followed that order. The PDF didn't:build_terms_html()walked the global order, so reordering the defaults would have moved the terms, and their numbers, in PDFs of existing and signed contracts, and made a contract's PDF disagree with its web page. The PDF now follows the contract's saved order too. Editing an existing contract also lists its sections in that saved order, with any it doesn't include after them, so saving a draft again doesn't quietly adopt the newer default order.
Version 9.10.3
3 changes
▼
- Fixed: bold and italic formatting was lost or printed as
{{B}}in PDFs. The PDF renderer flattened the HTML to plain lines and only applied bold or italic when it wrapped an entire line. Inside list items it never handled the markers at all, so a clause like "Generated images. Freelancer will..." printed as{{B}}Generated images.{{/B}}. Elsewhere, a bold or italic phrase mid-sentence silently came out plain.WriteHTML()now walks the markup tag by tag and builds each paragraph from styled runs, so bold, italic and bold-italic render where they appear. That covers,,and, plusandfont-style: italic, which is how text pasted from Google Docs carries its formatting. - Fixed: list items in PDFs had no hanging indent, and extra lines fell back to the page margin. Wrapped lines went back under the number instead of lining up with the text. A line break inside a list item, such as the (a), (b), (c) lines under "Permitted uses", dropped to the left margin, sitting further out than the item it belongs to. List numbers and bullets now sit in a gutter, every wrapped or continued line aligns with the item's text, and nested lists step in one level each. Line breaks follow the same rules as the web contract: a blank line starts a new paragraph, a single line break stays inside the paragraph or list item.
- Fixed: the space after a bold phrase disappeared in PDFs. Bold text was measured with an estimate (regular widths x 1.05) that runs up to 17% narrow on letters like r, b and f, so the next word was drawn on top of the space. The library now uses the real Helvetica-Bold metrics. Long unbroken strings such as URLs are split to fit the line instead of running off the page.
Version 9.10.2
5 changes
▼
- Added: Preview PDF on Settings → Contract Sections. A new Preview PDF button opens your full contract template as a PDF in a new tab: your header and branding, the Parties block, a sample service, every active section numbered in order (plus any required ones, since those are always added to real contracts), and the signature lines with your saved contractor signature. It uses the same generator as a real contract, fed a sample contract and client built in memory by
FS_Contract::get_preview_data(), so nothing is saved and the preview matches what clients receive. It runs in the early export handler alongside the other PDF downloads, before any admin output. - Fixed: contract PDFs printed placeholders unfilled. The web contract replaced
[Hourly Rate],[Percentage],[Number],[Client Name],[Your Business Name]and the rest, but both the draft PDF and the signed PDF passedbuild_terms_html()keys likeclient_namethat never appear in section text, so every contract PDF, including the signed copy saved for your records, showed the raw brackets. Both now useFS_Contract::build_placeholder_replacements(), the same map the web contract uses. - Fixed: accented letters and bullets were mangled in every PDF. The PDF library converts text from UTF-8 to the PDF's WinAnsi character set, but
MultiCell()converted its text and then passed each line toCell(), which converted it again. The second pass read single-byte WinAnsi characters as broken UTF-8, so list bullets disappeared and an accented letter swallowed the characters after it: "José Café" printed with letters missing. This affected client and business names, section text, invoices and proposals alike. The converter now leaves already-converted text alone, and maps the Windows-1252 extras (bullet, euro, dagger, trademark and so on) by code point instead of pre-converting them in a way the next step discarded. ©, ® and ° print correctly again for the same reason. - Fixed: the contract signature block split across pages. The two signature columns are drawn from a fixed starting height, and when the Signatures heading landed near the bottom of a page, the lines, names and client block scattered over two extra pages. The block (heading, signature images, lines, names, dates and footer) now moves to the next page as one piece when it doesn't fit, in both the draft and the signed PDF. The PDF library gains an
EnsureSpace()method for this. - Fixed:
[Hourly Rate]showed $0.00 on fixed-price contracts. A fixed-price contract stores no hourly rate, so clauses about additional work quoted $0.00. It now falls back to your default hourly rate from Settings.
Version 9.10.1
5 changes
▼
- Changed: the project page's services save uses the shared parser. v9.9.0 moved every services save onto
FS_Project::parse_services_from_post(), but the AJAX save behind the Services editor on the project page (update_project_services()) kept its own copy of the loop, and it had drifted. It now calls the shared parser like the project, proposal and contract forms, which brings three fixes with it: - Fixed: formatting in service descriptions was stripped when saving from the project page. The old copy ran descriptions through
sanitize_textarea_field(), so bold, italic and lists added in 9.10.0, or carried over from a contract or proposal, were flattened the first time anyone saved services there. - Fixed: retainer rollover switched itself on when saving from the project page. The page sends an unticked Rollover box as
0, and both save paths checked it withisset(), which is true for0. The parser now uses!empty(), which is right for form posts (box absent) and the AJAX save (box sent as0). - Fixed: blank service rows were saved from the project page. The old copy kept every row, including completely empty ones. The shared parser drops a row with no name and no amount, and labels an unnamed row that has money on it.
- Changed: the hour-cap warning threshold is stored as a whole number from 1 to 99 on every save path. Only the AJAX copy clamped it before; the form save stored any number.
Version 9.10.0
8 changes
▼
- Added: your own contract sections. Settings → Contract Sections has a new + Add Section button, so you are no longer limited to the seeded terms. A new section gets a title, full rich-text content and an Active toggle, is stored with a
custom_key and thecustomcategory, and goes to the end of the list. While Active it is ticked by default on new contracts, like the built-in sections, and can be unticked per contract. Sections you added can also be deleted, but only while no contract includes them: contracts reference sections live by key rather than keeping a copy of the text, so deleting one in use would quietly remove those terms from existing contracts, signed ones included. For a section that is in use, the Delete action is hidden and unticking Active is the way to retire it. - Added: formatting for contract and proposal text. Service and line-item descriptions now carry a small formatting toolbar (bold, italic, bulleted and numbered lists, links) above the box, and what you type is kept exactly as written on the client-facing page. These fields previously ran every entry through
sanitize_textarea_field(), which strips markup, and rendered it withnl2br(), so there was no way to emphasise a word or list deliverables. The toolbar is WordPress's own Quicktags rather than a full visual editor, because service rows are drag-sortable and moving a TinyMCE iframe in the DOM reloads it and loses whatever is inside. Proposal and contract PDFs render these descriptions through the same HTML writer the terms sections already use, so a bold word or a bulleted list comes out formatted rather than as visible tags. Invoice line items stay single-line plain text, so any markup carried across from a contract is flattened there, including in the recurring-invoice form when it is prefilled from a project. The Projects services editor gets the same toolbar, and its AJAX save path now keeps formatting the same way the shared services parser does, where it previously stripped it the first time services were saved from the project page. - Added: a Project Description block on contracts. Both the admin preview and the public contract already had a slot for an intro paragraph above the services table, but there was no column to store one and no field to write it in, so it never appeared. Contracts now have a full rich-text Project Description, added automatically to the database on update, and it appears on the public contract, the admin preview, and both the draft and signed PDFs.
- Fixed: paragraph breaks disappeared from proposal sections. Proposal custom sections were printed with
wp_kses_post()alone, while the same content on a contract went throughwpautop()first. WordPress stripstags on save by design and puts them back at render time, so a proposal section arrived on the client's page as one unbroken block and the only workaround was typingtags by hand in the code view. Custom sections now render throughwpautop()in the public proposal and the admin preview, matching contracts. The same bug existed in the PDFs: the PDF library's HTML cleanup collapsed\n\s+to a space, which swallowed every blank line, so paragraphs in proposal sections, contract terms and the new descriptions all ran together in the downloaded file. It now collapses indentation only, keeps blank lines as paragraph breaks, and never stacks more than one. - Fixed: bulleted lists had no bullets on client-facing documents. The shared document stylesheet opens with a
* { margin: 0; padding: 0 }reset, which removes the left indent that list markers hang off, and nothing ever set list styling back. Bulleted and numbered lists, nested lists, blockquotes, bold, italic and links now have explicit styling in both the public document styles and the admin preview, including high-contrast variants. - Changed: one contract sections editor instead of two. Settings → Contract Sections used a plain textarea, while a second, richer copy of the same editor existed on an
fs-contract-sectionspage that was never registered in the admin menu and so could not be reached. The reachable editor is now the rich one, complete with the placeholder reference, and the orphaned page, its view file, and its duplicate save handler have been removed. - Fixed: adding a service to a contract produced rows that overwrote each other. The contract form's service template used an
__INDEX__placeholder, but the shared JavaScript that clones it replaces{{index}}— so every added row posted under the same array key and only the last one survived. The contract template now matches the proposal one. - Fixed: contract section content was saved with escaping slashes. The save handler ran
wp_kses_post()on the raw$_POSTvalue withoutwp_unslash(), so an apostrophe in a terms section came back with a backslash in front of it. Same class of bug as the nested-array cases fixed in 9.9.3.
Version 9.9.3
4 changes
▼
- Fixed: tracked-hours services showed raw HTML on the project page. A service billed from tracked time renders its quantity with a small clock icon, so the label is built as markup — but the Project services list printed it through
esc_html(), which turned the markup into visible text reading.... The dynamic parts of the label (hours, rate) are now escaped where the label is built, and the finished label is printed withwp_kses_post(), so the icon renders as intended. The three plain-text labels (hourly, retainer, flat fee) are escaped the same way. - Fixed: apostrophes in saved names came back with a backslash in front of them. WordPress adds escaping slashes to everything in
$_POST, and the code that saves client contacts sanitised the submitted values without callingwp_unslash()first — so "Patrick O'Connor" was written to the database as "Patrick O\'Connor" and the backslash then appeared everywhere the contact was shown, including the Send To list on the invoice send modal. The same omission existed anywhere a nested$_POSTarray was read: invoice and recurring-invoice line item descriptions, form and form-template field labels, options and help text, and milestone and task names on project templates and settings. All of these now unslash the submitted row before sanitising it. - Fixed: existing contacts already saved with a stray backslash. A one-time upgrade routine strips the escaping slashes from client contact names, roles, and phone numbers already in the database. Only rows that actually contain a backslash are touched, and it runs automatically on the first admin page load after updating.
- Changed: contact names in the send-email modal are HTML-escaped. The modal built its contact rows by string concatenation with the name, email, and role dropped in raw. They now go through the existing
escapeHtml()helper, matching how every other list in the admin renders them.
Version 9.9.2
3 changes
▼
- Fixed: saving anything could fail with an alert reading "[object Object]". The plugin's AJAX handlers report failures in two shapes —
wp_send_json_error('Message')andwp_send_json_error(array('message' => 'Message'))— but the browser only ever read one of them. Around a hundred call sites readresponse.datadirectly, which prints[object Object]for the second shape, and the rest readresponse.data.message, which prints nothing at all for the first. Nonce failures use the object shape, so an expired session on the Kanban board produced an alert saying[object Object]and no clue what had gone wrong. All of them now go through one shared helper,fsErrorMessage()inadmin/js/fs-error-message.js, which understands both shapes plusWP_Errorarrays and always returns readable text. The helper is loaded on admin pages and on the standalone client-facing templates (portal, proposal, estimate, booking) and the popup timer, which are rendered outsidewp_head. - Fixed: a tab left open overnight silently failed every save. WordPress nonces stop validating after roughly 24 hours, and a Kanban board or timer window is exactly the kind of page that stays open for days. Every save from that page then failed the security check. The page now asks for a fresh nonce on each WordPress Heartbeat tick and updates it in place, so a long-lived tab keeps working. The task modal reads the nonce at request time rather than caching it at load.
- Changed: an expired session no longer looks like a broken save. The nonce check now returns a
fs_session_expiredcode alongside its message. The task modal recognises it, leaves the modal open with everything typed still in place, and says to log back in from another tab and click Save again — instead of throwing the entry away behind a generic error.
Version 9.9.1
3 changes
▼
- Fixed: task titles were only clickable on the Project and Client tabs. Those two views wrapped the title in
.fs-task-title-link, which the shared task modal script already listens for, but every other task list rendered the title as plain text — so opening a task to read its description, change its due date, or check whether it recurs meant hunting for the row's ⋯ menu and choosing Edit. The title is now the link in every task list: the Tasks list view (desktop table and mobile cards), the recurring templates table, Kanban card titles, the dashboard's My Tasks and Internal Tasks widgets, and the task list on the client overview. Every one of these pages already loaded the shared modal partial, so this is markup only — no new script, no new AJAX endpoint. - The Tasks list view now shows the ↻ recurring marker next to the title, matching the Project tab.
- Kanban card titles are marked
draggable="false"so the link doesn't intercept the card's drag-and-drop, and the inline status handler on the Tasks list now targets the link when it applies the strikethrough for a completed task.
Version 9.9.0
4 changes
▼
- Fixed: services with no name were silently thrown away on save. Every save path skipped any service row that had no name typed into it, and the service name field carried only a placeholder rather than a required marker — so a row with pricing filled in but no name vanished without warning. Because the running total is calculated in the browser and stored separately as the project's fixed price, the project page still showed the money while listing no services, and the invoice wizard had nothing to pull across. A row with a rate, amount, or retainer figure is now always kept, an unnamed one is labelled "Service 1", "Service 2" and so on, and the name field is marked required so the browser asks for it first. This applies to projects, proposals, and contracts.
- Fixed: hourly and retainer services came into the invoice wizard with the wrong amount.
FS_Invoice::calculate_service_amount()read atypekey that nothing writes — the saver storespricing_type— so every service fell through to the flat-fee branch and returned the bare rate. An hourly service at $125 x 8 hours billed as $125, and a retainer billed as $0. The wizard now uses the amount stored on the project, which is the figure you agreed with the client, and reads the correct pricing type. - Fixed: activity and hour-cap settings were dropped when saving a project. The save handler that actually runs for the project form (
early_save_project) was a stale copy of the one in the projects controller and was missing theactivityfield and every hour-cap field, so those settings were discarded on every save while the controller's complete version sat unreachable behind an earlier redirect. - Changed: one service parser for projects, proposals, and contracts. All four save paths had their own copy of the same loop and they had drifted apart. They now call
FS_Project::parse_services_from_post(), which takes flags for the retainer and hour-cap fields that only projects use. Fixing service handling now means changing one method rather than four.
Version 9.8.2
5 changes
▼
- Fixed: archived projects were showing up as overdue everywhere. Archiving a project is recorded in the
archivedcolumn, not as astatusvalue, so every query that filtered on status alone let archived projects straight through. The Needs Attention block in the weekly digest was the loudest symptom — projects closed out months ago were reported as 178 to 210 days overdue — but the dashboard calendar, the calendar view, and the calendar AJAX endpoint had the same gap. All of them now exclude archived projects. - Fixed: work belonging to archived projects came along with it. Archiving cascades
status = 'archived'onto a project's tasks, but the digest only excluded completed and cancelled ones, so they still appeared. Tasks are now filtered both on their own archived status and on their parent project, which also catches any task created or reopened after the project was archived. Milestones get no cascade at all, so they are filtered through their parent project. Tasks with no project — standalone client tasks — are unaffected and still appear. - Fixed: un-archiving a project left its tasks stuck.
archive()saved each task's prior status toprevious_statusbefore setting it to archived, butunarchive()never read it back, so restoring a project brought back the project and none of its work. Un-archiving now restores each task's previous status and clears the column; tasks with nothing stored fall back to "To Do". - The rule now lives in one place as
FS_Project::sql_not_archived(),sql_parent_not_archived(), andsql_project_id_not_archived(), so raw queries that don't go throughFS_Project::get_all()can apply it consistently instead of each one reinventing the check. - Also scoped the calendar AJAX endpoint's invoice query to
doc_type = 'invoice', the same estimates leak fixed in the digest in 9.8.0.
Version 9.8.1
3 changes
▼
- Fixed: table link text was too light to read comfortably. Every list view in the plugin ran its links through
.fs-table a, which set them to--fs-secondary(#81A5DF). That measures 2.5:1 against white — well under the 4.5:1 WCAG AA minimum for body text — and it was most obvious on the client Tasks tab, where task titles run long. Table links now use--fs-gray-800(10.3:1), matching the treatment the Clients and Projects name columns already used, with secondary links such as the project name on a task row one step down at--fs-gray-700(7.6:1). Hover still goes teal with an underline, so the underline rather than colour alone signals the link. - Sortable column headers were being caught by the same
.fs-table arule and rendered in that light blue instead of their intended muted grey; their specificity is now raised so they render as designed. .fs-task-title-linkhad its own colour of--fs-primary-dark(teal, 2.95:1) which was overridden inside tables anyway; it now matches the table link colour so it is correct in every context it is used.
Version 9.8.0
6 changes
▼
- New: "Needs Attention" block in the Week Ahead digest. Every section of the weekly digest was scoped to the window from today to seven days out, which meant anything already past due was structurally invisible — an overdue invoice could sit unpaid while the email reported "All Clear". The digest now opens with a Needs Attention block listing overdue invoices (with balance and days overdue), plus overdue projects, milestones, and tasks. Each type is capped at five items with a "+N more on the dashboard" line so a long tail of past-due work can't turn the email into a wall of text.
- New: "Billing This Week" section. The digest now reads the recurring invoice schedules and lists any that will generate during the week, showing client, amount, date, and a badge for whether the schedule sends automatically or produces a draft. Schedules whose next run date has already passed are listed as "Pending" rather than dropped. Amounts follow the client's invoice currency.
- New: monthly invoicing reminder. When the 1st of a month falls inside the digest window, the Billing This Week section includes a "Monthly invoicing day" line naming the date — the cue to invoice monthly clients who aren't on an automated schedule.
- Fixed: estimates could appear under "Invoices Due". The digest's invoice query was written before estimates began sharing the
fs_invoicestable in 9.5.0 and never picked up thedoc_typescoping applied everywhere else, so an estimate with a due date in the week ahead was listed as an invoice. The query now filtersdoc_type = 'invoice'. - Fixed: digest invoice amounts ignored multi-currency. The Invoices Due section hardcoded a
$prefix, so a EUR or GBP invoice was shown with a dollar sign. It now formats throughFS_Currency::format()using each invoice's stored currency. - The "All Clear" state now accounts for the new sections, so it only appears when there is genuinely nothing outstanding. Overdue invoices are reconciled against the forward window so an item can never be listed twice in one email.
Version 9.6.0
1 change
▼
- New: Per-client invoice currencies, with USD reporting. You can now set an invoice currency on each client (Billing Defaults on the client form). Invoices and estimates for that client are created, displayed, emailed, and exported to PDF in the client's currency, while every business report continues to total in USD.
Version 9.5.6
2 changes
▼
- New: Notes for Your Tax Advisor. The Tax tab now has a notes section where you can jot down questions and reminders for your tax advisor throughout the year — things like whether an equipment purchase should be expensed or depreciated, or a deduction to double-check at filing time. Notes are saved per tax year (they follow the year selector at the top of the tab), and each note can be marked Resolved once it's been handled and Reopened later if needed; resolved notes stay in the list for history but are visually dimmed and sorted below open ones. Notes are stored in a new
fs_tax_notestable created automatically on update. - New: Notes are included in the Tax Deduction Summary PDF. When you export the tax PDF for the selected year, a "Notes for Your Tax Advisor" section is appended after the deductions table listing each note and whether it's open or resolved, so everything your advisor needs travels in one document. The CSV export is unchanged — it stays a clean numbers-only file. An Export Tax PDF button is also available right in the notes section for convenience.
Version 9.5.3
3 changes
▼
- New: Archive estimates. Estimates can now be archived from the estimate list row menu and the estimate view actions. Archived estimates are hidden from the default Estimates list and only appear when you select the new Archived option in the status filter; viewing an archived estimate offers an Unarchive action that restores its prior status (converted, declined, approved, sent, or draft) based on its recorded history.
- New: Delete declined or orphaned estimates. Previously a converted estimate could never be deleted. You can now delete an estimate when it is not converted, when it has been declined, or when it was converted but its resulting invoice has since been deleted (orphaned). This is enforced both in the UI and server-side in the delete handler.
- New: Estimate-specific "viewed" notification email. When a client first opens an estimate you now receive an estimate-worded notification ("Estimate Viewed", estimate total, and a Valid Until line when set) with a View Estimate button, instead of the invoice-worded email that previously showed a bogus due date.
Version 9.5.1
1 change
▼
- New: Estimates dashboard card. The dashboard now includes an Estimates card showing how many estimates are awaiting client approval and how many are open overall, with a link straight to the Estimates tab in Finances. It's available in the default dashboard layout and the customizer card list like any other card.
Version 9.5.0
6 changes
▼
- New: Estimates. You can now send clients an itemized price quote that they approve with a single click, then convert 1:1 into an invoice. Estimates live in a new Estimates tab in the Finances area, next to Invoices, with a count badge when any are awaiting approval. They share the invoice engine — line items, totals, tax, discounts, the PDF renderer, the public token link, and email sending — so there's no separate, drift-prone machinery to maintain. Under the hood, estimates are stored in the existing invoices table behind a new
doc_typecolumn (invoice|estimate); every financial query defaults todoc_type = 'invoice', so estimates never count as income, accounts receivable, or tax, and the dashboard, reporting, tax, client charts, and recurring logic stay correct automatically. Estimates use a separate numbering sequence (EST-prefix by default), have a Valid Until date instead of a due date, and auto-expire after that date via the existing daily cron. The full lifecycle is draft → sent → viewed → approved (or declined / expired) → converted. - New: Public click-to-approve. The client-facing estimate page shows Approve and Decline buttons (with an optional decline reason) instead of Pay Now, plus the validity date. Approval and decline are handled over AJAX, validated by the estimate's view token — no client login required. You're notified by email when a client approves or declines. There's no e-signature step (estimates are a quick quote, not a contract).
- New: Convert to invoice. An approved estimate converts into a fresh draft invoice in one click, copying the client, project, line items, expenses, tax, discount, and notes, and assigning a new invoice number. The original estimate is kept for history and linked to the invoice it produced. Auto-conversion on approval is available as a setting but is off by default so you can review before converting.
- New: "Send an estimate for this project?" at project creation. When creating a new project, a checkbox in the Billing section builds an estimate pre-filled from the project's services (description, quantity/hours, rate, amount), sets the validity date from your default, and drops you on the estimate edit screen to review before sending — choose draft or send-now.
- New: Estimate settings on the Invoices settings tab — estimate number prefix, next number, default validity period (days), default estimate terms, and the auto-convert-on-approval toggle.
- Note: Estimates are available on all plans and estimate sends are unlimited — they do not count toward the free plan's monthly invoice-send limit.
Version 9.2.0
2 changes
▼
- New: Active / Archived tabs on the project Tasks tab — The Tasks tab inside a project now has Active and Archived sub-tabs (matching the global Tasks list). Active is shown by default and excludes archived tasks; Archived lists archived tasks with a count badge. Previously every task, including archived ones, rendered together with no way to separate them.
- New: Client portal invoice filtering & pagination — The Invoices tab in the client portal now has a filter bar (Status: All / Paid / Unpaid, a Year dropdown, and a From/To date range) and paginates at 10 invoices per page with a result summary and page controls. Filtering and paging happen instantly in the browser; a "Clear" button resets all filters. Previously every invoice was listed in one long unpaginated table.
Version 8.8.0
3 changes
▼
- New: Project Documents Tab — Projects now have a "Documents" tab showing all linked proposals, contracts, and recurring invoices with status badges, amounts, and direct links. Includes quick-create buttons for new contracts, proposals, and recurring invoices that pre-fill the project and client. A tab indicator dot appears when documents are linked.
- New: Recurring Invoice Send-To Contacts — When "Auto-send invoice when generated" is checked on a recurring invoice, a new "Send invoices to" section appears showing checkboxes for each client contact. Selected contacts are saved and used when the cron auto-generates and sends invoices. Contacts reload via AJAX when the client changes.
- New: Set Up Recurring Invoice from Project — The project billing schedule section now shows a "Set Up Recurring Invoice" button when billing frequency is set to anything other than "once". If a recurring invoice already exists for the project, it shows the linked recurring invoice name with status badge instead.
Version 8.7.0
3 changes
▼
- New: Realized Deductions — Annual estimated deductions can now be marked as "Realized" when the actual expense has been logged. This prevents double-counting on the tax report. Each deduction in the Tax page now shows an Estimated or Realized status badge. Clicking "Realize" opens a modal where you can optionally link the deduction to a specific tracked expense for reference. Realized deductions appear dimmed with a strikethrough amount and can be undone at any time.
- New: Merged Tax Report — The Tax Deduction Summary export (PDF and CSV) now produces a single consolidated table of deductions grouped by category, merging tracked expenses and unrealized estimated deductions together. Realized deductions are excluded automatically. Each row shows whether the amount came from tracked expenses, estimates, or both. This gives your CPA one clean list of deductible categories instead of two separate sections that could overlap.
- New: Expense Search — Search expenses by name, category, notes, or project name. The search input appears in the expense filter bar and works alongside existing category and date filters. A "Clear" link appears when any filters are active.
Version 8.6.0
3 changes
▼
- New: Email Delivery Settings — Built-in integration with Brevo and Postmark transactional email APIs. Ensures invoices, proposals, contracts, booking confirmations, and all other emails reliably reach clients' inboxes instead of landing in spam.
- New: Setup Wizard — Step-by-step wizard in Settings → Email Delivery walks users through choosing a provider, creating an account, entering their API key, verifying the connection, and sending a test email. Supports three paths: Brevo (free 300 emails/day), Postmark (fastest delivery), or "I already use an SMTP plugin."
- New: Test Email — Send a branded test email from both the setup wizard and the configured status view to confirm end-to-end delivery.
Version 8.4.0
1 change
▼
New Features
- Finances Hub: New unified "Finances" menu in the WordPress sidebar, consolidating all financial features into one location with sidebar navigation (same pattern as Bookings and Settings).
Version 8.3.3
1 change
▼
- Added: Expense CSV Import — the Settings > Import tool now supports importing expenses in addition to invoices. A type selector at the top lets you choose between "Invoices" and "Expenses" before uploading. Expense import auto-maps columns for Date, Amount, Merchant, Category, Description/Purpose, Notes, and Sales Tax. Expense names are built from Merchant + Purpose (e.g., "Adobe Creative Cloud — Software"). Preview shows expense count, total amount, and a full preview table before committing. Batched processing with progress bar, same as invoice import.
Version 8.3.2
1 change
▼
- Added: Pagination on the Invoices list page. Defaults to 25 per page with options for 10, 25, 50, or 100. Shows "Showing X–Y of Z invoices" info, page number links with ellipsis for large page counts, and prev/next navigation. Filters (status, client) are preserved across pages. Per-page preference is carried in the URL.
Version 8.3.0
3 changes
▼
- Added: CSV Import Tool in Settings > Import. Upload a CSV file containing historical invoice data (from Wave, FreshBooks, QuickBooks, or any other tool) and import clients, projects, and invoices into Freelance Studio. Features include:
- Added: Client Merge — reassign all projects, invoices, proposals, contracts, expenses, tasks, bookings, and contacts from one client to another, then delete the source client. Available via "Merge Into..." in the client card/row action menus. Includes a preview step showing exactly how many records will be reassigned before confirming.
- Added: Client Archive — archive and restore clients from the card/row action menus. Archived clients move to the existing "Archived" tab and are excluded from active views and dropdowns.
Version 7.10.0
1 change
▼
- New: Preset Service Templates are now available on Proposal and Contract forms (previously only on Projects). Select "Load Preset Service" to populate services from your saved templates.
Version 7.9.0
4 changes
▼
- New: Capped Hours for Hourly Projects — When a service is set to "Hourly > From tracked time," you can now enable "Cap my hours" to set a time budget. This shows a retainer-style progress widget on the project view with Hours Budgeted / Used / Remaining and a visual progress bar.
- New: Configurable overage rate — When hours exceed the cap, overage time can be billed at a different rate (e.g., 1.5x). Defaults to the service's standard hourly rate if not set.
- New: Cap reached notifications — When tracked hours cross the cap threshold, the system emails the freelancer automatically. Optionally notify the client too (per-project toggle).
- New: Invoice auto-split — When generating an invoice from tracked time on a capped project, line items automatically split into standard hours (at regular rate) and overage hours (at overage rate) with clear labeling.
Version 7.8.0
3 changes
▼
- New: Email sending for invoices, proposals, and contracts now sends a single email with the primary recipient in "To" and additional contacts in "CC", instead of separate emails to each contact. All recipients see the same email thread.
- New: Send modal redesigned with To/CC selection — users can choose which contact receives the email directly (To radio button) and which contacts get a copy (CC checkboxes). Primary client contact defaults to "To" with additional contacts defaulting to CC.
- New: Email personalization (e.g.,
{client_name}) now uses the selected "To" recipient's name, ensuring the greeting matches the primary recipient even when CC'd contacts have different names.
Version 7.7.0
3 changes
▼
- New: Each time entry now shows both actual logged time and rounded billable time when time rounding is enabled. A subtle "→ 00:20:00" indicator appears below the actual duration (e.g., 00:17:03 → 00:20:00 for 5-minute rounding), making it clear how rounding affects each entry. Only displays when rounding actually changes the value and the entry is billable.
- New: Summary bar on time tracking page now shows total rounded billable hours alongside the raw billable time (e.g., "Billable: 33:37:41 / 35.52 hrs rounded (5-min)"), making it transparent where the uninvoiced dollar amount comes from.
- New: Project view timer tab also shows rounded hours in the billable stat and per-entry rounded durations.
Version 7.5.0
4 changes
▼
- Added: Recurring Tasks tab on Tasks list page — view, edit, and delete recurring task templates from a central location
- Added: Mobile card view for Recurring Invoices list — matching the Invoices mobile design with name, client, frequency, amount, and next invoice date
- Added: Mobile card view for Invoices list — two-column layout with client name, invoice number, status badge, amount, and due date
- Added: Overdue invoice auto-detection — invoices with status sent/viewed/partial that are past due automatically update to "overdue" status when viewing the invoices page
Version 6.7.0
6 changes
▼
- Feature: Solid or gradient option for header color. Users can now choose between a solid color or a two-color gradient for document headers.
- Feature: Gradient direction picker with 8 directional presets (↑ ↗ → ↘ ↓ ↙ ← ↖) for controlling gradient angle.
- Feature: Live color swatch preview showing the current solid/gradient result.
- Feature: Solid/Gradient segmented toggle with animated active state, replacing the single color picker.
- Enhancement: Header preview in settings now reflects gradient in real-time as colors and direction change.
- Enhancement: Light logo thumbnail background also updates with gradient when in gradient mode.
Version 6.4.0
1 change
▼
- Improvement: Dashboard task widget status pills are now inline editable dropdowns — change task status directly from the dashboard without navigating away
Version 6.2.0
1 change
▼
- New Feature: High Contrast Mode — a toggle in Settings > Business > Accessibility that transforms the entire plugin UI to meet WCAG AAA contrast standards (7:1 ratio):
Version 6.1.0
3 changes
▼
- New Feature: Quick timer button on dashboard task list — each task now shows a clock icon on hover that opens a popup timer pre-populated with the task and project; only appears on tasks that have a project assigned
- New Feature: Timer task dropdown now filters by selected project — when a project is selected in the timer widget (on both the dashboard and time tracking page), the task dropdown automatically hides tasks from other projects, showing only relevant tasks; reverts to showing all tasks when no project is selected
- New Feature: Recurring invoices can now auto-populate with unbilled time entries — new "Billable Time" section on the recurring invoice form with options to:
Version 6.0.0
1 change
▼
- New Feature: Task Archiving — archive completed tasks to declutter your Kanban board and task list while preserving them for reference
Version 5.8.0
2 changes
▼
- New Feature: Weekly/biweekly recurring tasks now support day-of-week selection
- Recurring days carried forward to all future recurring instances
Version 5.7.0
7 changes
▼
- New Feature: Popup timer for tasks — click the play button on any task row to launch a small desktop timer window
- Timer popup pre-fills task name, project, and activity; includes start/stop/pause/resume/discard controls
- Timer window title updates with elapsed time for easy tracking from the taskbar
- Auto-closes with confirmation after stopping and saving
- Warns if another timer is already running
- Added to all task views: Tasks list, Kanban board, Project tasks tab, and Client tasks tab
- Timer button only shows on incomplete tasks
Version 5.3.0
6 changes
▼
Added
- Client Task Requests: Clients can now request tasks directly from the client portal — both from the project Tasks tab and a new Tasks section on the portal dashboard
- Portal Dashboard Tasks Section: New Tasks section on the portal dashboard displays all client-visible tasks across all active projects, with status labels, priority indicators, due dates, and a collapsible completed tasks section
- Task Request Modal: Clean modal interface for clients to submit task requests with title, description, priority, due date, and project selection
- "Requested" Task Status: New
requestedstatus for tasks submitted by clients — these appear in the Pending column on the admin kanban board with a distinctive pink "Request" badge - Accept Task Request Flow: Kanban cards with requested status show an Accept button (green checkmark) — clicking it changes the status to Pending and notifies the client
- Task Request Email Notifications: When a client submits a request, an email is sent to the freelancer with full task details and a link to the Tasks board; when the freelancer accepts, a confirmation email is sent to the client with a link to their portal
Version 3.10
2 changes
▼
- Client-Level Tasks: Tasks can now be created directly under a client without requiring a project. This is useful for ongoing client relationship tasks (follow-ups, check-ins, general consulting).
- Internal Tasks Dashboard Widget: When Internal Project Tracking is enabled, a new "Internal Tasks" widget appears on the dashboard showing pending tasks for your internal client.
Version 3.9.0
1 change
▼
- Retainer Service Pricing: Added "Monthly Retainer" as a pricing type option for services
Version 3.8.0
5 changes
▼
- 1099 Year-End Report: Generate a professional PDF summary of all clients paid $600 or more during the tax year
- Duplicate Project: One-click copy a project with all its tasks and milestones
- Batch Mark Invoices Paid: Select multiple invoices and mark them as paid at once
- Export to CSV: Export your data for use in spreadsheets or other software
- Retainer Tracking System: Comprehensive retainer/subscription management
Version 3.7.0
1 change
▼
New Features
- Invoice Wizard Button on Time Tracking Page: Added a prominent "Invoice Wizard" button to the Time Tracking page for quick access to create invoices from tracked time
Version 3.4.0
5 changes
▼
New Features
- Invoice Wizard: New multi-step wizard for creating invoices from the Invoices list or Project view
- Multi-Project Invoices: Create a single invoice that includes items from multiple projects
- Time Entry Grouping Options:
- Service Selection: Choose exactly which services to bill - no longer forced to include all services
- Service Partial Billing: Bill a percentage or custom amount of each service individually for deposits or milestone payments
Version 3.0.0
2 changes
▼
Added
- Invoice Project Modal - New modal interface when creating invoices from projects
Changed
- Invoice Creation from Projects - Completely redesigned workflow
Version 2.13.0
1 change
▼
Added
- Contract Sections Library - Reusable legal sections for contracts
Version 2.12.0
1 change
▼
Added
- Recurring Invoices - Automatically generate invoices on a schedule
Version 2.11.0
1 change
▼
Added
- Payment Reminders - Automated email reminders for unpaid invoices
Version 2.10.0
1 change
▼
Added
- Project Templates - Save and reuse project configurations
Version 2.9.0
1 change
▼
Added
- Time Tracking Reports - Detailed time analysis
Version 2.8.0
1 change
▼
Added
- Task Management - Track project tasks and to-dos
Version 2.7.0
1 change
▼
Added
- Milestones - Track project progress with milestones
Version 2.6.0
1 change
▼
Added
- Google Calendar Integration - Sync deadlines and events
Version 2.5.0
1 change
▼
Added
- Email Templates - Customizable email templates
Version 2.4.0
1 change
▼
Added
- Annual Tax Deductions - Track yearly deductible expenses
Version 2.3.0
1 change
▼
Added
- Client Portal - Secure area for clients to view their information
Version 2.2.0
1 change
▼
Added
- Proposals - Create and send professional proposals
Version 2.1.0
1 change
▼
Added
- Contracts - Digital contract management
Version 2.0.0
1 change
▼
Added
- Time Tracking - Track billable hours
Version 1.0.0
10 changes
▼
Added
- Initial release
- Client management with multiple contacts per client
- Project management with hourly, fixed-price, and retainer billing types
- Invoice generation with line items, discounts, and tax calculations
- Expense tracking with categories and receipt uploads
- Dashboard with YTD revenue, expenses, and tax summary
- Reporting with monthly income/expense breakdown and tax estimates
- Settings page for business info, branding, and tax configuration
- PDF invoice generation
- Tax calculator with federal and state tax rate support
