---
title: "Content model"
url: "https://rigelcms.dev/docs/content-model"
description: "Model JSON pages, reusable records and article collections without confusing Rigel schemas with Astro schemas."
---

Rigel has separate content models for structured documents, article collections and existing copy pages. Declaring content does not create website routes or discover bindings in arbitrary components. Your Astro templates read the data and render the site.

## Documents and sections

Use `defineSchema`, `defineDocument` and `defineObject` from `@rigel-cms/rigel/content`. A document type declares a unique `name`, a content `folder` and `fields`. See [Getting started](/docs/getting-started/) for a complete page example.

Schema scalar types are `string`, `text`, `markdown`, `boolean`, `number`, `date`, `image`, `url`, `color`, `code` and `password`. Structured fields use `object`, `array` or `reference`. Mark a field `required: true` when editors must supply a value; use `initialValue` for editable defaults.

These types describe the editing controls, not protected storage. A `password` field only masks its input; content files still store the value as data. Keep credentials out of content files and public builds.

Named sections belong in `schema.objects`:

```
const hero = defineObject({
  name: "hero",
  title: "Hero",
  type: "section",
  fields: [
    { key: "heading", label: "Heading", type: "string", required: true, initialValue: "Hello" },
  ],
});
```

Add `hero` to `objects`, then add this field to a page document’s `fields`:

```
{
  key: "sections",
  label: "Sections",
  type: "array",
  items: [{ type: "object", name: "hero" }],
}
```

A document’s `template: [{ type: "hero" }]` seeds new sections. This schema template is content initialization, not a preview URL. Array items keep stable `_key` values; named section objects also store `_type`. Preserve those identities when editing JSON by hand. Templates need [keyed preview bindings](/docs/preview/#bind-your-html) to show inserted or reordered sections before Save.

Document JSON stores `_id`, `_type`, `slug` and its field values. A page at `src/content/pages/about.json` uses `_id: "page:about"`, `_type: "page"` and `slug: "about"`. Use unique lowercase kebab-case slugs and avoid collisions with routes the site already owns.

## Shared records

Set `shared: true` on a document type for reusable data such as authors or site settings. Shared records use a form rather than a website canvas. `singleton: true` opens the existing single record directly; `creatable: false` prevents editors from adding records of that type. Document deletion remains developer-managed.

A schema reference field declares `to` as an array of type names:

```
{ key: "author", label: "Author", type: "reference", to: ["author"] }
```

Its stored value is an object such as `{ "_ref": "author:alex" }`. Resolve it in Astro with `derefDocument()` from `virtual:rigel-content`. The same module exposes `documents` and `getDocument("page:about")`. References do not expand themselves inside arbitrary templates.

## Article collections

Configure article collections in `rigel({ collections: [...] })`:

```
collections: [
  {
    name: "news",
    label: "News",
    folder: "src/content/news",
    fields: [
      { key: "title", label: "Title", type: "string", required: true },
      { key: "date", label: "Date", type: "date", required: true },
      { key: "draft", label: "Draft", type: "boolean" },
      { key: "tags", label: "Tags", type: "tags" },
    ],
    columns: ["title", "date", "tags"],
  },
]
```

Each article file is JSON with `{ frontmatter, body }`. `frontmatter` contains the configured metadata. `body` is Lexical editor state, not a Markdown or MDX string. Create articles in the editor and use `renderArticle` from `@rigel-cms/rigel/article` to render their saved bodies. The Markdown view is a read-only projection.

Collection field types are `string`, `text`, `boolean`, `date`, `tags`, `markdown`, `image` and `reference`. Collection references differ from schema references: use `to: "author"`, and the stored value is an ID string. `multiple: true` stores an array of ID strings. A fixed `options` array can replace `to` for developer-managed choices.

Rigel validates configured collection fields on load and before Save. Required strings need non-whitespace text, required arrays need an item and `false` is a valid required boolean. Dates use real `YYYY-MM-DD` dates. Existing unknown properties are preserved.

Rigel does not discover or execute Astro’s `src/content.config.ts` schemas. If your site also loads these files through Astro collections, align both schemas and their requiredness. A Rigel field declaration does not convert an existing Markdown collection to JSON articles.

## Drafts and SEO

Rigel adds missing `description`, `metaTitle` and `ogImage` declarations to website documents and article collections. Non-shared document types also receive a missing `draft` declaration. New collection drafts initialize configured fields, including the automatic SEO strings; existing entries are not backfilled.

The host must filter drafts and render SEO fields. For document routes, the starter uses:

```
const pages = documents.filter(
  (doc) => doc.type === "page" && (import.meta.env.DEV || doc.data.draft !== true),
);
```

Use `metaTitle` for the browser title with a fallback to the page or article title. Render `description` in the description meta tag and `ogImage` in social image tags. Editing these fields alone does not alter a host template that ignores them. Apply equivalent draft filtering to article routes and feeds.

Existing `defineCopy` pages remain supported. Their modules declare grouped fields and data-only values. GitHub mode accepts literal `defineCopy({ ... })` data, not computed JavaScript. New structured sites can use JSON schema documents without migrating every existing copy module at once.

Documentation

On this page