Enable CMS

Add an inline-editing CMS to your SvelteKit site. An app with a backend hosts the CMS itself: the pattern mounts the @velastack/cms backend at /api/cms with its SQLite database and uploads in the data directory. A static site, or any app, can instead read from a hosted CMS with --endpoint, and vela build prerenders the published content with its media. Either way it calls loadCms from the root +layout.server.ts so every page receives its content and deleted pages answer 404, 410 or a redirect, and renders an <AdminBar /> where editors sign in to edit copy in place, upload media, and publish. Wrap text in <CmsText> and images in <CmsImage> to make them editable; the Vite plugin finds every field at build time. With i18n enabled, the CMS speaks the site's locales.

$ vela enable cms
src/lib/cms.ts
import { apiAdapter, createCms } from "@velastack/cms/server";

/**
 * The CMS read path, called from the root `+layout.server.ts`.
 *
 * `/api/cms` is where `src/routes/api/cms/[...path]/+server.ts` mounts the
 * backend. It is also the admin bar's default, so the endpoint is named here
 * and nowhere else.
 *
 * `locales` lists every locale the site supports; the first is the default
 * that a missing translation falls back to.
 */
export const { load: loadCms, generateEntries } = createCms({
  adapter: apiAdapter({ endpoint: "/api/cms" }),
  locales: ["en"],
});
src/lib/server/cms.ts
import { dataPath } from "@velastack/kit/server";
import { createCmsBackend } from "@velastack/cms/backend";

/**
 * The CMS HTTP backend, mounted single-tenant at `/api/cms` by
 * `src/routes/api/cms/[...path]/+server.ts`.
 *
 * Same origin as the site, so there is no CORS to configure and the session
 * cookie is first-party. Editors sign in against the backend's own
 * `cms_editors` table — add one with `vela cms editor add <email>`.
 *
 * `dataPath` reads `VELA_DATA_DIR`, which `vela` sets wherever it runs the
 * app, so the database and uploads live in the data directory that outlives
 * a release rather than inside the build.
 */
export const cms = createCmsBackend({
  dbPath: dataPath("cms.sqlite"),
  uploadDir: dataPath("uploads"),
});
src/routes/api/cms/[...path]/+server.ts
import { cms } from "$lib/server/cms";

// `fallback` catches every method, including OPTIONS. The backend does its own
// sub-routing off `params.path`, so a new endpoint never touches this file.
export const prerender = false;
export const fallback = cms.handler;
src/routes/uploads/[filename]/+server.ts
import { cms } from "$lib/server/cms";

// Uploads land in the data directory after the build, so no static handler
// would find them. Media URLs written into content are root-relative
// `/uploads/<file>`, which is what this route serves.
export const prerender = false;
export const GET = cms.serveUpload;