<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://cecil.app/xsl/atom.xsl" media="all"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://cecil.app/documentation/developers/</id>
  <title>Cecil - Developers</title>
  <subtitle><![CDATA[Cecil is a command-line PHP application that merges Markdown pages, medias and Twig templates to generate a static website.]]></subtitle>
  <link href="https://cecil.app/documentation/developers/atom.xml" rel="self" type="application/atom+xml" />
  <link href="https://cecil.app/documentation/developers/" rel="alternate" type="text/html" />
  <updated>2026-10-06T00:34:15+00:00</updated>
  <author>
    <name>Cecil</name>
    <uri>https://cecil.app</uri>
  </author>
  <entry xml:lang="en">
    <id>https://cecil.app/documentation/developers/architecture/</id>
    <title>Architecture</title>
    <published>2026-05-27T00:00:00+00:00</published>
    <updated>2026-10-02T00:00:00+00:00</updated>
    <link href="https://cecil.app/documentation/developers/architecture/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<h1>Architecture</h1>
<h2 id="diagram">Diagram</h2>
<pre><code class="language-mermaid" translate="no">graph TD
    %% Entry
    CLI["bin/cecil\n(CLI entry point)"]
    APP["Application\n(Symfony Console)"]
    CMD["Commands\nbuild / serve / new:site\nnew:page / clear / show:content"]

    CLI --&gt; APP --&gt; CMD

    %% Orchestrator
    BUILDER["Builder\n(Orchestrator)"]
    CONFIG["Configuration\n(config.yml + default.php\n+ themes)"]
    CMD --&gt; BUILDER
    CONFIG --&gt; BUILDER

    %% Pipeline
    subgraph PIPELINE["Build pipeline (steps)"]
        S1["1. Pages/Load\n(Finder -&gt; Markdown files)"]
        S2["2. Data/Load\n(YAML files)"]
        S3["3. StaticFiles/Load\n(load static files)"]
        S4["4. Pages/Create\n(create Page objects)"]
        S5["5. Pages/Convert\n(Markdown -&gt; HTML\nfront matter)"]
        S6["6. Taxonomies/Create\n(create taxonomies)"]
        S7["7. Pages/Generate\n(generators)"]
        S8["8. Menus/Create\n(create menus)"]
        S9["9. StaticFiles/Copy\n(copy static files)"]
        S10["10. Pages/Render\n(Twig rendering)"]
        S11["11. Pages/Save\n(save pages)"]
        S12["12. Assets/Save\n(save assets)"]
        S13["13. Optimize/*\n(optimize HTML/CSS/JS/Images)"]

        S1 --&gt; S2 --&gt; S3 --&gt; S4 --&gt; S5 --&gt; S6 --&gt; S7 --&gt; S8 --&gt; S9 --&gt; S10 --&gt; S11 --&gt; S12 --&gt; S13
    end

    BUILDER --&gt; PIPELINE

    %% Subsystems
    subgraph COLLECTIONS["Collections"]
        PC["PagesCollection\n(Page objects)"]
        TC["TaxonomiesCollection\n(vocabularies/terms)"]
        MC["MenusCollection"]
    end

    subgraph GENERATORS["Generators (virtual pages)"]
        GP["Pagination"]
        GT["Taxonomy"]
        GS["Section"]
        GR["Redirect"]
        GD["DefaultPages (home, 404)"]
    end

    subgraph RENDERER["Twig rendering"]
        TW["Twig engine"]
        EXT["Extensions\n(Core, Content, Collection)"]
        PP["PostProcessors\n(metadata, excerpts, links)"]
        TH["Themes / Layouts"]
        TW --&gt; EXT
        TW --&gt; PP
        TH --&gt; TW
    end

    subgraph ASSETS["Assets"]
        AL["Asset locator"]
        AC["Compiler\n(SCSS -&gt; CSS)"]
        AI["Image processor\n(responsive, WebP, AVIF)"]
        AO["Optimizer\n(CSS/JS minification)"]
    end

    subgraph OUTPUT["Output (_site/)"]
        HTML[".html pages"]
        CSS2["CSS/JS assets"]
        IMG["images"]
        SF["static files"]
    end

    S4 --&gt; PC
    S6 --&gt; TC
    S8 --&gt; MC
    S7 --&gt; GENERATORS
    GENERATORS --&gt; PC
    S10 --&gt; RENDERER
    S12 --&gt; ASSETS

    PC --&gt; RENDERER
    TC --&gt; RENDERER
    MC --&gt; RENDERER

    RENDERER --&gt; S11
    S11 --&gt; HTML
    S12 --&gt; CSS2
    S12 --&gt; IMG
    S9 --&gt; SF

    %% Inputs
    subgraph INPUT["Sources"]
        MD["content/\n(Markdown + front matter)"]
        DATA["data/\n(YAML)"]
        STATIC["static/"]
        LAYOUTS["layouts/\n(Twig templates)"]
    end

    MD --&gt; S1
    DATA --&gt; S2
    STATIC --&gt; S3
    LAYOUTS --&gt; TH</code></pre>
