A static site generator with an extendable plugin architecture. I use this to power pierce.dev.
- Fast: Built with asyncio for concurrent processing
- Modular: Flexible plugin architecture for extensibility
- Developer-friendly: Hot reloading with
watchfiles
and live preview - Configurable: YAML configuration with sensible defaults
- Initialize a new project:
uv run scribe init
- Start the development server. If you're running on a new device that doesn't yet have an image cache, you can expect this to take some time as we convert all of your images into responsive formats. See the
image_encoding
plugin for more details.
uv run scribe dev
- Open your browser to
http://localhost:8000
scribe init
- Initialize a new projectscribe build --output <dir>
- Build the static site to specified directoryscribe dev
- Start development server with file watching and auto-rebuildscribe add-headers
- Add frontmatter headers to markdown files that don't have themscribe config
- Show current configuration
Configuration is stored in ~/.scribe/config.yml
:
source_dir: ./content
output_dir: ./dist
site_title: My Site
site_description: A great site
host: 127.0.0.1
port: 8000
note_plugins:
- name: frontmatter
enabled: true
- name: markdown
enabled: true
templates:
template_path: ./templates
base_templates:
- index.j2
- about.j2
note_templates:
- template_path: blog_post.j2
url_pattern: /blog/{slug}/
predicates:
- is_published
- has_tag:blog
static_path: ./static
Scribe uses a flexible plugin system where each markdown file is processed through a chain of plugins. Each plugin receives a PageContext
object containing metadata and content.
- FrontmatterPlugin: Extracts YAML frontmatter from markdown files
- MarkdownPlugin: Converts markdown to HTML
- SlugPlugin: Generates URL slugs
- DatePlugin: Parses and formats dates
from scribe.plugins.base import Plugin
from scribe.context import PageContext
class MyPlugin(Plugin):
async def process(self, ctx: PageContext) -> PageContext:
# Process the context
return ctx
Scribe supports YAML frontmatter headers in markdown documents for metadata and configuration.
Use standard YAML frontmatter with ---
delimiters at the beginning of your markdown files:
---
title: "My Blog Post"
description: "A comprehensive guide to something amazing"
author: "John Doe"
date: "2024-01-15"
tags: ["python", "web-development", "static-sites"]
draft: false
slug: "my-custom-slug"
---
# Your content title
Your markdown content goes here...
Field | Type | Description |
---|---|---|
title |
string | Page title (can be overridden by markdown # header) |
description |
string | Meta description for SEO |
author |
string | Author name |
date |
string | Publication date (formats: YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, YYYY/MM/DD) |
tags |
list/string | Tags as array or comma-separated string |
slug |
string | Custom URL slug (auto-generated from filename if not provided) |
template |
string | Specific template to use for this page |
layout |
string | Layout specification |
draft |
boolean | Whether the page is a draft (excludes from is_published predicate) |
permalink |
string | Custom permalink override |
- Title Priority: Markdown headers (
# Title
) override frontmatter titles - Tag Formats: Tags can be specified as
["tag1", "tag2"]
or"tag1, tag2"
- Date Parsing: Multiple date formats supported with intelligent fallback
- Auto-generation: Missing slugs and URLs are automatically generated from filenames
- Template Access: All frontmatter data is available in templates via
note.frontmatter
Scribe uses Jinja2 templates for rendering HTML pages. Templates are configured in your config.yml
file.
- Base Templates: Standalone templates that render 1:1 to HTML files with global site context
- Note Templates: Templates that wrap individual markdown notes with filtering predicates
All templates receive the following variables:
Variable | Type | Description |
---|---|---|
site.title |
string | Site title from configuration |
site.description |
string | Site description from configuration |
site.url |
string | Base URL for the site |
config |
dict | Full configuration object |
Note templates additionally receive a note
object with the following properties:
Variable | Type | Description |
---|---|---|
note.title |
string | Page title from frontmatter or filename |
note.content |
string | Rendered HTML content |
note.author |
string | Author from frontmatter |
note.date |
string | Formatted date |
note.tags |
list | List of tags from frontmatter |
note.description |
string | Meta description |
note.slug |
string | URL slug |
note.url |
string | Full URL path |
note.is_draft |
boolean | Whether the note is a draft |
note.frontmatter |
dict | Raw frontmatter data |
note.source_path |
string | Path to source markdown file |
note.relative_path |
string | Relative path from source directory |
note.modified_time |
float | File modification timestamp |
Filter which notes match templates using predicates:
Predicate | Description |
---|---|
all |
Matches all notes |
is_published |
Matches non-draft notes |
is_draft |
Matches draft notes only |
has_tag:tagname |
Matches notes with specific tag |
Scribe can copy static files (CSS, JavaScript, images, etc.) directly to the output directory without processing.
Configure the static_path
in your config to specify a directory containing static assets:
static_path: ./static
- All files in the
static_path
directory are copied recursively to the output directory - Files are only copied if they don't exist or are newer than the existing file
- Directory structures are preserved and merged with generated content
- Static files can coexist with generated HTML files in the same directories
MIT License