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 contacts and email." }, icon: "https://img.icons8.com/fluency/96/contacts.png", color: "#4F46E5", modules: [FeatureName.CONTACT, FeatureName.EMAIL], preSelectedExtensions: [ FeatureExtensionName.CONTACTS_TYPE, FeatureExtensionName.CONTACTS_BOM, FeatureExtensionName.EMAILS_TYPE, FeatureExtensionName.EMAILS_BOM ] }; 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 | Contacts, Email | | Blog Pack | blog-pack | Blog | | Handyman Pack | handyman-pack | Handyman | | SaaS Website Pack | website-saas-pack | Website SaaS | | Landing Page Pack | website-landing-page-pack | Website Landing Page | 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.