<h2 id="key-components-legend">Key Components Legend</h2>
<table>
<thead>
<tr>
<th>Component</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Builder</strong></td>
<td>Central orchestrator that executes steps in sequence</td>
</tr>
<tr>
<td><strong>Config</strong></td>
<td>Merges default + theme + project + CLI configuration</td>
</tr>
<tr>
<td><strong>Steps</strong></td>
<td>Modular pipeline (13 steps), each with <code translate="no">init()</code> / <code translate="no">canProcess()</code> / <code translate="no">process()</code></td>
</tr>
<tr>
<td><strong>Collections</strong></td>
<td>Pages, Taxonomies, Menus: core data structures</td>
</tr>
<tr>
<td><strong>Generators</strong></td>
<td>Create virtual pages (pagination, tags, redirects, etc.)</td>
</tr>
<tr>
<td><strong>Renderer (Twig)</strong></td>
<td>Applies templates + extensions + post-processors</td>
</tr>
<tr>
<td><strong>Assets</strong></td>
<td>Compiles SCSS, optimizes images, fingerprints files</td>
</tr>
</tbody>
</table>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil.app/documentation/developers/library/</id>
    <title>Library</title>
    <published>2023-12-13T00:00:00+00:00</published>
    <updated>2026-10-03T00:00:00+00:00</updated>
    <link href="https://cecil.app/documentation/developers/library/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<h1>Library</h1>
<p>Cecil provides a simple PHP API to build your website.</p>
<p>You can read the <a href="https://cecil.app/documentation/library/api/namespaces/cecil.html">API documentation</a> for more details.</p>
<h2 id="installation">Installation</h2>
<pre><code class="language-bash hljs bash" translate="no">composer require cecil/cecil</code></pre>
<h3 id="libvips-support">libvips support</h3>
<p>To process images with <a href="https://www.libvips.org/" target="_blank" rel="noopener noreferrer">libvips</a> (optional), install the libvips driver in your project:</p>
<pre><code class="language-bash hljs bash" translate="no">composer require intervention/image-driver-vips</code></pre>
<aside class="note note-important"><p>This driver requires <a href="https://www.libvips.org/install.html" target="_blank" rel="noopener noreferrer">libvips</a> installed on your system and the PHP <a href="https://www.php.net/manual/book.ffi.php" target="_blank" rel="noopener noreferrer">FFI</a> extension enabled.<br>
Without it, Cecil uses <a href="https://www.php.net/manual/book.imagick.php" target="_blank" rel="noopener noreferrer">Imagick</a> or <a href="https://www.php.net/manual/book.image.php" target="_blank" rel="noopener noreferrer">GD</a> instead.</p></aside>
<h2 id="usage">Usage</h2>
<h3 id="build">Build</h3>
<p>Build a new website with a custom configuration:</p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-keyword">require_once</span> <span class="hljs-string">'vendor/autoload.php'</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Builder</span>;

$config = [
    <span class="hljs-string">'title'</span>   =&gt; <span class="hljs-string">"My website"</span>,
    <span class="hljs-string">'baseurl'</span> =&gt; <span class="hljs-string">'https://domain.tld/'</span>,
];

Builder::create($config)-&gt;build();

exec(<span class="hljs-string">'php -S localhost:8000 -t _site'</span>); <span class="hljs-comment">// preview locally</span></code></pre>
<aside class="note note-info"><p>The main parameter of the <code translate="no">create</code> method should be a PHP <code translate="no">array</code> or a <a href="https://github.com/Cecilapp/Cecil/blob/main/src/Config.php" target="_blank" rel="noopener noreferrer"><code translate="no">Cecil\Config</code></a> instance.</p></aside>
<h3 id="diagnostic">Diagnostic</h3>
<p>You can also run doctor checks through dedicated domain services, without using CLI commands.</p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">require_once</span> <span class="hljs-string">'vendor/autoload.php'</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Builder</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Doctor</span>\<span class="hljs-title">SeoDoctor</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Doctor</span>\<span class="hljs-title">SiteDoctor</span>;

