---
title: Store Module — Types & Registry API
tab: developer
---

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

```ts
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 `StoreModule`s, 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

```ts
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

```ts
// 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.

```ts
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.

```ts
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.

```ts
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.

```ts
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:

```ts

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:

   ```ts
   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` | 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.
