Neato

Documentation

Neato is a static site generator written in PHP. You run it from the command line, and it reads your content, processes everything, and writes a complete static HTML site that can be deployed anywhere.

Running Neato

From the command line:

php neato.php

Neato expects to find a neato.yaml in the same directory as the main script. If it’s missing, it exits with an error and points you to where you can get one.

Directory Structure

Neato uses several paths, all configured in neato.yaml:

Path Purpose
content/ Your source content files
templates/ HTML templates
published/ Where the generated site is written
cache/ Build cache files
power-ups/ Optional plugin extensions

Neato creates any missing directories automatically (except published/, which it deletes and recreates if clear_published_directory is enabled in config).

Content Files

Neato processes files in the content/ directory with these extensions: .html, .mphp, .md, .txt, .neato. All other files (images, CSS, JS, etc.) are copied as-is to the published directory.

Frontmatter

Your content files can have YAML frontmatter at the top. For example:

---
title: My Page
color: pink
---

Page body here.

Neato automatically adds three metadata fields to any file that’s missing them, writing them back to the source file:

  • created: set initially from the file’s modification time and not changed again
  • modified: set from the file’s modification time
  • uuid: a generated UUID

Draft Status

Any file with status: draft in its frontmatter is skipped during generation, allowing you to work on draft content without publishing it.

Output Path Logic

By default, a content file like content/about/me.html becomes published/about/me/index.html, producing a clean URL (/about/me/).

Two ways to override this:

  • preserve: true in frontmatter keeps the original filename instead of wrapping it in a directory.
  • location: some/path in frontmatter overrides the output path entirely.

After processing all pages, Neato automatically copies published/home/index.html to published/index.html to serve as the site root.

Templates

Templates live in the templates/ directory. The default page template is set in neato.yaml. Individual content files can specify a different template with template: filename.html in their frontmatter.

Token Substitution

Templates use {token} syntax for variable substitution. Available tokens:

Token Value
{title} The page title (from the key defined in config)
{content} The rendered page body
{description} The page description
{uuid} The page UUID
{path} The page path
{unix-time} The current Unix timestamp
{microtime} The current microtime
{site.key} Any value from neato.yaml config (e.g. {site.domain})
{any-frontmatter-key} Any string value from the page’s frontmatter

Template Includes

Templates can include other templates with {templates/partial-name} (the .html extension is optional).

Function Tags

There’s a function tag syntax {function:parameter}. For example {fa:solid/face-grin-beam}, which Power-Ups can use to handle custom tag processing.

PHP in Content

If parse_php: true is set in the config, PHP code in content files is executed before the content is passed to the template. The following variables are available inside content files:

  • $data: the page’s metadata and the content array
  • $config: the full Neato config
  • $item: the source file path

Power-Ups

Power-Ups are plugins that live in subdirectories of the power-ups/ path. Each Power-Up is a directory containing a PHP file with the same name as the directory (e.g. power-ups/my-plugin/my-plugin.php).

To disable a Power-Up without deleting it, prefix its directory name with an underscore (e.g. _my-plugin).

Hooks

Power-Ups extend Neato by registering hooks:

Hook When it fires
init After config and Power-Ups load, before any pages are processed
before_page Before each page is rendered into its template
after_page After page data is assembled, before the file is written
after_template After a page has been fully rendered
shutdown After all pages are processed

To register a hook inside a Power-Up:

add_hook('init', function() {
  // runs once at startup
});

add_hook('after_page', function(&$data) {
  // $data is the page data array; modify it if needed
}, priority: 10);

Hooks run in ascending priority order (lower numbers runs earlier). If a hook function returns a non-null value, it replaces the first argument passed to the hook.

Build Cache

After each build, Neato writes a JSON cache file to the cache/ directory listing every file in the published directory and its size. The filename includes the microtime of the build (e.g. neato_17834921045.cache).

Terminal Output

Neato logs everything to the terminal with a timestamp and a level label:

Level Meaning
INFO General status messages
LOADING Power-Up loading
JOB Per-file processing
REPORT Build summary (page count, time elapsed)
ERROR Something went wrong

Time is reported in the most human-readable unit (microseconds, milliseconds, or seconds) depending on how long the build took.