ObjectStackObjectStack

Action

Action protocol schemas

Action Parameter Schema

Defines inputs required before executing an action.

Two declaration modes:

  1. Field-backed (preferred) — reference an existing object field; the runtime resolves the field's label (i18n), type, validation rules, options, placeholder, help text, and widget mapping from object metadata. Cross-object references use objectOverride.

    params: [
      { field: 'email' },                                 // same object
      { field: 'role', objectOverride: 'sys_member' },    // different object
    ]
  2. Inline (legacy / bespoke) — declare name, label, type etc. inline when no matching object field exists. Inline values may also be used alongside field to override individual properties. A lookup / master_detail param declared this way MUST name its target object via reference — there is no field to inherit it from:

    params: [
      { name: 'inspector', label: 'Inspector', type: 'lookup', reference: 'sys_user' },
    ]

name is required unless field is provided (in which case it defaults to the field name and is used as the request-body key).

Source: packages/spec/src/ui/action.zod.ts

TypeScript Usage

import { ActionSchema, ActionAiSchema, ActionLocationSchema, ActionParamSchema, ActionType, InlineActionSchema } from '@objectstack/spec/ui';
import type { Action, ActionAi, ActionLocation, ActionParam, ActionType, InlineAction } from '@objectstack/spec/ui';

// Validate data
const result = ActionSchema.parse(data);

Action

Properties

