CLI
Terminal
$ php cms run entries/create collection=pages title=About
$ php cms run entries/publish id=01m7c2q4
$ php cms build --strict
$ php cms links:check
$ php cms plugin enable letterncms/forms
$ php cms token:create you@studio.com --scope='entries/*'REST
REST
curl https://your-site.com/api/v1/abilities/entries/list \
-H "Authorization: Bearer cms_…" -G -d collection=posts
curl https://your-site.com/api/v1/batch \
-H "Authorization: Bearer cms_…" \
-d '{"calls": [{"ability": "entries/publish", "input": {"id": "01m7c2q4"}}]}'
curl https://your-site.com/api/v1/content/posts?sort=-date&limit=10MCP
MCP
claude mcp add --transport http cms https://your-site.com/mcp
# Signs in with OAuth, acts as you, with your permissions.
# Tools: entries.create, entries.publish, design.update, …
# Resources: cms://site/design, cms://collections, cms://blocks
# Prompts: Build a page, Write a blog post, Audit the sitepages/home.md
content/collections/pages/home.md
---
title: Home
status: published
---
::: box {"id":"hero","props":{"tag":"section"}}
:::: heading {"id":"word","props":{"text":"Ondine","level":"1"}}
::::
:::: paragraph {"id":"line","props":{"text":"Fish from the morning boat."}}
::::
:::: button {"id":"book","props":{"label":"Book a table","url":"/book"}}
::::
:::dishes.yaml
content/collections/dishes.yaml
title: Dishes
singular: Dish
icon: fork-knife
driver: files
sort: position
fields:
- { handle: price, type: text, label: Price }
- { handle: course, type: select, label: Course }
- { handle: description, type: textarea, label: Description }
- { handle: photo, type: media, label: Photo, max: 1 }
- { handle: available, type: toggle, label: On the menu }tokens.json
content/design/tokens.json
{
"tokens": {
"color": {
"background": { "$value": "#d9432a" },
"surface": { "$value": "#fbf1e3" },
"foreground": { "$value": "#fbf1e3" },
"accent": { "$value": "#ecb54a" }
},
"font": {
"display": { "$value": ["Instrument Serif", "serif"] }
}
}
}roles.yaml
config/roles.yaml
roles:
client:
label: Restaurant team
description: Changes dishes, prices and photos.
permissions:
- admin.access
- entries.dishes.view
- entries.dishes.edit
- entries.dishes.publish
- media.view
- media.uploadShopPlugin.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) =>
Product::setPrice($input['id'], $input['cents']),
]);In the admin@letterncms/sdk
run('shop/price-update', { id, cents })
Over HTTPREST
POST /api/v1/abilities/shop/price-update
In the terminalCLI
php cms run shop/price-update id=p1 cents=1200
In your AI toolMCP
shop.price-update
Nothing exotic underneath.
If you know these, you know it.
PHP 8.4
Any host
Laravel
The kernel
React
The admin
MCP
First-party plugin
Start reading.
It’s all documented, down to the hooks.
Your first plugin
A setting, an ability, a panel and a widget, in one folder.
Hooks
Every action and filter, listed.
Block Markdown
Pages as plain text, for files and agents.
REST API
Every ability over HTTP, and content without a token.
Admin extensions
Add screens and panels in React.
Deployment
From shared hosting to git and environments.