Skip to main content

Recurring Invoices

What it is

Templates that raise an invoice for a client on a schedule — weekly, monthly or quarterly. Each template holds the client, the amount, the frequency and the date it next runs; a nightly cron does the raising, and Trigger Now does it on demand.

Who can open it

Needs the finance.invoices feature grant while FEATURE_GRANULAR_RBAC is on; the owner bypasses it. With the flag off, any signed-in account can reach it by URL.

Before you start

recurring_invoices exists in production and currently holds zero rows. Nothing recurring is set up. That is worth knowing before you conclude the feature is broken — an empty list is an accurate empty list.

If the table were ever missing, the screen tells you and hands you the SQL. It detects a 42P01 and renders a setup panel with a Copy button rather than an error. That is deliberate and is not a fault state you should see today.

Trigger Now runs the real job, not a rehearsal. It raises live invoices for every template that is due. There is no preview and no dry run.

Deleting a template does not touch invoices it already raised. The confirm says so: "This recurring template will be permanently removed. Any invoices already generated will not be affected."

Both the template list and the client list follow the entity switcher.

Every control on the page

ControlWhat it does
New templateOpens the form
Trigger NowRuns the recurring job immediately and reports how many invoices were generated, plus any errors
Active toggle (per row)Turns a template on or off without deleting it
Delete (per row)Removes the template after a destructive confirm

The form:

FieldNotes
ClientClients for the selected entity
AmountThe invoice value
Frequencyweekly, monthly or quarterly
Start dateBecomes the template's next_run_date, so the first invoice is raised on this date

A new template is created active.

The table — one row per template with its client, amount, frequency, next run date and active state, newest first.

What it writes

  • New template inserts a recurring_invoices row with is_active: true and next_run_date set to the start date you chose.
  • The active toggle updates is_active on that row.
  • Delete removes the row. Invoices already raised are untouched.
  • Trigger Now posts to /api/cron/recurring-invoices, which requires a logged-in session (the scheduled GET uses the cron secret instead). It runs both the legacy recurring-invoice job and the recurring-orders job, then reports the combined result and refreshes the list.

When it goes wrong

"The recurring_invoices table does not exist yet." The setup panel is showing. Copy the SQL and run it. You should not see this in production — the table is there.

"Trigger failed" followed by a message. Nothing was generated.

Trigger Now reports generated: 0 with no errors. Nothing was due. A template only runs when its next_run_date has arrived and it is active.

An invoice was raised twice. Both the nightly cron and Trigger Now run the same job. Pressing it on a day the cron has already run will not re-raise a template whose next run date has moved on, but it will raise anything that has since fallen due.

The list is empty. Check the entity switcher first. In production there are genuinely no templates yet.

  • Invoices — where generated invoices appear.
  • Clients — the client each template bills.
  • Cashflow — where expected invoices show as inflows.