Nelson CMS

My personal “handmade” light flat file CMS

← Back to portfolio

Nelson CMS icon

Nelson - The small flat file CMS

Creation date: 2015

The project

During 6 months I worked on a way to use a templating system in my projects. I discover the Lex PHP template parser and played a lot with it :). After a while, I decided to create a website with it. The results was not so far from a flat file CMS. Now with an alpha version of the Admin area, it's totally a CMS.

So where can you see a website using it?
Well, in fact you currently use one. Yep, is running under Nelson CMS!

How it works

It uses the Markdown Extra Syntax (which can be mixed with HTML) for the content files, the Lex syntax for the template files and YAML syntax for the settings files and to store data in general, so no DB.

Folder structure example:

  • your-app-name
    • _app/
    • _cache/
    • _config/
    • _content/
    • _sitemap/
    • _themes/
    • admin/
    • medias/
    • .htaccess
    • autoload.php
    • index.php

Lex syntax example:

// PHP array sample
    'title'     => 'Lex is Awesome!',
    'name'      => 'World',
    'real_name' => array(
        'first' => 'Lex',
        'last'  => 'Luther',

{{# This is a comment #}}

    This is a comment too


{{# Parsed: Hello, World! #}}
Hello, {{ name }}!

{{# Parsed: <h1>Lex is Awesome!</h1> #}}
<h1>{{ title }}</h1>

{{# Parsed: My real name is Lex Luther! #}}
My real name is {{ real_name.first }} {{ real_name.last }}

Back to top ↑


Like any CMS, Nelson allows you to create your own custom theme. To do so, just create a folder with these files & directories:

  • _themes/
    • my-custom-theme/
      • _cssy/ (optional)
      • _jssy/ (optional)
      • translations/ (optional)
      • css/
      • js/
      • partials/
      • 404.html
      • body.html
      • footer.html
      • header.html
      • home.html (optional)
      • theme.yaml

To use a custom theme you have to define your theme directory name in the config.yaml file in the _config/ directory.

theme: "my-custom-theme"

Back to top ↑


The optional _cssy & _jssy directories allow theme developers separate the "working" CSS & JS files from the production files. All files in these two folders will be parsed, aggregated and minified to produce a single optimized CSS and JS file. The outputed file name is defined in the site config.yaml file

# default styles file name
cssfile: inspiredwebdesign.css

# default javascript file name
jsfile: inspiredwebdesign.js

Back to top ↑

Translation ready

The optional translations directory allows theme developers to avoid text in templates by using a simple variable which will be replaced by the proper translation, depending of your language option defined in the site config.yaml file:

# site language
# must be an ISO language code (
lang: en

Then you should have a file for each language you need in the translations directory:

  • translations/
    • de.yaml
    • en.yaml
    • fr.yaml

This makes it easier to switch the entire website language without searching for texts in template files and it ensures that any texts are well translated.


<nav class="navigation-menu">
        <li class="item">
            <a href="{{ site.urls.home }}">{{ }}</a>
        <li class="item">
            <a href="{{ }}">{{ }}</a>

Translation file

# French translation file
        home: Accueil
        contact: Contactez-nous


<nav class="navigation-menu">
        <li class="item">
            <a href="/">Accueil</a>
        <li class="item">
            <a href="/contact-us/">Contactez-nous</a>

Back to top ↑


Thanks to the Lex syntax, create a template file is really easy. Every data are accessible via some global variables like {{ site }} or {{ page }}.

Global variables in templates

{{ page.header }}
<section class="intro">
    <div class="wrapper">
        {{ page.content.main }}
{{ page.footer }}

Global variables list

{{ site }} # give access to all high level data such as site name, theme name etc.
{{ theme }} # give access to your theme files
{{ lang }} # give access to the translation files (located in the theme folder)
{{ page }} # give access to all current page files
{{ blog }} # give access to the list of blog articles (and their data)
{{ device }} # give access to info from the users device
{{ cookies }} # give access to all cookies & their value

Back to top ↑


Use the "site" variable

<h1> Welcome to {{ }}! </h1>
<p> Follow me on Twitter @{{ site.twitter }} </p>

Include a theme's partial file

{{# will be replaced by the content of the "partials/file_name.html" file #}}
<div class="extra-content"> {{ theme.partial.file_name }} </div>

Detect browser type or mobile devices

{{ if device.browser === "FireFox" }}
    <p> Your browser is FireFox </p>
{{ else }}
    <p> Your browser is {{ device.browser }} </p>
{{ endif }}

{{ unless device.is_mobile }}
    <p> You are on a desktop </p>
{{ else }}
    <p> You are on a mobile device </p>
{{ endif }}

Get a cookie value

{{ if cookies.my_cookie_name === "user_logged" }}
    <p> Login successful! </p>
{{ endif }}

Back to top ↑

Create a page

All the pages are generated from the _content directory. Each a page are represented as a folder containing at least two required files:

  • _content/
    • my_page/
      • settings.yaml

Page content

The file contains the "real" content, such as the texts, links to images etc. It is a Markdown (MD) file but as it uses the Markdown Extra library, it accepts standard HTML tags. This content is also parsed by the Lex parser so you can use it as well:

# Title
## Sub title
Some content here...

<div class="sepcial">
    <p> Some HTML tags </p>

**My social medias accounts**

- Twitter: {{ site.twitter }}
- Facebook: {{ site.facebook }}
- Google+: {{ site.google_plus }}

Back to top ↑

Page settings

The settings.yaml file contains the necessary information of the page like its URL, title, description and any information relative to this page. As it uses the YAML syntax, this file is really flexible! So you can easily play with it.

# Page settings
url: my-page-url

title: My awesome page

description: Some description here...

Example: create a simple photo gallery

Page settings

# Gallery
            class: first_pic
            picture: { thumbnail: "/path/to/image/small-01.jpg", fullsize: "/path/to/image/fullsize-01.jpg"}
            caption: First picture
            class: second_pic
            picture: { thumbnail: "/path/to/image/small-02.jpg", fullsize: "/path/to/image/fullsize-02.jpg"}
            caption: Second picture
            class: third_pic
            picture: { thumbnail: "/path/to/image/small-03.jpg", fullsize: "/path/to/image/fullsize-03.jpg"}
            caption: Third picture

Page content

# Discover the gallery
Here are some of my greatest pictures

<div class="gallery">
{{ if }}
    {{ }}
        <div class="item item-{{ class }}">
            <a href="{{ item.picture.fullsize }}" title="See the {{ item.caption }}">
                <img src="{{ item.picture.thumbnail }}" alt="{{ item.caption }} preview">
                <span class="caption">{{ item.caption }}</span>
    {{ / }}
{{ endif }}


<h1>Discover the gallery</h1>
<p>Here are some of my greatest pictures</p>
<div class="gallery">
    <div class="item item-first_pic">
        <a href="/path/to/image/fullsize-01.jpg" title="See the First picture">
            <img src="/path/to/image/small-01.jpg" alt="First picture preview">
            <span class="caption">First picture</span>
    <div class="item item-second_pic">
        <a href="/path/to/image/fullsize-02.jpg" title="See the Second picture">
            <img src="/path/to/image/small-02.jpg" alt="Second picture preview">
            <span class="caption">Second picture</span>
    <div class="item item-third_pic">
        <a href="/path/to/image/fullsize-03.jpg" title="See the Third picture">
            <img src="/path/to/image/small-03.jpg" alt="Third picture preview">
            <span class="caption">Third picture</span>

Back to top ↑

Create a template

To create a template file for a specific page, simply create a file in your theme folder with the name of the page you want. For example, if your page folder name in the _content/ directory is my_page/ so create a my_page.htmlfile in the root of your theme.

You can include your page content file using the {{ page.content.main }} variable. You can also include any additional content file by using its file name. For example: {{ page.content.file_name }} will call the my_page/ file.

Page template sample

{{ page.header }}
<section class="main">
    <div class="entry">{{ page.content.main }}</div>
<section class="more">
    <div class="entry">{{ page.content.more }}</div>
{{ page.footer }}

Back to top ↑

As shown above, all page templates starts with {{ page.header }} and ends with {{ page.footer }}. These required theme files are always accessible as well as the files from the partials folder.

Header sample

<!DOCTYPE html>
<html {{ if site.lang }}lang="{{ site.lang }}"{{ endif }}>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=2.0, minimum-scale=0.25, user-scalable=yes">
    <title>{{ if page.title }}{{ page.title }} - {{ endif }}{{ }}</title>
    <link rel="canonical" href="{{ site.urls.home }}{{ if page.url }}{{ page.url }}/{{ endif }}">

    {{ if page.description }}
        <meta name="descritpion" content="{{ page.description }}">
    {{ endif }}

    {{ if page.keywords }}
        <meta name="keywords" content="{{ page.keywords }}">
    {{ endif }}

    <script src=""></script>
    <link rel="stylesheet" type="text/css" href="{{ theme.styles }}">
    <div {{ if }}id="Page{{ }}"{{ endif }} class="page_wrapp">
        <header> {{ theme.partial.header_navigation }} </header>

Adding some SEO meta tags

<!-- START :: opengraph meta tags -->
    <meta property="og:url" content="{{ site.urls.home }}{{ if page.url }}{{ page.url }}/{{ endif }}">
    <meta property="og:type" content="website">
    <meta property="og:site_name" content="{{ }}">
    {{ if page.picture.url }} <meta property="og:image" content="{{ }}{{ page.picture.url }}"> {{ endif }}
    <!-- END :: opengraph meta tags -->

    <!-- START :: meta tags -->
    {{ if page.title }} <meta itemprop="name" content="{{ page.title }}"> {{ endif }}
    {{ if page.description }} <meta itemprop="description" content="{{ page.description }}"> {{ endif }}
    {{ if page.picture.url }} <meta itemprop="image" content="{{ }}{{ page.picture.url }}"> {{ endif }}
    <!-- END :: meta tags -->

    <!-- START :: twitter meta tags -->
    <meta name="twitter:card" value="summary">
    <meta name="twitter:title" value="{{ if page.title }}{{ page.title }} - {{ endif }}{{ }}">
    {{ if page.picture.url }} <meta name="twitter:image" content="{{ }}{{ page.picture.url }}"> {{ endif }}
    {{ if page.description }} <meta name="twitter:description" value="{{ page.description }}"> {{ endif }}
    {{ if site.twitter }}
        <meta name="twitter:site" value="{{ site.twitter }}">
        <meta name="twitter:creator" value="{{ site.twitter }}">
    {{ endif }}
    <!-- END :: twitter meta tags -->

↑ Back to top Continue to portfolio →

Beam me up, Scotty!