How the Contoprix CMS fits together#
Contoprix separates the model from the content and from the frontend. Editors define and fill structured fields; developers use stable codes and a small mapping layer to render the result.
Tenant
└─ Content types and component types (shared schemas)
└─ Fields, rules, relations, and allowed blocks
Website
├─ Languages
├─ Content entries and pages
├─ Media
└─ Delivery credential
└─ Published delivery API → SDK → frontendThis distinction matters: a content type is tenant-level, while an entry is created for a particular website and language. A delivery key also resolves the website context, so published requests only see that website's content.
The core building blocks#
| Building block | What it is | Use it for |
|---|---|---|
| Content type | A schema for a standalone record | Articles, products, authors, site settings |
| Component type | A schema for a reusable UI-shaped block | Hero, call to action, feature card |
| Field | A typed value in a type | Title, slug, image, relation, rich text |
| Content entry | One record made from a content type | One Article called “Hello, Contoprix” |
| Page | A route with ordered blocks | /about, /pricing |
| Media item | A reusable uploaded asset | Image, document, video |
Use a content type when editors manage a record independently. Use a component type when editors configure a reusable block inside a page or another structured field. Use a relation when one record should refer to another rather than copy its values.
A simple example#
For a blog, create two content types:
Article
title Text
slug Slug
summary Text Area
author Relation → Author
Author
name Text
bio Rich Text
photo ImageAn editor creates an Author once, then selects that Author on each Article. When the author bio changes, it changes in one place.
Model first, enter content second#
A reliable sequence is:
- Describe the editor's job in one sentence: for example, “Editors publish articles.”
- Create the content type and give it a stable code such as
article. - Add only the fields that the first UI needs.
- Create one realistic entry and publish it.
- Fetch it through the SDK and map
entry.datato UI props. - Add media, relations, components, and more languages one at a time.
Tip
Stable codes are more important than polished labels. Names can be improved for editors later; content-type and field codes are used by the SDK, generated types, and component registry.
What delivery returns#
Published content is delivered as an entry with metadata plus a data object containing your fields:
type ContoprixContentEntry = {
id: string;
contentTypeCode: string;
languageCode?: string;
slug?: string | null;
publishedAt?: string | null;
data: Record<string, unknown>;
};Media IDs are normalized to delivery-ready media objects, and direct relation fields are expanded to a small related-entry object. Custom fields still live in data, not at the top level.
const entry = await client.content.getBySlug("article", "hello-contoprix");
const data = entry.data as { title?: string };
const title = data.title ?? "Untitled article";Draft, preview, and published delivery#
| State/path | Intended audience | What it returns |
|---|---|---|
| Draft | Editors | Work in progress in the admin |
| Preview | Authenticated editorial preview | Current draft content/page |
| Published delivery | Visitors and public-site server code | Published pages and entries only |
Do not use a preview endpoint as the data source for a public page. Preview requires a protected integration and exists specifically so editors can review before publishing.
Editorial lifecycle#
Content entries can be saved as drafts, published, unpublished, archived, scheduled, and restored from a version. Workflows can require review and approval before a user can publish.
Every save creates a versioned content snapshot. A restore creates a new current draft from an older snapshot and validates it against the current schema, so a restored item should still be reviewed and published deliberately.
A safe model-change workflow#
Changing a model affects editors and frontend code. Use this sequence:
- Identify the components and routes that read the existing field code.
- Add a new optional field before making a UI depend on it.
- Update the frontend mapper to handle missing values.
- Test an existing entry and a new entry.
- Publish only after preview or delivery checks pass.
- If you use the CLI, pull the schema, regenerate types, and validate component coverage.
Avoid renaming codes casually. It is safer to add a replacement field, migrate entries, update the frontend, then retire the old field after it is unused.
Where to go next#
- Content Types — design a standalone record and its fields.
- Component Types — design blocks and map their codes to React components.
- Content Entries — draft, version, publish, and archive content.
- Relations — reuse content without copying it.
- Localization — configure website languages and deliver the correct entry.