Skip to content

Generated types

backd types: TypeScript types for a realm's documents, queries, functions, configuration, templates and roles, generated from the config the server enforces.

A realm’s config already describes its API: collections and their schemas, functions and their inputs, app configuration, templates and roles. backd types turns it into one TypeScript file, so editors and compilers know what the API holds. Nothing is checked at runtime from the generated types; the server validates every request regardless.

CONFIG_DIR=./config backd types --realm shop --out src/backd
# wrote src/backd/types.d.ts and src/backd/realm.json (types 3f9a0c61d2b7e485)

--from https://api.example.com reads the config of a running instance instead of CONFIG_DIR (an administrator of the realm, with the config area: the admin API). --db main limits the output to one database.

What it writes

FileContents
types.d.tsThe types, with a header naming the generator version, the realm and the fingerprint of the types
realm.jsonThe fingerprint, the generator version, the config fingerprint and one entry per field, function, setting, template and role: what --diff compares

Everything hangs off one Realm interface, addressed by path, so no naming rules are needed:

import type { Realm } from "./backd/types";

type Order = Realm["db"]["main"]["collections"]["orders"]["doc"];
type PlaceInput = Realm["db"]["main"]["functions"]["place"]["input"];
type Currency = Realm["config"]["paths"]["shop.currency"];

Friendly aliases are generated too, for every collection: MainOrders (the document), MainOrdersInput (what a client may write; MainOrdersCreate for a collection with custom ids), MainOrdersWhere, MainOrdersOrderBy. When two names would collide, the alias is left out, with a warning, and the path still works.

PartType
db.<database>.collections.<name>.docThe stored document: id, _meta and the fields of schema.json. File fields are typed as FileDetails
…​.inputWhat a client sends: the same without id, _meta and file fields (only uploads write files)
…​.createWhat a client sends to create: input, plus the required id in a collection with custom_id (MainInvoicesCreate). The same as input elsewhere
…​.whereEvery queryable field with the operators its type allows: $eq, $ne, $in, $nin, $isNull and $notNull for all; $gt, $gte, $lt, $lte and $between for strings, numbers and dates; the text operators for strings; and $or and $and of the same type
…​.order_byA field, -field, or a comma separated list of them
db.<database>.functions.<name>input and output from input.schema.json and output.schema.json (unknown without them), mode, internal and the calls the function declares
config.tree, config.pathsThe app configuration as one object, and the type of each value by its dot path
templates.email, templates.pushThe kinds that have templates; their data is Record<string, unknown> until templates declare data schemas
rolesThe role names of realm.yaml as a union

From JSON Schema to TypeScript

JSON SchemaTypeScript
object with properties and requiredan object type with required and optional members
additionalProperties: falsea closed object type; otherwise it also accepts other unknown keys
additionalProperties: {…} and no propertiesRecord<string, T>
array with items, or prefixItemsT[], or a tuple
enum, constliteral unions
type: ["string", "null"]string | null
integer, numbernumber
oneOf, anyOf, allOfa union, or an intersection
$ref to #/$defs/…a named type, MainOrders_Address
format: date-timeDateTimeString, a branded string

Keywords that only validate (minimum, pattern, maxLength, other format values) don’t change the type. A schema that uses not, if/then/else, patternProperties, propertyNames, dependentSchemas, unevaluatedProperties, unevaluatedItems or contains, or a $ref that isn’t local, is typed unknown, and the generator prints a warning with the file and the place.

Organization roles

In a realm with organizations the generated Realm has org_roles, the union of the roles declared under organizations.roles ("owner" | "admin" | "editor" | "member"). Use it to type what an app gives: const roles: Realm["org_roles"][] = ["editor"]. Collections that belong to organizations list _meta.org among the fields of where and orderBy, and inOrg(id) keeps the types of the collection.

In TypeScript and in plain JavaScript

Apps and functions get the same types in both languages; JavaScript needs no build step, because editors read the JSDoc:

// TypeScript
import type { Realm } from "./backd/types";
const client = createClient<Realm>({ url, realm: "shop" });
// JavaScript, checked in the editor (or by tsc --checkJs)
/** @typedef {import('./backd/types').Realm} Realm */
/** @type {import('backd-js').TypedClient<Realm>} */
const client = /** @type {any} */ (createClient({ url, realm: 'shop' }));
// A function in plain JavaScript: // @ts-check turns on the same checks as in TypeScript.
// @ts-check
import { defineFunction } from "@backd/functions";
export default defineFunction("main/place", async (ctx) => ({ order_id: ctx.input.items[0] }));

Keeping the types current (drift)

Types are only useful while they match the config. backd gives three ways to notice when they don’t, from the strictest to the softest:

WhereWhat
CI--check fails when the generated files are missing or differ from what the config produces now, naming what changed
Pull request reviews--diff prints the type-level changes since the files were generated
Developmentin dev mode the function types are rewritten after every reload, and the app’s stale-types warning tells you when its copy is out of date

Nothing is enforced at run time: the server validates every request against the config whatever the client’s types say.

--diff prints one line per change, + added, - removed, ~ changed:

+ field done of main/orders: boolean
~ field total of main/orders: number → number | null
- collection main/archive
~ function main/place input: { items: string[] } → { items: string[]; coupon?: string }

A ready-made step for GitHub Actions (other CI systems: the same three commands):

- name: Generated types are current
  run: |
    backd config check
    backd types --realm shop --out src/backd --check
    backd types --realm shop --functions --check
    backd openapi --realm shop --out openapi/shop.json --check   # if you commit the OpenAPI document
  env:
    CONFIG_DIR: ./config

When it fails, run the commands without --check and commit the result; the failure output lists what changed. The Shelf tutorial’s CI does exactly this (make shelf-types).

The JavaScript client takes the Realm interface as createClient<Realm>(). For functions, --functions writes each functions project’s backd.d.ts instead, with defineFunction (Typed functions). The same types are available from a running instance at GET /v1/{realm}/_admin/types (API reference), and the realm’s OpenAPI document is the way to type clients in other languages.

Edit this page · Last updated 15 Oct 2026