Quickstart
Three steps. No cliff.
Everything below is what actually happens: the transcript is real output from the published package, not marketing copy. Init leaves a running app, or tells you precisely why not.
Install
In any SvelteKit project (Svelte 5). Tailwind v4 is the one hard prerequisite; the admin is built on it, and init will tell you if it’s missing rather than letting the site 500.
npm i highseam
npm i -D tailwindcss @tailwindcss/viteScaffold
npx highseam-init copies thin route stubs that import from the package: REST, GraphQL, admin, media. They’re yours: delete what you don’t need, customize what you do. It wires your vite.config.ts and root layout for you.
$ npx highseam-init
highseam scaffolded.
created:
+ src/routes/admin/+layout.server.ts
+ src/routes/admin/+page.svelte
+ src/routes/admin/[collection]/+page.svelte
+ src/routes/admin/[collection]/[id]/+page.svelte
+ src/routes/admin/create-first-user/+page.svelte
+ src/routes/admin/login/+page.svelte
… 11 more admin stubs
+ src/routes/api/[collection]/+server.ts
+ src/routes/api/[collection]/[id]/+server.ts
+ src/routes/api/[collection]/[id]/publish/+server.ts
+ src/routes/api/[collection]/import/+server.ts
+ src/routes/api/graphql/+server.ts
… 9 more API stubs
+ src/routes/media/[...key]/+server.ts
+ src/cms.config.ts
+ src/hooks.server.ts
+ src/highseam.css
+ src/app.d.ts
wired:
~ vite.config.ts (added @tailwindcss/vite plugin)
~ src/routes/+layout.svelte (imported highseam.css)
Next steps:
1. REQUIRED — install Tailwind v4 (the admin needs it):
npm i -D tailwindcss @tailwindcss/vite
2. npm run dev → open /admin and create the first user (they become admin).
3. Shape your content in src/cms.config.ts — collections there get a database,
REST + GraphQL APIs, and admin UI automatically. Your own routes are untouched;
the CMS lives under /admin and /api.
Production (Cloudflare): bind D1 as DB, R2 as BUCKET, set CMS_SECRET.Run
Open /admin and it prompts you to create the first user; they become an admin. A pristine dashboard walks you through first steps.
npm run dev # → http://localhost:5173/adminThe starter config is three collections that show the flagship features, not a bare stub: auth users, a media library with the metadata conventions pre-declared, and pages with drafts, a tabbed form, and rich text carrying image embeds and a typed component:
// src/cms.config.ts — the starter ships three collections:
// users (auth), media (uploads + metadata conventions), pages
// (drafts, tabs, rich text with image embeds and a typed Callout).
const pages = defineCollection({
slug: 'pages',
versions: { drafts: true },
admin: { useAsTitle: 'title', groupsAs: 'tabs' },
access: {
read: ({ req }) =>
req.user ? true : { _status: { equals: 'published' } }
},
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'slug', type: 'text', required: true, unique: true },
{ name: 'coverImage', type: 'upload', relationTo: 'media' },
{ name: 'content', type: 'richText', uploads: 'media' }
]
});Production is two commands away
The same engine that ran on a local JSON file now runs on Workers against D1 and R2; the CMS auto-detects the bindings. This very page is the proof: you’re reading it out of D1, rendered on a Worker.
wrangler d1 create highseam-db # bind as DB in wrangler.toml
# uncomment the R2 bucket binding for uploads
# set CMS_SECRET as a Pages env var
npm run deployGotchas
- Table-backed collections in local dev: your local D1 starts empty, so a collection mapped to a remote table reports “table does not exist” until you seed it with
npx wrangler d1 execute <db> --local --file schema.sql. - pnpm monorepos: don’t nest the CMS app inside another pnpm project; hoisting breaks the package’s dependency resolution. Scaffold a sibling directory.
- Don’t name your app
highseam: npm refuses a dependency whose name matches the host package.
The full reference (fields, access control, hooks, localization, the lot) lives in the README on GitHub.