Plate — Product Requirements
Set the table. Your way.
Status: Stable Owner: Cozy Badger Last updated: April 2026
1. Overview
Plate is a presentation frontend that fetches content from Snackbox, renders Markdown to HTML, and serves the result to visitors through a Handlebars theme.
Plate is agnostic. It does not know which theme will be active, and it does not know anything about Pantry or any other ecosystem tool. It only knows two things: the Snackbox API contract, and the theme contract it owns.
2. Responsibilities
- Fetch content from the Snackbox REST API (
/api/v1/) - Render raw Markdown to sanitized HTML via the Remark pipeline
- Load a Handlebars theme from a configured directory at startup
- Pass well-defined template context objects to theme templates
- Serve rendered pages to visitors
- Fall back to Picnic when no external theme is configured or available
- Define and publish the theme contract (JSON Schema)
3. Non-Responsibilities
- Plate does not manage content — that is Snackbox's job
- Plate does not provide an admin UI — that is Pantry's job
- Plate does not authenticate users or manage sessions
- Plate does not write to Snackbox
4. Upstream Dependency: Snackbox
Plate is a read-only consumer of the Snackbox API. It uses the following public endpoints (no authentication required):
| Endpoint | Purpose |
|---|---|
GET /api/v1/settings | Site title, description, logo, language, theme name |
GET /api/v1/navigation | Main and secondary navigation menus |
GET /api/v1/posts | Paginated post listings, optional tag filter |
GET /api/v1/posts/{id} | Single post by ID or slug |
GET /api/v1/pages/{id} | Single page by ID or slug |
GET /api/v1/tags | All tags |
GET /api/v1/tags/{id} | Single tag by ID or slug |
GET /api/v1/settings/social-accounts | Social profile links for footer/header |
GET /health | Snackbox health check |
Content fields (content in posts and pages) are returned as raw Markdown. Plate is responsible for rendering them to HTML.
5. Theme System
Plate owns the theme contract. A theme is a directory containing:
- A
manifest.json(validated against the theme contract JSON Schema) - Handlebars templates for each page type
- Handlebars partials
- Static assets (CSS, fonts, images)
Plate loads one active theme at startup. The theme name is resolved from configuration (environment variable). If no theme is found or configured, Plate falls back to Picnic.
Plate serves theme static assets at a well-known path so templates can reference them without hardcoding URLs.
5.1 Picnic — Built-in Fallback Theme
Picnic is bundled directly into Plate. It is intentionally minimal — the floor, not the ceiling. It exists to:
- Ensure Plate always renders something reasonable out of the box
- Act as the reference implementation of the theme contract
- Show theme developers the minimum required structure
Picnic may use Crumbs as its CSS foundation, but this is not required by the theme contract.
5.2 External Themes
External themes are distributed as uploadable packages and stored in a configured themes directory on disk. Plate discovers them at startup by reading the directory and validating each manifest. Plate has no knowledge of any specific external theme by name.
6. Template Context
Plate constructs a context object for each request and passes it to the appropriate Handlebars template. The context always includes a global section (settings, navigation, site metadata) and a page-specific section.
Page types and their templates
| Page type | Template | Context |
|---|---|---|
| Home | home.hbs | Featured and recent posts |
| Post list | list.hbs | Paginated posts, optional tag filter |
| Single post | post.hbs | Post content (rendered HTML), tags, authors |
| Single page | page.hbs | Page content (rendered HTML) |
| Tag | tag.hbs | Tag metadata, paginated posts for tag |
| 404 | 404.hbs | Error info |
| 500 | 500.hbs | Error info |
7. Markdown Rendering
Plate uses the Remark/Rehype pipeline to render Markdown:
remark-parse— parse Markdown ASTremark-gfm— GitHub Flavoured Markdown support (tables, strikethrough, etc.)remark-rehype— convert Markdown AST to HTML ASTrehype-sanitize— sanitize HTML outputrehype-stringify— serialize to HTML string
Additional transformations applied during rendering:
- Lazy loading and async decoding on images
target="_blank"andrel="noopener noreferrer"on external links
8. Caching
Plate caches Snackbox API responses in memory to reduce upstream load. Cache TTL and maximum entry count are configurable via environment variables. The cache is a simple LRU store with per-entry TTL. There is no shared or distributed cache — each Plate instance manages its own.
9. Configuration
Plate is configured entirely via environment variables. No runtime config file is written or read. All values have sensible defaults so Plate can start without any configuration for local development.
Key variables:
| Variable | Default | Description |
|---|---|---|
PORT | 3000 | HTTP port |
HOST | localhost | Bind address |
NODE_ENV | development | Environment (development, production) |
SNACKBOX_API_URL | http://localhost:8080 | Snackbox base URL |
SNACKBOX_TIMEOUT | 5000 | Request timeout in ms |
THEME_NAME | picnic | Active theme name |
THEMES_DIR | themes | Directory for external themes |
CACHE_ENABLED | true | Enable API response cache |
CACHE_TTL_MULTIPLIER | 1 | Multiplies base content cache TTLs |
CACHE_MAX_ENTRIES | 100 | Maximum cache entries |
POSTS_PER_PAGE | 10 | Default pagination size |
10. Contracts
Plate owns and publishes the theme contract:
| Contract | Format | Location |
|---|---|---|
| Theme manifest | JSON Schema | contracts/theme-manifest.schema.json |
Plate consumes but does not own:
| Contract | Owner | Format |
|---|---|---|
| Snackbox API | Snackbox | OpenAPI YAML |