A template for building Total CMS extensions. Clone this repo, rename it, and start building.
This starter demonstrates every extension point:
| Feature | File | Description |
|---|---|---|
| Twig function | Extension.php |
starter_greet() function |
| Twig filter | Extension.php |
reverse_words filter |
| CLI command | src/Command/GreetCommand.php |
tcms acme:greet |
| API route | src/Action/ApiHelloAction.php |
Protected API at /ext/acme/starter/api/hello |
| Public route | src/Action/PublicStatusAction.php |
Unauthenticated at /ext/acme/starter/status |
| Admin page | src/Action/DashboardAction.php |
Admin page at /admin/ext/acme/starter/dashboard |
| Admin nav | Extension.php |
Sidebar link with custom SVG icon |
| Dashboard widget | templates/widgets/starter.twig |
Widget on admin home |
| Event listener | Extension.php |
Logs object create/update events |
| Custom field type | src/Field/ColorPickerField.php |
Native HTML color picker field |
| Admin CSS asset | assets/colorpicker.css |
Styles for the color picker field |
| Read-only schema | schemas/starter-items.json |
Example collection schema (Pro+) |
| Installable schema | Extension.php |
Example in boot() (commented out, Pro+) |
| Settings | settings-schema.json |
Configurable greeting message |
| Manifest links | extension.json |
"Documentation" and "Dashboard" links on the admin card |
- Clone this repo into your T3 extensions directory:
cd tcms-data/extensions/
mkdir your-vendor
cd your-vendor
git clone https://github.com/totalcms/extension-starter.git your-extension
cd your-extension- Update
extension.jsonwith your extension's ID, name, and details:
{
"id": "your-vendor/your-extension",
"name": "Your Extension"
}- Update the PHP namespace in
composer.jsonand all PHP files:
"autoload": {
"psr-4": {
"YourVendor\\YourExtension\\": "src/"
}
}- Install dependencies and generate the autoloader:
composer install- Enable the extension:
tcms extension:enable your-vendor/your-extension- Delete the parts you don't need and start building.
your-extension/
extension.json # Manifest (required)
Extension.php # Entry point (required)
composer.json # Dependencies and autoloading
settings-schema.json # Settings form definition
src/
Action/ # HTTP action handlers
Command/ # CLI commands
schemas/ # Read-only schemas (Pro+)
templates/ # Twig templates
Add a links array to extension.json to surface buttons on the admin Extensions card:
"links": [
{"label": "Documentation", "url": "https://docs.totalcms.co/extensions/"},
{"label": "Dashboard", "url": "/admin/ext/acme/starter/dashboard"}
]Each entry needs a label and a url.
- URLs starting with
http://orhttps://are treated as external and open in a new tab. - Relative URLs (admin pages your extension registers) only show when the extension is enabled — they wouldn't resolve otherwise. External links are always shown so users can read your docs before enabling.
Two ways to ship schemas with an extension. Both require the Pro edition.
| Approach | Where it lives | Editable by user? | Use when |
|---|---|---|---|
| Read-only | schemas/yourname.json in the extension |
No — owned by the extension | The schema is core to the extension's behavior and shouldn't be modified |
| Installable | $context->installSchema([...]) in boot() |
Yes — copied once into tcms-data/.schemas/ |
The schema is a starting point the user is expected to customize |
Read-only schemas are auto-discovered. They show up in the admin schemas view alongside built-in schemas, and can be referenced by collections. Disabling the extension's Schemas capability hides them.
installSchema() is a one-shot copy: it skips silently if a schema with the same id already exists, so subsequent boots don't overwrite the user's edits.
The requires block in extension.json is enforced before the extension can be enabled:
"requires": {
"totalcms": ">=3.3.0",
"php": ">=8.2"
}If the running Total CMS or PHP version doesn't satisfy these constraints, the extension is still listed on the Extensions admin page but the Enable button is disabled and a yellow warning panel explains why. This means users can see your extension exists and read its docs, but they can't enable it on an unsupported runtime.
MIT