Schema Registry
The JSON metadata file generated by the Quickback compiler that powers the CMS.
Schema Registry
The schema registry is a static JSON file generated by the Quickback compiler. It contains full metadata about every table in your project — columns, types, guards, masking rules, views, actions, validation, and firewall config. The CMS reads this file to render its entire UI.
Generation
Schema registry generation is enabled by default. Run quickback compile and the compiler outputs schema-registry.json alongside your compiled API files.
To disable generation:
export default defineConfig({
schemaRegistry: { generate: false },
// ... rest of your config
});Output Shape
The top-level structure of schema-registry.json:
{
"generatedAt": "2026-02-14T06:26:53.554Z",
"generatedBy": "quickback-compiler",
"version": "1.0.0",
"features": { ... },
"tables": { ... },
"tablesByFeature": { ... },
"featureActions": { ... },
"pages": { ... },
"pagesByFeature": { ... }
}| Field | Type | Description |
|---|---|---|
generatedAt | string | ISO timestamp of generation |
generatedBy | string | Always "quickback-compiler" |
version | string | Compiler version used |
features | Record<string, string[]> | Feature name to file list mapping |
tables | Record<string, TableMeta> | Table name to full metadata |
tablesByFeature | Record<string, string[]> | Feature name to table name list |
featureActions | Record<string, ActionMeta[]> | Feature name to standalone actions not owned by one table |
pages | Record<string, PageMeta> | Page slug to full page metadata |
pagesByFeature | Record<string, string[]> | Feature name to page slug list |
TableMeta
Each table entry contains everything the CMS needs to render its UI:
interface TableMeta {
name: string; // camelCase table name (e.g., "accountCode")
dbName: string; // SQL table name (e.g., "account_code")
feature: string; // Parent feature name
columns: ColumnMeta[]; // All columns including audit fields
firewall: Record<string, unknown>; // Tenant isolation config
crud: Record<string, CrudConfig>; // Legacy normalized write surface
create?: WriteOperationConfig | false; // Flat create config
update?: WriteOperationConfig | false; // Flat update config
delete?: WriteOperationConfig | false; // Flat delete config
upsert?: WriteOperationConfig | false; // Flat upsert config
guards: {
createable: string[]; // Fields allowed on create
updatable: string[]; // Fields allowed on update
immutable: string[]; // Fields locked after creation
protected: Record<string, string[]>; // Fields only updatable via actions
};
masking: Record<string, MaskingRule>; // Per-field masking rules
views: Record<string, ViewConfig>; // Named column projections
validation: Record<string, ValidationRule>; // Per-field validation
actions: ActionMeta[]; // Table-scoped actions for this table
displayColumn?: string; // Human-readable label column
internal?: boolean; // Hidden from CMS sidebar when true
}create, update, delete, and upsert mirror the preferred flat DSL from defineTable(...). The legacy crud block is still emitted for backwards compatibility with older consumers.
Feature Actions
Standalone actions do not always belong to a single table. When an action is attached to a multi-table feature root, the compiler emits it under featureActions[featureName] instead of duplicating it across every table.
interface SchemaRegistry {
featureActions: Record<string, ActionMeta[]>;
}The CMS uses featureActions for toolbar-style actions that apply to the feature as a whole. Use cms.placement on standalone actions to control whether they show for the whole feature or only selected tables.
Internal Tables
Tables without a defineTable() resource config are marked internal: true and hidden from the CMS sidebar. These are typically join tables or system tables.
ColumnMeta
Each column in the columns array:
interface ColumnMeta {
name: string; // Property name (camelCase)
dbName: string; // SQL column name (snake_case)
type: "text" | "integer" | "real" | "blob"; // SQLite type
mode?: "boolean"; // When an integer represents a boolean
primaryKey: boolean;
notNull: boolean;
defaultValue?: string | number | boolean;
fkTarget?: string; // Target table name for FK columns
}The compiler automatically includes the system columns (id, organizationId), the audit fields (createdAt, createdBy, modifiedAt, modifiedBy), and the soft-delete pair (deletedAt, deletedBy — when soft delete is enabled) at the beginning of every table's column list.
fkTarget — FK Resolution
Columns ending in Id may have a fkTarget property indicating which table they reference. This is resolved in priority order:
- Explicit
references— from yourdefineTable()config (highest priority) - Drizzle
.references()— parsed from schema source code - Convention — strip
Idsuffix, match table name directly
{
"name": "vendorId",
"type": "text",
"fkTarget": "contact"
}The CMS uses fkTarget to render typeahead/lookup inputs that search the correct table instead of showing raw IDs.
Input Hints
Tables with inputHints configured in defineTable() include an inputHints map in their metadata:
{
"name": "invoice",
"inputHints": {
"status": "select",
"sortOrder": "radio",
"isPartialPaymentDisabled": "checkbox",
"headerMessage": "textarea",
"description": "richtext"
}
}The CMS reads these hints to render the appropriate form control for each field. The "richtext" hint renders a tiptap editor in edit mode and formatted HTML in view mode. See Input Hints for the full list of supported values.
Display Column
The displayColumn field tells the CMS which column to use as a human-readable label for a record. This is used in:
- FK typeahead dropdowns (showing names instead of IDs)
- Record titles in detail views
- Breadcrumb labels
Auto-Detection
If you don't explicitly set displayColumn in your resource config, the compiler auto-detects it by scanning column names in priority order:
nametitlelabelheadlinesubjectcodedisplayNamefullNamedescription
The first match wins. If no candidate matches, the table has no display column and the CMS falls back to showing IDs.
Explicit Config
Set it explicitly in your table definition:
export default defineTable(contacts, {
displayColumn: "companyName",
// ...
});FK Label Resolution
When a table has foreign key columns (ending in Id), the API enriches list responses with _label fields. For example, a roomTypeId column gets a corresponding roomType_label field containing the display column value from the referenced table.
The CMS uses these _label fields to show human-readable names in table cells and FK typeahead dropdowns instead of raw UUIDs.
roomTypeId: "rt_abc123" → displayed as "Master Bedroom"
accountCodeId: "ac_xyz789" → displayed as "4100 - Revenue"The FK target table is resolved from the fkTarget property on each column. For simple cases like roomTypeId → roomType, the compiler auto-detects it. For non-obvious mappings (e.g., vendorId → contact), use explicit references in your defineTable() config.
Pages
When features include definePage() files (in features/{name}/pages/), the compiler includes them in the registry under two fields:
pages— Map of page slug to fullPageMetaobject (data sources, layout, matching rules, page actions)pagesByFeature— Map of feature name to array of page slugs belonging to that feature
The CMS reads these fields to render custom pages in the sidebar and route to the page renderer. See Custom Pages for the full definePage() API.
Next Steps
- Connecting — Demo mode vs. live mode setup
- Schema Format Reference — Full TypeScript types