What QUESTPIE is
You describe a table once. QUESTPIE derives the database schema, a typed API and a client from that description, and modules add the admin and the machine-readable surfaces on top.
This page walks you through one collection file, what the core builds from it, what the modules add, and which package each piece comes from. You do not have to install anything to follow along.
Defining a collection
A collection is one database table plus the rules around it. You define it in a file, and the filename is the registration. There is nothing to import into a central config.
To define a collection, create a file in src/questpie/server/collections/ and
export a collection() call. For example, to define a news table:
import { collection } from "#questpie/factories";
export const news = collection("news")
.fields(({ f }) => ({
title: f.text(255).label("Title").required(),
content: f.textarea(),
isPublished: f.boolean().default(false),
}))
.title(({ f }) => f.title);Three fields, and a .title() that tells the admin which field to show as the
label of a row. That is the whole definition.
What the core gives you
To build everything from that file, run the generator:
questpie generateThree things now exist that you did not write. All three come from questpie
itself, with no module to enable.
A typed object
app.collections.news carries the field names you declared:
const { docs } = await app.collections.news.create({
title: "Hello",
isPublished: true,
});Misspell one and the code does not compile, so it never reaches the database:
await app.collections.news.create({ titel: "Hello" }); // [!code error]
// TS2561: Object literal may only specify known properties,
// but 'titel' does not exist in type '...'.
// Did you mean to write 'title'?Rename title in the collection file and this breaks in your editor the moment
you run the generator, instead of failing for a user later.
Five REST routes
Every collection gets the same five, named after it:
GET /api/news
GET /api/news/:id
POST /api/news
PATCH /api/news/:id
DELETE /api/news/:idA typed client
The same calls in your frontend, carrying the types the server has:
const { docs } = await client.collections.news.find({
where: { isPublished: true },
});Change the definition, and all three follow
Change f.text(255) to f.text(500) and run the generator again. The column,
the routes and the client types all move together. Nothing is defined twice,
so nothing can drift.
What the modules add
The admin screen and the machine-readable surfaces are modules, not core. You enable them, and each one is a package you can read and replace.
The admin, from @questpie/admin
A list that uses your labels, and a form that picks each control from the field
type. text gets an input, textarea gets a multi-line box, boolean gets a
switch.
The module also contributes two field types of its own, so they appear on f
only when it is enabled: richText for a WYSIWYG document, and blocks for
stacked content.
It is generic over your schema rather than built for one kind of content. Its job is to let the people who own the data work with it in a normal interface, instead of opening a database client or keeping the real numbers in a spreadsheet.
Where a collection has a blocks field, that includes a visual canvas for
stacking and nesting content, and live preview streamed into your own frontend.
QUESTPIE never renders your site. Your app renders it, and the admin sends it
patches as the editor types.
The admin needs a runtime that renders
create-questpie turns it on by default for TanStack Start and Next. Hono and
Elysia are headless, so the module is rejected there and the project is API
plus typed client only.
OpenAPI, from @questpie/openapi
An OpenAPI document for the same routes, and a Scalar reference UI at
/api/docs. On by default for all four runtimes.
MCP, from @questpie/mcp
The same app as a Model Context Protocol server, so an AI agent can call it under the access rules you already wrote rather than a second set.
The ecosystem
Every package does one job, and the seams are where you would expect them.
| Package | What it is for |
|---|---|
questpie | The core. Collections, globals, fields, hooks, jobs, access rules, REST, the typed client. |
create-questpie | Scaffolds a project and picks the runtime and modules for you. |
@questpie/admin | The generated data workspace. |
@questpie/openapi | OpenAPI document and Scalar reference UI. |
@questpie/mcp | MCP server, under the app's own access rules. |
@questpie/hono · elysia · next | Runtime adapters. TanStack Start ships as a template rather than an adapter. |
@questpie/tanstack-query | Typed query-options factory for the client. |
@questpie/tanstack-db | Typed TanStack DB collections backed by the client. |
@questpie/workflows | Durable, replay-based workflow engine. |
@questpie/crdt-yjs | Yjs text engines for collaborative documents. |
@questpie/sandbox | Hardened isolated execution for untrusted scripts. |
@questpie/observability | OpenTelemetry traces and metrics over OTLP. |
What you still write yourself
QUESTPIE derives the plumbing around your data. It does not write your product.
- Your pages. You get a typed client, not a frontend.
- Your business rules. You declare who may read what. What "published" means for your company is code you write.
- Anything with an opinion. Pricing, workflows, the words on your buttons.
What it runs on
| Requirement | Version | Why |
|---|---|---|
| Bun | 1.3+ | The runtime the starter template targets. |
| PostgreSQL | 15+ | The minimum database version QUESTPIE supports. |
| Licence | MIT | Your servers. There is no hosted tier to graduate to. |
Next
The one-schema model goes one level down: what a declaration is, what the generator writes, and when you have to run it again.