Configuration
Guss is configured by a single guss.yaml file in the project root.
Minimal example
site:
title: "My Site"
description: "A site built with Guss"
url: "https://example.com"
language: "en"
source:
type: rest_api
base_url: "https://cms.example.com"
output:
output_dir: "dist"
copy_assets: true
collections:
posts:
item_template: "post.html"
archive_template: "index.html"
permalink: "/{year}/{month}/{slug}/"
paginate: 10
context_key: "post"
site
| Field | Type | Description |
|---|---|---|
title |
string | Site title — available as {{ site.title }} in all templates |
description |
string | Short description — {{ site.description }} |
url |
string | Canonical base URL, no trailing slash |
language |
string | BCP 47 language code, e.g. en |
logo |
string | Optional logo URL |
icon |
string | Optional favicon URL |
twitter |
string | Optional Twitter handle |
facebook |
string | Optional Facebook page URL |
navigation |
map | Named nav groups — {{ site.navigation.<group> }} |
source
Set type: rest_api for REST CMS sources or type: markdown for local Markdown files.
Markdown source
source:
type: markdown
recursive: true
collection_paths:
posts: "content/posts"
pages: "content/pages"
Each key in collection_paths maps a collection name to a directory. All .md files
in that directory become items in that collection.
REST API source
source:
type: rest_api
base_url: "https://cms.example.com/api/v2"
timeout_ms: 30000
auth:
type: api_key
header: "X-API-Key"
key: "your-key-here"
endpoints:
posts:
path: "/posts"
response_key: "posts"
params:
include: "tags,authors"
tags:
path: "/tags"
response_key: "tags"
Auth types
| Type | Fields | Description |
|---|---|---|
none |
— | No authentication |
api_key |
header, key |
Sends header: key as a request header |
basic |
username, password |
HTTP Basic Authentication |
bearer |
value |
Sends Authorization: Bearer <value> |
field_maps
Rename or project fields before enrichment. Works for both Markdown and REST adapters.
source:
field_maps:
posts:
body: "html" # rename source field "html" → "body"
author_name: "authors.0.name" # project nested value into new field
tag_slugs: "tags.slug" # project array field across all elements
Entries are target_field: "source.dot.path". Numeric path segments index into arrays;
array-of-object segments project a sub-field across every element.
cross_references
Inject related items across collections, or synthesize a taxonomy collection from data
embedded in another collection (e.g. build a tags collection from the tags embedded in
each post):
source:
cross_references:
tags:
from: "posts" # source collection
via: "tags.slug" # dot-path to the linking field on each source item
match_key: "slug" # field within each linked element to match against
With this config, each post item gets its tags array resolved to full tag objects.
If tags has no files of its own, Guss synthesizes the collection from the embedded data.
output
| Field | Type | Default | Description |
|---|---|---|---|
output_dir |
string | dist |
Output directory path |
copy_assets |
bool | true |
Copy templates/assets/ to dist/assets/ |
generate_sitemap |
bool | true |
Write dist/sitemap.xml |
generate_rss |
bool | true |
Write dist/feed.xml (RSS 2.0) |
minify_html |
bool | false |
Minify all output HTML |
collections
Each key must match a collection name in your source config.
| Field | Type | Default | Description |
|---|---|---|---|
item_template |
string | — | Template for individual item pages. Empty = no item pages. |
archive_template |
string | — | Template for archive/listing pages. Empty = no archive. |
permalink |
string | — | Pattern with {token} placeholders. |
paginate |
int | 0 |
Items per archive page. 0 = single archive page. |
context_key |
string | item |
Template variable name for the item. |
Archive pages are only generated when both item_template and archive_template are set.
Permalink tokens are plain field lookups on the item's Value:
| Token | Source |
|---|---|
{slug} |
Item's slug field |
{year} |
Extracted from published_at by the adapter |
{month} |
Extracted from published_at by the adapter |
{day} |
Extracted from published_at by the adapter |
{<field>} |
Any other field on the item |
Example: permalink: "/{year}/{month}/{slug}/" + slug: hello-world, year: 2024,
month: 01 → writes to dist/2024/01/hello-world/index.html.
Pagination strategies
When using a REST API source, Guss supports eight pagination strategies evaluated in priority order. Set the field(s) that match your CMS's API:
| Priority | Config field | Behavior |
|---|---|---|
| 1 | total_pages_header: "X-Total-Pages" |
Read header on first response — total pages known upfront |
| 2 | total_count_header: "X-Total-Count" |
Read header → derive pages via ceil(count / limit) |
| 3 | link_header: true |
Follow verbatim Link: rel="next" URL each round-trip (RFC 5988) |
| 4 | json_cursor: "meta.next_cursor" |
Extract cursor from body, append as ?<cursor_param>=<token> |
| 5 | json_next_url: "meta.next_page_url" |
Extract full next-page URL from body, follow verbatim |
| 6 | json_next: "meta.pagination.next" |
Dot-path non-null sentinel; increment page counter (Ghost-style) |
| 7 | optimistic_fetching: true |
Blind GET N+1 until 404, empty body, or parse error |
| 8 | (none set) | Single page fetch |
Configure globally or override per endpoint:
source:
pagination:
page_param: "page"
limit_param: "limit"
limit: 15
json_next: "meta.pagination.next"
endpoints:
posts:
path: "/posts"
response_key: "posts"
pagination: # overrides global for this endpoint only
total_pages_header: "X-Pages"
Additional pagination fields:
| Field | Description |
|---|---|
cursor_param |
Query param name for cursor-based pagination (default: "cursor") |
offset_param |
Query param name for item offset, sent alongside page_param when needed |
parallel_workers
parallel_workers: 0 # 0 = auto-detect (all CPU cores via OpenMP)
log_level
log_level: "info" # debug | info | warn | error