Actions
Custom API endpoints for business logic beyond CRUD. One file per action, in a feature's actions/ directory.
Actions are custom API endpoints for business logic beyond CRUD operations. They enable workflows, integrations, and complex operations.
Overview
Quickback supports two types of actions:
| Aspect | Record-Based | Standalone |
|---|---|---|
| Route | {METHOD} /:id/{actionName} | Custom path (required) |
| Record fetching | Automatic | None (record is undefined) |
| Firewall applied | Yes | No |
| Preconditions | Supported via access.record | Not applicable |
| Response types | JSON only | JSON, stream, file |
| Use case | Advance application, reject candidate | AI chat, bulk import from job board, webhooks |
Standalone actions are signalled by the presence of a path: field in the action config. Record-based actions omit path and bind to the feature's primary table.
Where do actions live?
One file per action. Each action is its own module under the feature's
actions/ directory:
features/applications/
├── applications.ts ← defineTable (primary table)
├── candidates.ts ← defineTable (sibling table)
├── actions/
│ ├── advance.ts ← defineAction, binds to applications
│ ├── reject.ts ← defineAction, binds to applications
│ ├── stats.ts ← defineAction, standalone (has `path:`)
│ └── candidates/
│ └── disqualify.ts ← defineAction, binds to candidates
└── lib/
└── inputs.ts ← shared schemas (optional, copied verbatim)| Layout | Binds to |
|---|---|
actions/<name>.ts | The feature's primary table (table file matching the feature name), or standalone if path: is set. |
actions/<table>/<name>.ts | The sibling table file <table>.ts at the feature root. Standalone actions inside a subdir ignore the binding. |
The action's filename (sans .ts) is the action name and the URL segment, and
it must be a valid camelCase identifier — sendMessage.ts, not
send-message.ts (hyphenated names are a compile error, since the name becomes
a generated identifier).
Uniqueness is enforced per (bound table, name) pair — sibling-bound
actions on different tables may share a verb (actions/episodes/publish.ts
and actions/shows/publish.ts mount at /episodes/:id/publish and
/shows/:id/publish and compile cleanly). The compiler errors only when
two action files would mount at the same URL — for example, a flat
actions/foo.ts (binds to primary) plus actions/<primary>/foo.ts, or
two standalones with the same (method, path). Standalone actions are
unique per (method, path).
Tableless features have no top-level *.ts files; every action under
actions/ must be standalone (have path:). Use this for utility endpoints
like webhooks, reports, or integrations that don't operate on a specific
record.
Retired layouts
These are rejected at load time with migration guidance:
actions.ts(bundled multi-action file)_feature.ts(shared schemas + non-table-bound actions)handlers/(separate handler-file directory)defineActions(table, {...})(the multi-action factory)defineActions(null, {...})(tableless variant)*-actions.ts/*.actions.tsfilename patterns
For shared Zod schemas or helpers, put them under <feature>/lib/ and import
from each action file. Files under lib/ are copied verbatim into the
generated output.
The rest of this section
- Defining actions — the action file, its configuration options, protected fields, and refs
- Record and standalone actions — the two shapes, and what
path:changes - Access — roles, record conditions, context substitution, relationship-roles
- Scoped database — the firewalled
dbhandle, unsafe mode, raw SQL policy, sharing code - Examples — end-to-end action files
Related: Transitions for state-machine policy, Triggers for after-commit hooks, and the Actions API for calling an action over HTTP.
Triggers - Postgres-Style Table Triggers
Run data rules and side-effects on writes. SQL triggers compile to real SQLite triggers on D1; handler triggers run as application hooks at the write chokepoint.
Defining Actions
The action file, its configuration options, protected fields, and scoped foreign-key inputs.