QUESTPIE

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.

View markdown

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:

src/questpie/server/collections/news.ts
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 generate

Three 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/:id

A 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.

PackageWhat it is for
questpieThe core. Collections, globals, fields, hooks, jobs, access rules, REST, the typed client.
create-questpieScaffolds a project and picks the runtime and modules for you.
@questpie/adminThe generated data workspace.
@questpie/openapiOpenAPI document and Scalar reference UI.
@questpie/mcpMCP server, under the app's own access rules.
@questpie/hono · elysia · nextRuntime adapters. TanStack Start ships as a template rather than an adapter.
@questpie/tanstack-queryTyped query-options factory for the client.
@questpie/tanstack-dbTyped TanStack DB collections backed by the client.
@questpie/workflowsDurable, replay-based workflow engine.
@questpie/crdt-yjsYjs text engines for collaborative documents.
@questpie/sandboxHardened isolated execution for untrusted scripts.
@questpie/observabilityOpenTelemetry 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

RequirementVersionWhy
Bun1.3+The runtime the starter template targets.
PostgreSQL15+The minimum database version QUESTPIE supports.
LicenceMITYour 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.

On this page