
Syrinx
Spreadsheet to website
Syrinx is a simple Python static site generator that converts markdown content into HTML websites.
Quick Start
pip install syrinx
syrinx new --scaffold blog-webawesome
syrinx serve
Or start from scratch:
pip install syrinx
mkdir content
echo "# Hello World" > content/index.md
syrinx serve
Visit http://localhost:8000 to see your site.
How It Works
Syrinx follows a three-stage pipeline:
- Preprocess: Generate markdown from TSV data files (optional)
- Read: Parse the
content/directory into a tree of nodes - Build: Render nodes to HTML using Jinja2 templates
Your project structure:
my-site/
├── syrinx.cfg # Configuration (optional)
├── content/
│ └── index.md # Your markdown content
├── data/ # TSV files for content generation (optional)
├── theme/
│ ├── templates/ # Jinja2 templates
│ └── assets/ # CSS, images, fonts
└── dist/ # Generated output
Content
Branches and Leaves
Syrinx organizes content as a tree:
- Branch: A directory containing
index.md— becomes a section page - Leaf: A non-index markdown file — becomes a child page (only built with
leaf_pages = true)
If a directory has no index.md, it won't generate a page but its children are still processed.
Frontmatter
Markdown files start with frontmatter in YAML (surrounded by ---) or TOML (surrounded by +++):
+++
Title = "My Page"
SequenceNumber = 1
LastModified = 2025-01-15
IncludeInSitemap = true
+++
# Page content here...
Frontmatter fields:
| Field | Description |
|---|---|
Title |
Page title (defaults to first heading) |
SequenceNumber |
Controls ordering in menus and lists |
LastModified |
Date for sitemap <lastmod> element |
LastModifiedBranch |
Git branch to track for last-modified date |
IncludeInSitemap |
Explicit sitemap inclusion (true/false) |
Archetype |
Template name for TSV data import |
Templates
Templates use Jinja2 and live in theme/templates/.
Template Selection
Syrinx tries templates in this order:
{node_name}.jinja2— named after the content file/directoryroot.jinja2— for the top-level page onlyleaf.jinja2— for leaf nodespage.jinja2— default fallback
Available Variables
| Variable | Description |
|---|---|
index |
The current node being rendered |
root |
The root node of the content tree |
Node Properties
| Property | Description |
|---|---|
title |
Page title |
content_html |
Rendered markdown content |
branches |
Child branch nodes |
leaves |
Child leaf nodes |
front |
Frontmatter dictionary |
path |
Filesystem path |
address |
URL path |
Configuration
Create syrinx.cfg in your project root:
domain = "example.com"
sitemap = "opt-out"
urlformat = "filesystem"
leaf_pages = false
clean = true
environment = "production"
verbose = false
Options:
| Option | Values | Description |
|---|---|---|
domain |
string | Domain for canonical URLs and sitemap |
sitemap |
opt-in, opt-out |
Default sitemap inclusion behavior |
urlformat |
filesystem, mkdocs, clean |
URL structure style |
leaf_pages |
true, false |
Whether to build pages for leaves |
clean |
true, false |
Clean dist/ before building |
environment |
string | Environment name (available in templates) |
verbose |
true, false |
Enable detailed logging |
CLI Commands
syrinx build # Build site to dist/
syrinx build --clean # Clean dist/ first
syrinx build --leaf-pages # Include leaf pages
syrinx serve # Dev server on port 8000
syrinx serve --port 3000 # Custom port
syrinx new --scaffold NAME # Create new site from template
All config file options can be overridden via CLI flags. Run syrinx <command> --help for details.
Advanced
Generating Content from Data
Syrinx can generate markdown files from TSV spreadsheets — useful for sites with many similar pages (team members, products, blog posts, etc.).
Setup:
- Create a TSV file in
data/(e.g.,data/people.tsv) - Create a matching archetype template in
archetypes/(e.g.,archetypes/people.md) - Run
syrinx build— markdown files are generated incontent/{name}/before the HTML build
The archetype filename must match the TSV filename (without extension). Archetypes use Jinja2 syntax for templating.
Example TSV (data/people.tsv):
PersonId FullName Role Image
jsmith Jane Smith Lead jane.jpg
bjones Bob Jones Developer bob.jpg
Example archetype (archetypes/people.md):
+++
Title = "{{ FullName }}"
SequenceNumber = {{ SequenceNumber }}
Image = "{{ Image }}"
+++
{{ FullName }} is a {{ Role }} on the team.
Generated output (content/people/jsmith.md):
+++
Title = "Jane Smith"
SequenceNumber = 0
Image = "jane.jpg"
+++
Jane Smith is a Lead on the team.
How it works:
- First column becomes the output filename (e.g.,
jsmith→jsmith.md) - Column headers become Jinja2 template variables
SequenceNumberis auto-generated if not in your TSV- With
clean = true, stale generated files are removed on each build
Git Integration
If you set LastModifiedBranch in frontmatter, Syrinx will track the last commit date on that branch for the content file, automatically updating LastModified for your sitemap.