Logo VelaStack
Código Abierto

A scope-aware CMS
for SvelteKit

Drop a few components into your routes and your site is editable. Works with every SvelteKit adapter — static, server, or serverless. No schemas to maintain, no separate admin app to host.

src/routes/(marketing)/about/+page.svelte
<script>
	import { CmsText, CmsRichText } from '@velastack/cms';
</script>

<h1>
	<CmsText name="hero.title" fallback="About us" />
</h1>

<CmsRichText name="body" />
SvelteKit TypeScript SSG SSR adapter-static adapter-node i18n MIT
Setup

Add the CMS to a SvelteKit project in four steps

Designed for projects you already have. No restructure, no new conventions, no second admin app to deploy.

1
Register the Vite plugin

The plugin walks your route imports at build time and emits a manifest of every editable field.

// vite.config.ts import { cms } from '@velastack/cms/vite'; export default defineConfig({ plugins: [cms(), sveltekit()] });
2
Create a CMS instance

Pick an adapter — mock for local dev, the hosted API, or your own. Locales are optional.

// src/lib/cms.ts import { createCms } from '@velastack/cms/server'; export const { load: loadCms } = createCms({ adapter: apiAdapter({ endpoint }) });
3
Wire it into your root layout

One server load resolves every editable field reachable from the current route.

// src/routes/+layout.server.ts import { loadCms } from '$lib/cms'; export const load = async (event) => { const { cms } = await loadCms(event); return { cms }; };
4
Mark anything editable

Replace static text with a CMS component. The admin bar takes over editing — visitors see plain HTML.

<!-- any +page.svelte --> <script> import { CmsText, AdminBar } from '@velastack/cms'; </script> <AdminBar /> <h1><CmsText name="hero.title" /></h1>
Scope-aware

Identity that follows your routes

A reusable <Header /> mounted in (marketing) and (app) shouldn't share the same headline. The plugin walks each route's import graph and binds every CMS field to the layout or page that reached it.

route → scope mapping
src/routes/
├── +layout.svelte layout:/
├── (marketing)/
   ├── +layout.svelte layout:/(marketing)
   ├── about/+page.svelte page:/(marketing)/about
   └── rooms/[slug]/+page.svelte
 page:/(marketing)/rooms/[slug]?slug=suite-1
└── (app)/
    └── +layout.svelte layout:/(app)

 Same Header.svelte distinct content per scope
 Dynamic params bound to scope identity
 Discovered statically no runtime cost
No field path bookkeeping

You name fields where you use them. Scope is inferred from the route that reached that component.

Layouts vs. pages

Layout fields are shared across every child route; page fields are scoped to that route alone — even with dynamic parameters.

Build-time discovery

The Vite plugin emits a typed manifest. No registration, no decorators, no runtime field scanning.

Static · Server · Edge

Works with every SvelteKit adapter

Render statically, on a Node server, or at the edge. The CMS is adapter-agnostic — its editing concerns and data layer are kept out of your runtime path.

Pure SSG

adapter-static with prerender = true. generateEntries() enumerates dynamic routes for you.

export const entries = () => generateEntries('/blog/[slug]'); export const prerender = true;
Server-rendered

adapter-node, adapter-vercel, adapter-cloudflare — pages resolve fresh on each request, with the same component code.

const { cms } = await loadCms(event);
Hybrid

Mix prerendered marketing pages with a server-rendered app shell in the same project. The CMS sees both — scopes don't care.

(marketing)/* — prerender (app)/* — SSR same CMS, both render
Editing

Edit on the page itself

An admin bar appears for signed-in editors. Click any field, edit in place. Visitors get a plain HTML site — none of the editor ships to public traffic.

Inline rich-text editor

TipTap-based toolbar for headings, lists, links, and inline formatting — without leaving the page.

Media library

Upload, replace, and reuse images. <CmsImage /> handles alt text and dimensions automatically.

SEO panel

Per-page title, description, canonical, and robots — with a live Google search preview and character counters.

Pages panel

Browse every route the plugin discovered. Create new entries for parameterized routes right from the bar.

Zero public payload

Editing UI is gated by auth and code-split out of public bundles. Your visitors don't download a kilobyte of admin code.

admin bar — page menu
PAGE
├── Edit content
├── Edit metadata · SEO
├── Copy preview link
└── Discard changes

SITE
├── Pages
├── Media
├── History
├── Review & publish…
└── Share preview link

STATUS
 3 pages drafted
 Editing: about
Workflow

Drafts, preview links, releases

A small editorial workflow that fits in one mental model — working copy, named release, full history.

1
Working copy

Every edit lands in your personal draft. The published site is unaffected until you publish.

/about └── working copy: 3 fields changed
2
Shareable previews

Generate a signed preview key. Stakeholders see your draft on the real site — no login required. Rotate or revoke at any time.

/about?preview=k_2J8…
3
Atomic releases

Publish a named snapshot of changes across many pages at once. Browse every prior release in history. Revert with a click.

v.2026-04-15 · spring launch v.2026-03-22 · pricing copy v.2026-03-01 · initial
Multilingual

Locale-aware from day one

List your locales when you create the CMS. Every field is stored per locale, queryable per request. Works alongside any SvelteKit i18n strategy.

src/lib/cms.ts
import { createCms } from '@velastack/cms/server';

export const { load: loadCms } = createCms({
	adapter: apiAdapter({ endpoint }),
	locales: ['en', 'es', 'fr-CA', 'ja']
});

// in your layout
const { cms } = await loadCms(event, { locale });
Per-locale storage

Field identity includes the BCP-47 locale tag. en, es-MX, and fr-CA all coexist.

Editor language switcher

Switch locales in the admin bar. Each locale ships its own working copy and release history.

i18n-router agnostic

Works with wuchale, Paraglide, sveltekit-i18n, or your own URL strategy. The CMS just needs the resolved locale at load time.

Components

A small primitive set, infinitely composable

Six components cover most editing needs. When you need more, drop a Svelte component into src/lib/components/cms/ and the plugin picks it up.

<CmsText />

Inline plain-text fields. Headings, labels, captions.

<CmsRichText />

Formatted body content with TipTap-powered inline editing.

<CmsImage />

Images with alt text, dimensions, and a built-in media picker.

<CmsRepeater />

Arrays — galleries, navigation, FAQs. Each item exposes a snippet for layout.

<CmsBoolean />

Toggle fields with a visual indicator in edit mode.

<CmsLink />

Editable links — internal route or external URL — with a route picker UI.

Plus <CmsEntries /> for iterating published pages, and your own components auto-discovered from src/lib/components/cms/.
For your content team

Editing without the developer in the loop

Once your developer drops the components in, your team owns the words. No new tool to learn — they edit on the live site.

Click to edit

No CMS dashboard to navigate. Click the field on the page; type the new copy.

Share for review

Send a preview URL to a stakeholder before publishing. They see the full site as it'll look.

Publish together

Stage changes across many pages, then ship them as one named release.

Undo, always

Every published release is a snapshot. Roll back to last week's copy in two clicks.

VelaStack
Open Source · Free Forever

Make your SvelteKit site editable today

Install the package, drop the components in, point at the hosted backend or self-host. No account required to try it.

npm i @velastack/cms Licencia MIT SvelteKit 2 TypeScript Self-host or hosted