> This page location: Docs > Reference > Business Logic > JsonLogic Extensions
> Full index for agents: https://www.semantius.com/llms.txt

# JsonLogic Extensions

> Semantius-specific variables and operators that extend standard JsonLogic for computed fields, validation rules, and row-level select rules.

- **URL**: https://www.semantius.com/docs/business-logic/extensions

---

Semantius extends the [standard JsonLogic spec](https://jsonlogic.com/operations.html) with additional variables and operators tailored to record-level computed fields, validation rules, and row-level select rules. This page documents only the extensions; all standard operators (`if`, `and`, `or`, `var`, `==`, `+`, `map`, `filter`, `reduce`, etc.) work as defined upstream.

---

## Built-in variables

These variables are automatically available inside `computed_fields`, `validation_rules`, and `select_rule` expressions. Access them with the standard `var` operator, e.g. `{"var": "$now"}`.

| Variable   | Description                                                                                                                                                                                                                               |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `$today`   | The current server date (no time component). Useful for date comparisons such as "expires before today".                                                                                                                                  |
| `$now`     | The current server timestamp. Useful for stamping fields like `submitted_at` or comparing against a deadline.                                                                                                                             |
| `$user_id` | The id of the currently authenticated user. `null` when the expression runs outside a user session (for example during a background job).                                                                                                 |
| `$old`     | A snapshot of the record's previous state, available on **update and delete** (where it holds the row being removed). `null` on insert. Use it to compare new vs. old values, e.g. `{"!=": [{"var": "status"}, {"var": "$old.status"}]}`. |
| `$mode`    | The operation being performed: `"insert"`, `"update"`, or `"delete"`. Use it to scope a rule to a particular operation — for example, forbid deletion with `{"!=": [{"var": "$mode"}, "delete"]}`.                                        |

> Built-in variables are scoped to the record being evaluated. They are not written back to the record, they exist only for the duration of the expression.

### Availability by context

`computed_fields` and `validation_rules` run during a mutation and see all five variables. `select_rule` runs during a read and sees only `$today`, `$now`, and `$user_id`. `$old` and `$mode` are not bound in a select rule — a read has no previous image and is not a mutation, so they would be meaningless there.

### Evaluation on delete

`validation_rules` also run on **delete**, evaluated against the row being removed (bound as the root context, with `$old` set to that same row and `$mode` equal to `"delete"`). A rule that evaluates falsy aborts the delete, so a rule such as `{"!=": [{"var": "$mode"}, "delete"]}` forbids deletion outright. `computed_fields` still run on delete, but their output is discarded — there is no surviving row to write it to.

---

## Custom operators

### Bind a local variable: `let`

Evaluates an expression once and exposes the result under a name for the body that follows.

**Form:** `{"let": ["<name>", <value>, <body>]}`

```json
{
  "let": [
    "total",
    {"+": [{"var": "subtotal"}, {"var": "tax"}]},
    {">": [{"var": "total"}, 100]}
  ]
}
```

Use it to avoid recomputing the same sub-expression multiple times or to make complex rules more readable.

---

### Load a related record by id: `set_record`

Fetches an entire record from another entity by its id and binds it to a name, then evaluates the body with that record available.

**Form:** `{"set_record": ["<name>", "<entityName>", <idExpression>, <body>]}`

```json
{
  "set_record": [
    "customer",
    "customers",
    {"var": "customer_id"},
    {"==": [{"var": "customer.status"}, "active"]}
  ]
}
```

The lookup runs through the same access boundary as a normal read. The bound name resolves to `null` when the referenced record does not exist **or** when the current user does not have permission to view it. These two cases are deliberately indistinguishable, so a rule cannot use `set_record` to probe whether a record the caller is not allowed to see exists. Use dotted paths in `var` to read fields of the loaded record (e.g. `{"var": "customer.email"}`).

---

### Check the current user's permission: `has_permission`

Returns `true` when the current user holds the named permission, `false` otherwise. Does not throw.

**Form:** `{"has_permission": "<permission_name>"}`

```json
{"if": [
  {"has_permission": "orders.approve"},
  "approved",
  "pending_review"
]}
```

---

### Enforce a permission: `require_permission`

Returns `true` when the current user holds the named permission, and raises an authorization error otherwise. Use this in validation rules to forbid an action outright.

**Form:** `{"require_permission": "<permission_name>"}`

```json
{"require_permission": "orders.delete"}
```

---

### Check the current user's RACI role: `is_raci_actor`

Returns `true` when the current user holds a role assigned the given RACI letter for the process that governs an `(entity, to_state)` transition, and `false` otherwise (including when no user is authenticated). Does not throw.

The RACI letter is one of `responsible`, `accountable`, `consulted`, or `informed`. The first two arguments name the governed table and the target lifecycle state; together they select the process gate, and the operator then checks the current user's roles against that process's RACI matrix.

**Form:** `{"is_raci_actor": ["<entity>", "<to_state>", "<letter>"]}`

```json
{"if": [
  {"is_raci_actor": ["orders", "approved", "accountable"]},
  "approved",
  {"throw_error": "Only the accountable approver can approve this order"}
]}
```

Use it in `validation_rules` to gate a transition on the caller's RACI role. It is the governance analog of `has_permission` for processes that are driven by a RACI matrix rather than a flat permission name.

---

### Require that a RACI consultation occurred: `has_consultation`

The companion to `is_raci_actor`, this operator enforces the **C** (Consulted) letter of RACI. Where `is_raci_actor` checks _who_ the caller is, `has_consultation` checks _whether the Consulted parties have actually been consulted_ before a record is allowed to move forward.

Returns `true` when an acted _consulted_ event already exists for the record under an `(entity, to_state)` gate, that is, a `raci_events` row with `raci = 'consulted'` and `status = 'acted'`. Returns `false` otherwise. Does not throw.

This backs **C-block** gates: a record may not enter the target state until the RACI actors marked _consulted_ (with `consult_mode = 'block'`) have responded. The third argument is the id of the record being gated, usually read from the current row with `{"var": "id"}`.

**Form:** `{"has_consultation": ["<entity>", "<to_state>", <record_id>]}`

```json
{"if": [
  {"has_consultation": ["orders", "approved", {"var": "id"}]},
  true,
  {"throw_error": "Required consultation has not been completed"}
]}
```

Use it in `validation_rules` to block a transition until the consultation it depends on has been recorded.

---

### Detect a field change on update: `value_changed`

Returns `true` when the named field's value differs from its previous value on update, and `true` on insert (treating a new record as "changed"). Returns `false` only when the field's value is the same as before.

**Form:** `{"value_changed": "<field_name>"}`

```json
{"if": [
  {"value_changed": "status"},
  {"throw_error": "Status changes require manager approval"},
  true
]}
```

---

### Concatenate strings with SQL semantics: `concat`

Concatenates all arguments into a single string. Unlike the standard `cat` operator, `concat` treats `null` arguments as empty strings (mirroring SQL `CONCAT`) and serializes non-string values (numbers, booleans, arrays, objects) to their JSON text form.

**Form:** `{"concat": [<value1>, <value2>, ...]}`

```json
{"concat": ["Order #", {"var": "id"}, " for ", {"var": "customer_name"}]}
```

Choose `concat` when some inputs may be null and you want them to disappear rather than produce the string `"null"`.

---

### Test a string against a regular expression: `is_match`

Tests whether a string value matches a regular expression pattern. Returns `true` on a match, `false` otherwise. `null` values always return `false`.

**Form:** `{"is_match": [<value>, "<pattern>"]}`

```json
{"is_match": [{"var": "email"}, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"]}
```

Useful in validation rules for format checks (email, phone, postal code, SKU prefix) without needing a separate constraint mechanism.

---

### Raise a validation error: `throw_error`

Aborts the current operation with the given error message. Typically used as a branch of an `if` expression to enforce a business rule with a custom message.

**Form:** `{"throw_error": "<message>"}`

```json
{"if": [
  {"<": [{"var": "quantity"}, 1]},
  {"throw_error": "Quantity must be at least 1"},
  true
]}
```

The message surfaces to the caller as a constraint-violation error.

---

## Related

- [JsonLogic](https://www.semantius.com/docs/business-logic/jsonlogic.md)
- [MCP Connectors](https://www.semantius.com/docs/mcp-connectors.md)
- [Docs home](https://www.semantius.com/docs.md)
- [Full index for agents](https://www.semantius.com/llms.txt)