$builder = Builder::create(<span class="hljs-keyword">require</span> <span class="hljs-string">'config.php'</span>)
    -&gt;setSourceDir(<span class="hljs-keyword">__DIR__</span>)
    -&gt;setDestinationDir(<span class="hljs-keyword">__DIR__</span>);

$siteDoctor = <span class="hljs-keyword">new</span> SiteDoctor();
$diagnosis = $siteDoctor-&gt;diagnose($builder, <span class="hljs-keyword">__DIR__</span>, [<span class="hljs-string">'cecil.yml'</span>]);

$seoDoctor = <span class="hljs-keyword">new</span> SeoDoctor();
$seoAudit = $seoDoctor-&gt;audit($builder, [
    <span class="hljs-string">'page'</span> =&gt; <span class="hljs-string">''</span>,
    <span class="hljs-string">'include_virtual'</span> =&gt; <span class="hljs-keyword">false</span>,
]);

var_dump($diagnosis[<span class="hljs-string">'errors'</span>], $seoAudit[<span class="hljs-string">'summary'</span>]);</code></pre>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil.app/documentation/developers/extend/</id>
    <title>Extend</title>
    <published>2023-04-17T00:00:00+00:00</published>
    <updated>2026-10-02T00:00:00+00:00</updated>
    <link href="https://cecil.app/documentation/developers/extend/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<h1>Extend</h1>
<p>Because Cecil is powered by PHP it's easy to extend its capabilities.</p>
<h2 id="pages-generator">Pages Generator</h2>
<p>A generator helps you create pages without Markdown files (for example, with data from an API or a database) or alter existing pages.</p>
<p>Just create a new PHP class in the <code translate="no">Cecil\Generator</code> namespace and add the class name to the <a href="/documentation/configuration/pages/#pages-generators"><code translate="no">pages.generators</code></a> list.</p>
<p><strong>Example:</strong></p>
<p><em>/extensions/Cecil/Generator/DummyPage.php</em></p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Generator</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Collection</span>\<span class="hljs-title">Page</span>\<span class="hljs-title">Page</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Collection</span>\<span class="hljs-title">Page</span>\<span class="hljs-title">Type</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DummyPage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractGenerator</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">GeneratorInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generate</span><span class="hljs-params">()</span>: <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-comment">// create a new page $page, then add it to the site collection</span>
        $page = (<span class="hljs-keyword">new</span> Page(<span class="hljs-string">'my-page'</span>))
            -&gt;setType(Type::PAGE-&gt;value)
            -&gt;setPath(<span class="hljs-string">'mypage'</span>)
            -&gt;setBodyHtml(<span class="hljs-string">'&lt;p&gt;My page body&lt;/p&gt;'</span>)
            -&gt;setVariable(<span class="hljs-string">'language'</span>, <span class="hljs-string">'en'</span>)
            -&gt;setVariable(<span class="hljs-string">'title'</span>, <span class="hljs-string">'My page'</span>)
            -&gt;setVariable(<span class="hljs-string">'date'</span>, now())
            -&gt;setVariable(<span class="hljs-string">'menu'</span>, [<span class="hljs-string">'main'</span> =&gt; [<span class="hljs-string">'weight'</span> =&gt; <span class="hljs-number">99</span>]]);
        <span class="hljs-keyword">$this</span>-&gt;generatedPages-&gt;add($page);
    }
}</code></pre>
<p><em>/extensions/Cecil/Generator/Database.php</em></p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Generator</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Collection</span>\<span class="hljs-title">Page</span>\<span class="hljs-title">Page</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Collection</span>\<span class="hljs-title">Page</span>\<span class="hljs-title">Type</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Database</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractGenerator</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">GeneratorInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generate</span><span class="hljs-params">()</span>: <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-comment">// create pages from a SQLite database</span>
        $db = <span class="hljs-keyword">new</span> SQLite3(<span class="hljs-string">'database.sqlite'</span>);
        $statement = $db-&gt;prepare(<span class="hljs-string">'SELECT * FROM blog'</span>);
        $result = $statement-&gt;execute();
        <span class="hljs-keyword">while</span> ($row = $result-&gt;fetchArray(SQLITE3_ASSOC)) {
            $page = (<span class="hljs-keyword">new</span> Page($row[<span class="hljs-string">'page-id'</span>]))
                -&gt;setType(Type::PAGE-&gt;value)
                -&gt;setPath($row[<span class="hljs-string">'path'</span>])
                -&gt;setBodyHtml($row[<span class="hljs-string">'html'</span>])
                -&gt;setVariable(<span class="hljs-string">'title'</span>, $row[<span class="hljs-string">'title'</span>])
                -&gt;setVariable(<span class="hljs-string">'date'</span>, $row[<span class="hljs-string">'date'</span>]);
            <span class="hljs-keyword">$this</span>-&gt;generatedPages-&gt;add($page);
        }
        $result-&gt;finalize();
        $db-&gt;close();
    }
}</code></pre>
<p><em>configuration</em></p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">pages:</span>
  <span class="hljs-attr">generators:</span>
    <span class="hljs-comment"># priority: class name</span>
    <span class="hljs-attr">99:</span> <span class="hljs-string">Cecil\Generator\DummyPage</span>
    <span class="hljs-attr">35:</span> <span class="hljs-string">Cecil\Generator\Database</span></code></pre>
