Beta . Ortha CMS is in public beta. Every plan is free until it ends.
Get started
Menu

Content · 4 / 23

Content model

Content types are declared in your repository, generate real Postgres tables, and the editor is built from the same file.

collections/route.ts
export const route = collection('route', {    publishable: true,    fields: {        title: field.text({ required: true, localized: true }),        body: field.richtext({ localized: true }),        fare: field.money({ min: 0 }),        featured: field.boolean(),        departsOn: field.date(),        region: field.select({ options: ['alps', 'coast'] }),        tags: field.multiselect({ options: ['night', 'scenic'] }),    }});
RoutesSpring timetable changes
ContentAccessHistory
Spring timetable changes Draft EN · English Saved 2 minutes ago
Title *
Spring timetable changes
Body
Start writing…
Fare All locales
64.00
Featured All locales
Enabled Disabled
Departs on All locales
Select date
Region All locales
alps
Tags All locales
night scenic

How it works

  1. 01

    Declare a content type and its fields in TypeScript.

  2. 02

    Each type becomes its own Postgres table.

  3. 03

    The editor, the API and the permission checks follow from that one file.

Field types
12
Storage
One Postgres table per type
Editor tabs
Content, Access, History

In depth · For the engineer who signs off

Your content model is code

Content types are TypeScript in your codebase: they generate real Postgres tables, go through review, and roll back like any other change.

Declare it, and the table follows

A content type is a collection() or single() call with its fields. Each becomes a content_<name> table, and the admin form is generated from the same file — add a field and it is in the editor on the next boot.

A collection file on the left and the Postgres table it generates on the right, column by column.

publishable: true adds status and published_at.

Twelve field types

Show the table
Field What it stores
text A string, with optional length limits.
richtext A structured document in jsonb.
number Integer or decimal, with an optional range.
money An exact amount, never a float.
boolean True or false.
date A calendar date.
datetime A moment, with its time zone.
select One value from a list you define.
multiselect Several values from that list.
json An arbitrary document.
relation A link to another type, four cardinalities.
media One or several assets from the media library.

Four cardinalities

Shape How it is declared
many-to-one field.relation({ to }), a foreign key on this row.
one-to-many The inverse side, declared with relationInverse.
one-to-one A single relation with unique: true.
many-to-many many: true, an ordered join table.

Adding a field is an ordinary change

  1. Edit the collection file

    By hand, or with the schema builder in development.

  2. Generate the migration

    The SQL lands in your repository as a file you can read.

  3. Review it

    The schema change arrives in the same pull request as the code that uses it.

  4. Deploy and migrate

    Rolling back is rolling back a deploy.

Questions

Real tables, or one JSON blob?

Real tables: a column per scalar field, foreign keys and join tables. Query them with psql and join them to the rest of your database.

Can non-engineers change the schema in production?

No. Everything about an entry is editable in the browser; the shape of an entry is a code change.

Full reference in the docs