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/<slug>). 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 -- <slug>; 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"`
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

  1. The user opens Project Settings → Store.

  2. StoreLayout loads all modules via listModules() and filters them by search query, tag, or collection.

  3. The user expands a StoreModuleRow, optionally toggles optional extensions, and clicks Install or Update.

  4. 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
    }
    
  5. A PUT request is sent to /api/projects/[projectId]/extensions.

  6. 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.