PropertyTypeRequiredDescription
namestringMachine name (lowercase snake_case)
labelstring | Record<string, string>Display label
descriptionstring | Record<string, string>optionalExplanatory line shown under the title in the action's param dialog. Carries the confirm question for an action that collects params (one dialog, not two — #7278). Not the LLM-facing ai.description.
objectNamestringoptionalTarget object this action belongs to. When set, the action is auto-merged into the object's actions array by defineStack().
iconstringoptionalIcon name
locationsEnum<'list_toolbar' | 'list_item' | 'record_header' | 'record_more' | 'record_related' | 'record_section'>[]optionalLocations where this action is visible
componentEnum<'action:button' | 'action:icon' | 'action:menu' | 'action:group'>optionalVisual component override
typeEnum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'>optionalAction functionality type
targetstringoptionalURL, Script Name, Flow ID, or API Endpoint. Supports ${param.X} and ${ctx.X} interpolation.
openInEnum<'self' | 'new-tab'>optionalFor type:'url' — where to open target. 'new-tab' opens a new browser tab; 'self' navigates in place. When omitted, external/absolute URLs open in a new tab and relative URLs navigate in place. Static execution option — keep it OUT of params (which is user-input-collection only).
body{ language: 'expression'; source: string } | { language: 'js'; source: string; capabilities?: Enum<'api.read' | 'api.write' | 'api.transaction' | 'crypto.uuid' | 'log'>[]; timeoutMs?: integer; … }optionalAction body — expression (L1) or sandboxed JS (L2). Only used when type is script.
executeneveroptional[REMOVED] execute was removed in @objectstack/spec 17 (#3855) — use target. Rename the key; the value (a handler / flow / URL ref) is unchanged. Run os migrate meta --from 16 to rewrite existing sources automatically.
params{ name?: string; field?: string; objectOverride?: string; label?: string | Record<string, string>; … }[]optionalInput parameters required from user — an ActionParam[] DEFINITION array, never a payload map (a static request body goes in bodyExtra).
variantEnum<'primary' | 'secondary' | 'danger' | 'ghost' | 'link'>optionalButton visual variant for styling (primary = highlighted, danger = destructive, ghost = transparent)
ordernumberoptionalSort order within a location group (lower = higher). Promotes/demotes an action toward the record_header primary button; stable, so actions without order keep their registration order.
confirmTextstring | Record<string, string>optionalConfirmation message before execution. On a registered action, pairing this with a non-empty params is refused (#7428) — that opens a second dialog for one decision; put the question on description instead. Correct on a param-LESS action, where the confirm is the only dialog there is.
successMessagestring | Record<string, string>optionalSuccess message to show after execution
errorMessagestring | Record<string, string>optionalError message to show when the action fails (overrides the raw error).
refreshAfterbooleanoptionalRefresh view after execution
undoablebooleanoptionalOffer an Undo affordance after this single-record update action succeeds.
resultDialog{ title?: string | Record<string, string>; description?: string | Record<string, string>; acknowledge?: string | Record<string, string>; format?: Enum<'qrcode' | 'code-list' | 'secret' | 'text' | 'json'>; … }optionalRender API response in a one-shot reveal dialog (suppresses successMessage when set).
visibleboolean | string | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object }optionalVisibility predicate — true/false literal, CEL string, or {dialect, source} envelope. The action is offered when it evaluates TRUE. Omit = always visible.
requiresFeatureEnum<'twoFactor' | 'organization' | 'multiOrgEnabled' | 'degradedTenancy' | 'oidcProvider' | 'sso' | 'ssoEnforced' | 'deviceAuthorization' | 'admin' | 'phoneNumber' | 'phoneNumberOtp'>optionalPublic auth feature flag gating this action; lowered into visible at parse time.
disabledboolean | string | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object }optionalDisabled predicate — true/false literal, CEL string, or {dialect, source} envelope. The action is shown but refused when it evaluates TRUE. Omit = never disabled.
requiredPermissionsstring[]optional[ADR-0066 D4] Capabilities required to invoke this action. Enforced with 403 on the platform action route (script/flow/modal + MCP) and mirrored as a UI hide; a type: api action pointed at a custom endpoint must re-check it there.
shortcutneveroptional[REMOVED] action.shortcut was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it never triggered anything: no keydown listener feeds ActionEngine.getShortcuts(), and objectui's keyboard stack (useKeyboardShortcuts) is hand-registered and never consults action metadata. Delete the key. For a real shortcut, register the key in the Console keyboard stack and have its handler invoke the action by name. Run os migrate meta --from 16 to rewrite existing sources automatically.
bulkEnabledneveroptional[REMOVED] action.bulkEnabled was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — the multi-select toolbar is driven by the LIST VIEW's bulkActions / bulkActionDefs, never by this flag, so setting it changed nothing. Delete the key and declare the action in the view's bulkActions instead. Run os migrate meta --from 16 to rewrite existing sources automatically.
ai{ exposed?: boolean; description?: string; category?: Enum<'data' | 'action' | 'flow' | 'integration' | 'vector_search' | 'analytics' | 'utility'>; paramHints?: Record<string, object>; … }optionalAI exposure (opt-in). Set ai.exposed=true + ai.description to make this callable by agents.
recordIdParamstringoptionalBody key to inject the row id into when running from a list_item context.
recordIdFieldstringoptionalRow field whose value seeds recordIdParam. Defaults to "id".
bodyShape'flat' | { wrap: string }optionalBody wrapping: flat (default) or { wrap: key } to nest user-collected params under a key.
methodEnum<'POST' | 'PATCH' | 'PUT' | 'DELETE'>optionalHTTP method for type:"api" actions. Defaults to POST.
bodyExtraRecord<string, any>optionalStatic request-body fields for a type:"api" action, merged last (overrides user params). {{page.<var>}} tokens are resolved by the runtime. This — not params — is where a payload goes.
modeEnum<'create' | 'edit' | 'delete' | 'custom'>optionalSemantic mode of the action.
opensInNewTabbooleanoptionalOpen the action result in a new tab. The renderer pre-opens the tab synchronously on click (popup-blocker-safe) and navigates it to the handler's redirectUrl.
newTabUrlstringoptionalDirect new-tab URL template ({recordId} placeholder). When set with opensInNewTab, the renderer navigates the pre-opened tab here immediately — no action POST. The endpoint must enforce auth itself.
aria{ ariaLabel?: string | Record<string, string>; ariaDescribedBy?: string; role?: string }optionalARIA accessibility attributes
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

ActionAi

Properties

PropertyTypeRequiredDescription
exposedbooleanExpose this action to AI agents. Requires description when true.
descriptionstringoptionalLLM-facing description (≥40 chars). Required when exposed.
categoryEnum<'data' | 'action' | 'flow' | 'integration' | 'vector_search' | 'analytics' | 'utility'>optionalTool category override (defaults to "action").
paramHintsRecord<string, { description?: string; enum?: (string | number)[]; examples?: any[] }>optionalPer-parameter AI hints keyed by param name.
outputSchemaRecord<string, any>optionalJSON Schema for the action return value.
requiresConfirmationbooleanoptionalOverride HITL confirmation for AI invocations.

ActionLocation

