Reference for tinkerdown.yaml - the optional configuration file for complex apps.
Recommendation: For most apps, configure sources directly in frontmatter. Use
tinkerdown.yamlonly for shared configuration across multiple pages or complex setups.
| Use Case | Recommendation |
|---|---|
| Single-page app | Use frontmatter |
| Simple multi-page app | Use frontmatter per page |
| Shared sources across pages | Use tinkerdown.yaml |
| Complex caching strategies | Use tinkerdown.yaml |
| Server settings (port, host) | Use tinkerdown.yaml |
| Secrets via environment variables | Use tinkerdown.yaml |
Place tinkerdown.yaml in your app's root directory:
myapp/
├── tinkerdown.yaml # Optional
├── index.md
└── ...
# Server settings (can't be in frontmatter)
server:
port: 8080
host: localhost
# REST API (optional)
api:
enabled: true
auth:
api_key: ${API_KEY} # Legacy single key (coexists with keys:)
header_name: X-API-Key # Default; set to "Authorization" for Bearer tokens
keys:
- name: reader
key: ${READ_KEY}
permissions: [read]
cors:
origins: ["http://localhost:3000"]
rate_limit:
requests_per_second: 10
burst: 20
max_tracked_ips: 10000
# Global styling (can also be per-page in frontmatter)
styling:
theme: clean # clean, dark, minimal
# Shared data sources
sources:
source_name:
type: sqlite|rest|exec|json|csv|markdown|wasm
# Type-specific options...
cache:
ttl: 5m
strategy: simple|stale-while-revalidate
timeout: 10sServer settings must be in tinkerdown.yaml (not available in frontmatter):
server:
port: 8080 # Server port (default: 8080)
host: localhost # Server host (default: localhost)The optional api: block enables a REST API for programmatic access to your app's data sources.
api:
enabled: true # Enable REST API endpoints (default: false)Important: If
api.authis omitted whileapi.enabled: true, all API requests are allowed through without authentication.
Configure api.auth to require API key authentication on all API endpoints.
The simplest setup — one key with full permissions (read, write, delete):
api:
enabled: true
auth:
api_key: ${API_KEY}For finer-grained access, define named keys with specific permissions:
api:
enabled: true
auth:
keys:
- name: dashboard
key: ${DASHBOARD_KEY}
permissions: [read]
- name: admin
key: ${ADMIN_KEY}
permissions: [read, write, delete]Available permissions:
| Permission | Allowed HTTP methods |
|---|---|
read |
GET, HEAD |
write |
POST, PUT, PATCH |
delete |
DELETE |
Note:
OPTIONSrequests bypass permission checks entirely to support CORS preflight.
If permissions is omitted for a named key, the key can authenticate but has no permissions — all method-level checks will be denied.
Both formats can coexist — the legacy api_key is treated as a key named "default" with full permissions.
By default, all keys (both legacy and named) are sent via the X-API-Key header. To use Bearer token authentication instead:
api:
enabled: true
auth:
api_key: ${API_KEY}
header_name: Authorization # Expects "Authorization: Bearer <token>"When using Authorization, clients send Authorization: Bearer <token> and the middleware strips the Bearer prefix before matching. Set api_key (or keys[].key) to the raw token value, not the full Bearer ... string.
Secure default: If any key (
api_keyorkeys[].key) references an environment variable that is not set, authentication is still treated as enabled. The expanded key is empty, so no request can match it and all API requests are rejected. Auth is never silently disabled by a missing env var.
Configure allowed origins for cross-origin API requests:
api:
enabled: true
cors:
origins:
- "http://localhost:3000"
- "https://myapp.example.com"Use "*" to allow all origins (not recommended for production with authenticated APIs).
Protect API endpoints with per-IP rate limiting:
api:
enabled: true
rate_limit:
requests_per_second: 10 # Per IP (default: 10; supports floats, e.g. 0.5)
burst: 20 # Max requests in a spike before rate kicks in (default: 20)
max_tracked_ips: 10000 # Max unique IPs tracked; LRU eviction (default: 10000)Can be in frontmatter or tinkerdown.yaml. Config file applies globally:
styling:
theme: clean # Theme name (default: clean)
# Options: clean, dark, minimalSources in tinkerdown.yaml are available to all pages. Page-specific sources should go in frontmatter.
sources:
example:
type: <source_type> # Required: sqlite, rest, graphql, exec, json, csv, markdown, wasm
cache: # Optional: caching configuration
ttl: 5m # Time-to-live
strategy: simple # simple or stale-while-revalidate
timeout: 10s # Optional: request timeoutsources:
tasks:
type: sqlite
path: ./data.db
query: SELECT * FROM taskssources:
users:
type: rest
from: https://api.example.com/users
method: GET
headers:
Authorization: Bearer ${API_TOKEN}sources:
issues:
type: graphql
from: https://api.github.com/graphql
query_file: queries/issues.graphql # Path to .graphql file
variables: # Optional query variables
owner: livetemplate
repo: tinkerdown
result_path: repository.issues.nodes # Dot-path to extract array
options:
auth_header: "Bearer ${GITHUB_TOKEN}"GraphQL-specific options:
| Option | Required | Description |
|---|---|---|
query_file |
Yes | Path to .graphql file (relative to app directory) |
variables |
No | Map of query variables (supports ${ENV_VAR} expansion) |
result_path |
Yes | Dot-notation path to extract array from response |
options.auth_header |
No | Authorization header value |
sources:
system_info:
type: exec
command: uname -asources:
config:
type: json
path: ./_data/config.jsonsources:
products:
type: csv
path: ./_data/products.csv
delimiter: ","
header: truesources:
posts:
type: markdown
path: ./_data/posts/
glob: "*.md"sources:
custom:
type: wasm
module: ./custom.wasm
config:
api_key: ${API_KEY}Caching is where tinkerdown.yaml shines - complex cache strategies:
sources:
api_data:
type: rest
from: https://api.example.com/data
cache:
ttl: 5m # Cache duration
strategy: stale-while-revalidate # Background refresh| Strategy | Description |
|---|---|
simple |
Return cached data until TTL expires |
stale-while-revalidate |
Return stale data immediately, refresh in background |
Use ${VAR_NAME} syntax for secrets - a key reason to use tinkerdown.yaml:
sources:
api:
type: rest
from: ${API_URL}
headers:
Authorization: Bearer ${API_TOKEN}Validate your configuration:
tinkerdown validateWhen you have shared authentication, caching, and multiple pages:
# tinkerdown.yaml
server:
port: 3000
styling:
theme: dark
sources:
# Shared auth - used by all pages
current_user:
type: rest
from: ${AUTH_API}/me
headers:
Authorization: Bearer ${AUTH_TOKEN}
cache:
ttl: 10m
strategy: stale-while-revalidate
# Shared data with aggressive caching
products:
type: rest
from: https://api.example.com/products
cache:
ttl: 1h
strategy: stale-while-revalidate
# Shared database
orders:
type: sqlite
path: ./data/orders.db
query: SELECT * FROM ordersThen each page uses these sources:
---
title: Dashboard
# No need to redefine sources - they come from tinkerdown.yaml
---
# Dashboard
Welcome, {{.current_user.name}}!
<table lvt-source="orders" lvt-columns="id,product,status">
</table>When the same source is defined in both places:
- Frontmatter wins for that page
- Config file provides defaults
- Frontmatter Reference - Recommended configuration approach
- Data Sources Guide - Using sources