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)
  1. Add admin interface under static/
static/
  admin/
    index.html
    config.yml

static/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 }
  1. Create GitHub OAuth app
  • In GitHub, create an OAuth app:
  • Copy the client ID into the CMS config under auth or your Netlify Identity config.
  1. 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.dev

Result: Sveltia CMS redirects through your worker for a clean GitHub OAuth flow.

🧭 Auth decision matrix

Hosting / scenarioRecommended auth pathNotes
Netlify hostingGitHub/GitLab backend via NetlifySimple if you already use Netlify; use api.netlify.com flow
GitHub Pages + custom domainSveltia Auth worker (Cloudflare)Avoids Netlify assumptions; explicit base_url in backend
Cloudflare Pages / custom hostSveltia Auth worker or other OAuthDeploy sveltia-cms-auth or use a compatible external OAuth client
Internal/dev-only usagePersonal Access Token (PAT)Fine for technical users; less ideal for a broader editorial team
Legacy Git Gateway + IdentityMigrate off Git GatewayNot 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.md

Matching 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
    - HeadlessCMSConfig

Create 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.js to hugo-sveltia-lab/static/admin/.

Usage:

  1. cd /home/dv/sveltia-cms
  2. ./scripts/deploy-lab.sh
  3. cd /home/dv/hugo-sveltia-lab
  4. hugo server -D -p 55155
  5. Open http://localhost:55155/admin/

To test your own Sveltia build inside a Hugo site without using the CDN:

  1. Build Sveltia locally
cd /home/dv/sveltia-cms
pnpm build

This produces package/dist/sveltia-cms.js and sveltia-cms.mjs.

  1. 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
  1. 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:

  1. 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.
  2. Sveltia config
    • Update collections.format (“yaml” or “toml”).
    • Ensure config.yml vs config.toml and link type attribute match.
  3. 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.
  4. 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.