Skip to content
API and agents

Abilities

An ability is a named action with an input schema, a permission and a handler. Everything in Lettern is built on them.

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']),
]);
KeyMeaning
namenamespace/action, lowercase.
label, descriptionShown to people and to models — write them for both.
inputA JSON schema. Input is validated (and defaults applied) before the handler runs; errors come back per field.
permissionA permission string, or a closure fn (?User $user, array $input, AbilityContext $context): bool for checks that depend on the input.
annotationsreadOnly, 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.
exposeTurn a transport off: ['mcp' => false], ['rest' => false], ['assistant' => false], ['cli' => false].
handlerA 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.input can adjust input, ability.executing and ability.executed fire around the call, ability.result can adjust the result.
  • Errors in one shape: { "type": "invalid_input", "message": "…", "details": { "field": "…" } } with types invalid_input, forbidden, unauthenticated, not_found, conflict and failed.

See every ability on this installation in the abilities reference.