Cecil logo Cecil
What's on this page
October 26, 2026

Cecil 9.25.0: plugins and a reworked extension mechanism

Cecil 9.25.0 reworks how Cecil is extended with PHP: extensions are declared the same way, can be bundled in a plugin, react to build events, and are easier to ship with a theme.

Unified configuration

Page generators, Twig extensions, output post-processors, event subscribers and plugins are now declared the same way, as a map indexed by name:

pages:
  generators:
    Database: { class: MySite\Generator\Database, priority: 35 }
    Redirect: false # disables a built-in generator
output:
  postprocessors:
    MyProcessor: MySite\PostProcessor\MyProcessor # default priority (100)

Built-in extensions are named too: they can be disabled, or moved with a new priority, by their name.

An extension that cannot be loaded (unknown class, wrong interface, invalid option) is skipped and the error is logged. To make the build fail instead, enable the strict mode:

extensions:
  strict: true

Plugins

A plugin is a single entry point that registers a set of extensions (generators, Twig extensions, post-processors, event subscribers and listeners) and default options:

class ExamplePlugin extends AbstractPlugin
{
    public function register(Registry $registry): void
    {
        $registry
            ->config(['example' => ['feed' => ['limit' => 10]]])
            ->twigExtension(Twig\ExampleExtension::class)
            ->generator(Generator\Feed::class, priority: 35)
            ->subscriber(SearchIndex::class);
    }
}
extensions:
  plugins:
    Example: Example\ExamplePlugin

It's the recommended way to ship extensions with a theme: declared in the theme config.yml, the plugin is enabled with the theme, with no other configuration. The site configuration can still override its options, and disable or move its extensions by name.

Build events

Cecil now dispatches events during the build: before and after the build and each step, and at each step of the life cycle of a page (page.created, page.converted, page.rendered and page.saved). Listen to them with an event subscriber to create a search index, notify a service, or modify the output of pages:

extensions:
  subscribers:
    SearchIndex: MySite\SearchIndex

Autoloading

Custom classes are autoloaded from the extensions directory of the site or of a theme, the file path matching the namespace (e.g.: MySite\Generator\Database → extensions/MySite/Generator/Database.php). You can also:

  • declare PSR-4 mappings with extensions.autoload;
  • install third-party libraries with Composer in the extensions directory: extensions/vendor/autoload.php is loaded automatically, even with the Cecil phar.

Markdown converter and parser

The Markdown converter (pages.converter) and parser (pages.body.parser) can now be replaced by your own classes, for example to add your own Markdown syntax.

New new:extension command

The new:extension command creates the skeleton of an extension (generator, Twig extension, post-processor, event subscriber or plugin), and displays the configuration to enable it:

cecil new:extension --type=plugin --name=Plugin --namespace=MySite

Examples

  • The Paper theme now registers its Twig extension with a plugin.
  • The example theme ships a small plugin: a reading time Twig filter.
  • The plugin example is a component theme shipping a plugin with a generator, a Twig extension, a post-processor and an event subscriber.

Deprecations

Existing configurations still work, with a deprecation warning. They will not be supported in Cecil 10:

  • Indexing extensions by priority (e.g.: 35: MySite\Generator\Database) or by position (a list): use a name instead (Database: { class: MySite\Generator\Database, priority: 35 }).
  • Using the Cecil\ namespace for custom classes: it's now reserved, use your own namespace (e.g.: MySite\ or the name of your theme).
  • In Cecil 10, the strict mode will be enabled by default.

See the Extend Cecil documentation for more details, and update Cecil to get this release.

Release notes on GitHub