The content-modelling plugin. It turns code-defined content types into physical Postgres tables, holds them in a runtime registry, serves that schema to the admin, and validates every value written against it.
Install
npm install @apograph/content-server @apograph/content-adminRegister
// apps/server/src/plugins.ts
import { ContentPlugin } from '@apograph/content-server';
const content = ContentPlugin({
types: contentTypes,
migrations: {
dir: () => join(process.cwd(), 'migrations'),
table: '__drizzle_migrations_content'
}
});Hold it in a variable rather than inlining it: the GraphQL adapter takes the plugin itself, not just its types.
// apps/admin/src/plugins.ts
import { ContentPlugin } from '@apograph/content-admin';
ContentPlugin();An app with no content types is valid
ContentPlugin({ types: [] }) owns no tables, serves its generic routes
against an empty registry, and boots. That is what lets a freshly scaffolded
app migrate and run before its first content type exists.
The host owns the tables
This is the one plugin that does not ship migrations. Your content types
are yours, so their tables are generated into your app’s own migrations/
directory by apograph generate, and applied by apograph migrate along with
everybody else’s.
Pass the migrations descriptor at the point you define your first type. Until
then there is nothing to generate.
The DSL
Types are declared in code, in your app:
export const post = collection('post', {
label: 'Blog posts',
fields: {
title: field.text({ required: true, minLength: 3 }),
author: field.relation({ to: () => author, required: true, onDelete: 'restrict' }),
tags: field.relation({ to: () => tag, many: true })
}
});collection() for a type with many records, single() for a page there is
exactly one of. See content types for the full
vocabulary and field types for every field.*.
Configuration
It takes no environment variables. Everything about a content model is code — which is the point: the model is reviewable, diffable and deployable, and it cannot drift between environments.
What it serves
The admin API the Content Library drives, and the public content API at
/api/v1/… that bearer tokens are spent against. Reading, filtering,
expanding, writing and the error shapes are documented under
delivering content; the tokens themselves are
managed by the API tokens plugin.