open source · MIT · v1.2.0

A CMS that runs where your site does.

One config file in — database, REST + GraphQL APIs, and a full admin panel out. Built for SvelteKit and Cloudflare, and happy to sit on top of the database you already have.

GitHub
npx highseam-init
$ npx highseam-init

highseam scaffolded.

  created:
    + src/cms.config.ts
    + src/routes/admin/[collection]/[id]/+page.svelte
    + src/routes/api/graphql/+server.ts
    + src/routes/media/[...key]/+server.ts
      … 32 more (REST + admin + media stubs, hooks, theme)
  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.
  3. Shape your content in src/cms.config.ts — collections there get a
     database, REST + GraphQL APIs, and admin UI automatically.

Real output, v1.2.0. The quickstart actually working is a feature.

One file in. Three out.

Declare a collection in cms.config.ts and it gets a database presence, REST + GraphQL APIs, and an admin UI, automatically. Point it at a table you already own and nothing migrates.

src/cms.config.ts
import { defineCollection } from 'highseam';

const venues = defineCollection({
  slug: 'venues',
  table: { name: 'venues', autoId: true },  // ← your existing table
  admin: {
    useAsTitle: 'name',
    groupsAs: 'tabs',
    filters: ['suburb', 'dog_friendly']
  },
  fields: [
    // names match your columns
    { name: 'name', type: 'text', required: true },
    { name: 'suburb', type: 'text' },
    { name: 'dog_friendly', type: 'checkbox' },  // 0/1 in SQL
    { name: 'rich_html', type: 'html' }  // sanitized on write
  ]
});
/admin/venues table-backed
Details Media SEO

name

Coburg Dog Beach

dog_friendly

✓

8,412 documents · page 1 of 421

Save

REST

GET /api/venues?where[suburb][equals]=brunswick&sort=-updated_at

GraphQL

{ Venues { docs { name suburb dog_friendly } } }

An 8,000-row table owned by your pipeline? It just got an admin, faceted search, and APIs. Nothing migrated.

Sits on your database

Your tables. Its admin.

Add table: to a collection and it maps onto an existing SQL table: one column per field, names matching your columns. A pipeline-fed table, a legacy schema, same admin, same APIs, same drafts as everything else.

Filters and sorts compile to plain SQL. Writes touch only the columns you declare; the rest of the table is none of the CMS’s business.

Table mode skips localized fields and upload collections, and drafts want a _status column. Document mode has no such limits.

what the adapter actually runs
-- the admin's faceted filters become plain SQL
SELECT * FROM venues
WHERE suburb = ?1 AND dog_friendly = 1
ORDER BY name ASC LIMIT 20;

-- writes touch only the columns you declared
UPDATE venues SET name = ?1, rich_html = ?2 WHERE id = ?3;
the engine, from the README
src/cms.config.ts     your collections — the file you touch
src/lib/core/         framework-agnostic, no SvelteKit imports
  crypto.ts           Web Crypto only: PBKDF2 + HS256
  adapters/memory.ts  JSON file in dev
  adapters/d1.ts      Cloudflare D1 in production
deploy
wrangler d1 create highseam-db
npm run deploy        # same core, now on Workers

Runs at the edge

No Node-only corners.

The core uses only Web Crypto: no bcrypt, no jsonwebtoken, no Node built-ins. The engine that runs in vite dev is the engine that runs on Cloudflare Workers, byte for byte.

Documents live in D1, files in R2, passwords as PBKDF2, sessions as HS256, all through the platform. Nothing is bolted on. Storage is an adapter, so dev runs on a JSON file you can read with your eyes.

Media with a memory

Every image knows its own story.

Alt text, caption, credit and focal point live on the image and are inherited everywhere it’s used; each use overrides only what it needs. The focal point is honoured with plain CSS. The picker is a real library: search, filters, keyboard. It loads nothing until opened, so it doesn’t care whether you have forty images or forty thousand.

Imports are provenance-aware: batches keyed on external identity (never URL equality), and files already sitting in your storage are adopted in place. A corpus backfill moves no bytes. Every image shows where it’s used, with backlinks.

+page.server.ts
import { resolveMedia, focalObjectPosition } from 'highseam';

// the image's own metadata is the default; each use overrides
// only what it needs — '' means "deliberately empty"
const img = resolveMedia(post.coverImage, { alt: post.coverAlt });
+page.svelte
<img src={img.url} alt={img.alt} class="object-cover"
  style:object-position={focalObjectPosition(img.focalPoint)} />

And the parts you’d expect.

The long tail is where recreating a serious CMS architecture pays off.

  • Drafts & versions

    Save draft, publish, unpublish, restore, plus scheduled publishing with no job runner.

  • Structured rich text

    Typed nodes with embedded components, and sanitized HTML fields for pipeline-written content.

  • Live preview across two deploys

    The admin mints short-lived signed tokens; your public site verifies and renders drafts.

  • Access control

    Booleans or query constraints, enforced identically in REST, GraphQL, and the admin.

  • Localization

    Per-field locales with default-locale fallback; filters and unique checks are locale-aware.

  • Document locking

    Five-minute rolling edit locks; a second editor sees who is already in the document.

  • API keys

    Per-user keys authenticate any REST or GraphQL call.

  • Plugins

    A plugin is a config transform, applied in order. No runtime magic to debug.

  • Generated TypeScript types

    Selects become literal unions; blocks become discriminated unions on blockType.

  • Faceted filters & bulk actions

    URL-synced facet controls; select rows, then publish, unpublish, or delete across the set.

  • Globals & Local API

    Singletons in the same config, and server-side CRUD that skips HTTP entirely.

  • Light / dark / system theming

    A theme is a CSS token file. Copy it, change values. That’s the entire contract.

  • Galleries & embeds

    Ordered image sets with per-use overrides, and click-to-load video embeds resolved server-side from a provider allowlist.

  • Upload dedupe

    Identical bytes return the existing document (SHA-256 content hash) instead of a second copy. Opt out per collection.

  • Drift-checked upgrades

    npx highseam-init --check-stubs diffs your route stubs against the installed templates. Your customisations are never flagged.

What it isn’t

  • No collaborative cursors. One editor per document, enforced by rolling edit locks.
  • No built-in image transform service. Focal point is honoured via CSS today; resizing is yours to bring.
  • A young ecosystem. The plugin API is there; a marketplace isn’t.
  • One production deployment of note: an 8,000-row directory with a pipeline-fed table and an 8.4k-image media mirror. It’s where most of this was proven.

Weighing alternatives? There’s an honest comparison table

highseam ·MIT·built on SvelteKit + Cloudflare ·this site runs on highseam