Allowed Values

  • list_toolbar
  • list_item
  • record_header
  • record_more
  • record_related
  • record_section

ActionParam

Properties

PropertyTypeRequiredDescription
namestringoptional
fieldstringoptionalSnake case identifier (lowercase with underscores only)
objectOverridestringoptionalSnake case identifier (lowercase with underscores only)
labelstring | Record<string, string>optionalDisplay label — the default-language string, or an inline locale map ({ en, "zh-CN" }) resolved at render time
typeEnum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | 'markdown' | 'html' | 'richtext' | 'number' | 'currency' | 'percent' | 'date' | … +35 more>optional
requiredbooleanoptional
options{ label: string | Record<string, string>; value: string; visibleWhen?: string | object }[]optional
placeholderstringoptional
helpTextstringoptional
defaultValueanyoptional
multiplebooleanoptionalAllow multiple values (array value shape); mirrors FieldSchema.multiple.
acceptstring[]optionalAccepted upload types (MIME types / extensions) for file/image params.
maxSizeintegeroptionalMax upload size in bytes for file/image params.
referencestringoptionalReference target object for inline lookup/master_detail params; mirrors FieldSchema.reference.
defaultFromRowbooleanoptional
visiblestring | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object }optionalParam visibility predicate (CEL); omits the param when false.
requiresFeatureEnum<'twoFactor' | 'organization' | 'multiOrgEnabled' | 'degradedTenancy' | 'oidcProvider' | 'sso' | 'ssoEnforced' | 'deviceAuthorization' | 'admin' | 'phoneNumber' | 'phoneNumberOtp'>optionalPublic auth feature flag gating this param; lowered into visible at parse time.

Allowed Values: ActionParam.type

  • text
  • textarea
  • email
  • url
  • phone
  • password
  • secret
  • markdown
  • html
  • richtext
  • number
  • currency
  • percent
  • date
  • datetime
  • time
  • boolean
  • toggle
  • select
  • multiselect
  • radio
  • checkboxes
  • lookup
  • master_detail
  • tree
  • user
  • image
  • file
  • avatar
  • video
  • audio
  • formula
  • summary
  • autonumber
  • composite
  • repeater
  • record
  • location
  • address
  • code
  • json
  • color
  • rating
  • slider
  • signature
  • qrcode
  • progress
  • tags
  • vector

ActionType

Allowed Values

  • script
  • url
  • modal
  • flow
  • api
  • form

InlineAction

Properties

PropertyTypeRequiredDescription
typeEnum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'>optionalAction functionality type
namestringoptionalMachine name (lowercase snake_case)
labelstring | Record<string, string>optionalDisplay label
targetstringoptionalURL, Script Name, Flow ID, or API Endpoint. Supports ${param.X} and ${ctx.X} interpolation.
openInEnum<'self' | 'new-tab'>optionalFor type:'url' — where to open target. 'new-tab' opens a new browser tab; 'self' navigates in place. When omitted, external/absolute URLs open in a new tab and relative URLs navigate in place. Static execution option — keep it OUT of params (which is user-input-collection only).
methodEnum<'POST' | 'PATCH' | 'PUT' | 'DELETE'>optionalHTTP method for type:"api" actions. Defaults to POST.
params{ name?: string; field?: string; objectOverride?: string; label?: string | Record<string, string>; … }[]optionalInput parameters required from user — an ActionParam[] DEFINITION array, never a payload map (a static request body goes in bodyExtra).
bodyExtraRecord<string, any>optionalStatic request-body fields for a type:"api" action, merged last (overrides user params). {{page.<var>}} tokens are resolved by the runtime. This — not params — is where a payload goes.
confirmTextstring | Record<string, string>optionalConfirmation message before execution. On a registered action, pairing this with a non-empty params is refused (#7428) — that opens a second dialog for one decision; put the question on description instead. Correct on a param-LESS action, where the confirm is the only dialog there is.
successMessagestring | Record<string, string>optionalSuccess message to show after execution
errorMessagestring | Record<string, string>optionalError message to show when the action fails (overrides the raw error).
refreshAfterbooleanoptionalRefresh view after execution
opensInNewTabbooleanoptionalOpen the action result in a new tab. The renderer pre-opens the tab synchronously on click (popup-blocker-safe) and navigates it to the handler's redirectUrl.

On this page