Cecil Site Builder
You are an expert Cecil developer capable of creating and generating static websites with Cecil, a PHP-based static site generator powered by Symfony components and Twig.
When to Use This Skill
Use this skill when:
- Creating or scaffolding a new Cecil site
- Building and generating static websites with Cecil
- Configuring site settings, taxonomies, and content organization
- Creating or updating Twig templates and layouts
- Managing assets, including images and stylesheets
- Deploying Cecil-generated static sites
- Troubleshooting build issues or optimizing the performance of the generated site and build process
- Working with Cecil's plugin/extension system
Project Structure
Directory Layout
my-site/ ├── cecil.yml # Main configuration file (or config.yml) ├── pages/ # Markdown pages ├── layouts/ # Twig templates ├── assets/ # Processed files (CSS, JS, images) ├── static/ # Static files copied as-is ├── data/ # Data collections (YAML/JSON/...) └── extensions/ # Custom PHP classes (generators, Twig extensions, post-processors)
Key Directories
- pages/ - Markdown content files organized into sections
- layouts/ - Twig templates and partials
- assets/ - Files handled by Cecil (Sass compilation, minification, image handling)
- static/ - Files copied to output without transformation
- data/ - Data files exposed in templates via
site.data - extensions/ - Custom PHP classes autoloaded by Cecil (file path must match the class namespace, e.g.
extensions/MyProject/Generator/CustomGenerator.php)
Cecil Fundamentals
Architecture
Cecil follows a build pipeline:
Builder → Steps → Generators → Renderer → Output
-
Steps (
Step/): Sequential build phases, in this order- Load: pages, data files and static files
- Pages Create / Convert: create pages collection, convert front matter and Markdown body
- Taxonomies Create: build vocabularies and terms
- Pages Generate: run generators (see below)
- Menus Create: build navigation structures
- StaticFiles Copy: copy static files
- Pages Render / Save: render with Twig and write output files
- Assets Save: save processed assets
- Optimize: HTML, CSS, JS and images
-
Generators (
Generator/): Page generators executed via priority queue- Generators are ordered by numeric weight; lower numbers execute first (e.g., DefaultPages at weight 10 runs before Alias at weight 80).
- DefaultPages (10) → ExternalBody (30) → Section (40) → Taxonomy (50) → Homepage (60) → Pagination (70) → Alias (80) → Redirect (90)
-
Renderer (
Renderer/): Twig-based rendering with custom extensions -
Output: Built static site in
_site/directory
Content Model
- Pages: Markdown files composed of front matter and body
- Front matter: Metadata surrounded by separators (
---,+++, or<!-- -->) - Section: Root folder in
pages/(e.g.pages/blog/post-1.md-> sectionblog) - File-based routing: Files under
pages/define generated paths - Collections: Pages, taxonomies, data and static files are exposed to templates
Nested Sections (Sub-sections)
A nested folder that explicitly contains an index.md file becomes a sub-section of its parent Section. A nested folder without an index.md file is not a sub-section: its pages simply belong to the parent section.
plaintextpages/ └─ blog # Section "blog" ├─ index.md ├─ post-1.md # Page in "blog" └─ 2024 # Sub-section (contains an "index.md") ├─ index.md └─ post-2.md # Page in "blog" AND "blog/2024"
A sub-section:
- Is a full Section (same
type, variables, and layout resolution) available at its own URL (e.g./blog/2024/) - Falls back to the templates of its parent sections (e.g.
blog/list.html.twigforblog/2024ifblog/2024/list.html.twigdoesn't exist) - Can be nested at any depth (e.g.
blog/2024/06/) - Lists its own pages; those pages also belong to each parent section
- Is not listed among the pages of its parent section
Sub-sections support the same front matter variables as any section (sortby, pagination, cascade, circular). Use cascade on a parent index.md to propagate variables down to sub-sections and their pages.
In templates, use page.parent, page.ancestors, page.sections and page.toplevel to build navigation, or include the ready-to-use partials/breadcrumb.html.twig partial.
Previous/next navigation (page.prev / page.next) follows the sections tree: the pages of a top level section and of all its sub-sections are chained depth-first, sorted with the section's sortby, each sub-section index page being followed by its own pages.
Front Matter
Front matter is YAML by default (pages.frontmatter also accepts ini, toml and json). Common variables:
| Variable | Purpose |
|---|---|
title, description | Title (default: file name without prefix and language suffix) and description |
date, updated | Creation and update dates |
layout | Template to use (see Lookup Rules) |
slug, path | Override the URL (values must already be slugified) |
weight | Sort order |
published / draft | draft: true is the same as published: false (preview with build --drafts / serve --drafts) |
schedule | {publish: <date>, expiry: <date>}: published if publish is reached and expiry is not passed |
menu | Add the page to a menu (e.g. menu: main or menu: {main: {weight: 10}}) |
redirect, alias | Redirect the page to a URL; generate redirections from old paths to the page |
output | Output format(s) of the page (e.g. [html, json]) |
uglyurl | true to output page.html instead of page/index.html |
excluded | true to hide the page from lists, sitemap and feeds (formerly exclude) |
language, langref | Language code (default: file suffix) and translations reference |
A file name prefix sets the date (2019-04-23_My post.md) or the weight (1_First project.md) of the page; separators are - and _ by default (pages.prefix.separator).
A section (<section>/index.md) also accepts:
sortby:date(default),updated,titleorweight, or a map{variable: date, desc_title: false, reverse: false}(desc_titlesorts pages with the same date by descending title); the site default ispages.sortbypagination:{max: 5, path: page}to overridepages.pagination,falseto disable it, or{enabled: true}to enable it when it is disabled globally (pages.pagination: false)cascade(variables propagated to the section pages) andcircular(circular prev/next navigation)
Configuration
Configuration is defined in cecil.yml or config.yml at project root:
- Core options are top-level keys such as
title,baseurl,description,taxonomies,menus - Dot notation in templates applies to
sitevariable access (for examplesite.title) - Defaults (including generators, default pages and output formats) are defined in
config/default.php; priority is defaults < themesconfig.yml< site config < env vars (CECIL_*) - Maps are merged key by key with the defaults; a boolean applied to a map toggles its
enabledkey (e.g.cache: false); in templates, check it with theenabledtest ({% if config.cache is enabled %}) - A list of values (
pages.ext,output.pagetypeformats.<type>,assets.images.responsive.widths…) replaces the default list but is added to the theme list; a map of booleans adjusts the default or theme list instead (e.g.homepage: {rss: true, atom: false}) output.formatsentries are merged byname: override a default format with only itsnameand the properties to change- Deprecated options (renamed, moved or removed, listed in
config/deprecated.php) make the build fail, with a message giving the source (site, theme or env vars) and the new YAML snippet to use - Pages without content file (front matter only) are declared in
pages.default, indexed by ID (pages.virtualis removed); like any page they are listed (showable, sitemap, homepage) unless they setexcluded: true - A page is "generated" (
page.generated, formerlypage.virtual) when it has no content file (section, term, page declared in configuration, etc.) or is a paginated page
Building a Cecil Site
Step 1: Download Cecil
Download Cecil using curl:
bashcurl -LO https://cecil.app/cecil.phar chmod +x cecil.phar
If PHP is not available, use the standalone binary for the platform instead (e.g.: cecil-linux-x64, cecil-macos-arm64, cecil-windows-x64.exe from the latest GitHub release), and replace php cecil.phar with ./cecil in the commands below. The binary has no self-update command, only supports GD for image processing, and its serve command requires a local PHP executable (for the PHP built-in web server).
Step 2: Create a New Site
Use the new:site command to scaffold a new website:
bashphp cecil.phar new:site
Step 3: Configure the Site
Edit cecil.yml:
yamltitle: My Site baseurl: https://example.com/ description: My awesome static site taxonomies: categories: category tags: tag
Step 4: Create Content
Create a page with:
bashphp cecil.phar new:page
Then edit the generated file in pages/:
markdown--- title: My First Post description: Welcome to my blog date: 2024-05-14 tags: [Welcome, "First post"] --- # My First Post This is my first post content.
Step 5: Create Templates (Optional)
Cecil ships with built-in templates (resources/layouts/), so a site builds without any template in layouts/. Only create templates to customize the rendering, and prefer extending the built-in ones (see Built-in Templates).
Create Twig templates in layouts/ (for example layouts/page.html.twig):
twig<!DOCTYPE html> <html lang="{{ site.language }}"> <head> <meta charset="utf-8"> {# generates <title>, description, canonical, Open Graph, etc. #} {{ include('partials/metatags.html.twig') }} </head> <body> <header> <h1>{{ site.title }}</h1> </header> <main> {{ page.content }} </main> <footer> <p>© {{ site.title }}</p> </footer> </body> </html>
Step 6: Build the Site
bashphp cecil.phar build
Output is generated in _site/ directory.
CLI Commands
| Command | Purpose |
|---|---|
php cecil.phar new:site | Create a new website (--demo to add demo content) |
php cecil.phar new:page | Create a new page |
php cecil.phar build | Build the static site (--drafts, --optimize, --baseurl, --output, --config, --page, --dry-run, --clear-cache) |
php cecil.phar serve | Start local server with live reload (same options as build, plus --open, --host, --port, --no-watch) |
php cecil.phar serve --incremental | Serve with incremental builds (rebuild only changed pages) |
php cecil.phar serve --background | Run the server in the background (serve:log to show its log) |
php cecil.phar serve:stop | Stop the local server |
php cecil.phar edit | Open pages directory with the configured editor |
php cecil.phar show:config | Display effective configuration (--key=pages.dir to show a single key) |
php cecil.phar show:content | Display content tree (pages, data, static files) |
php cecil.phar doctor | Diagnose site and environment (see also doctor:frontmatter, doctor:seo, doctor:links, doctor:cache) |
php cecil.phar cache:clear | Clear all cache files (or only cache:clear:assets, cache:clear:templates, cache:clear:translations) |
php cecil.phar clear | Remove generated files (or only clear:output, clear:temporary / clear:tmp) |
php cecil.phar util:templates:extract | Extract built-in templates into layouts/ (Phar and standalone binary only) |
php cecil.phar util:translations:extract | Extract translation strings from templates |
php cecil.phar about | Display Cecil version and environment information |
php cecil.phar self-update | Update Cecil to the latest version (Phar only) |
doctor:links and doctor:seo return a failure exit code according to --fail-on: bad (default), ok, feedback or never. doctor:links also accepts --page, --exclude, --format=json and --include-generated; its defaults can be set under the doctor.links configuration key.
Template Development
Twig templates live in layouts/ and follow Cecil naming conventions.
Naming Convention
Use this pattern:
plaintextlayouts/(<section>/)<type>|<layout>.<format>(.<language>).twig
Examples:
layouts/page.html.twig- default page templatelayouts/list.html.twig- section/home listing templatelayouts/blog/list.atom.twig- Atom feed template forblogsection (RSS is not output by default: enable it withoutput.pagetypeformats.section: {rss: true})layouts/page.html.fr.twig- French page templatelayouts/_default/page.html.twig- fallback template
Lookup Rules (How Cecil Chooses a Template)
Cecil uses the first existing template, in priority order, for each page type. <layout> is the front matter layout variable, and each entry resolves to <name>.<format>.twig (e.g. blog/list.html.twig):
| Page type | Lookup order |
|---|---|
| Homepage | <layout> → index → home → list → _default/<layout> → _default/index → _default/home → _default/list → _default/page |
| Page | <section>/<layout> → <layout> → <section>/page → _default/<layout> → page → _default/page |
| Section | <layout> → <section>/index → <section>/list → section/<section> → <parent>/index → <parent>/list → section/<parent> → _default/section → list → _default/list |
| Vocabulary | taxonomy/<plural> → vocabulary → _default/vocabulary |
| Term | taxonomy/<term> → taxonomy/<singular> → term → _default/term → _default/list |
For a sub-section, <section> is its full path (e.g. blog/2024) and the <parent> entries are repeated for each parent section, nearest first.
To render a section with the templates of another section, map it with layouts.sections (applies to the section and its pages):
yamllayouts: sections: news: blog # "news" uses blog/list.html.twig and blog/page.html.twig
Each candidate is searched in layouts/ (site), then in themes/<theme>/layouts/, then in Cecil's built-in templates (resources/layouts/). Most _default/* templates exist built-in, which is why a site renders without any custom layout.
In practice, you usually need only:
layouts/page.html.twiglayouts/list.html.twig- optional overrides per section
Built-in Templates
Cecil embeds default templates in resources/layouts/. They are always available to Twig (lowest priority, after site and theme layouts), so they can be rendered, included or extended without being copied into layouts/.
_default/- fallback layouts:page.html.twig,list.html.twig,home.html.twig,vocabulary.html.twig,term.html.twig,404.html.twig,404.json.twig,redirect.html.twig, feeds (list.atom.twig,list.rss.twig,list.jsonfeed.twig), embeds (page.embed.twig,page.oembed.twig), JSON/Markdown/LLMs outputs,sitemap.xml.twig,robots.txt.twig, etc.partials/- reusable fragments (see Built-in Partials)extended/- base templates extended by other built-ins (feed.twig, extended by the Atom, RSS and JSON Feed templates)components/- none built-in, but site and theme components live inlayouts/components/shortcodes.twig- built-in shortcodes
Rules to follow:
-
Don't recreate what already exists: before writing a template, check whether a built-in one covers the need (feeds, sitemap, robots.txt, 404, redirects, JSON outputs are already provided).
-
Extend rather than copy:
_default/page.html.twigexposes the blockshead,head_metatags,head_css,header,contentandfooter.twig{# layouts/page.html.twig #} {% extends '_default/page.html.twig' %} {% block content %} <article>{{ page.content }}</article> {% endblock %} -
Don't shadow a built-in template by accident: a site file with the same path (e.g.
layouts/_default/page.html.twigorlayouts/partials/metatags.html.twig) fully replaces the built-in one for the whole site, and can'textendsitself. -
Extract only as a last resort:
php cecil.phar util:templates:extractcopies all built-in templates intolayouts/; the copies then no longer receive Cecil updates.
Metatags (partials/metatags.html.twig)
Always use the built-in partials/metatags.html.twig partial in the <head> of HTML layouts instead of hand-writing SEO/social tags. It is already included by _default/page.html.twig (block head_metatags).
twig<head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> {{ include('partials/metatags.html.twig') }} </head>
It generates:
<title>(page title + divider + site title; site title + baseline on the homepage; page number on paginated lists)description,keywords(fromtags),author,robots(noindexon paginated pages; pages with anoindexmetatags.robotsare also excluded from the sitemap)- favicons (from
favicon.ico,favicon.svg,favicon.pngassets, resized) prev/next/first/lastlinks, canonical and alternate formats, feeds,hreflangalternatesrel=melinks, Open Graph, Facebook, Twitter/X Card, Fediverse creator- optional Dublin Core and JSON-LD structured data
Important:
-
Never add a separate
<title>,<meta name="description">, canonical orog:*tags next to this partial: they would be duplicated. -
Feed it through front matter (page) or configuration (site fallback):
title,description,tags,author,image,canonical.url,social.*. -
Tune it with the
metatagsconfiguration (per page with front mattermetatags):yamlmetatags: title: divider: " · " only: false # page title only robots: "index,follow" image: true # default social image (`false` to disable) favicon: true # or `{enabled: true, image: favicon.png, sizes: {...}}` og: true twitter: true mastodon: true articles: "blog" # site config only: section(s) rendered as Open Graph "article" and JSON-LD `BlogPosting` dc: false # Dublin Core data: false # JSON-LD structured data (`true` or a map of options) -
JSON-LD structured data (
metatags.data) is a@graphof linked entities (WebSite, publisher,WebPage/CollectionPage,BreadcrumbList,BlogPosting,VideoObject, main menuItemList). For a personal site, set the publisher as aPerson; per page, set a specific type (AboutPage,ContactPage, etc.):yamlmetatags: data: article: BlogPosting # or NewsArticle publisher: type: Person # Organization by default name: "Jane Doe" # site title (or site author for a Person) by default logo: logo.png # favicon image by defaultyaml# page front matter metatags: data: type: AboutPage -
Override
titleorimagefor a specific template:twig{{ include('partials/metatags.html.twig', {title: 'Custom title', image: og_image}) }} -
Customize one part with
embedand its blocks (title,description,metatags_favicon,metatags_alternates,metatags_og,metatags_twitter,metatags_dc,metatags_structured_data), instead of copying the whole file:twig{% embed 'partials/metatags.html.twig' %} {% block metatags_twitter %}{% endblock %} {% endembed %} -
Run
php cecil.phar doctor:seoto check the generated metatags.
See the metatags documentation for all options.
Template Variables
Most useful variables in Twig:
site.title,site.baseurl,site.descriptionsite.pages- pages collection (current language)site.allpages- pages in all languagessite.taxonomies- vocabularies and termssite.menus.<name>- menu entries (amainmenu is created automatically with the homepage and the top-level sections, unless they define their ownmenuvariable)page.title,page.date,page.content,page.path,page.type,page.section
Multilingual Sites
Configure languages in cecil.yml:
yamllanguage: en languages: - code: en name: English locale: en_US - code: fr name: Français locale: fr_FR
Use suffixed filenames for translations:
plaintextpages/about.md pages/about.fr.md
You can render a language switcher in templates with:
twig{% include 'partials/languages.html.twig' %}
Useful collection helpers:
site.pages.showableto skip draft/paginated/redirect/excluded pages (generated sections and taxonomies are included)sort_by_weightfilter for menu entries
Example Template
twig{# layouts/page.html.twig #} <!DOCTYPE html> <html lang="{{ site.language }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> {# no <title> here: metatags.html.twig generates it #} {{ include('partials/metatags.html.twig') }} </head> <body> <header> <h1><a href="{{ url('/') }}">{{ site.title }}</a></h1> {% if site.menus.main is defined %} <nav> <ul> {% for entry in site.menus.main|sort_by_weight %} <li><a href="{{ url(entry.url) }}">{{ entry.name }}</a></li> {% endfor %} </ul> </nav> {% endif %} </header> <main> <article> <h2>{{ page.title }}</h2> {% if page.date %} <time datetime="{{ page.date|date('c') }}">{{ page.date|date('Y-m-d') }}</time> {% endif %} {{ page.content }} </article> </main> </body> </html>
Built-in Partials and Utilities
Available in every site, without extraction (include them rather than rewriting them):
partials/metatags.html.twig- all<head>SEO/social tags, including<title>(see Metatags)partials/alternates.html.twig- canonical and alternate formats links (included by metatags)partials/alternates-languages.html.twig-hreflanglinks (included by metatags)partials/feeds-from-section.html.twig- section feeds links (included by metatags)partials/jsonld.js.twig- JSON-LD structured data (included by metatags whenmetatags.datais enabled)partials/navigation.html.twig- main menu navigationpartials/page-navigation.html.twig- previous/next page links (follows the sections tree)partials/paginator.html.twig- pagination linkspartials/languages.html.twig- language switcherpartials/breadcrumb.html.twig- breadcrumb (nested sections aware)partials/terms-list.html.twig- taxonomy terms listpartials/theme-selector.html.twig- light/dark theme togglepartials/googleanalytics.js.twig- Google Analytics snippetpartials/data.json.twig- page data serialized as JSON (used by JSON outputs)partials/pico.css.twig,partials/highlight.css.twig- CSS used by the default layoutspartials/notes/<type>.html.twig- rendering of the Markdown:::<type>notes (tabs,code-group,steps,accordion,cards), rendered during conversion with anotevariable only (nopageorsite); add one to render another note type. To nest notes, open the parent with a longer fence (::::, then:::::…): a note is closed by a line with at least as many:as its opening line
If a built-in template really needs to be modified, extract them all into layouts/ (last resort, see Built-in Templates):
bashphp cecil.phar util:templates:extract
Pagination
Pagination is configured globally under pages.pagination (defaults: max: 5, path: page), and can be overridden in section front matter (see Front Matter).
yamlpages: pagination: max: 10
pages.pagination: false disables it globally; a section can then enable it again with pagination: {enabled: true}.
In templates, paginated lists expose page.paginator (pages, count, current, links).
In list templates, include paginator links with:
twig{% include 'partials/paginator.html.twig' %}
Custom Filters and Functions
Cecil Twig helpers commonly used in templates (full list in the templates reference):
- Functions:
url()(internal/absolute URLs),asset()(reference and process assets),html()(HTML element of an asset:<img>,<picture>,<link>,<script>),image(),css(),js(),integrity(),readtime() - Asset filters:
resize,webp,avif,lqip,dataurl,inline,minify,fingerprint,to_css - Content filters:
markdown_to_html,toc,excerpt,excerpt_html,slugify,highlight - Collection filters:
filter_by,sort_by_date,sort_by_title,sort_by_weight - Tests:
enabled(config options),external(URL or menu entry outside the site),asset,image_large,image_square
twig{{ html(asset(page.image), {alt: page.title}, {responsive: true, formats: ['webp']}) }}
The html filter (asset|html) is deprecated: use the html() function.
Components
Reusable Twig components are stored in layouts/components/ (e.g. layouts/components/button.twig) and used with the x tag or an HTML-like syntax; the slot variable holds the inner content and attributes the passed attributes:
twig{% x:button with {class: 'primary'} %}Click me{% endx %} <x-button class="primary">Click me</x-button>
Build Optimization
Asset Processing
Configure asset optimization:
yamlassets: minify: true fingerprint: true compile: style: compressed images: optimize: true
Image Variants (Dark and Mobile)
Cecil can serve alternative versions of an image in a <picture> element, when files with a configured suffix exist next to the original image (e.g. photo.dark.jpg, photo.mobile.jpg, photo.mobile.dark.jpg):
yamllayouts: images: # images rendered with the `html` Twig function dark_suffix: .dark # adds <source media="(prefers-color-scheme: dark)"> mobile_suffix: .mobile # adds <source> with the mobile media query mobile_media_query: "(max-width: 767px)" pages: body: images: # same options for images in Markdown content dark_suffix: .dark mobile_suffix: .mobile
Both are disabled by default (null).
Code Syntax Highlighting
Fenced code blocks are highlighted at build time (pages.body.highlight: true by default). The engine can be changed:
yamlpages: body: highlight: engine: tempest # `highlightjs` (default, `hljs-*` CSS classes) or `tempest` (fewer languages, `hl-*` CSS classes)
The theme CSS must match the engine's classes (the built-in partials/highlight.css.twig handles both).
In templates, the highlight filter does not use this option: its engine is set by layouts.highlight.engine (highlightjs by default) or by its engine argument: {{ code|highlight('php', 'tempest') }}.
Performance Tips
- Use
draft: trueto exclude non-published content from builds - Enable output optimization in production (
optimize: trueorbuild --optimize); asset minification and fingerprinting are already enabled by default - Use output and format settings adapted to your pages types
- Use responsive image options and image optimization when needed
Extension & Plugins
Custom Generators
Extend Cecil by creating custom generators:
php<?php namespace MyProject\Generator; use Cecil\Generator\AbstractGenerator; class CustomGenerator extends AbstractGenerator { public function generate(): void { // Custom generation logic } }
Save it as extensions/MyProject/Generator/CustomGenerator.php, then register it in configuration with pages.generators.
yamlpages: generators: 100: MyProject\Generator\CustomGenerator
Note: use single backslashes in YAML. Double backslashes (
\\) are only needed inside JSON or PHP strings.
Twig Extensions and Post-processors
You can also extend Twig (via layouts.extensions) and post-process output (via output.postprocessors).
yamllayouts: extensions: MyExtension: MyProject\Twig\MyExtension
The Twig extension class should implement Twig\Extension\ExtensionInterface (or extend Twig\Extension\AbstractExtension).
yamloutput: postprocessors: MyProcessor: MyProject\Renderer\PostProcessor\MyProcessor
Post-processors should implement Cecil\Renderer\PostProcessor\PostProcessorInterface.
Deployment
Static Site Hosting
Cecil generates pure static HTML, compatible with:
- GitHub Pages
- Netlify
- Vercel
- AWS S3
- Any web server
Build & Deploy Workflow
bash# Check internal links (add --internal --external to also check external links); fails on broken links php cecil.phar doctor:links # Build php cecil.phar build # Deploy output directory (_site/) # to your hosting platform
GitHub Pages Example
bashphp cecil.phar build # Commit _site/ directory and push to gh-pages branch
Code Quality Standards
When extending or contributing to Cecil:
- Follow PSR-12 coding standards
- Use
declare(strict_types=1);in all PHP files - Include proper PHPDoc blocks for all classes and methods
- Use 4-space indentation for PHP, 2-space for YAML/Twig
Useful Resources
- Official website: https://cecil.app
- GitHub Repository: https://github.com/Cecilapp/Cecil
- Issue Tracker: https://github.com/Cecilapp/Cecil/issues
- Documentation: https://cecil.app/documentation/
Common Workflows
Create a Blog
- Create
pages/blog/index.mdfor blog section - Add individual posts in
pages/blog/post-*.md - Configure taxonomy for tags/categories
- Rely on built-in
_default/list.html.twigand_default/page.html.twig, or extend them inlayouts/blog/ - Build with
php cecil.phar build
Add Custom Pages
- Create markdown files in
pages/directory - Add front matter with
titleand, if needed,layout - If needed, create a template in
layouts/(preferably extending a built-in one, withpartials/metatags.html.twigin<head>) - Let lookup rules pick the template, or set
layout: <name>in front matter - Build to generate output
Use Dynamic Content (Twig in Page Body)
By default, Twig syntax in a Markdown body is output as is. To evaluate it, create a dedicated layout and use it only for pages that need it:
twig{# layouts/dynamic.html.twig #} {% extends '_default/page.html.twig' %} {% block content %} {{ include(template_from_string(page.content, "dynamic content for page " ~ page.id)) }} {% endblock content %}
Then set layout: dynamic in the page front matter. The body can use page.*, site.*, functions, filters and Twig tags.
Caveats (Markdown is converted to HTML before Twig rendering):
<,>and=>are escaped: comparisons and arrow functions fail (Unexpected character "&")- Twig expressions inside HTML attributes (e.g.
href="{{ url(post) }}") are URL-encoded and not evaluated - An expression alone on its line is wrapped in
<p>: wrap it in an HTML element separated by line breaks - Twig in code spans/blocks is evaluated: use
{% verbatim %}to display it as is
Keep the body simple and move complex logic into a partial template or a macro. See the dynamic content documentation.
Implement Search
- Create
pages/search.mdwith front matterlayout: searchandoutput: json - Use JavaScript library (e.g., Lunr.js) on frontend
- Create
layouts/search.json.twigthat iteratessite.pages.showableand emits a JSON array of{title, url, content}objects - Add search functionality to templates
Troubleshooting
When a user reports unexpected behavior or asks about a specific feature, ask them to run php cecil.phar doctor and include the output. If you are uncertain whether a feature is available in the user's Cecil version, say so explicitly and direct them to the official documentation at https://cecil.app/documentation/ rather than guessing version ranges.
Common Issues
- Site not generating: Check
cecil.ymlsyntax and configuration Deprecated configuration option(s): move or remove the listed options as shown in the error message- Missing pages: Ensure content files are in
pages/directory - Template not loading: Verify the
layoutfront matter variable, template naming and lookup rules - Build errors: Run
php cecil.phar build -vvfor verbose output - Cache issues: Clear cache with
php cecil.phar cache:clear - Broken links or anchors: Run
php cecil.phar doctor:links(internal links) orphp cecil.phar doctor:links --internal --external
Debug Output
Get detailed build information:
bashphp cecil.phar build -v # Verbose php cecil.phar build -vv # Very verbose php cecil.phar build -vvv # Debug


