jezweb

sveltia-cms

224
26
# Install this skill:
npx skills add jezweb/claude-skills --skill "sveltia-cms"

Install specific skill from multi-skill repository

# Description

|

# SKILL.md


name: sveltia-cms
description: |
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.
user-invocable: true
allowed-tools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep']


Sveltia CMS Skill

Complete skill for integrating Sveltia CMS into static site projects.


Current Versions

  • @sveltia/cms: 0.128.5 (verified January 2026)
  • Status: Public Beta (v1.0 expected early 2026)
  • Maturity: Production-ready (270+ issues solved from predecessor)

When to Use This Skill

✅ Use Sveltia CMS When:

  • Git-based workflow desired (content as Markdown/YAML/TOML/JSON in repository)
  • Lightweight solution required (<500 KB vs 1.5-2.6 MB for competitors)
  • Migrating from Decap/Netlify CMS (drop-in replacement, change 1 line)
  • Non-technical editors need access without Git knowledge

❌ Don't Use Sveltia CMS When:

  • Real-time collaboration needed (multiple users editing simultaneously) - Use Sanity, Contentful, or TinaCMS instead
  • Visual page building required (drag-and-drop) - Use Webflow, Builder.io instead
  • React-specific visual editing needed - Use TinaCMS instead

Breaking Changes & Updates (v0.105.0+)

v0.127.0 (December 29, 2025) - slug_length Deprecation

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: 50

Timeline: Will be removed in Sveltia CMS 1.0 (expected early 2026).

Source: Release v0.127.0


v0.120.0 (November 24, 2025) - Author Template Tags

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 name

Usage:

fields:
  - label: Author Email
    name: author_email
    widget: hidden
    default: '{{author-email}}'

Commit message templates also support {{author-email}} tag.


v0.119.0 (November 16, 2025) - TOML Config Support

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.


v0.118.0 (November 15, 2025) - TypeScript Breaking Change

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.


v0.117.0 (November 14, 2025) - Enhanced Validation

New Features:
- Exported CmsConfig type for direct TypeScript import
- Enhanced config validation for collection names, field types, and relation references
- Better error messages for invalid configurations


v0.115.0 (November 5, 2025) - Field-Specific Media Folders

New 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/avatars

Use case: Different media folders for different image types in same collection.


v0.113.5 (October 27, 2025) - Logo Deprecation

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.svg

v0.105.0 (September 15, 2024) - Security Breaking Change

BREAKING: sanitize_preview default changed to true for Markdown widget (XSS prevention).

Impact:
- Before v0.105.0: sanitize_preview: false (compatibility with Netlify/Decap CMS, but vulnerable to XSS)
- After v0.105.0: 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 users

Recommendation: Keep default (true) unless disabling fixes broken preview AND you fully trust all CMS users.


Configuration Options

Global Slug Options (v0.128.0+)

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


Raw Format for Text Files (v0.126.0+)

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:
- Edit configuration files (JSON, YAML)
- Manage CSV data
- Edit plain text files

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: json

Restrictions:
- Only one field allowed (must be named body)
- Widget must be: code, markdown, richtext, or text
- No front matter parsing

Source: Release v0.126.0


