Title: Schema Extensions Quick Reference Description: Reusable, versioned JSON schemas for data types Tags: schema-extensions, json-schema, types, reference --- Schema Extensions Quick Reference Audience: Developers extending the platform. For comprehensive implementation details, see the internal developer docs. Schema extensions provide reusable, versioned JSON schemas for data types. Quick Start Using Built-in Extensions import type { Type } from "@smallstack/shared"; const emailType: Type = { schemaExtensions: [ { name: "core", version: 1 }, { name: "email", version: 1 } ], // ... other fields }; Creating Custom Extensions import { registerSchemaExtension } from "@smallstack/shared"; registerSchemaExtension({ id: "custom", version: "v1", schema: { type: "object", properties: { customField: { type: "string" } } } }); Built-in Extensions | Extension | Description | |-----------|-------------| | core:v1 | Base entity fields (id, tenantId, timestamps, access) | | contacts:v1 | Contact information (name, email, phone, address) | | email:v1 | Email schema with Microsoft Graph support | | todos:v1 | Task/todo schema (title, status, priority, due date) | | real-estate:v1 | Real estate property information | | products:v1 | Product catalog entries with variants, pricing, files and supplier info | | warehouse-location:v1 | Physical storage locations with name, zone and description | | warehouse:v1 | Warehouse stock items with product link, storage location and quantity | | visitor:v1 | Visitor records with contact, check-in status, and GDPR retention fields | | visitor-events:v1 | Visitor sign-in/sign-out event log linked to visitor records | Products (products:v1) Provides a data type for product catalog entries. Included in the Warehouse and Product modules. Schema properties: | Property | Type | Description | |----------|------|-------------| | title | inline translation | Product title (required) | | shortTitle | inline translation | Short display title | | description | inline translation | Full product description | | shortDescription | inline translation | Brief product description | | tags | string[] | Searchable tags | | prices | ProductPriceSchema[] | Price entries with value and price zone ID | | options | ProductOptionSchema[] | Configurable product options | | variants | ProductVariantSchema[] | Product variants (e.g. size, colour) | | files | ProductFileSchema[] | Attached files and media | | status | "active" \| "inactive" | Availability status (default: "active") | | supplier | ProductSupplierSchema | Supplier information | Navigation entry: /products — icon fa-box-open Storage Locations (warehouse-location:v1) Provides a data type for physical storage locations within a warehouse. Included in the Warehouse module. Schema properties: | Property | Type | Description | |----------|------|-------------| | name | inline translation | Location name, e.g. "Hauptlager München" (required) | | zone | string | Area or zone, e.g. "Eingang", "Versand", "Kühllager" | | description | string (textarea) | Free-text description of the location | Navigation entry: /warehouse-locations — icon fa-map-marker-alt Warehouse Stock Items (warehouse:v1) Provides a data type for warehouse stock entries. Each item links a product to a storage location and records a quantity. Included in the Warehouse module. Dependency note: The warehouse stock type is only created when both the products type and the warehouse-location type have been installed. If either was skipped during setup, the warehouse stock type will not be created. Schema properties: | Property | Type | Description | |----------|------|-------------| | name | inline translation | Name of the stock item | | productId | foreign ID (string) | Link to a product entry | | locationId | foreign ID (string) | Link to a storage location | | quantity | number | Current stock quantity | Navigation entry: /warehouse — icon fa-warehouse Visitors (visitor:v1) Provides a data type for visitor records. Included in the Check-in template. Stores personal contact details, the latest check-in status, and GDPR data-retention preferences. Schema properties: | Property | Type | Description | |----------|------|-------------| | firstName | string | Visitor's first name | | lastName | string | Visitor's last name | | email | string | Visitor's email address | | company | string | Visitor's company or organisation | | contactPerson | foreign ID (string) | Link to an internal contact person (from the Contacts type) | | latestEventType | "in" \| "out" | Whether the visitor is currently checked in or out | | latestEventAt | number (timestamp ms) | Unix timestamp of the most recent check-in or check-out event | | deleteOn | number (timestamp ms) | Unix timestamp after which the record will be deleted by the GDPR cleanup action | | deleteOnPreference | number (duration ms) | The visitor's chosen retention period in milliseconds | Note: The contactPerson foreign-ID field is only populated when the Contacts feature type has been installed alongside the Check-in template. Navigation entry: /visitors — icon fa-address-book Visitor Events (visitor-events:v1) Provides a data type for logging individual visitor sign-in and sign-out events. Included in the Check-in template. Each event record links back to a visitor and records when and in which direction the transition occurred. Schema properties: | Property | Type | Description | |----------|------|-------------| | visitorId | foreign ID (string) | Link to the corresponding visitor record | | type | "in" \| "out" | Whether this event is a sign-in or a sign-out | | createdAt | number (timestamp ms) | Unix timestamp of when the event was recorded | Dependency note: The visitorId foreign-ID field is only wired up when the Visitor type has been installed first. The Check-in template installs the Visitor type before the Visitor Events type to ensure this link is always present. Navigation entry: /visitor-events — icon fa-clock-rotate-left Version Compatibility Rules When creating new versions of schema extensions, certain changes are allowed while others will break existing code. ✅ Allowed Changes | Change | Example | Why It's Safe | |--------|---------|---------------| | Add new properties | Add phone field | Old code ignores unknown properties | | Add enum values | Add "urgent" to priority | Existing code handles known values | | Add new required fields | Make field required | Validation enforces requirement | | Change constraints | minLength: 1 → minLength: 5 | Widgets read from schema | | Change description/title | Update docs | Metadata only | ❌ Prohibited Changes | Change | Example | Why It Breaks | |--------|---------|---------------| | Change property types | email: string → number | TypeScript types break, widgets render wrong | | Remove enum values | Remove "pending" status | Switch statements fail, UI breaks | Note: Constraint changes may make existing data invalid but won't break code. Handle with data migrations if needed. Example: Creating Version 2 // v1 - Initial schema registerSchemaExtension({ id: "product", version: "v1", schema: { type: "object", properties: { name: { type: "string" }, price: { type: "number" } }, required: ["name", "price"] } }); // v2 - Add new optional field (OK) registerSchemaExtension({ id: "product", version: "v2", schema: { type: "object", properties: { name: { type: "string" }, price: { type: "number" }, description: { type: "string" } // New field }, required: ["name", "price"] } }); Common Patterns Multiple Extensions { schemaExtensions: [ { name: "core", version: 1 }, { name: "contacts", version: 1 }, { name: "todos", version: 1 } ], schemaOverrides: { // Type-specific customizations } } Override Extension Fields { schemaExtensions: [{ name: "contacts", version: 1 }], schemaOverrides: { properties: { email: { format: "email", "x-type-schema": { inputWidgetConfiguration: { string: { type: "email" } } } } } } } Feature Extension Dependencies The requires Field FeatureExtension objects can declare inter-extension dependencies via the optional requires field. When an extension lists other extensions in requires, those extensions must be installed before the declaring extension. import type { FeatureExtension } from "@smallstack/shared"; const myExtension: FeatureExtension = { name: "my-reports", label: { en: "My Reports" }, // This extension can only be installed after "contacts-type" and "todos-type" requires: ["contacts-type", "todos-type"], check: (options) => { / ... / }, execute: (options) => { / ... / } }; The requires field is purely declarative — it signals intent and is used by the resolveExtensionDependencies helper to automatically expand a user's selection to include all transitive prerequisites. resolveExtensionDependencies Helper When building extension selection UIs (such as the Store's install modal or the new project wizard), use resolveExtensionDependencies to expand a set of user-chosen extensions into the full set needed for installation. The helper traverses the requires graph transitively. import { resolveExtensionDependencies, getFeatureExtension, type FeatureExtensionName } from "@smallstack/shared"; const userSelected: FeatureExtensionName[] = ["my-reports"]; const result = resolveExtensionDependencies(userSelected, getFeatureExtension); // result.resolved — full set including auto-added dependencies // result.autoAdded — extensions added automatically (not chosen by the user) // result.missing — named dependencies whose FeatureExtension could not be found Result shape | Property | Type | Description | |----------|------|-------------| | resolved | FeatureExtensionName[] | Complete list of extensions to install (user selection + dependencies) | | autoAdded | Map | Maps each auto-added extension to the extension that required it | | missing | FeatureExtensionName[] | Required extension names that are not registered (useful for warnings) | Rendering auto-added extensions In a selection UI, extensions that were auto-added due to a requires relationship should be shown as disabled checkboxes with a badge indicating which extension pulled them in. StoreExtensionToggle handles this out of the box via its isAutoAdded and requiredByExt props. toggleExtension(ext)} /> Full Example: Extension with Dependencies // packages/shared/src/templates/features/my-feature.ts import type { FeatureExtension, FeatureExtensionName } from "@smallstack/shared"; export const myReportsExtension: FeatureExtension = { name: "my-reports" as FeatureExtensionName, label: { en: "My Reports", de: "Meine Berichte" }, requires: [ "contacts-type" as FeatureExtensionName, "todos-type" as FeatureExtensionName ], check: ({ project }) => ({ canInstall: true, hints: [] }), execute: ({ applyEntity }) => { applyEntity({ / report type definition / }); } }; When a user selects my-reports in the Store UI, resolveExtensionDependencies automatically adds contacts-type and todos-type to the installation set, and those extensions are rendered as disabled (auto-added) in the toggle list. See Also x-type-schema i18n Documentation Widget System Overview