Abilities
An ability is a named action with an input schema, a permission and a handler. Everything in Lettern is built on them.
2 min readView as Markdown
On this page
#Anatomy
PHP
$this->cms()->abilities()->register('shop/price-update', [
'label' => 'Update a price',
'description' => 'Set the price of a product, in cents.',
'permission' => 'shop.prices',
'input' => [
'type' => 'object',
'properties' => [
'id' => ['type' => 'string'],
'cents' => ['type' => 'integer', 'minimum' => 0],
],
'required' => ['id', 'cents'],
],
'annotations' => ['idempotent' => true],
'handler' => fn (array $input, AbilityContext $context) => Product::setPrice($input['id'], $input['cents']),
]);| Key | Meaning |
|---|---|
| name | namespace/action, lowercase. |
label, description | Shown to people and to models — write them for both. |
input | A JSON schema. Input is validated (and defaults applied) before the handler runs; errors come back per field. |
permission | A permission string, or a closure fn (?User $user, array $input, AbilityContext $context): bool for checks that depend on the input. |
annotations | readOnly, destructive, idempotent — used for audit, REST verbs, MCP hints and the assistant's approval rules. trustedOutput marks a read whose result holds no text people could have written (catalogs, static data); any other result makes the assistant ask before every later change in that chat. |
expose | Turn a transport off: ['mcp' => false], ['rest' => false], ['assistant' => false], ['cli' => false]. |
handler | A closure, an invokable class or Class@method. Return anything JSON-serializable. |
#Running abilities
PHP
$result = cms()->abilities()->execute('entries/publish', ['id' => $id], AbilityContext::forUser($user, 'internal'));From the admin's JavaScript:
JavaScript
import { run } from '@letterncms/sdk'
const entry = await run('entries/get', { id })#What every call gets
- Validation against the input schema.
- Authorization — the user's permissions and, for tokens, the token's scopes.
- Audit — every change (anything not read-only) is logged with who, how and when; see Settings → Audit Log, and Settings → Auditing for what is recorded and for how long.
- Hooks —
ability.inputcan adjust input,ability.executingandability.executedfire around the call,ability.resultcan adjust the result. - Errors in one shape:
{ "type": "invalid_input", "message": "…", "details": { "field": "…" } }with typesinvalid_input,forbidden,unauthenticated,not_found,conflictandfailed.
See every ability on this installation in the abilities reference.