Cecil logo
What's on this page


Cecil is powered by the Twig template engine, so please refer to the official documentation to learn how to use it.


{# this is a template example #}
<h1>{{ page.title }} - {{ site.title }}</h1>
<span>{{ page.date|date('j M Y') }}</span>
<p>{{ page.content }}</p>
{% for tag in page.tags %}
  <li>{{ tag }}</li>
{% endfor %}
  • {# #}: adds comments
  • {{ }}: outputs content of variables or expressions
  • {% %}: executes statements, like loop (for), condition (if), etc.
  • |filter(): filters or formats content

Files organization

There is two kinds of templates, layouts and others templates: layouts are used to render pages, and each of them can include templates.

Templates files are stored in the layouts/ directory and must be named according to the following convention:

<section> (optional)
The section of the page (e.g.: blog).
The page type: home (or index) for homepage, list for list, page for page, etc. (See Lookup rules for details).
<layout> (optional)
The custom layout name defined in the front matter of the page (e.g.: layout: my-layout).
<language> (optional)
The language of the page (e.g.: fr).
The output format of the rendered page (e.g.: html, rss, json, xml, etc.).
The mandatory Twig file extension.


layouts/home.html.twig       # `type` is "homepage"
layouts/page.html.twig       # `type` is "page"
layouts/page.html.fr.twig    # `type` is "page" and `language` is "fr"
layouts/my-layout.html.twig  # `layout` is "my-layout"
layouts/blog/list.html.twig  # `section` is "blog"
layouts/blog/list.rss.twig   # `section` is "blog" and `format` is "rss"
├─ ...
├─ layouts                  <- Layouts and templates
|  ├─ my-layout.html.twig
|  ├─ index.html.twig       <- Used by type "homepage"
|  ├─ list.html.twig        <- Used by types "homepage", "section" and "term"
|  ├─ list.rss.twig         <- Used by types "homepage", "section" and "term", for RSS output format
|  ├─ page.html.twig        <- Used by type "page"
|  ├─ ...
|  ├─ _default              <- Default layouts, that can be easily extended
|  |  ├─ list.html.twig
|  |  ├─ page.html.twig
|  |  └─ ...
|  └─ partials
|     ├─ footer.html.twig   <- Included template
|     └─ ...
└─ themes
   └─ <theme>
      └─ layouts            <- Theme layouts and templates
         └─ ...

Lookup rules

In most of cases you don’t need to specify the layout: Cecil selects the most appropriate layout, according to the page type.

For example, the HTML output of home page (index.md) will be rendered:

  1. with my-layout.html.twig if the layout variable is set to "my-layout" (in the front matter)
  2. if not, with home.html.twig if the file exists
  3. if not, with index.html.twig if the file exists
  4. if not, with list.html.twig if the file exists
  5. etc.

All rules are detailed below, for each page type, in the priority order.

Type homepage

  1. <layout>.<format>.twig
  2. index.<format>.twig
  3. home.<format>.twig
  4. list.<format>.twig
  5. _default/<layout>.<format>.twig
  6. _default/index.<format>.twig
  7. _default/home.<format>.twig
  8. _default/list.<format>.twig
  9. _default/page.<format>.twig

Type page

  1. <section>/<layout>.<format>.twig
  2. <layout>.<format>.twig
  3. <section>/page.<format>.twig
  4. page.<format>.twig
  5. _default/<layout>.<format>.twig
  6. _default/page.<format>.twig

Type section

  1. <layout>.<format>.twig
  2. <section>/index.<format>.twig
  3. <section>/list.<format>.twig
  4. section/<section>.<format>.twig
  5. _default/section.<format>.twig
  6. _default/list.<format>.twig

Type vocabulary

  1. taxonomy/<plural>.<format>.twig
  2. _default/vocabulary.<format>.twig

Type term

  1. taxonomy/<term>.<format>.twig
  2. taxonomy/<singular>.<format>.twig
  3. _default/term.<format>.twig
  4. _default/list.<format>.twig


The application passes variables to the templates for manipulation in the template. Variables may have attributes or elements you can access, too.

Use a dot (.) to access attributes of a variable: {{ foo.bar }}

You can use variables from different scopes: site, page, cecil.


The site variable contains all variables from the configuration and built-in variables.


title: "My amazing website!"

Can be displayed in a template with:

{{ site.title }}

Built-in variables

Variable Description
site.home ID of the home page.
site.pages Collection of pages, in the current language.
site.pages.showable Same as site.pages but filtered by "showable" status (published pages and not virtual/redirect/excluded).
site.page('id') A page with the given ID.
site.allpages Collection of all pages, regardless of their language.
site.taxonomies Collection of vocabularies.
site.time Timestamp of the last generation.
site.debug Debug mode: true or false.

Loop on site.menus.<menu> to get each entry of the <menu> collection (e.g.: main).

Variable Description
<entry>.name Menu entry name.
<entry>.url Menu entry URL.
<entry>.weight Menu entry weight (useful to sort menu entries).


  {% for entry in site.menus.main|sort_by_weight %}
    <li><a href="{{ url(entry.url) }}" data-weight="{{ entry.weight }}">{{ entry.name }}</a></li>
  {% endfor %}


Informations about the current language.

Variable Description
site.language Language code (e.g.: en).
site.language.name Language name (e.g.: English).
site.language.locale Language locale code (e.g.: en_EN).
site.language.weight Language position in the languages list.


The static files collection can be accessed via site.static if the static load is enabled.

Each file exposes the following properties:

  • path: relative path (e.g.: /images/img-1.jpg)
  • date: creation date (timestamp)
  • updated: modification date (timestamp)
  • name: name (e.g.: img-1.jpg)
  • basename: name without extension (e.g.: img-1)
  • ext: extension (e.g.: jpg)
  • exif: image EXIF data (array)
  • audio: Mp3Info object


A data collection can be accessed via site.data.<filename> (without file extension).


  • data/authors.yml : site.data.authors
  • data/galleries/gallery-1.json : site.data.galleries.gallery-1


Contains built-in variables of a page and those set in the front matter.

Variable Description Example
page.id Unique identifier. blog/post-1
page.title File name (without extension). Post 1
page.date File creation date. DateTime
page.updated File modification date. DateTime
page.body File body. Markdown
page.content File body converted in HTML. HTML
page.section File root folder (slugified). blog
page.path File path (slugified). blog/post-1
page.slug File name (slugified). post-1
page.tags Array of tags. [Tag 1, Tag 2]
page.categories Array of categories. [Category 1, Category 2]
page.pages Collection of all sub pages. Collection
page.pages.showable page.pages with "showable" pages only. Collection
page.type homepage, page, section, vocabulary or term. page
page.filepath File system path. Blog/Post 1.md
page.translations Collection of translated pages. Collection


Navigation between pages in a same Section.

Variable Description Example
page.prev Previous page. Page
page.next Next page. Page


<a href="{{ url(page.prev) }}">{{ page.prev.title }}</a>


Paginator help you to build a navigation for list's pages: homepage, sections, and taxonomies.

Variable Description
page.paginator.pages Pages Collection.
page.paginator.pages_total Number total of pages.
page.paginator.count Number of paginator's pages.
page.paginator.current Position index of the current page.
page.paginator.links.first Page ID of the first page.
page.paginator.links.prev Page ID of the previous page.
page.paginator.links.self Page ID of the current page.
page.paginator.links.next Page ID of the next page.
page.paginator.links.last Page ID of the last page.
page.paginator.links.path Page ID without the position index.


{% if page.paginator %}
  {% if page.paginator.links.prev is defined %}
  <a href="{{ url(page.paginator.links.prev) }}">Previous</a>
  {% endif %}
  {% if page.paginator.links.next is defined %}
  <a href="{{ url(page.paginator.links.next) }}">Next</a>
  {% endif %}
{% endif %}


{% if page.paginator %}
  {% for paginator_index in 1..page.paginator.count %}
    {% if paginator_index != page.paginator.current %}
      {% if paginator_index == 1 %}
  <a href="{{ url(page.paginator.links.first) }}">{{ paginator_index }}</a>
      {% else %}
  <a href="{{ url(page.paginator.links.path ~ '/' ~ paginator_index) }}">{{ paginator_index }}</a>
      {% endif %}
    {% else %}
  {{ paginator_index }}
    {% endif %}
  {% endfor %}
{% endif %}


Variables available in vocabulary and term templates.

Variable Description
page.plural Vocabulary name in plural form.
page.singular Vocabulary name in singular form.
page.terms List of terms (Collection).
Variable Description
page.term Term ID.
page.pages List of pages in this term (Collection).


Variable Description
cecil.url URL of the official website.
cecil.version Cecil current version.
cecil.poweredby Print Cecil v%s with %s is the current version.


Functions can be called to generate content. Functions are called by their name followed by parentheses (()) and may have arguments.


Creates a valid URL for a page, an asset, a page ID or a path.

{{ url(value, {options}) }}
Option Description Type Default
canonical Prefixes path with baseurl or use canonical.url. boolean false
format Defines page output format (e.g.: json). string html
language Trying to force page language (e.g.: fr). string null


# page
{{ url(page) }}
{{ url(page, {canonical: true}) }}
{{ url(page, {format: json}) }}
{{ url(page, {language: fr}) }}
# asset
{{ url(asset('styles.css')) }}
# page ID
{{ url('page-id') }}
# path
{{ url(menu.url) }}
{{ url('tags/' ~ tag) }}


Creates an asset (CSS, JavaScript, image, audio, etc.) from a file path, an URL or an array of files path (bundle).

{{ asset(path, {options}) }}
Option Description Type Default
fingerprint Add the file content finger print to the file name. boolean true
minify Compress file content (CSS or JavaScript). boolean true
filename File where to save content. string styles.css or scripts.js
ignore_missing Do not stop build if file don't exists. boolean false
remote_fallback Load a local asset if the remote one don't exists. string null


{{ asset('styles.css') }}
# CSS bundle
{{ asset(['poole.css', 'hyde.css'], {filename: styles.css}) }}
# JavaScript
{{ asset('scripts.js') }}
# image
{{ asset('image.jpeg') }}
# remote file
{{ asset('https://cdnjs.cloudflare.com/ajax/libs/anchor-js/4.3.1/anchor.min.js', {minify: false}) }}
# with filter
{{ asset('styles.css')|minify }}
{{ asset('styles.scss')|to_css|minify }}

Asset attributes

Assets created with the asset() function expose some useful attributes:

  • file: filesystem path
  • files: array of filesystem path in case of bundle
  • filename: file name
  • path_source: relative path before processing
  • path: relative path
  • missing: true if file not found, but missing is ollowed
  • ext: extension
  • type: media type (e.g.: image)
  • subtype: media sub type (e.g.: image/jpeg)
  • size: size in octets
  • content_source: content before processing
  • content: file content
  • integrity: integrity hash
  • width: image width
  • height: image height
  • exif: image EXIF data as array
  • audio: Mp3Info object
  • video: array of video dimensions (width and height)


# image width in pixels
{{ asset('image.png').width }}px
# photo's date in seconds
{{ asset('photo.jpeg').exif.DateTimeOriginal|date('U') }}
# MP3 song duration in minutes
{{ asset('title.mp3').audio.duration|round }} min
# file integrity hash
{% set integrity = asset('styles.scss').integrity %}


Builds the HTML img srcset (responsive) attribute of an image Asset.

{{ image_srcset(asset) }}


{% set asset = asset(image_path) %}
<img src="{{ url(asset) }}" width="{{ asset.width }}" height="{{ asset.height }}" alt="" class="asset" srcset="{{ image_srcset(asset) }}" sizes="{{ image_sizes('asset') }}">


Returns the HTML img sizes attribute based on a CSS class name.
It should be use in conjunction with the image_srcset function.

{{ image_sizes('class') }}


{% set asset = asset(image_path) %}
<img src="{{ url(asset) }}" width="{{ asset.width }}" height="{{ asset.height }}" alt="" class="asset" srcset="{{ image_srcset(asset) }}" sizes="{{ image_sizes('asset') }}">


Creates the hash (sha384) of a file (from an asset or a path).

{{ integrity(asset) }}

Used for SRI (Subresource Integrity).


{{ integrity('styles.css') }}


Determines read time of a text, in minutes.

{{ readtime(text) }}


{{ readtime(page.content) }} min


Gets the value of an environment variable from its key.

{{ getenv(var) }}


{{ getenv('VAR') }}


The dump function dumps information about a template variable. This is mostly useful to debug a template that does not behave as expected by introspecting its variables:

{{ dump(user) }}


The d() function is the HTML version of dump() and use the Symfony VarDumper Component behind the scenes.

{{ d(variable, {theme: light}) }}
  • If variable is not provided then the function returns the current Twig context
  • Available themes are « light » (default) and « dark »


Sorting collections (of pages, menus or taxonomies).


Sorts a collection by title (with natural sort).

{{ <collection>|sort_by_title }}


{{ site.pages|sort_by_title }}


Sorts a collection by date (most recent first).

{{ <collection>|sort_by_date(variable='date', desc_title=false) }}


# sort by date
{{ site.pages|sort_by_date }}
# sort by updated variable instead of date
{{ site.pages|sort_by_date(variable='updated') }}
# sort items with the same date by desc title
{{ site.pages|sort_by_date(desc_title=true) }}
# reverse sort
{{ site.pages|sort_by_date|reverse }}


Sorts a collection by weight (lighter first).

{{ <collection>|sort_by_weight }}


{{ site.menus.main|sort_by_weight }}


For more complex cases, you should use Twig’s native sort.


{% set files = site.static|sort((a, b) => a.date|date('U') < b.date|date('U')) %}


Variables can be modified by filters. Filters are separated from the variable by a pipe symbol (|). Multiple filters can be chained. The output of one filter is applied to the next.

{{ page.title|truncate(25)|capitalize }}


Filters a pages collection by variable name/value.

{{ <collection>|filter_by(variable, value) }}


{{ pages|filter_by('section', 'blog') }}


For more complex cases, you should use Twig’s native filter.


{% pages|filter(p => p.virtual == false and p.id not in ['page-1', 'page-2']) %}


Converts a Markdown string to HTML.

{{ markdown|markdown_to_html }}
{% apply markdown_to_html %}
{# Markdown here #}
{% endapply %}


{% set markdown = '**This is bold text**' %}
{{ markdown|markdown_to_html }}
{% apply markdown_to_html %}
**This is bold text**
{% endapply %}


Extracts table of content from a Markdown string, in the given format ("html" or "json", "html" by default) and an optional base URL.

{{ markdown|toc(format, url) }}


{{ page.body|toc }}
{{ page.body|toc('json') }}
{{ page.body|toc(url=url(page)) }}


Converts a JSON string to an array.

{{ json|json_decode }}


{% set json = '{"foo": "bar"}' %}
{% set array = json|json_decode %}
{{ array.foo }}


Converts a YAML string to an array.

{{ yaml|yaml_parse }}


{% set yaml = 'key: value' %}
{% set array = yaml|yaml_parse %}
{{ array.key }}


Converts a string to a slug.

{{ string|slugify }}


Truncates a string and appends suffix.

{{ string|excerpt(integer, suffix) }}
Option Description Type Default
length Truncates after this number of characters. integer 450
suffix Appends characters. string


{{ variable|excerpt }}
{{ variable|excerpt(250, '...') }}


Reads characters before or after <!-- excerpt --> or <!-- break --> tag.
See Content documentation for details.

{{ string|excerpt_html({separator, capture}) }}
Option Description Type Default
separator String to use as separator. string excerpt|break
capture Part to capture, before or after the separator. string before


{{ variable|excerpt_html }}
{{ variable|excerpt_html({separator: 'excerpt|break', capture: 'before'}) }}
{{ variable|excerpt_html({capture: 'after'}) }}


Compiles a Sass file to CSS.

{{ asset(path)|to_css }}
{{ path|to_css }}


{{ asset('styles.scss')|to_css }}


Add the file content finger print to the file name.

{{ asset(path)|fingerprint }}
{{ path|fingerprint }}


{{ asset('styles.css')|fingerprint }}


Minifying a CSS or a JavaScript file.

{{ asset(path)|minify }}


{{ asset('styles.css')|minify }}
{{ asset('scripts.js')|minify }}


Minifying a CSS string.

{{ variable|minify_css }}
{% apply minify_css %}
{# CSS here #}
{% endapply %}


{% set styles = 'some CSS here' %}
{{ styles|minify_css }}
{% apply minify_css %}
  html {
    background-color: #fcfcfc;
    color: #444;
{% endapply %}


Minifying a JavaScript string.

{{ variable|minify_js }}
{% apply minify_js %}
{# JavaScript here #}
{% endapply %}


{% set script = 'some JavaScript here' %}
{{ script|minify_js }}
{% apply minify_js %}
  var test = 'test';
{% endapply %}


Compiles a Sass string to CSS.

{{ variable|scss_to_css }}
{% apply scss_to_css %}
{# SCSS here #}
{% endapply %}

Alias: sass_to_css.


{% set scss = 'some SCSS here' %}
{{ scss|scss_to_css }}
{% apply scss_to_css %}
  $color: #fcfcfc;
  div {
    color: lighten($color, 20%);
{% endapply %}


Resizes an image to a specified with.

{{ asset(image_path)|resize(integer) }}


{{ asset(page.image)|resize(300) }}


Converts an image to WebP format.


    <source type="image/webp" srcset="{{ asset(image_path)|webp }}">
    <img src="{{ url(asset(image_path)) }}" width="{{ asset(image_path).width }}" height="{{ asset(image_path).height }}" alt="">


Returns the data URL of an asset.

{{ asset(path)|dataurl }}
{{ asset(image_path)|dataurl }}


Returns a Low Quality Image Placeholder (100x100 px, 50% blurred) as data URL.

{{ asset(image_path)|lqip }}


Returns the dominant hexadecimal color of an image.

{{ asset(image_path)|dominant_color }}
# #F2D07F


Outputs the content of an Asset.

{{ asset(path)|inline }}


{{ asset('styles.css')|inline }}


Converts an asset into an HTML element.

{{ asset(path)|html({attributes, options}) }}
Option Description Type Default
attributes Adds name="value" couple to the HTML element. array
options {responsive: true}: creates responsives images.
{webp: true}: creates WebP versions of the image.
{preload: true}: preloads CSS.


{{ asset('image.png')|html }}
{{ asset('image.jpg')|html({alt: 'Description', loading: 'lazy'}, {responsive: true, webp: true}) }}
{{ asset('styles.css')|html({media: 'print'}) }}
{{ asset('styles.css')|html({title: 'Main theme'}, {preload: true}) }}


Splits a string into an array using a regular expression.

{{ string|preg_split(pattern, limit) }}


{% set headers = page.content|preg_split('/<h3[^>]*>/') %}


Performs a regular expression match and return the group for all matches.

{{ string|preg_match_all(pattern, group) }}


{% set tags = page.content|preg_match_all('/<[^>]+>(.*)<\\/[^>]+>/') %}


Converts a hexadecimal color to RGB.

{{ color|hex_to_rgb }}


Cecil support text translation and date localization.

Text translation

Uses the trans tag or filter to translate texts in templates.

{% trans with variables into locale %}{% endtrans %}
{{ message|trans(variables = []) }}


{% trans %}Hello World!{% endtrans %}
{{ message|trans }}

Include variables:

{% trans with {'%name%': 'Arnaud'} %}Hello %name%!{% endtrans %}
{{ message|trans({'%name%': 'Arnaud'}) }}

Force locale:

{% trans into 'fr_FR' %}Hello World!{% endtrans %}


{% trans with {'%count%': 42}%}{0}I don't have apples|{1}I have one apple|]1,Inf[I have %count% apples{% endtrans %}

Translation files

Translation files must be named messages.<locale>.<format> and stored in the translations directory.
Cecil supports yaml and mo (Gettext) file formats by default.


└─ translations
   ├─ messages.fr_FR.mo   <- Machine Object format
   └─ messages.fr_FR.yaml <- Yaml format

Date localization

Uses the Twig format_date filter to localize a date in templates.

{{ page.date|format_date('long') }}
# September 30, 2022

Supported values are: short, medium, long, and full.

Built-in templates

Cecil comes with a set of built-in templates.

Default templates

A simple main template with a clean CSS.
A pages list with (an optional) pagination.
An Atom feed.
A RSS feed.
A simple list of all terms of a vocabulary.
The sitemap.xml template: list all pages sorted by date.
The robots.txt template: allow all pages except 404, with a reference to the XML sitemap.
A basic error 404 ("Page not found") template.
The redirect template.

Partial templates

A main menu navigation.
A simple paginated navigation for list templates with "Previous" and "Next" links.
All metatags in one template: title, description, canonical, open-graph, twitter card, etc. See configuration.
A basic switcher between languages.

Custom extension

Suggest a modification