---
title: Lifecycles
description: "Automate emails, in-app and browser push notifications, field updates, timed schedules, and create-entry actions when entries are created or changed (schema-driven; up to 20 events per object type)."
---

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.

```json
{
  "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.

```json
{
  "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.

```json
{
  "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.

```json
{
  "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.
