Extension Points Since 3.3.0
Extensions interact with Total CMS through the ExtensionContext object passed to register() and boot(). This page covers every available extension point.
Twig Functions
Section titled “Twig Functions”Add custom functions available in all Twig templates.
use Twig\TwigFunction;
public function register(ExtensionContext $context): void{ $context->addTwigFunction( new TwigFunction('seo_title', function (string $title, ?string $suffix = null): string { return $suffix ? "{$title} | {$suffix}" : $title; }) );}Permission: twig:functions
Usage in templates:
<title>{{ seo_title(post.title, 'My Site') }}</title>Twig Filters
Section titled “Twig Filters”Add custom filters for transforming values in templates.
use Twig\TwigFilter;
public function register(ExtensionContext $context): void{ $context->addTwigFilter( new TwigFilter('reading_level', function (string $text): string { $words = str_word_count($text); return match (true) { $words < 100 => 'Quick read', $words < 500 => 'Medium read', default => 'Long read', }; }) );}Permission: twig:filters
Usage in templates:
<span class="reading-level">{{ post.content|reading_level }}</span>Twig Globals
Section titled “Twig Globals”Add global variables accessible in all templates.
public function register(ExtensionContext $context): void{ $context->addTwigGlobal('seo', new SeoHelper());}Permission: twig:globals
Usage in templates:
{{ seo.metaTags(post) }}CLI Commands
Section titled “CLI Commands”Register custom commands for the tcms CLI tool. Commands must use namespaced names (vendor:command).
use Symfony\Component\Console\Command\Command;use Symfony\Component\Console\Input\InputInterface;use Symfony\Component\Console\Output\OutputInterface;
public function register(ExtensionContext $context): void{ $context->addCommand(new class extends Command { protected function configure(): void { $this->setName('acme:generate-sitemap'); $this->setDescription('Generate an XML sitemap'); }
protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln('Sitemap generated.'); return Command::SUCCESS; } });}Permission: cli:commands
Usage:
tcms acme:generate-sitemapRoutes (API and Admin)
Section titled “Routes (API and Admin)”Register routes under your extension’s namespace. Routes are automatically prefixed with /ext/{vendor}/{name}/.
use Slim\Routing\RouteCollectorProxy;
public function register(ExtensionContext $context): void{ $context->addRoutes(function (RouteCollectorProxy $group): void { $group->get('/dashboard', MyDashboardAction::class); $group->post('/api/analyze', AnalyzeAction::class); });}Permissions: routes:api, routes:admin
The routes above are accessible at:
/ext/acme/seo-pro/dashboard/ext/acme/seo-pro/api/analyze
Admin Navigation
Section titled “Admin Navigation”Add items to the admin sidebar.
use TotalCMS\Domain\Extension\Data\AdminNavItem;
public function register(ExtensionContext $context): void{ $context->addAdminNavItem(new AdminNavItem( label: 'SEO Pro', icon: 'seo', url: '/ext/acme/seo-pro/dashboard', permission: 'admin', priority: 50, ));}Permission: admin:nav
The priority field controls ordering (lower numbers appear first). The permission field controls visibility (admin = admin users only).
Dashboard Widgets
Section titled “Dashboard Widgets”Add widgets to the admin home screen.
use TotalCMS\Domain\Extension\Data\DashboardWidget;
public function register(ExtensionContext $context): void{ $context->addDashboardWidget(new DashboardWidget( id: 'seo-score', label: 'SEO Score', template: 'widgets/seo-score.twig', position: 'main', priority: 30, ));}Permission: admin:widgets
The template path is relative to the extension’s templates/ directory. Position is main or sidebar.
Custom Field Types
Section titled “Custom Field Types”Register new field types for use in collection schemas.
public function register(ExtensionContext $context): void{ $context->addFieldType('colorpicker', Acme\Fields\ColorPickerField::class);}Permission: fields:register
The class must extend TotalCMS\Domain\Admin\FormField\FormField. Once registered, the field type can be used in schemas:
{ "properties": { "accentColor": { "type": "colorpicker", "label": "Accent Color" } }}Event Listeners
Section titled “Event Listeners”Subscribe to content events. See Events for the full event reference.
public function register(ExtensionContext $context): void{ $context->addEventListener('object.created', function (array $payload): void { // $payload contains: collection, id $this->notifyWebhook($payload); });}Permission: events:listen
Container Definitions
Section titled “Container Definitions”Register services in the DI container for dependency injection.
public function register(ExtensionContext $context): void{ $context->addContainerDefinition( SeoAnalyzer::class, fn () => new SeoAnalyzer() );}Permission: container:definitions
Settings
Section titled “Settings”Read and write per-extension settings. Settings are stored in tcms-data/.system/extension-settings/.
public function boot(ExtensionContext $context): void{ $apiKey = $context->setting('api_key', ''); $allSettings = $context->settings();}Permissions: settings:read, settings:write
Define a settings_schema in your manifest to enable a settings form in the admin UI.
Service Resolution (Boot Phase)
Section titled “Service Resolution (Boot Phase)”During boot(), resolve any service from the DI container:
public function boot(ExtensionContext $context): void{ $config = $context->get(\TotalCMS\Support\Config::class); $cache = $context->get(\TotalCMS\Domain\Cache\CacheManager::class);}Only use $context->get() in boot(), never in register(). The container is not fully built during registration.