<h2 id="twig-extension">Twig extension</h2>
<p>You can add custom <a href="/documentation/templates/reference/functions/">functions</a> and <a href="/documentation/templates/reference/filters/">filters</a>:</p>
<ol>
<li><a href="https://twig.symfony.com/doc/advanced.html#creating-an-extension" target="_blank" rel="noopener noreferrer">create a Twig extension</a> in the <code translate="no">Cecil\Renderer\Extension</code> namespace</li>
<li>add the PHP file in the <code translate="no">extensions</code> directory</li>
<li>add the class name to the configuration</li>
</ol>
<p><strong>Example:</strong></p>
<p><em>/extensions/Cecil/Renderer/Extension/MyTwigExtension.php</em></p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Renderer</span>\<span class="hljs-title">Extension</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyTwigExtension</span> <span class="hljs-keyword">extends</span> \<span class="hljs-title">Twig</span>\<span class="hljs-title">Extension</span>\<span class="hljs-title">AbstractExtension</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getFilters</span><span class="hljs-params">()</span>
    </span>{
        <span class="hljs-comment">// add a new filter named 'md5'</span>
        <span class="hljs-keyword">return</span> [
            <span class="hljs-keyword">new</span> \Twig\TwigFilter(<span class="hljs-string">'md5'</span>, <span class="hljs-string">'md5'</span>),
        ];
    }
}</code></pre>
<p><em>configuration</em></p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">layouts:</span>
  <span class="hljs-attr">extensions:</span>
    <span class="hljs-attr">MyExtension:</span> <span class="hljs-string">Cecil\Renderer\Extension\MyTwigExtension</span></code></pre>
<h2 id="output-post-processor">Output Post Processor</h2>
<p>You can post process page output.</p>
<p>Just create a new PHP class in the <code translate="no">Cecil\Renderer\PostProcessor</code> namespace and add the class name to the `output.postprocessors list.</p>
<p><strong>Example:</strong></p>
<p><em>/extensions/Cecil/Renderer/PostProcessor/MyProcessor.php</em></p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Renderer</span>\<span class="hljs-title">PostProcessor</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Cecil</span>\<span class="hljs-title">Collection</span>\<span class="hljs-title">Page</span>\<span class="hljs-title">Page</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyProcessor</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractPostProcessor</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">process</span><span class="hljs-params">(Page $page, string $output, string $format)</span>: <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-comment">// add a meta tag to the head of the HTML output</span>
        <span class="hljs-keyword">if</span> ($format == <span class="hljs-string">'html'</span>) {
            <span class="hljs-keyword">if</span> (!preg_match(<span class="hljs-string">'/&lt;meta name="test".*/i'</span>, $output)) {
                $meta = \sprintf(<span class="hljs-string">'&lt;meta name="test" content="Test"&gt;'</span>);
                $output = preg_replace_callback(<span class="hljs-string">'/([[:blank:]]*)(&lt;\/head&gt;)/i'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">($matches)</span> <span class="hljs-title">use</span> <span class="hljs-params">($meta)</span> </span>{
                    <span class="hljs-keyword">return</span> str_repeat($matches[<span class="hljs-number">1</span>] ?: <span class="hljs-string">' '</span>, <span class="hljs-number">2</span>) . $meta . <span class="hljs-string">"\n"</span> . $matches[<span class="hljs-number">1</span>] . $matches[<span class="hljs-number">2</span>];
                }, $output);
            }
        }

        <span class="hljs-keyword">return</span> $output;
    }
}</code></pre>
<p><em>configuration</em></p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">output:</span>
  <span class="hljs-attr">postprocessors:</span>
    <span class="hljs-attr">MyProcessor:</span> <span class="hljs-string">Cecil\Renderer\PostProcessor\MyProcessor</span></code></pre>]]>
    </content>
  </entry>
</feed>
