Bulk Action
Bulk Action protocol schemas
Bulk Action Schemas
The vocabulary of a list view's bulkActionDefs — one entry per button in
the multi-select toolbar. Use a def for a mass data-plane mutation that no
action expresses (operation: 'update' with a patch, or 'delete'), or for
an operation: 'custom' + execution: 'aggregate' entry that dispatches the
action it NAMES once for the whole selection.
For the per-record dispatch, name the action in the view's
bulkActions: ['<name>'] instead — the bare-string form, promoted with the
action's own label, params and visible.
Source: packages/spec/src/ui/bulk-action.zod.ts
TypeScript Usage
import { BulkActionDefSchema, BulkActionExecutionSchema, BulkActionOperationSchema, BulkActionParamSchema } from '@objectstack/spec/ui';
import type { BulkActionDef, BulkActionExecution, BulkActionOperation, BulkActionParam } from '@objectstack/spec/ui';
// Validate data
const result = BulkActionDefSchema.parse(data);BulkActionDef
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Stable identifier — the audit-log action key, and (for an aggregate def) the name of the object action to dispatch. |
| label | string | optional | Button + dialog-header text. Plain string: an authored def is not i18n-resolved (declare a real action and name it in bulkActions to get localization). |
| icon | string | optional | Lucide icon name (e.g. "user-check", "trash-2"). |
| variant | Enum<'primary' | 'secondary' | 'danger' | 'ghost' | 'outline'> | optional | Visual treatment of the button. |
| operation | Enum<'update' | 'delete' | 'custom'> | ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see execution). |
| execution | Enum<'perRecord' | 'aggregate'> | optional | For operation: 'custom' — 'aggregate' dispatches the named action ONCE for the whole selection, carrying every id in params._selectedIds (objectui#3139). Required on a custom def: the per-record form is declared as bulkActions: ['<name>'] instead. |
| patch | Record<string, any> | optional | For operation: 'update' — static field values applied to every selected record, merged UNDER the user-supplied params so a fixed value can be declared without exposing it in the dialog. |
| params | ({ name: string; label?: string; help?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>; … } & Record<string, any>)[] | optional | Inputs collected once before the run. Omit to skip the params step and go straight to confirm. |
| confirmText | string | optional | Confirmation text shown above the affected-record summary. |
| confirmLabel | string | optional | Custom Confirm button label (default: "Run"). |
| visible | string | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object } | optional | Eligibility predicate (CEL) — a string or a {dialect, source} envelope, i.e. action.visible without its boolean-literal arm (#5970): a per-record predicate has nothing to say as a constant. Evaluated once PER SELECTED RECORD with that record bound: the button is offered when at least one passes, the run covers only those, and the rest are reported as skipped. A record-free predicate (features.x, current_user.y) therefore behaves as a plain button-level gate. Fail-closed — a predicate that faults excludes the record. |
| requiredPermissions | string[] | optional | [ADR-0066 D4] Capability gate on the button, action.requiredPermissions semantics verbatim: absent or empty always passes, several are AND-ed, and a client that cannot resolve the caller's capabilities fails OPEN (the server stays the authority). This key exists for INLINE defs — notably the update/delete data-plane forms, which dispatch no action and so have nothing to inherit a gate from; a def promoted from bulkActions: ['<name>'] (or an aggregate def naming a declared action) inherits the action's own declaration instead. On a data-plane def the gate governs visibility only — the write itself is still authorized by the data API's object permissions and server hooks. |
| maxRecords | integer | optional | Selection size above which the run is blocked. Set it on defs whose server work is expensive — an aggregate def carries every selected id in one request. |
| batchSize | integer | optional | Records per executor batch (default 200). Data-plane operations only — an aggregate run is a single call by definition. |
BulkActionExecution
Allowed Values
perRecordaggregate
BulkActionOperation
Allowed Values
updatedeletecustom
BulkActionParam
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Param key — becomes params[name] in the patch / action params bag. |
| label | string | optional | Field label in the dialog. Plain string: an authored def is not i18n-resolved (see module header). |
| help | string | optional | Help text under the field. (An ActionParam spells this helpText — known divergence, module header.) |
| type | Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | 'markdown' | 'html' | 'richtext' | 'number' | 'currency' | 'percent' | 'date' | … +35 more> | ✅ | Field widget to render, from the standard field-type vocabulary (text/number/select/lookup/date/…). |
| required | boolean | optional | Blocks the Confirm button until a value is present. |
| default | any | optional | Value applied when the dialog opens. (An ActionParam spells this defaultValue.) |
| options | ({ label: string; value: string | number | boolean } & Record<string, any>)[] | optional | Static options for select-style widgets. Each entry is { label, value } plus any extra widget config — the entry is open (.passthrough()) because the renderer forwards unknown option keys to the field widget, which reads color / icon / disabled / visibleWhen beyond the declared pair. |
| object | string | optional | Target object for a lookup widget. (An ActionParam spells this reference.) |
| labelField | string | optional | Related-object field used as the option label for a lookup widget (defaults to name/full_name/email/id). |
| multiple | boolean | optional | Allow picking multiple values — the param value becomes an array and is written to the patch as-is. |
| placeholder | string | optional | Placeholder text. |
Allowed Values: BulkActionParam.type
texttextareaemailurlphonepasswordsecretmarkdownhtmlrichtextnumbercurrencypercentdatedatetimetimebooleantoggleselectmultiselectradiocheckboxeslookupmaster_detailtreeuserimagefileavatarvideoaudioformulasummaryautonumbercompositerepeaterrecordlocationaddresscodejsoncolorratingslidersignatureqrcodeprogresstagsvector