Set up Sveltia CMS - lightweight Git-backed CMS successor to Decap/Netlify CMS (300KB bundle, 270+ fixes). Framework-agnostic for Hugo, Jekyll, 11ty, Astro. Prevents 10 documented errors. Use when adding CMS to static sites, migrating from Decap CMS, or fixing OAuth, YAML parse, datetime timezone, GDPR font loading, or CORS/COOP errors.
Complete skill for integrating Sveltia CMS into static site projects.
DEPRECATION: The slug_length collection option is deprecated and will be removed in v1.0.
Migration:
# ❌ Deprecated (pre-v0.127.0)
collections:
- name: posts
slug_length: 50
# ✅ New (v0.127.0+)
slug:
maxlength: 50Timeline: Will be removed in Sveltia CMS 1.0 (expected early 2026).
Source: Release v0.127.0
New Feature: Hidden widget now supports author template tags:
{{author-email}} - Signed-in user's email{{author-login}} - Signed-in user's login name{{author-name}} - Signed-in user's display nameUsage:
fields:
- label: Author Email
name: author_email
widget: hidden
default: '{{author-email}}'Commit message templates also support {{author-email}} tag.
New Feature: Configuration files can now be written in TOML format (previously YAML-only).
Migration:
# admin/config.toml (NEW)
[backend]
name = "github"
repo = "owner/repo"
branch = "main"
media_folder = "static/images/uploads"
public_folder = "/images/uploads"Recommendation: YAML is still preferred for better tooling support.
BREAKING: Renamed SiteConfig export to CmsConfig for compatibility with Netlify/Decap CMS.
Migration:
// ❌ Old (v0.117.x)
import type { SiteConfig } from '@sveltia/cms';
// ✅ New (v0.118.0+)
import type { CmsConfig } from '@sveltia/cms';
const config: CmsConfig = {
backend: { name: 'github', repo: 'owner/repo' },
collections: [/* ... */],
};Impact: TypeScript users only. Breaking change for type imports.
New Features:
CmsConfig type for direct TypeScript importNew Feature: Override media_folder at the field level (not just collection level).
Usage:
collections:
- name: posts
label: Blog Posts
folder: content/posts
media_folder: static/images/posts # Collection-level default
fields:
- label: Featured Image
name: image
widget: image
media_folder: static/images/featured # ← Field-level override
public_folder: /images/featured
- label: Author Avatar
name: avatar
widget: image
media_folder: static/images/avatars # ← Another override
public_folder: /images/avatarsUse case: Different media folders for different image types in same collection.
DEPRECATION: logo_url option is now deprecated. Migrate to logo.src.
Migration:
# ❌ Deprecated
logo_url: https://yourdomain.com/logo.svg
# ✅ New (v0.113.5+)
logo:
src: https://yourdomain.com/logo.svgBREAKING: sanitize_preview default changed to true for Markdown widget (XSS prevention).
Impact:
sanitize_preview: false (compatibility with Netlify/Decap CMS, but vulnerable to XSS)sanitize_preview: true (secure by default)Migration:
collections:
- name: posts
fields:
- label: Body
name: body
widget: markdown
sanitize_preview: false # ← Add ONLY if you trust all CMS usersRecommendation: Keep default (true) unless disabling fixes broken preview AND you fully trust all CMS users.
Configure slug generation behavior globally:
slug:
encoding: unicode-normalized
clean_accents: false
sanitize_replacement: '-'
lowercase: true # Default: convert to lowercase (v0.128.0+)
maxlength: 50 # Default: unlimited (v0.127.0+)lowercase (v0.128.0+): Set to false to preserve original casing in slugs (e.g., "MyBlogPost" instead of "myblogpost").
Use case: Mixed-case URLs or file names where case matters.
Source: Release v0.128.0, GitHub Issue #594
New raw format allows editing files without front matter (CSV, JSON, YAML, plain text). Must have single body field with widget type: code, markdown, richtext, or text.
Use Cases:
Configuration:
collections:
- name: config
label: Configuration Files
files:
- label: Site Config
name: site_config
file: config.json
format: raw # ← NEW format type
fields:
- label: Config
name: body
widget: code
default_language: jsonRestrictions:
body)code, markdown, richtext, or textSource: Release v0.126.0
New value_type option for Number field accepts int/string and float/string to save numbers as strings instead of numbers in front matter.
Use Case: Some static site generators or schemas require numeric values stored as strings (e.g., age: "25" instead of age: 25).
Configuration:
fields:
- label: Age
name: age
widget: number
value_type: int/string # Saves as "25" not 25
- label: Price
name: price
widget: number
value_type: float/string # Saves as "19.99" not 19.99Source: Release v0.125.0, GitHub Issue #574
Override editor locale via URL query parameter ?_locale=fr to get edit links for specific locales.
Use Case: Generate direct edit links for translators or content editors for specific languages.
Example:
https://yourdomain.com/admin/#/collections/posts/entries/my-post?_locale=frSource: Release v0.126.0, GitHub Issue #585
Added richtext as an alias for markdown widget to align with Decap CMS terminology. Both work identically.
Configuration:
fields:
- label: Body
name: body
widget: richtext # ← NEW alias for markdownFuture: HTML output support planned for richtext field type.
Source: Release v0.124.0
All frameworks follow the same pattern:
Create admin directory in public/static folder:
static/admin/admin/admin/ (with passthrough copy)public/admin/public/admin/Create admin/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Content Manager</title>
</head>
<body>
<script src="https://unpkg.com/@sveltia/cms@0.128.5/dist/sveltia-cms.js" type="module"></script>
</body>
</html>Create admin/config.yml:
backend:
name: github
repo: owner/repo
branch: main
base_url: https://your-worker.workers.dev # OAuth proxy (required)
media_folder: static/images/uploads # Framework-specific path
public_folder: /images/uploads
collections:
- name: posts
label: Blog Posts
folder: content/posts
create: true
fields:
- { label: 'Title', name: 'title', widget: 'string' }
- { label: 'Date', name: 'date', widget: 'datetime' }
- { label: 'Body', name: 'body', widget: 'markdown' }Access admin: http://localhost:<port>/admin/
Framework-specific details: See templates/ directory for complete examples.
Why Cloudflare Workers: Fastest, free tier available, works with any deployment platform.
Steps:
Deploy Worker:
git clone https://github.com/sveltia/sveltia-cms-auth
cd sveltia-cms-auth
npm install
npx wrangler deployRegister OAuth App on GitHub:
https://your-worker.workers.dev/callbackConfigure Worker Environment Variables:
npx wrangler secret put GITHUB_CLIENT_ID
# Paste your Client ID
npx wrangler secret put GITHUB_CLIENT_SECRET
# Paste your Client SecretUpdate CMS config:
backend:
name: github
repo: owner/repo
branch: main
base_url: https://your-worker.workers.dev # ← Add this lineTest authentication:
/admin/Alternative: Vercel serverless functions - See templates/vercel-serverless/
This skill prevents 10 common errors encountered when setting up Sveltia CMS.
Error Message:
https://api.netlify.com/auth instead of GitHub loginSymptoms:
Causes:
base_url in backend configSolution:
Step 1: Verify config.yml has base_url:
backend:
name: github
repo: owner/repo
branch: main
base_url: https://your-worker.workers.dev # ← Must be presentStep 2: Check GitHub OAuth App callback:
https://your-worker.workers.dev/callbackhttps://yourdomain.com/callbackStep 3: Test Worker directly:
curl https://your-worker.workers.dev/health
# Should return: {"status": "ok"}Error Message:
+++ delimitersSymptoms:
Causes:
Solution:
Use YAML instead of TOML (recommended):
collections:
- name: posts
folder: content/posts
format: yaml # or md (Markdown with YAML frontmatter)
# NOT: format: tomlIf you must use TOML:
Error Message:
Symptoms:
Causes:
Solution:
Step 1: Validate YAML:
pip install yamllint
find content -name "*.md" -exec yamllint {} \;Step 2: Common fixes:
Problem: Smart quotes
# ❌ Bad - smart quotes from copy-paste
title: "Hello World" # Curly quotes
# ✅ Good - straight quotes
title: "Hello World" # Straight quotesStep 3: Auto-fix with yamlfmt:
go install github.com/google/yamlfmt/cmd/yamlfmt@latest
find content -name "*.md" -exec yamlfmt {} \;Error Message:
Symptoms:
Causes:
Solution:
Step 1: Verify folder path:
# Config says:
collections:
- name: posts
folder: content/posts # Expects files here
# Check actual location:
ls -la content/posts # Files must exist hereStep 2: Match format to actual files:
# If files are: content/posts/hello.md with YAML frontmatter
collections:
- name: posts
folder: content/posts
format: yaml # or md (same as yaml for .md files)Error Message:
Uncaught ReferenceError: SVELTIA is not definedSymptoms:
Causes:
type="module" attributeSolution:
Use correct script tag:
<!-- ✅ Correct -->
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js" type="module"></script>
<!-- ❌ Wrong - missing type="module" -->
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>Use version pinning (recommended):
<script src="https://unpkg.com/@sveltia/cms@0.128.5/dist/sveltia-cms.js" type="module"></script>Error Message:
/admin/Symptoms:
Causes:
Solution:
Framework-specific fixes:
Hugo: Files in static/ are automatically copied
Jekyll: Add to _config.yml:
include:
- admin11ty: Add to .eleventy.js:
module.exports = function(eleventyConfig) {
eleventyConfig.addPassthroughCopy('admin');
};Astro: Files in public/ are automatically copied
Error Message:
Symptoms:
Causes:
Solution:
On iPhone:
Or enable image optimization:
media_libraries:
default:
config:
max_file_size: 10485760 # 10 MB
transformations:
raster_image:
format: webp # Auto-converts to WebP
quality: 85Error Message:
Symptoms:
Causes:
format is specified, Sveltia becomes strict - existing entries with different formats show as blank⚠️ CRITICAL: Format Strictness Warning (v0.124.1+)
When you specify a format option, Sveltia becomes strict:
Solution:
Option 1: Use picker_utc (Recommended)
fields:
- label: Date
name: date
widget: datetime
picker_utc: true # Most flexible - outputs with timezoneOption 2: Specify format with timezone (RISKY - see warning above)
fields:
- label: Date
name: date
widget: datetime
format: "YYYY-MM-DDTHH:mm:ss.SSSZ" # Only if ALL existing dates match this formatOption 3: Configure Hugo to accept missing timezone
# config.toml
[frontmatter]
date = [":default", ":fileModTime"]Prevention:
format if you have mixed date formatsformatpicker_utc: true instead (more flexible)Source: GitHub Issue #565, fixed in v0.126.0 (improved date parser but format strictness remains)
Error Message:
Symptoms:
Causes:
⚠️ Impact: Blocking issue for EU public sector and privacy-focused sites
Solution (Vite Plugin - Recommended):
// vite.config.ts
import { defineConfig, type Plugin } from 'vite'
function ensureGDPRCompliantFonts(): Plugin {
const fontsURLRegex = /fonts\.googleapis\.com\/css2/g
const replacement = 'fonts.bunny.net/css'
return {
name: 'gdpr-compliant-fonts',
enforce: 'post',
transform(code) {
if (fontsURLRegex.test(code)) {
return code.replaceAll(fontsURLRegex, replacement)
}
},
}
}
export default defineConfig({
plugins: [ensureGDPRCompliantFonts()],
})Alternative Solutions:
Option 2: Bunny Fonts (1:1 Google Fonts replacement)
Option 3: Self-hosted fonts
@fontsource npm packagesMaintainer Response:
Source: GitHub Issue #443
Error Message:
Symptoms:
Causes:
Cross-Origin-Opener-Policy header blocking OAuthSolution:
Cloudflare Pages (_headers file):
/*
Cross-Origin-Opener-Policy: same-origin-allow-popups
# NOT: same-origin (this breaks OAuth)Netlify (_headers file):
/*
Cross-Origin-Opener-Policy: same-origin-allow-popupsVercel (vercel.json):
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Cross-Origin-Opener-Policy",
"value": "same-origin-allow-popups"
}
]
}
]
}These issues were present in earlier versions but have been fixed. Documented here for reference.
Issue: Sveltia CMS failed to load existing entries when the path option contains parentheses (). This affected Next.js/Nextra users using route groups like app/(content)/(writing)/.
Symptoms (pre-v0.128.1):
Example that failed:
collections:
- name: pages
folder: app/(pages)
path: "{{slug}}/page" # ← Failed to load existing entries
extension: mdxStatus: Fixed in v0.128.1 (January 13, 2026)
Source: GitHub Issue #596
Issue: Collections with folder: "" or folder: "." or folder: "/" failed when creating new entries via GitHub backend. GraphQL query incorrectly constructed path starting with / (absolute) instead of relative path.
Symptoms (pre-v0.125.0):
Example that failed:
collections:
- name: root-pages
folder: "" # or "." or "/"
# ← Broke when creating entries via GitHub backendUse Case: VitePress and other frameworks storing content at repository root.
Status: Fixed in v0.125.0 (December 20, 2025)
Source: GitHub Issue #580
Sveltia CMS is a drop-in replacement for Decap CMS.
Step 1: Update script tag (1 line change):
<!-- OLD: Decap CMS -->
<script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
<!-- NEW: Sveltia CMS -->
<script src="https://unpkg.com/@sveltia/cms@0.128.5/dist/sveltia-cms.js" type="module"></script>Step 2: Keep existing config.yml (no changes needed)
Step 3: Test locally (verify login, content listing, editing, saving)
That's it! Your content, collections, and workflows remain unchanged.
Not Supported:
Workaround: Use Cloudflare Workers or Vercel OAuth proxy instead.
Note: The official documentation site is being actively developed. GitHub README remains a useful fallback for some topics.
Estimated Savings: 65-70% (~6,300 tokens saved)
Without Skill (~9,500 tokens):
With Skill (~3,200 tokens):
This skill prevents 10 common errors (100% prevention rate):
Last Updated: 2026-01-21 Skill Version: 2.1.0 Sveltia CMS Version: 0.128.5 (Beta) Status: Production-ready, v1.0 GA expected early 2026 Changes: Added 6 new configuration options (slug lowercase, raw format, number string encoding, locale URL param, richtext alias, Gemini 2.5 Flash-Lite support), updated datetime error with format strictness warning, added GDPR Google Fonts workaround, documented 2 fixed historical issues, deprecated slug_length option
fa91c34
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.