How it works

Schema → CMS → content → API

One model. Four surfaces. Nothing to keep in sync. This is the full path from a collection definition to a page on your site.

01

Define

Schema lives in TypeScript.

Your collections and globals are ordinary TypeScript. defineCollection is the source of truth for field types, relations, uploads, and access — the same file your application already compiles.

There is no parallel CMS schema to export, click together, or keep in sync. Change a field, and the editing UI and the client types move with it.

No codegen step. The types you import are inferred from the definition you already wrote.

collections.ts

1export const posts = defineCollection('posts', {)
2 fields: {
3 title: { type: 'text', required: true },
4 slug: { type: 'text', unique: true },
5 content: { type: 'richText' },
6 cover: { type: 'upload' },
7 author: { type: 'relation', to: 'authors' },
8 }
9})
10 
11// Same file. Same types. No codegen.

02

Edit

Stackpress generates the editing experience.

The admin is generated from that schema. Text, rich text, uploads, and relations become the controls editors actually use — not a second model they have to learn.

The document they save is the document your loaders query. Access rules sit next to the fields, so who can read or update a collection is part of the same definition.

Editors get a real CMS. You keep a single source of truth in the repo.

cms / collections / posts

collections / posts / hello-world

Titletext
Shipping types from schema
ContentrichText
BIUP

Stackpress infers a typed client from the same collection you edit.

Coverupload

cover-hero.jpg

image/jpeg · 248 KB

Authorrelation
Maya Chenauthors

03

Query

Your application consumes fully typed content.

From a TanStack loader — or any server function — you call stackpress.find or getGlobal. Populate expands uploads to URLs and relations to documents.

TypeScript follows the populate list. post.cover.url and post.author.name are typed because the schema said so, not because you maintained a second interface.

The client is not an SDK you regenerate after dashboard changes. It is the schema.

routes/posts.$slug.tsx

1const [post] = await stackpress.find('posts', {)
2 where: { slug: { equals: 'hello-world' } },
3 populate: ['author', 'cover'],
4 limit: 1,
5})
6 
7post.title // string
8post.content // RichText
9post.cover.url // string
10post.author.name // string
11 
12Post is inferred from the collection — not maintained twice.

04

Ship

Content powers your website.

The same document renders on your site. Title, excerpt, cover, author — all from one model, queried with the types you already have.

This marketing site is that loop: a home global, loaders, and pages. The CMS is the editing surface, not a second product.

Ship the app. The content is already typed.

your-app.com/posts/hello-world

your-app.com/posts/hello-world

TypeScript

Shipping types from schema

by Maya Chen

Define once. Edit in the CMS. Query with types.

Content infrastructure, without the abstraction tax.

Define your model in TypeScript. Give your team a powerful editing experience. Ship typed content wherever you need it.

Open the CMS