---
title: "Effects (EffectDecl)"
description: "Generated from the doc schema (CatalogDocSchema, @canonify/kernel-catalog/doc): each field’s type, default and description are the schema’s own, and each example is a bundle file that readBundle…"
order: 4
id: "model/effect"
---

# Effects (EffectDecl)

Generated from the doc schema (`CatalogDocSchema`, @canonify/kernel-catalog/doc): each field’s type, default and description are the schema’s own, and each example is a bundle file that readBundle decodes. A field’s Example is its value in one of those examples.

## EffectDecl {#effectdecl}

An effect: one write, delivery, name or call, in order.

One of, told apart by `op`:

- [EffectDecl.create](effect.md#effectdecl.create)
- [EffectDecl.update](effect.md#effectdecl.update)
- [EffectDecl.delete](effect.md#effectdecl.delete)
- [EffectDecl.transition](effect.md#effectdecl.transition)
- [EffectDecl.emit](effect.md#effectdecl.emit)
- [EffectDecl.let](effect.md#effectdecl.let)
- [EffectDecl.allocate](effect.md#effectdecl.allocate)
- [EffectDecl.call](effect.md#effectdecl.call)
- [EffectDecl.forEach](effect.md#effectdecl.foreach)

## EffectDecl.create {#effectdecl.create}

Create a row.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"create"` | yes | — | `"create"` | Create a row. |
| `when` | [Expr](shapes.md#expr) | no | — | `"input.amount > 0"` | The effect runs only while this CX condition holds; absent = always. Unknown refuses the call. |
| `type` | [TypeId](shapes.md#typeid) | yes | — | `"ty_9d4r7t1x5b"` | The type of the new row. |
| `set` | Record<[PropId](shapes.md#propid), [Expr](shapes.md#expr)> | yes | — | `{"pr_0q2s4v6x8z":"this","pr_7e9g1j3m5p":"input.amount"}` | The CX value of each property it sets, by property id. |
| `as` | [Ident](shapes.md#ident) | no | — | `"line"` | The name later effects and outputs read the new row by. |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "create",
  "type": "ty_9d4r7t1x5b",
  "set": { "pr_0q2s4v6x8z": "this", "pr_7e9g1j3m5p": "input.amount" },
  "as": "line"
}
```

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "create",
  "type": "ty_9d4r7t1x5b",
  "when": "input.amount > 0",
  "set": { "pr_0q2s4v6x8z": "this", "pr_7e9g1j3m5p": "input.amount" }
}
```

## EffectDecl.update {#effectdecl.update}

Update a row.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"update"` | yes | — | `"update"` | Update a row. |
| `when` | [Expr](shapes.md#expr) | no | — | `"input.note != ''"` | The effect runs only while this CX condition holds; absent = always. Unknown refuses the call. |
| `target` | [Expr](shapes.md#expr) | yes | — | `"this"` | The row it updates: a CX ref. |
| `set` | Record<[PropId](shapes.md#propid), [Expr](shapes.md#expr)> | yes | — | `{"pr_4p6r8t0v2x":"input.note"}` | The CX value of each property it sets, by property id. |
| `omitAbsent` | `true` | no | — | `true` | A property whose value is absent is left as it is, not cleared. |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "update",
  "target": "this",
  "when": "input.note != ''",
  "set": { "pr_4p6r8t0v2x": "input.note" },
  "omitAbsent": true
}
```

## EffectDecl.delete {#effectdecl.delete}

Delete a row.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"delete"` | yes | — | `"delete"` | Delete a row. |
| `when` | [Expr](shapes.md#expr) | no | — | `"isSet(input.line)"` | The effect runs only while this CX condition holds; absent = always. Unknown refuses the call. |
| `target` | [Expr](shapes.md#expr) | yes | — | `"input.line"` | The row it deletes: a CX ref. |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{ "op": "delete", "target": "input.line", "when": "isSet(input.line)" }
```

## EffectDecl.transition {#effectdecl.transition}

Move a row along its lifecycle.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"transition"` | yes | — | `"transition"` | Move a row along its lifecycle. |
| `when` | [Expr](shapes.md#expr) | no | — | `"input.cancel"` | The effect runs only while this CX condition holds; absent = always. Unknown refuses the call. |
| `target` | [Expr](shapes.md#expr) | yes | — | `"this"` | The row it moves: a CX ref. |
| `lifecycle` | [FsmId](shapes.md#fsmid) | yes | — | `"lc_2k4m6p8r0t"` | The lifecycle. |
| `transition` | [TransitionId](shapes.md#transitionid) | yes | — | `"tn_4g6j8m0p2r"` | The transition of that lifecycle. |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "transition",
  "target": "this",
  "when": "input.cancel",
  "lifecycle": "lc_2k4m6p8r0t",
  "transition": "tn_6s8v0x2z4b"
}
```

## EffectDecl.emit {#effectdecl.emit}

Send a delivery after the commit.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"emit"` | yes | — | `"emit"` | Send a delivery after the commit: the only I/O. |
| `when` | [Expr](shapes.md#expr) | no | — | `"isSet(input.notify)"` | The effect runs only while this CX condition holds; absent = always. Unknown refuses the call. |
| `to` | string (matching `^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$`) | yes | — | `"webhook.post"` | A delivery operation: the only I/O, sent after commit (e.g. webhook.post). |
| `input` | Record<string, [Expr](shapes.md#expr)> | yes | — | `{"url":"'https://example.com/hooks/orders'","body":"id"}` | The CX value of each input of the delivery operation. |
| `maps` | Record<string, Record<string (matching `^[A-Za-z][A-Za-z0-9_]*$`), [Expr](shapes.md#expr)>> | no | — | `{"params":{"order_no":"number","total":"total"}}` | The inputs that hold an object (e.g. params, or a webhook body): each field name with its CX value. |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "emit",
  "to": "webhook.post",
  "when": "isSet(input.notify)",
  "input": { "url": "'https://example.com/hooks/orders'", "body": "id" }
}
```

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "emit",
  "to": "email.send_template",
  "input": { "recipient": "customer.email", "template_id": "'order_sent'" },
  "maps": { "params": { "order_no": "number", "total": "total" } }
}
```

## EffectDecl.let {#effectdecl.let}

Name a value for the effects after it.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"let"` | yes | — | `"let"` | Name a value. |
| `name` | [Ident](shapes.md#ident) | yes | — | `"net"` | The name later effects read it by. |
| `value` | [Expr](shapes.md#expr) | yes | — | `"total * 0.8"` | The CX value. |
| `rights` | [Elevated](action.md#elevated) | no | — | (see the example) | A widened read: the value is one aggregate over rows the invoker may not read. |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{ "op": "let", "name": "net", "value": "total * 0.8" }
```

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "let",
  "name": "open_orders",
  "value": "count(customer.orders)",
  "rights": {
    "discloses": "count",
    "reason": { "text": "The order count is shown to every clerk." }
  }
}
```

## EffectDecl.allocate {#effectdecl.allocate}

Place an amount over rows in order.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"allocate"` | yes | — | `"allocate"` | Place an amount over rows in order, each row up to its cap. |
| `when` | [Expr](shapes.md#expr) | no | — | `"input.amount > 0"` | The effect runs only while this CX condition holds; absent = always. Unknown refuses the call. |
| `amount` | [Expr](shapes.md#expr) | yes | — | `"input.amount"` | The amount it places: a CX number, quantity or money. Blank refuses the call; zero places nothing. |
| `over` | [Expr](shapes.md#expr) | yes | — | `"customer.orders"` | The rows it places the amount over: a CX collection of rows. |
| `each` | [Ident](shapes.md#ident) | yes | — | `"o"` | The name the cap and the order keys read each row by. |
| `cap` | [Expr](shapes.md#expr) | yes | — | `"o.total"` | The most a row takes: a CX value of the amount’s type over the row. Blank or zero takes nothing. |
| `order` | [SortKey](effect.md#sortkey)[] (at least one) | yes | — | `[{"by":"o.created_at","desc":true}]` | The order the rows take their shares in: the first key decides, then the next; ties by the row. |
| `remainder` | `"refuse"` \| `"keep"` | yes | — | `"keep"` | refuse: an amount the rows cannot hold refuses the call; keep: what is left stays unplaced. |
| `as` | [Ident](shapes.md#ident) | yes | — | `"paid"` | The name later effects and outputs read the shares by: the rows that took a share, each with its share. |
| `message` | [MsgRef](shapes.md#msgref) | no | — | (see the example) | What the refusal says when the rows cannot hold the amount (remainder refuse only). |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "allocate",
  "when": "input.amount > 0",
  "amount": "input.amount",
  "over": "customer.orders.where(o => o.status == 'placed')",
  "each": "o",
  "cap": "o.total",
  "order": [{ "by": "o.created_at" }, { "by": "o.total", "desc": true }],
  "remainder": "refuse",
  "as": "paid",
  "message": {
    "text": "The open orders take only {placed} of the payment.",
    "params": { "placed": "paid.sum(s => s.share)" }
  }
}
```

## EffectDecl.call {#effectdecl.call}

Run a shared effects function.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"call"` | yes | — | `"call"` | Run an effects function. |
| `when` | [Expr](shapes.md#expr) | no | — | `"input.close"` | The effect runs only while this CX condition holds; absent = always. Unknown refuses the call. |
| `fn` | [FnId](shapes.md#fnid) | yes | — | `"fn_5j7m9p1r3t"` | The effects function. |
| `with` | Record<[Ident](shapes.md#ident), [Expr](shapes.md#expr)> | yes | — | `{"order":"this"}` | The CX value of each parameter. |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "call",
  "fn": "fn_5j7m9p1r3t",
  "when": "input.close",
  "with": { "order": "this" }
}
```

## EffectDecl.forEach {#effectdecl.foreach}

Run effects once per item of a bounded collection.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `op` | `"forEach"` | yes | — | `"forEach"` | Run the body once per item of a collection. |
| `over` | [Expr](shapes.md#expr) | yes | — | `"input.lines"` | The collection: a CX collection, at most limit items. |
| `as` | [Ident](shapes.md#ident) | yes | — | `"item"` | The name the body reads the item by. |
| `limit` | integer (greater than 0) | yes | — | `50` | The most items it runs over; more is invoke.foreach_limit. Required: every write has a static bound (P7). |
| `preconditions` | [ItemPreconditionDecl](action.md#itempreconditiondecl)[] | no | — | (see the example) | The conditions each item needs, checked before the body runs for it. |
| `body` | [EffectDecl](effect.md#effectdecl)[] | yes | — | (see the example) | The effects run for each item. |

An item of `effects` in an action file (or in an effects function’s `body`).

```json
{
  "op": "forEach",
  "over": "input.lines",
  "as": "item",
  "limit": 50,
  "preconditions": [
    {
      "id": "positive_amount",
      "holds": "item.amount > 0",
      "severity": "refuse",
      "message": { "text": "Each line needs an amount above zero." }
    }
  ],
  "body": [
    {
      "op": "create",
      "type": "ty_9d4r7t1x5b",
      "set": { "pr_0q2s4v6x8z": "this", "pr_7e9g1j3m5p": "item.amount" }
    }
  ]
}
```

## SortKey {#sortkey}

One key of the order.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `by` | [Expr](shapes.md#expr) | yes | — | `"o.created_at"` | The CX key over the row; a blank key sorts first ascending, last descending. |
| `desc` | `true` | no | — | `true` | Descending; absent = ascending. |

An item of `order` in an allocate effect of an action file.

```json
{ "by": "o.created_at", "desc": true }
```