Number Field String Encoding (v0.125.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.99

Source: Release v0.125.0, GitHub Issue #574


Editor Pane Locale via URL Query (v0.126.0+)

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=fr

Source: Release v0.126.0, GitHub Issue #585


Richtext Field Type Alias (v0.124.0+)

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 markdown

Future: HTML output support planned for richtext field type.

Source: Release v0.124.0


Setup Pattern (Framework-Agnostic)

All frameworks follow the same pattern:

  1. Create admin directory in public/static folder:
  2. Hugo: static/admin/
  3. Jekyll: admin/
  4. 11ty: admin/ (with passthrough copy)
  5. Astro: public/admin/
  6. Next.js: public/admin/

  7. Create admin/index.html:
    ```html

Content Manager

```

  1. Create admin/config.yml:
    ```yaml
    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' }
```

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

  1. Deploy Worker:
    bash git clone https://github.com/sveltia/sveltia-cms-auth cd sveltia-cms-auth npm install npx wrangler deploy

  2. Register OAuth App on GitHub:

  3. Go to https://github.com/settings/developers
  4. Click "New OAuth App"
  5. Authorization callback URL: https://your-worker.workers.dev/callback
  6. Save Client ID and Client Secret

  7. Configure Worker Environment Variables:
    ```bash
    npx wrangler secret put GITHUB_CLIENT_ID
    # Paste your Client ID

npx wrangler secret put GITHUB_CLIENT_SECRET
# Paste your Client Secret
```

  1. Update CMS config:
    yaml backend: name: github repo: owner/repo branch: main base_url: https://your-worker.workers.dev # ← Add this line

  2. Test authentication:

  3. Open /admin/
  4. Click "Login with GitHub"
  5. Should redirect to GitHub → Authorize → Back to CMS

Alternative: Vercel serverless functions - See templates/vercel-serverless/


Common Errors & Solutions

This skill prevents 10 common errors encountered when setting up Sveltia CMS.

1. ❌ OAuth Authentication Failures

Error Message:
- "Error: Failed to authenticate"
- Redirect to https://api.netlify.com/auth instead of GitHub login

Symptoms:
- Login button does nothing
- Authentication popup closes immediately

Causes:
- Missing base_url in backend config
- Incorrect OAuth proxy URL
- Wrong GitHub OAuth callback URL

Solution:

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 present

Step 2: Check GitHub OAuth App callback:
- Should be: https://your-worker.workers.dev/callback
- NOT: https://yourdomain.com/callback

Step 3: Test Worker directly:

curl https://your-worker.workers.dev/health
# Should return: {"status": "ok"}

2. ❌ TOML Front Matter Errors

Error Message:
- "Parse error: Invalid TOML"
- Files missing +++ delimiters

Symptoms:
- New files created by CMS don't parse in Hugo
- Content appears above body separator

Causes:
- Sveltia's TOML generation is buggy in beta
- Mixed TOML/YAML in same collection

Solution:

Use YAML instead of TOML (recommended):

collections:
  - name: posts
    folder: content/posts
    format: yaml  # or md (Markdown with YAML frontmatter)
    # NOT: format: toml

If you must use TOML:
1. Manually fix delimiters after CMS saves
2. Use pre-commit hook to validate TOML
3. Wait for beta fixes (track GitHub issues)


3. ❌ YAML Parse Errors

Error Message:
- "YAML parse error: Invalid YAML"
- "Error: Duplicate key 'field_name'"

Symptoms:
- Existing posts won't load in CMS
- CMS shows empty fields

Causes:
- Sveltia is stricter than Hugo/Jekyll about YAML formatting
- Incorrect indentation or smart quotes

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 quotes

Step 3: Auto-fix with yamlfmt:

go install github.com/google/yamlfmt/cmd/yamlfmt@latest
find content -name "*.md" -exec yamlfmt {} \;

4. ❌ Content Not Listing in CMS

Error Message:
- "No entries found"
- "Failed to load entries"

Symptoms:
- Admin loads but shows no content
- Files exist in repository but CMS doesn't see them

Causes:
- Format mismatch (config expects TOML, files are YAML)
- Incorrect folder path

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 here

Step 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)

5. ❌ "SVELTIA is not defined" Error

Error Message:
- Console error: Uncaught ReferenceError: SVELTIA is not defined

Symptoms:
- Admin page shows white screen

Causes:
- Missing type="module" attribute

Solution:

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/[email protected]/dist/sveltia-cms.js" type="module"></script>

6. ❌ 404 on /admin

Error Message:
- "404 Not Found" when visiting /admin/

Symptoms:
- Works locally but not in production

Causes:
- Admin directory not in correct location for framework

Solution:

Framework-specific fixes:

Hugo: Files in static/ are automatically copied

Jekyll: Add to _config.yml:

include:
  - admin

11ty: Add to .eleventy.js:

module.exports = function(eleventyConfig) {
  eleventyConfig.addPassthroughCopy('admin');
};

Astro: Files in public/ are automatically copied


7. ❌ Images Not Uploading (HEIC Format)

Error Message:
- "Unsupported file format"

Symptoms:
- iPhone photos won't upload

Causes:
- HEIC format not supported by browsers

Solution:

On iPhone:
- Settings > Camera > Formats > Most Compatible
- This saves photos as JPEG instead of HEIC

Or enable image optimization:

media_libraries:
  default:
    config:
      max_file_size: 10485760  # 10 MB
      transformations:
        raster_image:
          format: webp  # Auto-converts to WebP
          quality: 85

8. ❌ Datetime Widget Format Strictness

Error Message:
- Silent data loss (no error shown)
- Date fields show blank in CMS

Symptoms:
- Posts with existing dates appear blank when editing
- Saving overwrites dates with blank values
- Hugo marks posts as "future" and skips building them

Causes:
- Default datetime widget outputs timestamps WITHOUT timezone suffix
- When format is specified, Sveltia becomes strict - existing entries with different formats show as blank
- Hugo infers UTC when timezone missing, causing timezone issues

⚠️ CRITICAL: Format Strictness Warning (v0.124.1+)

When you specify a format option, Sveltia becomes strict:
- Existing entries with different formats show as blank
- Saving will overwrite with blank value (SILENT DATA LOSS)
- No error message shown

Solution:

Option 1: Use picker_utc (Recommended)

fields:
  - label: Date
    name: date
    widget: datetime
    picker_utc: true  # Most flexible - outputs with timezone

Option 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 format

Option 3: Configure Hugo to accept missing timezone

# config.toml
[frontmatter]
  date = [":default", ":fileModTime"]

Prevention:
1. Don't specify format if you have mixed date formats
2. Normalize all dates first before adding format
3. Use picker_utc: true instead (more flexible)

Source: GitHub Issue #565, fixed in v0.126.0 (improved date parser but format strictness remains)


