Hybrid Package Model
How SveltyCMS lets developers embed CMS capabilities into their own SvelteKit apps — without running a separate server.
On this page
The Problem
You’re building a website with SvelteKit. You need a CMS for blog articles.
With traditional headless CMS platforms, you must:
-
Install and run the CMS as a separate server (
npm run developin a second terminal) -
Your SvelteKit app makes HTTP requests to that server:
fetch("http://localhost:1337/api/articles") -
Every request goes through: Network → HTTP Parser → Middleware → Controller → Database
-
Latency: ~50ms per request, even on localhost
With SveltyCMS’ Hybrid Package Model:
-
npm install @sveltycms/coreinto your existing SvelteKit app -
Direct database access:
cms.collections.find("articles") -
No network, no HTTP, no second server process
-
Latency: <0.05ms — 1000× faster
What Does “Hybrid” Mean?
Hybrid means the same code works in two ways.
Same LocalCMS class:
Path A: As an SDK Path B: As part of the full CMS
┌──────────────────┐ ┌──────────────────────────┐
│ Your SvelteKit │ │ SveltyCMS Monorepo │
│ App │ │ (npx create-sveltycms) │
│ │ │ │
│ import {LocalCMS} │ │ Admin Panel │
│ from "@sveltycms │ │ Setup Wizard │
│ /core" │ │ GraphQL + REST API │
│ │ │ Media Management │
│ Just the engine. │ │ The engine + everything. │
└──────────────────┘ └──────────────────────────┘
The engine (LocalCMS) is identical. Once embedded in your own app, once embedded in the full CMS interface.
Before vs After
Without the SDK (how Strapi/Directus work)
// +page.server.ts — your SvelteKit page
export async function load() {
// Step 1: HTTP request to a separate CMS server
const res = await fetch("http://localhost:1337/api/articles?status=published");
// ↑ Network latency
// ↑ JSON parsing
// ↑ HTTP overhead
const data = await res.json();
return { articles: data.data };
}
With the SDK (how SveltyCMS works)
// +page.server.ts — your SvelteKit page
import { LocalCMS } from "@sveltycms/core";
import { myDatabase } from "$lib/db";
export async function load() {
const cms = new LocalCMS(myDatabase);
// ↑ Direct database access
// ↑ No network
// ↑ No HTTP
// ↑ <0.05ms
const articles = await cms.collections.find("articles", {
filter: { status: "published" },
});
return { articles };
}
Why Can Only SveltyCMS Do This?
Because SvelteKit has a unique architecture: server code and client code live in the same file, but run on different sides.
// +page.server.ts — runs ONLY on the server
import { LocalCMS } from "@sveltycms/core"; // ← Direct DB access
import { db } from "$lib/db";
export async function load() {
const cms = new LocalCMS(db);
return { posts: await cms.collections.find("posts") };
}
| Platform | Can you embed the CMS as an SDK? | Why? |
|---|---|---|
| SvelteKit | ✅ Yes | .server.ts files run exclusively on the server. |
| | Next.js | ⚠️ Partially | Server Components can import DB drivers. Payload leverages this for React. | | | Strapi | ❌ No | Strapi IS a server. Cannot embed it. | | | Directus | ❌ No | Directus IS a server. Same problem. |
SveltyCMS is one of the few headless CMS platforms that can be embedded as an SDK into SvelteKit apps.
Based on publicly available documentation as of July 2026, no other SvelteKit-compatible CMS offers this pattern.
Advantages for Data Input, Management, and Output
Input (Saving Data)
// SDK: Direct insert — no HTTP, no queue
const newArticle = await cms.collections.create("articles", {
data: {
title: "My Article",
content: "...",
status: "draft", // ← Draft-by-Default
},
tenantId: "my-tenant", // ← Multi-tenant isolation built in
});
// Valibot validation happens AUTOMATICALLY
// The article is written directly to the database
// No fetch(), no JSON.stringify(), no await res.json()
What this saves: No client-side form handling for validation.
No manual serialization required.
No error handling for network failures.
Validation happens where the data arrives — on the server.
Management (Managing Data)
// SDK: Complex queries without GraphQL overhead
const articles = await cms.collections.find("articles", {
filter: {
status: "published",
author: userId,
createdAt: { $gte: "2026-01-01" },
},
sort: { createdAt: "desc" },
limit: 20,
offset: 0,
});
// This is the SAME API as REST/GraphQL in the full CMS
// But without HTTP parsing, without JSON serialization, without auth middleware
// (Auth and tenant isolation are built into the DB adapter)
What this saves: No GraphQL query language to learn.
No REST URL construction needed.
TypeScript IntelliSense shows all available filters as you type.
Output (Displaying Data)
// +page.server.ts
export async function load() {
const cms = new LocalCMS(db);
// Fetch all page data in ONE step
const [articles, categories, authors] = await Promise.all([
cms.collections.find("articles", { filter: { status: "published" } }),
cms.collections.find("categories"),
cms.collections.find("authors"),
]);
return { articles, categories, authors };
// → SSR-rendered in <50ms total
// → Google sees the complete content immediately (SEO)
// → No client-side "Loading..." for CMS data
}
What this saves: No fetch() chains needed.
No client-side waterfall loading.
SSR with CMS data in under 50ms — faster than most static site generators.
Packages
| Package | Description | Key Exports |
|---|---|---|
@sveltycms/core |
Database-agnostic CMS engine | LocalCMS, IDBAdapter, AppError, core types |
|
| @sveltycms/widgets | Type-safe widget system | createWidget, FieldConfig, WidgetDefinition, validation |
Sub-path Exports (Core)
| Import Path | Contents |
|---|---|
@sveltycms/core |
LocalCMS, IDBAdapter, core types, AppError |
|
| @sveltycms/core/types | DatabaseId, ISODateString, ContentNode, Schema, User, Session |
|
| @sveltycms/core/db-interface | IDBAdapter contract, DatabaseResult, PaginatedResult, FindOptions |
|
| @sveltycms/core/errors | AppError class |
|
| @sveltycms/core/local-cms | LocalCMS class |
Sub-path Exports (Widgets)
| Import Path | Contents |
|---|---|
@sveltycms/widgets |
createWidget, FieldConfig, WidgetDefinition, WidgetFactory, scanner |
|
| @sveltycms/widgets/factory | createWidget() only |
|
| @sveltycms/widgets/types | All widget type definitions |
|
| @sveltycms/widgets/validation | Validators: schema, layout, dependencies, rendering |
Building a DB Adapter
Implement the IDBAdapter interface from @sveltycms/core to add support for any database:
import type { IDBAdapter, DatabaseResult } from "@sveltycms/core/db-interface";
class MyAdapter implements IDBAdapter {
// Implement CRUD, Auth, Media, Settings, Widget methods...
}
Build Pipeline
# Type-check all packages (~2s)
bun run packages:check
# Full build for npm publishing
bun run packages:build
# Single package
bun run packages:build --pkg=core
Summary
| Without SDK | With SDK |
|---|
| 2 server processes (App + CMS) | 1 server process |
| HTTP requests for EVERY data operation | Direct database access |
| ~50ms latency per request | <0.05ms latency |
| Network error handling required | No network = no network errors |
| JSON serialization/deserialization | Native JS objects |
| Write REST URLs or GraphQL queries | TypeScript autocompletion |
| Separate server to monitor | Everything in one app |
Related
See Local vs HTTP API for when to use the SDK vs REST/GraphQL.
See Widget Development for building custom field types.
See Error Handling for unified error patterns.