Title: Store Module — Types & Registry API --- The Business Platform Store provides a curated catalogue of pre-built modules that can be installed into a project with a single click. This document describes the core types and the registry API that powers the Store. Core Types StoreModule Represents a single installable module in the Store. | Field | Type | Description | |---|---|---| | name | FeatureName | Unique identifier for the module (matches the underlying feature name). | | label | InlineTranslation | Localised display name shown in the Store UI. | | description | InlineTranslation | Short localised description of what the namespace provides. | | icon | string | URL to the module's icon image. | | version | string | Current published version of the module (semver string). | | tags | string[] | Tag names used for filtering (see StoreTag). | | requiredExtensions | FeatureExtensionName[] | Extensions that are always installed with this module. | | extensions | FeatureExtensionName[] | All available extensions (required + optional). | | changelog? | StoreChangelog[] | Optional ordered list of changelog entries, newest first. | Example const contactModule: StoreModule = { name: FeatureName.CONTACT, label: { en: "Contacts", de: "Kontakte" }, description: { en: "Manage your contacts and relationships." }, icon: "https://img.icons8.com/fluency/96/contacts.png", version: "1.2.0", tags: ["crm", "function"], requiredExtensions: [FeatureExtensionName.CONTACTS_TYPE], extensions: [ FeatureExtensionName.CONTACTS_TYPE, FeatureExtensionName.CONTACTS_BOM ] }; StoreCollection A curated bundle of multiple StoreModules, presented as a ready-made starter pack. When a user selects a collection, all listed modules are pre-selected along with a set of recommended extensions. | Field | Type | Description | |---|---|---| | name | string | Unique identifier for the collection. | | label | InlineTranslation | Localised display name. | | description | InlineTranslation | Localised description. | | icon | string | URL to the collection's icon image. | | color | string | Accent colour (hex) used in the UI. | | modules | FeatureName[] | Ordered list of namespace names included in this collection. | | preSelectedExtensions? | FeatureExtensionName[] | Extensions that are pre-checked when the collection is applied. | Example const crmCollection: StoreCollection = { name: "crm-pack", label: { en: "CRM Pack", de: "CRM-Paket" }, description: { en: "Customer relationship management with companies, contacts, opportunities and email." }, icon: "https://img.icons8.com/fluency/96/contacts.png", color: "#4F46E5", modules: [ FeatureName.COMPANY, FeatureName.CONTACT, FeatureName.OPPORTUNITY, FeatureName.EMAIL ], preSelectedExtensions: [ FeatureExtensionName.COMPANIES_TYPE, FeatureExtensionName.COMPANIES_BOM, FeatureExtensionName.CONTACTS_TYPE, FeatureExtensionName.CONTACTS_BOM, FeatureExtensionName.OPPORTUNITIES_TYPE, FeatureExtensionName.OPPORTUNITIES_BOM, FeatureExtensionName.EMAILS_TYPE, FeatureExtensionName.EMAILS_ACTIONS, FeatureExtensionName.EMAILS_BOM ] }; Website showcase fields (optional) These fields power the public product detail page rendered by the Module Showcase Detail widget (/products/). Every field is optional, and each page section hides itself when its field is empty. | Field | Type | Description | |---|---|---| | slug? | string | URL segment for the detail page, e.g. "real-estate". | | showOnWebsite? | boolean | Opt-in flag for website visibility. | | priorityDisplay? | boolean | Opt-in flag for priority placement in the product list. | | marketingDescription? | InlineTranslation | Richer hero copy (falls back to description). | | highlights? | InlineTranslation[] | Plain feature bullets — rendered only when features is empty. | | features? | StoreFeature[] | Rich feature cards { icon, title, description } (preferred over highlights). | | stats? | StoreStat[] | Headline metrics { value, label } shown as a stat strip. | | useCases? | StoreUseCase[] | Persona cards { icon, title, description }. | | testimonials? | StoreTestimonial[] | Social proof { quote, author, role?, company?, avatarUrl? }. | | faqs? | StoreFaq[] | FAQ accordion entries { question, answer }. | | heroImageUrl? | string | CDN URL of the hero banner image. | | videoUrl? | string | CDN URL of an intro/demo video. | | screenshots? | StoreScreenshot[] | Carousel images { url, caption? } — see the warning below. | screenshots and videoUrl must be REAL captures of the actual product. They render under "See it in action" and read as genuine product UI, so AI-generated or mock images would be misleading advertising (a UWG §5 risk in Germany). Capture them from the running backoffice (e.g. via Playwright). The hero/icon images are decorative illustrations and may be AI-generated. icon (FontAwesome v6 class), the hero illustration and the icon are served from https://cdn.smallstack.com/images/store/…. Generate the decorative hero/icon images with npm run store:generate-collection-image -- ; prompts live in agents/_shared/store-image-prompts.json. The Powered by these modules section is derived automatically from modules via getCollectionModules(collection) — no extra fields needed; it surfaces each StoreModule's icon, label, longDescription (or description) and color. StoreTag A classification label used to filter modules in the Store sidebar. | Field | Type | Description | |---|---|---| | name | string | Unique identifier for the tag. | | label | InlineTranslation | Localised display name. | | type | "industry" | "function" | Controls which sidebar section the tag appears in. | | icon? | string | Optional Font Awesome class string (e.g. "far fa-building"). | Tags of type "industry" are shown under the Industry heading; tags of type "function" are shown under Function. StoreChangelog A single entry in a module's changelog. | Field | Type | Description | |---|---|---| | version | string | The version this entry describes. | | description | InlineTranslation | Short localised summary of changes. | InstalledModule Recorded on the Project document when a module is successfully installed. Updated in-place when the same module is re-installed or upgraded. | Field | Type | Description | |---|---|---| | moduleName | FeatureName | Name of the installed module. | | installedAt | string | ISO 8601 timestamp of first installation. | | installedVersion | string | Version that was installed. | | updatedAt? | string | ISO 8601 timestamp of most recent update. | | updatedVersion? | string | Version applied in the most recent update. | | installedExtensions | InstalledExtension[] | List of extensions that were installed. | InstalledExtension | Field | Type | Description | |---|---|---| | extensionName | FeatureExtensionName | Name of the installed extension. | | installedAt | string | ISO 8601 timestamp of when this extension was installed. | Store Registry API The registry is populated at application startup by importing the side-effect module store.init.ts. Once initialised, the following functions are available. Initialisation // Import once — typically in your app's entry point or server bootstrap. import "@smallstack/shared/templates/store.init"; This registers all built-in modules, collections, and tags. After this import the query functions below work correctly. Module functions registerModule(module: StoreModule): void Adds a module to the registry. Throws if a module with the same name is already registered. getModule(name: FeatureName): StoreModule | undefined Returns the registered module for the given name, or undefined if not found. listModules(): StoreModule[] Returns all registered modules in registration order. searchModules(query: string): StoreModule[] Case-insensitive full-text search across module name, label values, and description values. Returns matching modules. const results = searchModules("contact"); // → [contactModule, ...] isModuleInstalled(project: Project, moduleName: FeatureName): boolean Returns true if the given module has a recorded InstalledModule entry on the project. getInstalledVersion(project: Project, moduleName: FeatureName): string | undefined Returns the installedVersion (or updatedVersion if an update was applied) for the given module, or undefined if not installed. hasModuleUpdate(project: Project, moduleName: FeatureName): boolean Returns true when the module's current registry version is greater than the version recorded on the project. getModulesWithUpdates(project: Project): StoreModule[] Returns all registered modules for which hasModuleUpdate is true. Used to populate the Updates Available banner. const updatable = getModulesWithUpdates(project); if (updatable.length > 0) { // show StoreUpdatesBanner } Collection functions registerCollection(collection: StoreCollection): void Adds a collection to the registry. getCollection(name: string): StoreCollection | undefined Returns the collection for the given name, or undefined if not found. listCollections(): StoreCollection[] Returns all registered collections in registration order. Tag functions registerTag(tag: StoreTag): void Adds a tag to the registry. getTag(name: string): StoreTag | undefined Returns the tag for the given name, or undefined if not found. getTagsByType(type: "industry" | "function"): StoreTag[] Returns all tags of the given type. Used to populate the Store sidebar sections. const industryTags = getTagsByType("industry"); const functionTags = getTagsByType("function"); Extension dependencies FeatureExtension supports an optional requires field that declares other extensions that must be present before this one can be installed. interface FeatureExtension { name: string; label: InlineTranslation; / Other extensions that must be installed before this one. */ requires?: FeatureExtensionName[]; check: (options: FeatureExtensionCheckOptions) => FeatureExtensionCheckResult; execute: (options: FeatureExtensionExecuteOptions) => void; } Use resolveExtensionDependencies to expand a user's selection into the full set of required extensions: import { getFeatureExtension, resolveExtensionDependencies } from "@smallstack/shared"; const resolved = resolveExtensionDependencies( [...selectedExtensions], getFeatureExtension ); // resolved.extensions — full set including auto-added dependencies // resolved.autoAdded — extensions added automatically due to requires Module installation flow The user opens Project Settings → Store. StoreLayout loads all modules via listModules() and filters them by search query, tag, or collection. The user expands a StoreModuleRow, optionally toggles optional extensions, and clicks Install or Update. InstallModuleModal builds an ApplyTemplateBody and enriches it with module-tracking fields: interface ApplyTemplateBodyWithModule extends ApplyTemplateBody { moduleName?: string; // FeatureName of the module being installed moduleVersion?: string; // Version string from StoreModule.version installedExtensions?: string[]; // FeatureExtensionName[] of selected extensions } A PUT request is sent to /api/projects/[projectId]/extensions. The server applies the template entities inside a MongoDB transaction, then — if moduleName and moduleVersion are present — writes or updates an InstalledModule record on the project document. New install: pushes a new entry to project.installedModules. Re-install / update: uses a positional $set to update updatedAt, updatedVersion, and installedExtensions in place. Built-in collections The following collections ship with the platform out of the box. | Name | Key | Included modules | |---|---|---| | Real Estate Pack | real-estate-pack | Contacts, Todos, Real Estate | | CRM Pack | crm-pack | Companies, Contacts, Opportunities, Emails | | Blog Pack | blog-pack | Blog | | Handyman Pack | handyman-pack | Handyman | | Field Service Pack | field-service-pack | Contacts, Field Service | | SaaS Website Pack | website-saas-pack | Website SaaS | | Landing Page Pack | website-landing-page-pack | Website Landing Page | | Portfolio Pack | website-portfolio-pack | Website Portfolio | | Warehouse Pack | warehouse-pack | Warehouse | | Check-in Pack | checkin-pack | Contacts, Visitor | Migration from Templates The previous Templates section (route /settings/templates) has been replaced by the Store. Any existing bookmark or direct link to the templates route is automatically redirected to /settings/store via a client-side goto with replaceState: true. No data migration is required — installed template state is transparently superseded by the new installedModules tracking.