sveltekit-runelayer Svelte Themes

Sveltekit Runelayer

SvelteKit RuneLayer

SvelteKit RuneLayer is a CMS-as-a-package for SvelteKit apps. It runs inside your existing Node process with schema-driven content, auth, query APIs, hooks, and an admin UI.

Project status

RuneLayer is shipping as 0.x (alpha/beta maturity). The APIs are usable and tested, but you should expect iterative changes while the platform hardens toward a v1 line.

What you get

  • SvelteKit-native integration: handle, admin loaders/actions, request-scoped query helpers
  • Schema as a single source of truth for collections, globals, and generated DB shape
  • Better Auth integration with role checks and deny-by-default access behavior
  • libsql + Drizzle ORM persistence with host-managed drizzle-kit migrations
  • Local filesystem storage adapter with path traversal checks
  • Svelte admin UI built with Svelte 5 runes and Carbon components

Prerequisites

  • Node.js >= 22.18.0 (repo pinned via .nvmrc)
  • pnpm 10+
  • SvelteKit 2.x
  • Svelte 5.x

Install

pnpm add @flaming-codes/sveltekit-runelayer
pnpm add -D drizzle-kit

Quick start

1. Define collections

// src/lib/server/schema.ts
import { defineCollection, text, slug, richText } from "@flaming-codes/sveltekit-runelayer";

export const Posts = defineCollection({
  slug: "posts",
  fields: [
    { name: "title", ...text({ required: true }) },
    { name: "slug", ...slug({ from: "title" }) },
    { name: "content", ...richText() },
  ],
});

2. Export schema for drizzle-kit

drizzle-kit discovers Drizzle table instances from top-level named exports only. Destructure and re-export each table individually:

// src/lib/server/drizzle-schema.ts
import { createDrizzleKitSchema } from "@flaming-codes/sveltekit-runelayer/drizzle";
import { Posts } from "./schema.js";

const _schema = createDrizzleKitSchema([Posts]);
export const { posts, user, session, account, verification } = _schema;

If your app uses globals, pass them as the second argument so drizzle-kit also exports __runelayer_globals (and __runelayer_global_versions when global versioning is enabled).

// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
import { defineRunelayerDrizzleConfig } from "@flaming-codes/sveltekit-runelayer/sveltekit/drizzle";

export default defineConfig(
  defineRunelayerDrizzleConfig({
    schema: "./src/lib/server/drizzle-schema.ts",
    out: "./drizzle",
    database: {
      url: process.env.DATABASE_URL ?? "file:./data/sveltekit-runelayer.db",
      authToken: process.env.DATABASE_AUTH_TOKEN,
    },
  }),
);

3. Create the SvelteKit integration instance

// src/lib/server/runelayer.ts
import { redirect, error, fail } from "@sveltejs/kit";
import { createRunelayerApp } from "@flaming-codes/sveltekit-runelayer/sveltekit/server";
import { Posts } from "./schema.js";

let _runelayer: ReturnType<typeof createRunelayerApp> | undefined;

export function getRunelayerApp() {
  if (!_runelayer) {
    _runelayer = createRunelayerApp({
      kit: { redirect, error, fail },
      collections: [Posts],
      auth: {
        secret: process.env.AUTH_SECRET ?? "dev-secret-change-in-production",
        baseURL: process.env.ORIGIN ?? "http://localhost:5173",
      },
      database: {
        url: process.env.DATABASE_URL ?? "file:./data/sveltekit-runelayer.db",
        authToken: process.env.DATABASE_AUTH_TOKEN,
      },
      admin: {
        path: "/admin",
      },
    });
  }

  return _runelayer;
}

4. Wire hook and admin route

// src/hooks.server.ts
import { createRunelayerHandle } from "@flaming-codes/sveltekit-runelayer/sveltekit/server";
import { getRunelayerApp } from "$lib/server/runelayer";

export const handle = createRunelayerHandle(getRunelayerApp);
// src/routes/(admin)/admin/[...path]/+page.server.ts
import { createRunelayerAdminRoute } from "@flaming-codes/sveltekit-runelayer/sveltekit/server";
import { getRunelayerApp } from "$lib/server/runelayer";

export const { load, actions } = createRunelayerAdminRoute(getRunelayerApp);
<!-- src/routes/(admin)/admin/[...path]/+page.svelte -->
<script lang="ts">
  import { AdminRoutePage } from "@flaming-codes/sveltekit-runelayer/sveltekit/components";
  import type { RunelayerAdminPageProps } from "@flaming-codes/sveltekit-runelayer/sveltekit/components";

  let { data, form }: RunelayerAdminPageProps = $props();
</script>

<AdminRoutePage {data} {form} />

5. Run migrations before startup

npx drizzle-kit generate
npx drizzle-kit migrate

6. Query from request context

// src/routes/(site)/+page.server.ts
import { getRunelayerApp } from "$lib/server/runelayer";
import { Posts } from "$lib/server/schema";

export async function load({ request }) {
  const runelayer = getRunelayerApp();
  const posts = await runelayer.withRequest(request).find(Posts, {
    limit: 10,
    sort: "createdAt",
    sortOrder: "desc",
  });

  return { posts };
}

Host integration rules

  • Always pass SvelteKit's redirect, error, and fail into kit during createRunelayerApp.
  • Use createRunelayerHandle(getRunelayerApp) in hooks.server.ts.
  • Use createRunelayerAdminRoute(getRunelayerApp) in your admin +page.server.ts.
  • Use runelayer.withRequest(request) for request-bound access checks.
  • Use runelayer.system only for trusted server-side tasks (seeding, internal jobs, migrations).
  • Keep admin routes in a dedicated route group so frontend layouts do not bleed into /admin.

Required Vite alias for Better Auth + zod v4

Add this alias in your app vite.config.ts so production builds resolve Better Auth against zod v4:

resolve: {
  alias: {
    zod: "zod/v4",
  },
}

Docs

Contributing and release hygiene

Top categories

Loading Svelte Themes