Objects

Object lifecycles

Per object type, lifecycles run automated actions when entries are created, updated, or become due on a schedule. Configure them in Admin → Objects → Lifecycles, or via the API/MCP lifecycle endpoints.

How it works

A lifecycle config is `{ version: 3, events: [...] }`. Each event has: `id`, `name`, optional `enabled` (default true), `when` (trigger), `constraints` (all must pass), and `actions`. On create/update, matching events run in order. Schedule events are evaluated by a periodic worker sweep (not one-shot enqueue). Actions can send email, create in-app notifications, modify fields, or create related entries. Nested email/notification actions can include links or one-click `trigger` tokens that apply `modify_field` effects when opened.

Scheduled events

Use `when: { type: "schedule", anchorPath, offsetDays }` where `anchorPath` is an own `date`/`dateTime` field or `created_at`/`mutated_at`. `offsetDays` is an integer (default 0; negative allowed). Due instant is the start of the UTC calendar day of the anchor plus the offset. The event is eligible when `now >= due`. Each firing is recorded once per `(entry, event, due_at)`; if the anchor date later changes, a new due key can fire again.

Field bindings & templates

Binding paths reference own fields (`title`) or short relation chains (`author.display_name`, max depth limited by schema). Email/notification subject and title use template segments: `{ kind: "text", text }` or `{ kind: "field", fieldKey }`. HTML bodies may embed `{{fieldKey}}` placeholders for field chips from the rich-text editor.

Constraints

Constraints are `field_compare` (leftPath + op + optional right) or `related_count` (inverse relation count). `field_compare` rights may be a literal, another field path, or `{ kind: "relative_date", path, offsetDays }` for date comparisons. `created_at` / `mutated_at` may be used as date sides. Use `changed` / `changed_to` only on update events. `related_count` counts entries of another type whose relation field points at the current entry (`op`: eq/neq/lt/gt).

Safety guards

The engine rejects configs that form modify-field cycles across events, or that modify the same field twice in one event. `create_entry` may trigger the destination type's `entry_created` events with the same depth/`ruleStack` caps as nested updates. Prefer a single `modify_field` per field per event; chain follow-up work with constraints on the next update.

API & MCP

Read/update lifecycle JSON with `GET/PUT /api/v1/{locale}/{websiteId}/objects/{objectTypeId}/lifecycle`. MCP tools: `get_object_lifecycle`, `update_object_lifecycle`. Use `get_lifecycle_docs` for this guide.

Reference

Event triggers

  • entry_created: Fires after a new entry is successfully created.
  • entry_updated: Fires after an entry is updated. Pair with `changed` / `changed_to` constraints.
  • schedule: Fires from the task worker when now is on/after anchorPath + offsetDays (day granularity).

Actions

  • send_email: Email to `self`, a user-relation path, or legacy email field. Optional nested link/trigger actions.
  • create_notification: In-app notification to a relation_user path. Optional link actions.
  • modify_field: Set a field on the current entry to a literal value.
  • create_entry: Create an entry of another type. Each target field maps from a literal, a compatible source field, or `self` (relation to the triggering entry).

Constraint operators

  • eq / neq: Equal / not equal to literal or field.
  • empty / not_empty: Presence checks.
  • lt / gt: Numeric comparisons (and related_count).
  • before / after: Date/time comparisons (literal, field, or relative_date).
  • changed / changed_to: Update-only: field changed, or changed to a value.
  • related_count: Compare how many inverse-related entries exist (eq/neq/lt/gt).

Relation hops

  • forward: Follow a relation field on the current type.
  • inverse: Hop via another type that points at this entry.
  • match: Match another type on a shared field value.

Examples

Welcome email after approval

On update, when `approved` changes to true, email the member and include a login link.

{
  "version": 3,
  "events": [
    {
      "id": "welcome_after_approval",
      "name": "Welcome member",
      "when": {
        "type": "entry_updated"
      },
      "constraints": [
        {
          "type": "field_compare",
          "leftPath": "approved",
          "op": "changed_to",
          "right": {
            "kind": "literal",
            "value": true
          }
        }
      ],
      "actions": [
        {
          "type": "send_email",
          "emailFieldKey": "self",
          "subject": [
            {
              "kind": "text",
              "text": "Welcome to "
            },
            {
              "kind": "field",
              "fieldKey": "display_name"
            }
          ],
          "bodyHtml": "<p>Your account is ready.</p>",
          "actions": [
            {
              "type": "link",
              "label": "Sign in",
              "pathTemplate": "/login"
            }
          ]
        }
      ]
    }
  ]
}

Email verification with trigger

On create, send a verification email whose button is a trigger token that sets `email_verified` (and optionally `approved`) when clicked.

{
  "version": 3,
  "events": [
    {
      "id": "send_email_verification",
      "name": "Send email verification",
      "when": {
        "type": "entry_created"
      },
      "constraints": [],
      "actions": [
        {
          "type": "send_email",
          "emailFieldKey": "self",
          "subject": [
            {
              "kind": "text",
              "text": "Verify your email"
            }
          ],
          "bodyHtml": "<p>Click below to verify.</p>",
          "actions": [
            {
              "type": "trigger",
              "label": "Verify your email",
              "tokenTtlMinutes": 1440,
              "effects": [
                {
                  "type": "modify_field",
                  "fieldKey": "email_verified",
                  "value": true
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Notify author on status change

Create an in-app notification for a related user when a status field changes.

{
  "version": 3,
  "events": [
    {
      "id": "notify_status",
      "name": "Notify author of status",
      "when": {
        "type": "entry_updated"
      },
      "constraints": [
        {
          "type": "field_compare",
          "leftPath": "status",
          "op": "changed"
        }
      ],
      "actions": [
        {
          "type": "create_notification",
          "targetPath": [
            {
              "kind": "forward",
              "fieldKey": "author"
            }
          ],
          "title": [
            {
              "kind": "text",
              "text": "Status updated"
            }
          ],
          "bodyHtml": "<p>Your item status changed.</p>",
          "actions": [
            {
              "type": "link",
              "label": "View",
              "pathTemplate": "/items/[id]",
              "appendEntryHash": true
            }
          ]
        }
      ]
    }
  ]
}

Schedule first related entry

When `first_service_after` is due and no service appointments point at this order yet, create one with mapped fields.

{
  "version": 3,
  "events": [
    {
      "id": "schedule_first_service",
      "name": "Create first service appointment",
      "when": {
        "type": "schedule",
        "anchorPath": "first_service_after",
        "offsetDays": 0
      },
      "constraints": [
        {
          "type": "related_count",
          "objectTypeSlug": "service_appointment",
          "fieldKey": "order",
          "op": "eq",
          "value": 0
        }
      ],
      "actions": [
        {
          "type": "create_entry",
          "objectTypeSlug": "service_appointment",
          "fields": [
            {
              "fieldKey": "order",
              "source": {
                "kind": "self"
              }
            },
            {
              "fieldKey": "scheduled_for",
              "source": {
                "kind": "field",
                "path": "first_service_after"
              }
            },
            {
              "fieldKey": "status",
              "source": {
                "kind": "literal",
                "value": "planned"
              }
            }
          ]
        }
      ]
    }
  ]
}

Notes

  • Omit `enabled` or set `enabled: true` to keep an event active; set `enabled: false` to soft-disable.
  • Locale overlays can localize event names, email subject/body, and action labels per website locale.
  • Trigger tokens are time-limited (`tokenTtlMinutes`) and apply effects once when redeemed.
  • Schedule events use day granularity only; hour/minute offsets are out of scope for v1.