9. ❌ GDPR Violation: Google Fonts CDN

Error Message:
- No error shown (privacy compliance issue)

Symptoms:
- Privacy-focused sites blocked from using Sveltia
- EU public sector cannot adopt
- GDPR non-compliance

Causes:
- Sveltia loads Google Fonts and Material Symbols from Google CDN without user consent
- Google tracks font usage and collects IP addresses
- Violates EU data protection law

⚠️ 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)
- Use https://fonts.bunny.net instead of fonts.googleapis.com
- EU-based, GDPR-compliant
- Same API as Google Fonts

Option 3: Self-hosted fonts
- Use @fontsource npm packages
- Bundle fonts with application
- No external requests

Maintainer Response:
- Acknowledged issue
- Plans to add system font option (post-v1.0)
- Sveltia itself collects no data (fonts are only concern)

Source: GitHub Issue #443


10. ❌ CORS / COOP Policy Errors

Error Message:
- "Authentication Aborted"
- "Cross-Origin-Opener-Policy blocked"

Symptoms:
- OAuth popup opens then closes

Causes:
- Strict Cross-Origin-Opener-Policy header blocking OAuth

Solution:

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-popups

Vercel (vercel.json):

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Cross-Origin-Opener-Policy",
          "value": "same-origin-allow-popups"
        }
      ]
    }
  ]
}

Fixed Issues (Historical Reference)

These issues were present in earlier versions but have been fixed. Documented here for reference.

✅ Paths with Parentheses Break Entry Loading (Fixed in v0.128.1)

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):
- Creating new entries worked
- Loading/listing existing entries failed silently
- CMS showed "No entries found" despite files existing

Example that failed:

collections:
  - name: pages
    folder: app/(pages)
    path: "{{slug}}/page"  # ← Failed to load existing entries
    extension: mdx

Status: Fixed in v0.128.1 (January 13, 2026)

Source: GitHub Issue #596


✅ Root Folder Collections Break GitHub Backend (Fixed in v0.125.0)

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):
- Worked locally via browser File API
- Failed with GitHub backend (GraphQL error)
- Leading slash broke GraphQL mutation

Example that failed:

collections:
  - name: root-pages
    folder: ""  # or "." or "/"
    # ← Broke when creating entries via GitHub backend

Use Case: VitePress and other frameworks storing content at repository root.

Status: Fixed in v0.125.0 (December 20, 2025)

Source: GitHub Issue #580


Migration from Decap CMS

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/[email protected]/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:
- Git Gateway backend (for performance reasons)
- Azure backend (may be added later)

Workaround: Use Cloudflare Workers or Vercel OAuth proxy instead.


Bundled Resources

Templates

  • hugo/ - Complete Hugo blog setup
  • jekyll/ - Jekyll site configuration
  • 11ty/ - Eleventy blog setup
  • astro/ - Astro content collections
  • cloudflare-workers/ - OAuth proxy implementation
  • vercel-serverless/ - Vercel auth functions
  • collections/ - Pre-built collection patterns

References

  • common-errors.md - Extended error troubleshooting
  • migration-from-decap.md - Complete migration guide
  • cloudflare-auth-setup.md - Step-by-step OAuth setup
  • config-reference.md - Full config.yml documentation

Scripts

  • init-sveltia.sh - Automated setup for new projects
  • deploy-cf-auth.sh - Deploy Cloudflare Workers OAuth

Official Documentation

  • Documentation Site: https://sveltiacms.app/en/docs (under active development - some sections may be incomplete)
  • GitHub: https://github.com/sveltia/sveltia-cms
  • OAuth Worker: https://github.com/sveltia/sveltia-cms-auth
  • npm Package: https://www.npmjs.com/package/@sveltia/cms
  • Discussions: https://github.com/sveltia/sveltia-cms/discussions

Note: The official documentation site is being actively developed. GitHub README remains a useful fallback for some topics.


Token Efficiency

Estimated Savings: 65-70% (~6,300 tokens saved)

Without Skill (~9,500 tokens):
- Framework setup trial & error: 2,500 tokens
- OAuth configuration attempts: 2,500 tokens
- Error troubleshooting: 2,500 tokens
- Deployment configuration: 2,000 tokens

With Skill (~3,200 tokens):
- Skill loading (SKILL.md): 2,000 tokens
- Template selection: 400 tokens
- Project-specific adjustments: 800 tokens


Errors Prevented

This skill prevents 10 common errors (100% prevention rate):

  1. ✅ OAuth authentication failures
  2. ✅ TOML front matter generation bugs
  3. ✅ YAML parse errors (strict validation)
  4. ✅ Content not listing in CMS
  5. ✅ "SVELTIA is not defined" errors
  6. ✅ 404 on /admin page
  7. ✅ Image upload failures (HEIC format)
  8. ✅ Datetime widget format strictness / timezone issues
  9. ✅ GDPR violation (Google Fonts CDN)
  10. ✅ CORS / COOP policy errors

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

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.