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
| Control | What it does |
|---|---|
| New template | Opens the form |
| Trigger Now | Runs 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:
| Field | Notes |
|---|---|
| Client | Clients for the selected entity |
| Amount | The invoice value |
| Frequency | weekly, monthly or quarterly |
| Start date | Becomes 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_invoicesrow withis_active: trueandnext_run_dateset to the start date you chose. - The active toggle updates
is_activeon 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 scheduledGETuses 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.