Goal: wire Sveltia CMS to a Hugo site for Git-based editing.
🔗 Related cheat sheets:
- 🚀 Hugo core: hugo.md
- 🧾 Hugo Markdown: hugo_markdown.md
- 🎨 Hugo themes & addons: hugo_themes-and-addons.md
- 🧠 Sveltia CMS: sveltia-cms.md
⚡ One-shot minimal setup (GitHub + Netlify OAuth)
Assumes:
- Hugo site in a GitHub repo
- Netlify account (used only for OAuth if you like the Netlify flow)
- Add admin interface under static/
static/
admin/
index.html
config.ymlstatic/admin/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Sveltia CMS</title>
<script
src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"
onload="SVELTIA.start()"
onerror="document.body.innerHTML='Failed to load Sveltia CMS'"
></script>
<link href="/admin/config.yml" type="application/yaml" rel="cms-config-url" />
</head>
<body></body>
</html>static/admin/config.yml (YAML, targeting content/blog):
backend:
name: github
repo: yourusername/yourrepo
branch: main
# For Netlify-based OAuth you will configure a GitHub OAuth app
# and let Netlify proxy auth via api.netlify.com.
media_folder: "static/uploads"
public_folder: "/uploads"
collections:
- name: blog
label: Blog posts
folder: content/blog
create: true
slug: "{{slug}}"
extension: "md"
format: "yaml"
fields:
- { name: title, label: Title, widget: string }
- { name: date, label: Date, widget: datetime }
- { name: description, label: Description, widget: text, required: false }
- { name: tags, label: Tags, widget: list, required: false }
- { name: body, label: Body, widget: markdown }- Create GitHub OAuth app
- In GitHub, create an OAuth app:
- Homepage: your site URL
- Callback: https://api.netlify.com/auth/done
- Copy the client ID into the CMS config under auth or your Netlify Identity config.
- Configure Netlify (optional, for auth only)
- Create a dummy site from your GitHub repo.
- Enable Identity and Git Gateway.
- Link GitHub as the external provider and supply the client ID.
- Sveltia uses api.netlify.com for GitHub login; hosting can stay on GitHub Pages or elsewhere.
🔐 Non-Netlify auth with Sveltia Auth (Cloudflare Workers)
For pure GitHub Pages, Cloudflare Pages, or custom hosting, Sveltia recommends its own OAuth worker.
High-level steps:
- Deploy https://github.com/sveltia/sveltia-cms-auth as a Cloudflare Worker.
- Create a GitHub OAuth app with callback URL set to your worker’s /callback path.
- Configure worker environment variables:
- GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
- ALLOWED_DOMAINS to restrict where the CMS can run.
- In static/admin/config.yml, set backend.base_url to the worker URL:
backend:
name: github
repo: yourusername/yourrepo
branch: main
base_url: https://sveltia-cms-auth.example.workers.devResult: Sveltia CMS redirects through your worker for a clean GitHub OAuth flow.
🧭 Auth decision matrix
| Hosting / scenario | Recommended auth path | Notes |
|---|---|---|
| Netlify hosting | GitHub/GitLab backend via Netlify | Simple if you already use Netlify; use api.netlify.com flow |
| GitHub Pages + custom domain | Sveltia Auth worker (Cloudflare) | Avoids Netlify assumptions; explicit base_url in backend |
| Cloudflare Pages / custom host | Sveltia Auth worker or other OAuth | Deploy sveltia-cms-auth or use a compatible external OAuth client |
| Internal/dev-only usage | Personal Access Token (PAT) | Fine for technical users; less ideal for a broader editorial team |
| Legacy Git Gateway + Identity | Migrate off Git Gateway | Not supported by Sveltia; move to real Git backend + OAuth or PAT |
🧩 Hugo content and collections
Typical Hugo blog structure:
content/
blog/
_index.md # section index
first-post.md
second-post.mdMatching folder collection in Sveltia:
collections:
- name: blog
label: Blog posts
folder: content/blog
create: true
slug: "{{slug}}"
extension: "md"
format: "yaml"
fields:
- { name: title, label: Title, widget: string }
- { name: date, label: Date, widget: datetime }
- { name: description, label: Description, widget: text, required: false }
- { name: tags, label: Tags, widget: list, required: false }
- { name: body, label: Body, widget: markdown }Including Hugo’s _index.md via index_file
Hugo uses a special _index.md file at section roots. Sveltia’s index_file option lets you manage it in the same collection:
collections:
- name: blog
label: Blog posts
folder: content/blog
fields:
- { name: title, label: Title }
- { name: date, label: Published Date, widget: datetime }
- { name: description, label: Description }
- { name: body, label: Body, widget: markdown }
index_file:
fields:
- { name: title, label: Title }
- { name: body, label: Body, widget: markdown }You can fully customize index_file (name, label, icon, fields) if needed.
Singletons for Hugo settings and special pages
Use singletons for files like content/home.yaml or content/settings.yaml:
singletons:
- name: home
label: Home Page
file: content/home.yaml
icon: home
fields: ...
- divider: true
- name: settings
label: Site Settings
file: content/settings.yaml
icon: settings
fields: ...Ideal for site-wide config that you would otherwise edit manually.
🧱 Hugo Modules options
If your Hugo site already uses modules, you can plug in a ready-made module.
Example: privatemaker/headless-cms module (simplified view):
# hugo.yaml or config/_default/config.yaml
module:
imports:
- path: github.com/privatemaker/headless-cms
outputs:
home:
- HTML
- RSS
- HeadlessCMSConfigCreate content/admin/_index.md:
---
title: Your Headless CMS
layout: headless-cms
---Then configure Sveltia via params in hugo.yaml or config/_default/params.yaml:
params:
headless_cms:
engine: "sveltia" # draw attention to Sveltia
site_url: "https://your-site.org"
backend:
name: github
repo: org/repo
collections:
blog:
create: true
fields:
- label: Title
name: title
widget: string
# ...This module centralizes the admin setup and lets you toggle engines (Sveltia vs Decap) via config.
🚀 Full example: Hugo + Sveltia + GitHub Pages + Sveltia Auth + CI
High-level, opinionated stack:
my-hugo-site/
content/
blog/
_index.md
first-post.md
static/
admin/
index.html
config.yml
.github/workflows/hugo.yml
hugo.yaml- hugo.yaml uses YAML (not TOML) for consistency with Sveltia content.
- static/admin/index.html loads Sveltia via CDN and references config.yml.
- static/admin/config.yml:
- backend: github with base_url pointing at Sveltia Auth worker.
- collections: blog (YAML front matter, extension: “md”).
- Sveltia Auth worker:
- Deployed to Cloudflare Workers.
- Configured with GITHUB_CLIENT_ID/SECRET and ALLOWED_DOMAINS.
- GitHub Actions workflow (.github/workflows/hugo.yml):
- Cleans public/.
- Runs hugo —minify.
- Publishes to GitHub Pages.
🧪 Local consumer lab (Active on this machine)
We have a pre-configured lab at /home/dv/hugo-sveltia-lab.
Deploy Script:
Located at /home/dv/sveltia-cms/scripts/deploy-lab.sh.
- Builds Sveltia CMS (IIFE).
- Copies
sveltia-cms.jstohugo-sveltia-lab/static/admin/.
Usage:
cd /home/dv/sveltia-cms./scripts/deploy-lab.shcd /home/dv/hugo-sveltia-labhugo server -D -p 55155- Open
http://localhost:55155/admin/
To test your own Sveltia build inside a Hugo site without using the CDN:
- Build Sveltia locally
cd /home/dv/sveltia-cms
pnpm buildThis produces package/dist/sveltia-cms.js and sveltia-cms.mjs.
- Copy the IIFE bundle into your Hugo site
cp /home/dv/sveltia-cms/package/dist/sveltia-cms.js /path/to/your-hugo-site/static/admin/sveltia-cms.js- Point Hugo’s admin index.html at the local bundle
<script
src="/admin/sveltia-cms.js"
onload="SVELTIA.start()"
onerror="document.body.innerHTML='Failed to load Sveltia CMS'"
></script>
<link href="/admin/config.yml" type="application/yaml" rel="cms-config-url" />You now have a self-contained Hugo + Sveltia lab using your own built bundle instead of the public CDN.
🧾 Front matter and format choices
From real-world Hugo + Sveltia setups:
- YAML front matter is the most robust choice today.
- Keep all content files consistent (all YAML or all TOML), and set format accordingly.
YAML front matter example (preferred):
---
title: "My post"
date: 2025-07-10
description: "Valid YAML description."
tags: ["tag1", "tag2"]
---
Body text goes here.Sveltia is stricter about YAML than Hugo:
- Only one YAML document per file (--- at top and bottom only).
- No extra --- markers inside the body unless escaped (---).
- Quotes must be balanced.
If you experimented with TOML and saw broken output:
- Sveltia may treat body as a front matter field if delimiters are wrong.
- Many users successfully switched back to YAML for stability.
🧾 Format migration checklist (YAML ↔ TOML)
When switching formats, update everything together:
- Hugo config
- If moving to YAML: rename hugo.toml → hugo.yaml and convert syntax.
- If moving to TOML: ensure Hugo config and tooling expect TOML.
- Sveltia config
- Update collections.format (“yaml” or “toml”).
- Ensure config.yml vs config.toml and link type attribute match.
- Content files
- Convert all existing front matter to the new format:
- YAML: --- delimiters, key: value.
- TOML: +++ delimiters, key = “value”.
- Make sure body is outside the front matter block.
- Convert all existing front matter to the new format:
- Sanity checks
- Run Hugo locally and verify no front matter parse errors.
- Open Sveltia, ensure collections list and edit existing entries correctly.
🔁 CI and cleaning before build
For GitHub Actions or other CI, clean Hugo’s output before building to avoid stale files:
- name: Clean Hugo output directory
run: rm -rf public/Then run hugo or hugo —minify as usual.
⚠️ Hugo + Sveltia gotchas
-
/admin 404:
- Ensure static/admin/index.html and static/admin/config.yml exist and are deployed.
-
SVELTIA is not defined:
- Check the script src URL, network errors, and that SVELTIA.start() is called only after load.
-
Wrong file extension (.toml instead of .md):
- Set extension: “md” in your collections.
-
CMS not listing existing content:
- Check that format matches existing front matter (yaml vs toml).
- Ensure folders and file paths match your Hugo content tree.
-
Broken front matter / pages rendering as plain text:
- Usually caused by malformed front matter or wrong delimiters.
- Prefer YAML with clean --- delimiters and a single document per file.
-
Images not uploading or previewing:
- Convert HEIC or exotic formats to JPEG/PNG.
- Confirm media_folder and public_folder point to static/ and URL paths Hugo actually serves.