# Quickback CMS

> **Early Access**
>
> The Quickback CMS is in early access. It works with any Quickback-compiled API that outputs a schema registry. Expect active development and potential changes.


# Quickback CMS

A schema-driven admin interface that reads `schema-registry.json` generated by the Quickback compiler. Every table, column, action, view, and security rule is rendered automatically. Zero UI code per table.

## Overview

The CMS generates its entire UI from your Quickback definitions. Define a table with columns, guards, masking, views, and actions in your feature files. Run the compiler. The CMS reads the resulting schema registry and renders a complete admin interface — data tables, inline editing, action dialogs, role-based access, and field masking — all without writing a single line of UI code.

## Key Features

- **Schema-driven** — Zero UI code per table. Add a table, recompile, and it appears in the CMS.
- **Dashboard** — Stats grid and feature cards showing tables, columns, actions, views, and masked fields at a glance.
- **Custom pages** — Split-panel layouts with drag-and-drop, matching engine, and page-level actions for workflows like reconciliation.
- **Embedded in your Worker** — Set `cms: true` in config. The CMS is served as static assets from the same Cloudflare Worker as your API — same origin, no CORS, auth cookies work naturally.
- **Dual view modes** — Table browse mode for navigation and Data Table mode for spreadsheet-style editing.
- **Role-based access** — Owner, admin, and member roles with live switching. CRUD buttons hidden when unauthorized.
- **Multi-tenant & single-tenant** — Org-scoped access by default, with single-tenant mode for simpler projects.
- **Inline spreadsheet editing** — Excel/Google Sheets-like editing with keyboard navigation (arrows, Tab, Enter, Escape).
- **FK typeahead** — Server-side search for foreign key fields with debounced queries and keyboard navigation.
- **Field masking** — Email, phone, SSN, and redaction patterns applied per role. Masked fields show a lock icon.
- **Custom actions** — Action dialogs with auto-generated input forms, access filtering, CMS metadata (icons, categories, confirmations), and side effects warnings.
- **Views** — Named column-level projections per role. "All Fields" plus custom views in the toolbar.
- **Auto-form generation** — Create and edit forms built from guards (createable/updatable fields).
- **Display column auto-detection** — FK labels resolved automatically from `name`, `title`, `label`, `code`, and other common patterns.

## Architecture

The CMS sits at the end of the Quickback compilation pipeline:

```
Quickback Definitions (feature files)
        |
        v
    Compiler
        |
        v
  schema-registry.json
        |
        v
    CMS reads it
        |
        v
  Renders admin UI
```

Your feature definitions are the single source of truth. The compiler extracts all metadata — columns, types, guards, masking rules, views, actions, validation, and firewall config — into a static JSON file. The CMS consumes that file and renders the appropriate UI for each table.

### How Embedded Serving Works

When `cms: true` is set, the compiler builds the CMS SPA from source at compile time (with your project-specific env vars baked in) and outputs the assets to `src/apps/cms/`. It also configures `wrangler.toml`:

```toml
[assets]
binding = "ASSETS"
directory = "src/apps"
not_found_handling = "none"
run_worker_first = true
```

All requests go through the Worker first. The Worker handles SPA routing — serving CMS at `/cms/` on the unified domain and at root (`/`) on a custom CMS domain. API paths (`/api/*`, `/auth/*`, etc.) are handled by Hono as normal.

See [Multi-Domain Architecture](/compiler/config/domains) for details on hostname-based routing.

> **Zero UI Code**
>
> The CMS generates its entire UI from your Quickback definitions. Add a table, recompile, and it appears in the CMS. No UI code to write.


## Quick Start

### 1. Enable CMS in config

```typescript title="quickback/quickback.config.ts"
export default defineConfig({
  name: "my-app",
  cms: true,
  // ...providers
});
```

### 2. Compile

```bash
quickback compile
```

The compiler generates `schema-registry.json` and copies CMS static assets to `src/apps/`. It also adds an `[assets]` section to `wrangler.toml` so Cloudflare serves the CMS SPA automatically.

### 3. Run

```bash
npm run dev
```

Open your Worker URL in a browser — the CMS is served at the root. API routes (`/api/*`, `/auth/*`, etc.) pass through to your Hono app as normal. Everything runs on the same origin — no CORS configuration needed, auth cookies work naturally.

### Optional: Custom CMS domain

```typescript
cms: { domain: "cms.example.com" }
```

This adds a custom domain route to `wrangler.toml`. Both domains serve the same Worker — `api.example.com` for the API, `cms.example.com` for the CMS. The compiler also auto-infers a unified `quickback.example.com` domain where everything is available. See [Multi-Domain Architecture](/compiler/config/domains).

### Access gate (admin-only by default)

CMS access defaults to `access: "admin"` — only `user.role === "admin"` (the user-table role from Better Auth's admin plugin) can reach `/cms/*`. Non-admin authenticated users are redirected to `/account/profile`. The gate is enforced at **four layers** — defense in depth:

1. **Account UI link** — the "Go to CMS" button is hidden for non-admins.
2. **CMS SPA shell** — the Worker returns `403 Admin access required` before serving `index.html` to a non-admin. Unauthenticated requests are redirected to the login page.
3. **`/api/v1/schema`** — the CMS metadata endpoint checks `ctx.userRole === "admin"` and returns `403` otherwise.
4. **`custom_view` routes** — the CMS-internal saved-views table is gated on `userRole: ["admin"]` (see the [`userRole` access primitive](/compiler/definitions/access#userrole)) rather than org membership.

Your resource API endpoints (`/api/v1/<resource>`) are **not** affected — they continue to use the per-resource `read`, `create`, `update`, `delete`, and `upsert` access rules you defined.

To allow any authenticated user, opt out explicitly:

```typescript
cms: { access: "user" }
```

### Optional: Skip SPA rebuild

After the first compile, you can skip rebuilding the CMS SPA on subsequent compiles:

```typescript
cms: { build: false }
```

This is useful when you're iterating on API features and don't need to rebuild the CMS UI each time — it saves significant compile time. Set `build: true` (or omit it) when you need to update the CMS assets.

### Optional: Custom output directory

```typescript
cms: { outputDir: "my-custom-path/cms" }
```

This changes where the compiled CMS assets are placed (relative to project root), instead of the default `src/apps/cms/`.

## Next Steps

- **[Schema Registry](/stack/cms/schema-registry)** — Understand the JSON format the compiler generates
- **[Connecting](/stack/cms/connecting)** — Demo mode, live mode, CLI command, and auth modes
- **[Dashboard](/stack/cms/dashboard)** — Stats grid and feature navigation
- **[Table Views](/stack/cms/table-views)** — Browse and Data Table view modes
- **[Custom Pages](/stack/cms/pages)** — Split-panel layouts, matching engine, and drag-drop
- **[Inline Editing](/stack/cms/inline-editing)** — Spreadsheet-style editing and FK typeahead
- **[Security](/stack/cms/security)** — How the CMS enforces all four security layers
- **[Actions](/stack/cms/actions)** — Custom actions with input forms, access filtering, and CMS metadata
- **[Schema Format Reference](/stack/cms/schema-format)** — Full TypeScript types for schema-registry.json
- **[Components Reference](/stack/cms/components)** — All CMS components and their props
