Basics
plym is highly configurable on both the backend and the templating side. Learn how to customise your plym instance or template.
Updated Aug 04, 2026
On this page
plym reads its settings from two YAML files: config.yaml, which you own, and template.yaml, which ships inside the active template.
config.yaml sits at the root of your blog directory and holds every operator setting — identity, branding, caching, HTML injection, pagination.
template.yaml sits at templates/<name>/template.yaml and holds the design defaults the template author picked: fonts, colors, and the Prism theme.
plym merges the two at startup, key by key, and your config.yaml wins every collision. Unlike .env, neither file holds credentials — no database password, JWT secret, or admin login belongs here.
Precedence
template.yaml is merged under config.yaml. The merge is per key, not per block, so setting one color in config.yaml keeps the template's other three.
The docs template declares four colors:
colors:
primary: "#1C1712"
secondary: "#6B6055"
accent: "#D2340A"
background: "#FFFCF6"
Your config.yaml overrides one of them:
template: docs
colors:
accent: "#00FF00"
The effective values:
| Key | Value | Source |
|---|---|---|
colors.primary |
#1C1712 |
template.yaml |
colors.secondary |
#6B6055 |
template.yaml |
colors.accent |
#00FF00 |
config.yaml |
colors.background |
#FFFCF6 |
template.yaml |
template.yaml accepts three keys
fonts, colors, and prism.theme. Any other key — including prism.enabled and prism.languages, which are yours alone — fails validation and the api does not start.
Configuration is file-only
Edit the file, then apply the change with the CLI.
GET /api/config reads back the merged result, which is the fastest way to see which file won:
curl http://localhost:9173/api/config \
-H "Authorization: Bearer $PLYM_TOKEN"
{
"name": "plym",
"description": null,
"website": "plym.io",
"blog_home": "plym.io/blog",
"blog_prefix": "/blog",
"language": "en",
"template": "docs",
"fonts": {"heading": "Space Grotesk", "body": "Archivo"},
"colors": {"primary": "#1C1712", "secondary": "#6B6055", "accent": "#00FF00", "background": "#FFFCF6"},
"prism": {"enabled": true, "languages": "python", "theme": "tomorrow"},
"logo": "/blog/static/logo-f5924e8c.webp",
"favicon": "/blog/static/favicon-8b458c56.ico"
}
logo and favicon come back as local paths because plym downloads both at startup and serves them from its own origin.
Changes apply on restart
The api reads both files once, at startup, and caches the result. Nothing is re-read per request, so every edit needs plym reload or plym rebuild. See Instance configuration for which command to run.
A rejected value stops the api
Validation runs at import time. A bad value — a string where an integer belongs, an inject snippet containing </body> — raises a ValidationError and the process exits.
Published posts keep serving through it, because they are static files on disk. The blog index returns 502 until you fix the file and restart, since the index is rendered by the api on request. Check docker compose logs api for the failing key.