# SCHEMAS

The schemas that ship with the Generic CMS Module. Plus a quick guide to defining your own.

## Built-in

### `siteSettings` (singleton)

Site-wide defaults. Exactly one record exists, with `id === 'site'`.

- **File:** `schemas/siteSettings.js`
- **Storage key:** `cms:admin:site_settings`
- **Fields:** `siteName`, `siteDescription`, `contactEmail`, `contactPhone`, `defaultLanguage`, `timezone`, `homepageSlug`, `maintenanceMode`, `analyticsEnabled`, `footerText`, `updatedNotice`

```js
import { siteSettingsSchema } from './cms/schemas/siteSettings.js';
// Singleton — id is always 'site'
await cmsUpsert({ schemaId: 'site_settings', record: { id: 'site', siteName: 'My Site' } });
```

## Example: `book` (adoption example)

A 9-field "Book" content type. Generic — works for any site that lists books.

- **File:** `schemas/book.js`
- **Storage key:** `cms:admin:book`
- **Fields:** `id`, `title`, `author`, `blurb`, `cover_url`, `buy_url`, `published`, `tags`, `order`

```js
import { bookSchema } from './cms/schemas/book.js';
import { registerSchemas } from './cms/index.js';
registerSchemas({ book: bookSchema });

await cmsUpsert({
  schemaId: 'book',
  record: {
    id: 'forest_trees',
    title: 'Forest Trees',
    author: 'Oscar',
    published: true,
    tags: ['nature'],
  },
});
```

The schema includes `aiPromptHint`, `defaults`, and `identityKey: 'id'` (pressure-test A.L4 — stable identity across rebuilds).

## Defining your own

A content type = one file in `cms/schemas/`. Drop a JS module that exports a `Schema` instance.

```js
// cms/schemas/lesson.js
import { Schema } from '../schema.js';

export const lessonSchema = new Schema({
  id: 'lesson',
  label: 'Lesson',
  storageKey: 'cms:admin:lesson',
  fields: {
    id: { type: 'string', required: true, pattern: '^[a-z0-9_]+$' },
    title: { type: 'string', required: true, maxLength: 200 },
    book: {
      type: 'references',
      references: { storageKey: 'cms:admin:book', label: 'Book' },
    },
    body: { type: 'markdown', required: false, maxLength: 5000 },
  },
  identityKey: 'id',
});
```

Then register:

```js
import { registerSchemas } from './cms/index.js';
import { lessonSchema } from './cms/schemas/lesson.js';
registerSchemas({ lesson: lessonSchema });
```

That's it. Editor form, AI prompt, CSV columns, validation — all auto-generated.

## Field type reference

| Type | Renders as | Validation | Notes |
|---|---|---|---|
| `string` | text input | type, pattern, maxLength, enum, format | most common |
| `textarea` | text area | type, maxLength | long plain text |
| `markdown` | textarea + preview | type, maxLength | sanitized on render |
| `url` | URL input | URL parse, allowlist | http/https only |
| `image` | URL + upload | URL allowlist OR upload-only | same as url |
| `integer` | number step=1 | integer, min, max | whole numbers |
| `number` | number | number, min, max | decimals allowed |
| `boolean` | checkbox | boolean | default false |
| `array` | dynamic list | array, maxLength, itemType | for tags etc. |
| `enum` | select dropdown | enum check | enum: [...] |
| `references` | FK dropdown | FK integrity (in ctx) | references: { storageKey } |
| `datetime` | datetime-local | ISO 8601 | |
| `color` | color picker | hex | #abc or #aabbcc |

Unknown types are rejected at Schema construction (doctrine §12).

## See also

- `USAGE.md` — adding content types
- `MIGRATION.md` — moving from another CMS
- `cms-doctrine.md` §3, §12 — schema-as-source-of-truth, field types
