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.
<script>
import { CmsText, CmsRichText } from '@velastack/cms';
</script>
<h1>
<CmsText name="hero.title" fallback="About us" />
</h1>
<CmsRichText name="body" />
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.
The plugin walks your route imports at build time and emits a manifest of every editable field.
Pick an adapter — mock for local dev, the hosted API, or your own. Locales are optional.
One server load resolves every editable field reachable from the current route.
Replace static text with a CMS component. The admin bar takes over editing — visitors see plain HTML.
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.
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
You name fields where you use them. Scope is inferred from the route that reached that component.
Layout fields are shared across every child route; page fields are scoped to that route alone — even with dynamic parameters.
The Vite plugin emits a typed manifest. No registration, no decorators, no runtime field scanning.
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.
adapter-static with prerender = true. generateEntries() enumerates dynamic routes for you.
adapter-node, adapter-vercel, adapter-cloudflare — pages resolve fresh on each request, with the same component code.
Mix prerendered marketing pages with a server-rendered app shell in the same project. The CMS sees both — scopes don't care.
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.
TipTap-based toolbar for headings, lists, links, and inline formatting — without leaving the page.
Upload, replace, and reuse images. <CmsImage /> handles alt text and dimensions automatically.
Per-page title, description, canonical, and robots — with a live Google search preview and character counters.
Browse every route the plugin discovered. Create new entries for parameterized routes right from the bar.
Editing UI is gated by auth and code-split out of public bundles. Your visitors don't download a kilobyte of admin code.
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
Drafts, preview links, releases
A small editorial workflow that fits in one mental model — working copy, named release, full history.
Every edit lands in your personal draft. The published site is unaffected until you publish.
Generate a signed preview key. Stakeholders see your draft on the real site — no login required. Rotate or revoke at any time.
Publish a named snapshot of changes across many pages at once. Browse every prior release in history. Revert with a click.
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.
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 });
Field identity includes the BCP-47 locale tag. en, es-MX, and fr-CA all coexist.
Switch locales in the admin bar. Each locale ships its own working copy and release history.
Works with wuchale, Paraglide, sveltekit-i18n, or your own URL strategy. The CMS just needs the resolved locale at load time.
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.
Inline plain-text fields. Headings, labels, captions.
Formatted body content with TipTap-powered inline editing.
Images with alt text, dimensions, and a built-in media picker.
Arrays — galleries, navigation, FAQs. Each item exposes a snippet for layout.
Toggle fields with a visual indicator in edit mode.
Editable links — internal route or external URL — with a route picker UI.
<CmsEntries /> for iterating published pages, and your own components auto-discovered from src/lib/components/cms/.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.
No CMS dashboard to navigate. Click the field on the page; type the new copy.
Send a preview URL to a stakeholder before publishing. They see the full site as it'll look.
Stage changes across many pages, then ship them as one named release.
Every published release is a snapshot. Roll back to last week's copy in two clicks.
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.