Store & Template System

Module store for rapid application development, replacing the legacy template system

Store & Template System

The Store provides a curated catalogue of pre-built modules that can be installed into a project. Each module encapsulates one or more features together with optional extensions, allowing rapid application development with consistent data modelling patterns.

Migration note: The legacy /settings/templates route has been removed. Users navigating to the old URL will receive a 404. All references should be updated to point directly to /settings/store.

Overview

The store system is built from three registry concepts defined in packages/shared/src/templates/:

Concept Description
StoreModule A single installable unit of functionality (e.g. Contacts, Real Estate)
StoreTag A classification label attached to modules (industry or function)
StoreCollection A curated bundle of modules with pre-selected extensions

Registries are populated at startup via a side-effect import of store.init.ts, which calls registerModule, registerTag, and registerCollection for every entry defined in store.modules.ts, store.tags.ts, and store.collections.ts.


  1. Open a project in the backoffice.
  2. Go to Project Settings.
  3. Click the Store tile (icon: fa-puzzle-piece).

The store page is served from:

/projects/[projectId]/settings/store

Note: The old /projects/[projectId]/settings/templates route no longer exists. Update any bookmarks or links to use the store URL directly.


Store UI

Layout

The store page (+page.svelteStoreLayout.svelte) is composed of:

  • Search bar – full-text search across module names and descriptions (searchModules()).
  • Updates banner (StoreUpdatesBanner.svelte) – shown when one or more installed modules have a newer version available. Lists each outdated module with current and available version numbers, and the latest changelog entry.
  • Sidebar (StoreSidebar.svelte) – filter panel with three sections:
    • All Modules – clears any active filter.
    • Industry tags – e.g. Real Estate, Handyman.
    • Function tags – e.g. CRM, Blog.
    • Collections – curated bundles (see below).
  • Module list (StoreModuleList.svelte) – renders a StoreModuleRow for each filtered module.

Module Row (StoreModuleRow.svelte)

Each row displays:

  • Module icon, label, and current/installed version.
  • Tag labels derived from the module's tags array.
  • A status badge or action button:
    • Install (primary) – module not yet installed.
    • Update (warning) – a newer version is available.
    • Installed (success badge) – up to date.
  • An expand chevron that reveals the module description and its extension list.

Expanding a row shows all extensions via StoreExtensionToggle.svelte. Required extensions are checked and disabled; optional extensions can be toggled by the user. Dependencies between extensions are resolved automatically via resolveExtensionDependencies().

Clicking Install or Update opens the InstallModuleModal.

Extension Toggle (StoreExtensionToggle.svelte)

Each extension is shown with:

  • Its translated label.
  • A badge indicating whether it is required, auto-added (pulled in as a dependency of another selected extension, showing the requiring extension's name), or optional.
  • A disabled checkbox when required or auto-added.

Install Module Modal (InstallModuleModal.svelte)

The modal drives the installation flow:

  1. Calls executeTemplate() with the module's feature name passed directly via the features array, along with the user's chosen extensions and a priorExecutionResult seeded from existing project data (see Cross-Module Dependency Resolution).
  2. Sends a PUT request to /api/projects/[projectId]/extensions with an ApplyTemplateBodyWithModule payload that includes moduleName, moduleVersion, and installedExtensions.
  3. Re-executes already-installed modules to resolve cross-module dependencies (see Re-Execution of Installed Modules).
  4. On success the project's installedModules array is updated server-side (see Module Installation Tracking).

Creating a New Project with Modules

The New Project page (/projects/new) allows modules to be selected before the project is created. Project creation always goes through this UI flow — modules cannot be selected via the API.

UI Flow

  1. Navigate to /projects/new.
  2. Optionally click a Collection in the sidebar to pre-select an entire bundle of modules with their recommended extensions. The project name field is auto-populated from the collection label.
  3. Toggle individual modules on/off; expand a module row to adjust its extensions.
  4. Enter or adjust the project name using the project name input field.
  5. Click Create to submit.

After creation, the browser is redirected to the new project URL (/projects/[projectId]). The page waits for a full load before proceeding.

Note: The POST /api/projects endpoint no longer accepts a templateName parameter. Module selection is exclusively handled through the new project UI. Any integrations or scripts that previously passed templateName to the API must be updated to use the UI flow.

On creation the server applies all selected module templates in a single transaction.


StoreModule

Defined in store.types.ts:

interface StoreModule {
  name: FeatureName;           // unique identifier (matches FeatureName enum)
  label: InlineTranslation;
  description: InlineTranslation;
  icon: string;                // URL to module icon image
  version: string;             // semver string, e.g. "1.2.0"
  tags: string[];              // StoreTag names
  requiredExtensions: FeatureExtensionName[];
  optionalExtensions?: FeatureExtensionName[];
  extensions: FeatureExtensionName[];   // all available extensions
  changelog?: ChangelogEntry[];
}

FeatureExtension Dependencies

Extensions can declare dependencies on other extensions via the optional requires field (added in this release):

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;
}

When a user selects an extension that has requires, the required extensions are automatically added to the selection (shown with an "auto-added" badge). This is handled by resolveExtensionDependencies() in packages/shared.


StoreCollection

Collections are curated bundles defined in store.collections.ts:

interface StoreCollection {
  name: string;
  label: InlineTranslation;
  description: InlineTranslation;
  icon: string;
  color: string;
  modules: FeatureName[];
  preSelectedExtensions?: FeatureExtensionName[];
}

Built-in Collections

Collection Modules included Description
Real Estate Pack Contact, Todo, Real Estate
CRM Pack Contact, Email
Blog Pack Blog
Handyman Pack Handyman
Warehouse Pack Warehouse Product catalog and inventory management — ready for warehouse teams
SaaS Website Pack Website SaaS
Landing Page Pack Website Landing Page

Selecting a collection in the sidebar filters the module list to the collection's modules and pre-selects the recommended extensions.

The Warehouse Pack (warehouse-pack) is identified by the color #65A30D and the icon at https://img.icons8.com/fluency/96/warehouse.png. Selecting it on the new project page auto-populates the project name with "Warehouse" / "Lagerverwaltung".


StoreTag

Tags classify modules for filtering. Each tag has a type of either "industry" or "function":

interface StoreTag {
  name: string;
  label: InlineTranslation;
  type: "industry" | "function";
  icon?: string;   // FontAwesome class
}

All tags are registered through store.tags.ts and can be queried with getTagsByType("industry") or getTagsByType("function").


Module Reference

Check-in Module (FeatureName.CHECKIN)

Visitor registration and check-in management with GDPR-compliant data retention, QR code scanning, and email notifications.

  • Features: VISITOR, CONTACT

Note: The comingSoon flag has been removed — this module is now fully available for installation.

Extensions

Extension Name constant Type Required?
Visitor Data Type VISITORS_TYPE Data type ✅ Required
Visitor Events Data Type VISITOR_EVENTS_TYPE Data type ✅ Required
Check-in Application CHECKIN_APP Application ✅ Required
GDPR Visitor Cleanup CHECKIN_GDPR Action (cron) ✅ Required
Check-in Notifications CHECKIN_NOTIFICATIONS Actions (email) Optional
Visitors Backoffice Module VISITORS_BOM BOM Optional
Visitor Events Backoffice Module VISITOR_EVENTS_BOM BOM Optional

Extension Details

Visitor Data Type (VISITORS_TYPE)

Creates a data type for storing visitor records. Fields include:

  • firstName — First name (string)
  • lastName — Last name (string)
  • email — Email address (string)
  • company — Company name (string)
  • contactPersonId — Foreign-key reference to a Contact record (linked to FeatureRef.CONTACT_TYPE when the Contact module is installed)
  • latestEventType — Most recent check-in event type ("in" | "out")
  • latestEventAt — Timestamp of the latest event (datetime)
  • deleteOn — Scheduled deletion timestamp for GDPR retention (datetime)
  • deleteOnPreference — Visitor-selected retention period in milliseconds (number)

Navigation entry: /visitors (icon: fa-address-book)

Visitor Events Data Type (VISITOR_EVENTS_TYPE)

Creates a data type for tracking individual sign-in and sign-out events. Fields include:

  • visitorId — Foreign-key reference to a Visitor record (linked to FeatureRef.VISITOR_TYPE)
  • type — Event type enum: "in" (Sign In) | "out" (Sign Out)
  • createdAt — Event timestamp (datetime)

Navigation entry: /visitor-events (icon: fa-clock-rotate-left)

Check-in Application (CHECKIN_APP)

Creates a SvelteKit application with a single public-facing page at / containing the CheckIn widget (WIDGET_CHECKIN). The application is configured with:

  • No navbar or tab navbar (kiosk-style layout)
  • Public access (no authentication required)
  • Default widget data pre-populated with welcome text and GDPR small print in English and German
  • QR code scanner enabled by default
  • Configurable deletion period options for visitor data retention

The CheckIn widget (WIDGET_CHECKIN / "CheckIn") can also be added independently to any page via the widget catalogue (category: content, visible in catalogue).

GDPR Visitor Cleanup (CHECKIN_GDPR)

Creates a cron-triggered action that runs daily at 02:00 (0 2 * * *) to automatically delete visitor records and associated events whose deleteOn timestamp has passed. The action is pre-configured with:

  • visitorCollectionName — derived from the Visitor type
  • visitorEventCollectionName — derived from the Visitor Events type

Dependency: This extension requires both VISITORS_TYPE and VISITOR_EVENTS_TYPE to be present. If either is absent, the extension is skipped silently.

Check-in Notifications (CHECKIN_NOTIFICATIONS)

Creates three separate email actions triggered via API:

Action Purpose
Visitor Pass Sends a visitor pass email to the registered visitor
Contact Person Notification Notifies the designated contact person of an arrival
Calendar Invitation Sends a calendar invite (iCal format) to relevant parties

Each action uses a SendMail block connected to an API trigger. The trigger's configuration schema includes a sendEmailConfigurationId field with a foreign-ID selector pre-scoped to the project's send-email configurations collection.

Visitors Backoffice Module (VISITORS_BOM)

Adds a Visitors management page to the backoffice navigation sidebar with:

  • Header text: "Visitors" / "Besucher"
  • Navigation entry icon: fa-address-book
  • Navigation path: /visitors
Visitor Events Backoffice Module (VISITOR_EVENTS_BOM)

Adds a Visitor Events log page to the backoffice navigation sidebar with:

  • Header text: "Visitor Events" / "Besucherereignisse"
  • Navigation entry icon: fa-clock-rotate-left
  • Navigation path: /visitor-events

Data Model Cross-Module Dependencies

Extension Looks for Effect when found
visitor.type FeatureRef.CONTACT_TYPE Adds contactPersonId foreign-key field linking visitors to contacts
visitor-events.type FeatureRef.VISITOR_TYPE Adds visitorId foreign-key field linking events to visitor records
checkin-app FeatureRef.VISITOR_TYPE Configures widget visitorTypeId
checkin-app FeatureRef.VISITOR_EVENTS_TYPE Configures widget visitorEventTypeId
checkin-app FeatureRef.CONTACT_TYPE Configures widget contactsTypeId
checkin-gdpr FeatureRef.VISITOR_TYPE Sets visitorCollectionName on the cron trigger
checkin-gdpr FeatureRef.VISITOR_EVENTS_TYPE Sets visitorEventCollectionName on the cron trigger

Check-in Widget (CheckInWidget.svelte)

The CheckIn widget provides a self-contained visitor registration UI. Its schema-driven configuration options include:

Field Type Description
title InlineTranslation Heading shown on the welcome screen
description InlineTranslation Sub-heading / instructions
successSignInMessage InlineTranslation Message displayed after a successful check-in
successSignOutMessage InlineTranslation Message displayed after a successful check-out
smallPrint InlineTranslation GDPR / privacy notice text
scannerEnabled boolean Whether the QR code scanner button is shown
scannerButtonText InlineTranslation Label for the scanner button
deletionPeriods DeletionPeriod[] Selectable GDPR retention periods offered to the visitor
visitorTypeId string ID of the Visitor data type
visitorEventTypeId string ID of the Visitor Events data type
contactsTypeId string ID of the Contacts data type

The widget's internal state machine moves through the following states: welcomeregistersuccess (check-in path) or welcomescannerprofilesuccess (QR scan / check-out path).

Fields latestEventType, latestEventAt, deleteOn, and deleteOnPreference are automatically managed by the widget and are hidden from the registration form shown to visitors.

Visitor API Endpoints

Three server-side API endpoints are registered under /api/projects/[projectId]/visitors/:

POST /api/projects/[projectId]/visitors

Creates a new visitor record.

Request body:

{
  "collectionName": "<visitor collection name>",
  "data": { "firstName": "Jane", "lastName": "Doe", "..." }
}

Response: 201 Created with { "id": "<new visitor id>" }


GET /api/projects/[projectId]/visitors/[visitorId]

Retrieves a single visitor record.

Query parameters:

  • collectionName (required) — the visitor collection name

Response: 200 OK with the visitor document, or 404 if not found. Returns 400 if collectionName is omitted.


PATCH /api/projects/[projectId]/visitors/[visitorId]

Partially updates a visitor record.

Request body:

{
  "collectionName": "<visitor collection name>",
  "data": { "latestEventType": "out", "latestEventAt": 1700000000000 }
}

Response: 200 OK with { "ok": true }


DELETE /api/projects/[projectId]/visitors/[visitorId]

Deletes a visitor record.

Query parameters:

  • collectionName (required) — the visitor collection name

Response: 200 OK with { "ok": true }, or 400 if collectionName is omitted.


POST /api/projects/[projectId]/visitors/[visitorId]/events

Creates a new visitor event (sign-in or sign-out) linked to the given visitor.

Request body:

{
  "collectionName": "<visitor events collection name>",
  "data": { "visitorId": "<visitorId>", "type": "in", "createdAt": 1700000000000 }
}

Response: 201 Created with { "id": "<new event id>" }

iCal Generation (packages/server/src/utils/ical.ts)

A utility function generateICalContent(options: ICalEventOptions) is available on the server for generating RFC 5545-compliant .ics file content, used by the Calendar Invitation notification action.

interface ICalEventOptions {
  title: string;
  description?: string;
  location?: string;
  start: Date;
  end: Date;
  organizer?: { name: string; email: string };
  attendee?: { name: string; email: string };
  url?: string;
}

The generated content uses PRODID:-//smallstack//Check-in//EN, METHOD:REQUEST, and includes proper text escaping (backslashes, semicolons, commas, newlines) per the RFC.


Warehouse Module (FeatureName.WAREHOUSE) — v1.0.0

Inventory management with product catalog, stock levels, locations and movements.

  • Tags: logistics, ecommerce
  • Icon: https://img.icons8.com/fluency/96/warehouse.png
  • Color: #65A30D

Extensions

Extension Name constant Type Required?
Product Data Type PRODUCTS_TYPE Data type ✅ Required
Products Backoffice Module PRODUCTS_BOM BOM Optional
Storage Location Data Type WAREHOUSE_LOCATION_TYPE Data type ✅ Required
Storage Locations Backoffice Module WAREHOUSE_LOCATION_BOM BOM Optional
Warehouse Stock Data Type WAREHOUSE_TYPE Data type ✅ Required
Warehouse Backoffice Module WAREHOUSE_BOM BOM Optional

The three data-type extensions (PRODUCTS_TYPE, WAREHOUSE_LOCATION_TYPE, WAREHOUSE_TYPE) are required and always installed. The three BOM (Backoffice Module) extensions are optional and provide the corresponding navigation entries and list views in the backoffice sidebar.

Note: The Warehouse Stock type (WAREHOUSE_TYPE) depends on both the Product type and the Storage Location type being present. If either is skipped, the warehouse stock type is also skipped automatically.

Data Model

The Warehouse module installs three interconnected data types:

Products (FeatureRef.PRODUCTS_TYPE)

  • Multilingual title, short title, description, and short description
  • tags — array of strings
  • prices — array of price objects (value + price zone ID)
  • options — product options (e.g. size, colour)
  • variants — product variants
  • files — attached files
  • status"active" | "inactive" (default: "active")
  • supplier — supplier information
  • Required field: title
  • Navigation: /products (icon: fa-box-open)

Storage Locations (FeatureRef.WAREHOUSE_LOCATION_TYPE)

  • name — multilingual name of the storage location (e.g. "Hauptlager München", "Keller")
  • zone — area or zone within the warehouse (e.g. Receiving, Shipping, Cold Storage)
  • description — free-text description (textarea)
  • Required field: name
  • Navigation: /warehouse-locations (icon: fa-map-marker-alt)

Warehouse Stock (FeatureRef.WAREHOUSE_TYPE)

  • name — multilingual name of the stock item
  • productId — foreign-key reference to a Product record (uses INPUT_WIDGET_FOREIGN_ID / VIEW_WIDGET_FOREIGN_ID)
  • locationId — foreign-key reference to a Storage Location record
  • quantity — numeric stock quantity
  • minQuantity — minimum stock threshold
  • unit — unit of measurement
  • status"in_stock" | "low_stock" | "out_of_stock" (default: "in_stock")
  • notes — free-text notes (textarea)
  • Required fields: name, productId, locationId, quantity
  • Navigation: /warehouse (icon: fa-warehouse)

Product Module (FeatureName.PRODUCT) — v1.0.0

Standalone product catalog with variants, pricing and media.

  • Tags: ecommerce, logistics
  • Icon: https://img.icons8.com/fluency/96/box.png
  • Color: #EA580C

Extensions

Extension Name constant Type Required?
Product Data Type PRODUCTS_TYPE Data type ✅ Required
Products Backoffice Module PRODUCTS_BOM BOM Optional

The Product module can be installed independently when only a product catalog is needed, without the full warehouse stock and location management provided by the Warehouse module.


Module Installation Tracking

When a module is installed through the store, the server records the installation on the project document.

API endpoint

PUT /api/projects/[projectId]/extensions

The request body extends ApplyTemplateBody with optional module tracking fields:

interface ApplyTemplateBodyWithModule extends ApplyTemplateBody {
  /** When provided, records the installation on the project */
  moduleName?: string;
  moduleVersion?: string;
  installedExtensions?: string[];
}

Server logic (+server.ts)

If moduleName and moduleVersion are present in the request body, the handler (inside the existing MongoDB transaction) writes to the projects collection:

  • New module: $push an InstalledModule document onto project.installedModules.
  • Existing module (re-install or update): $set the updatedAt, updatedVersion, and installedExtensions fields on the matching array element using the positional operator ($).

The InstalledModule shape (from @smallstack/shared):

interface InstalledModule {
  moduleName: string;
  installedAt: string;       // ISO 8601
  installedVersion: string;
  installedExtensions: InstalledExtension[];
  updatedAt?: string;
  updatedVersion?: string;
}

interface InstalledExtension {
  extensionName: string;
  installedAt: string;
}

All writes participate in the same session/transaction as the template entity creation, so a failed entity application will also roll back the tracking update.


Registry API (store.registry.ts)

Helper functions available from @smallstack/shared:

Function Description
listModules() All registered StoreModule objects
getModule(name) Look up a module by FeatureName
searchModules(query) Full-text search over module names, labels, and descriptions
listCollections() All registered StoreCollection objects
getTagsByType(type) Tags filtered by "industry" or "function"
getTag(name) Look up a tag by name
isModuleInstalled(project, name) Whether a module is recorded in project.installedModules
getInstalledVersion(project, name) Returns the installed version string or undefined
hasModuleUpdate(project, name) true when the registry version is higher than the installed version
getModulesWithUpdates(project) Array of StoreModule objects that have updates available
resolveExtensionDependencies(selected, getExt) Expands a selection to include all required extensions

Template Execution (executeTemplate)

The executeTemplate() function in packages/shared/src/services/template-execution.service.ts drives all module installation logic. It accepts a features array of feature name strings directly, rather than a FeatureTemplate wrapper object.

interface TemplateExecutionOptions {
  /** Feature names whose extensions should be executed */
  features: string[];
  currentData: FeatureExtensionExecuteCurrentDataOptions;
  answers?: { [extensionName: string]: { [code: string]: string } };
  autoSelectRecommended?: boolean;
  selectedExtensions?: FeatureExtensionName[];
  priorExecutionResult?: FeatureTemplateExecutionResult;
}

Callers pass the module's feature name(s) directly:

executeTemplate({
  features: [mod.name],
  currentData: ...,
  autoSelectRecommended: true,
  priorExecutionResult: accumulatedResult,
});

This applies in all three call sites:

  • Install Module Modal — installs a single module into an existing project.
  • New Project page — accumulates results across all selected modules before project creation.
  • Re-execution of installed modules — updates dependent modules after a new module is installed.

Placeholder ID Generation & Updates

When templates define relationships between newly added entities they rely on stable IDs to preserve referential integrity. The platform uses placeholder tokens of the form #generateId(<token>)#.

Lifecycle

  1. Collection: All placeholder tokens are recursively collected from the template payload.
  2. Mapping: A deterministic mapping from placeholder token → newly generated ObjectId is built. The same placeholder string always resolves to the same ID within one update execution.
  3. Replacement: All occurrences of placeholder tokens are replaced before any entity is created.
  4. Persistence: The mapping is stored with the corresponding ProjectUpdate so later incremental updates can extend the existing mapping.
  5. Validation: After replacement a defensive assertion aborts the update if any #generateId( pattern still exists.

Reserved Tokens

  • #projectId# – replaced with the current project identifier wherever it appears in navigation definitions or other project-scoped structures.

Update Operation Model (Discriminated Union)

Template version diffs are expressed as a discriminated union:

interface AddOperation {
  action: "add";
  collectionName: string;
  templateId: string;      // placeholder id
  from: null;
  to: unknown;             // full document payload
  modifier: Modifier;
  description: InlineTranslation;
  safetyCheck?: SafetyCheckResult;
}

interface ChangeOrRemoveOperation {
  action: "change" | "remove";
  collectionName: string;
  templateId: string;
  actualId: string;        // existing DB id resolved from prior mapping
  from: unknown;           // expected current value
  to: unknown;             // new value or null for remove
  modifier: Modifier;
  description: InlineTranslation;
  safetyCheck?: SafetyCheckResult;
}

type UpdateOperation = AddOperation | ChangeOrRemoveOperation;

Invariants:

  • action === 'add' ⇒ no actualId field; inherently safe.
  • action !== 'add'actualId must be present; safety-checked against the live DB value.

Template Versioning Policy

Released Template Immutability

CRITICAL RULE: Once a project template is released and deployed to production, it must NEVER be modified. Any changes require creating a new version.

Why This Rule Exists

  • Data Integrity: Existing projects depend on the template structure.
  • Update Safety: The template update system relies on version comparison.
  • Rollback Safety: Users can revert to previous template versions.
  • Testing Consistency: Tests and validations are tied to specific versions.

Version Management Process

  1. New Features/Changes: Always increment the version number.
  2. File Naming: Follow the convention template-name.v{X}.json.
  3. Testing Only: Use a separate template code (e.g. real-estate-test); never modify released versions.

Allowed Operations on Released Templates

  • ✅ Bug fixes in new versions — create a higher version
  • ✅ Adding new fields — create a higher version
  • ✅ Performance improvements — create a higher version
  • ❌ Modifying existing versioned files
  • ❌ Removing fields
  • ❌ Breaking changes to existing field behaviour

Extension Deselection

When installing a module, users can toggle optional extensions on or off. The executeTemplate() function accepts an optional selectedExtensions parameter — an array of FeatureExtensionName values. When provided, only extensions in this list are executed during the check, execute, and postExecute phases. Required extensions should always be included.

This filtering applies in three places:

  1. Install Module Modal — passes the user's toggle selection as selectedExtensions.
  2. New Project page — passes the extensions chosen during project creation.
  3. Re-execution of dependent modules — uses the installedExtensions stored on the project's InstalledModule record, so previously deselected extensions remain excluded.

If selectedExtensions is omitted, all extensions are executed (backward-compatible default).


Cross-Module Dependency Resolution

Some extensions produce entities that reference entities from other modules. For example, the Real Estate type extension checks for an existing Contact type (via FeatureRef.CONTACT_TYPE) to add a contact-linking field. When modules are installed one-by-one in any order, these cross-module references must resolve regardless of installation sequence.

The Problem

executeTemplate() builds entities in currentExecutionResult, keyed by FeatureRef. Extensions look up peer entities in this map (e.g. currentExecutionResult[FeatureRef.CONTACT_TYPE]). When installing Real Estate into a project that already has Contacts, the Contact entities exist in the database but not in currentExecutionResult — so the cross-module link is silently skipped.

The Solution: buildPriorResultFromCurrentData()

Before calling executeTemplate(), the Install Module Modal calls buildPriorResultFromCurrentData(currentData). This helper scans all entity collections in the current project data for entries with a featureRef field and builds a FeatureTemplateExecutionResult map with action: "ignore" for each. This map is passed as priorExecutionResult to seed the execution context.

The execution engine merges priorExecutionResult into currentExecutionResult before running extensions. Extensions can now find peer entities from previously installed modules. After execution, prior keys are stripped from the result so the caller only sees newly produced entities.

Entity collections scanned: types, compoundWidgets, actions, aiAgents, applications, roles, configurations, navigationEntries.

Known Cross-Module Dependencies

Extension Looks for Effect when found
real-estate.type FeatureRef.CONTACT_TYPE Adds contact-linking field to Real Estate type
contact.type FeatureRef.EMAILS_TYPE Adds saved-queries filter for email-linked contacts
email.type FeatureRef.CONTACT_TYPE Adds contact widget to Email type
warehouse.type FeatureRef.WAREHOUSE_LOCATION_TYPE Links stock items to storage locations via foreign-key field
warehouse.type FeatureRef.PRODUCTS_TYPE Links stock items to products via foreign-key field
visitor.type FeatureRef.CONTACT_TYPE Adds contactPersonId foreign-key field to Visitor type
visitor-events.type FeatureRef.VISITOR_TYPE Adds visitorId foreign-key field to Visitor Events type
checkin-app FeatureRef.VISITOR_TYPE Configures the Check-in widget visitorTypeId
checkin-app FeatureRef.VISITOR_EVENTS_TYPE Configures the Check-in widget visitorEventTypeId
checkin-app FeatureRef.CONTACT_TYPE Configures the Check-in widget contactsTypeId
checkin-gdpr FeatureRef.VISITOR_TYPE Sets visitorCollectionName on the GDPR cron trigger
checkin-gdpr FeatureRef.VISITOR_EVENTS_TYPE Sets visitorEventCollectionName on the GDPR cron trigger

Re-Execution of Installed Modules

When a new module is installed, already-installed modules may need to update their entities to pick up newly available cross-module links. For example, installing Contacts after Real Estate should retroactively add the contact-linking field to the Real Estate type.

How It Works

After the primary module is installed, InstallModuleModal calls getInstalledModulesForReExecution(project, newModuleName) which returns all other installed modules. For each:

  1. executeTemplate() is called with the dependent module's feature name passed directly via the features array, along with:
    • priorExecutionResult — accumulated results from the primary install plus any prior re-executions
    • selectedExtensions — the extensions originally chosen when the dependent module was installed (read from project.installedModules)
  2. Only entities with action: "update" are sent to the server — new entities and ignored entities are skipped.
  3. The server applies updates to existing entities without creating duplicates.

This ensures that cross-module features work regardless of installation order.


Integration

The store integrates with:

  • Widget System: Automatic UI generation based on extension-defined field types.
  • Actions System: Workflow automation provided by action extensions.
  • File Sharing: Document attachment extensions.
  • SignalDB: Local-first synchronisation and offline support.
  • Permissions / RLS: Row-level security enforced on every PUT /extensions call (RLS_UPDATE_PERMISSION).
  • MongoDB Transactions: All entity creation and module tracking writes are wrapped in a single session to guarantee atomicity.

Future Enhancements

Planned improvements:

  • Community modules: Third-party contributed store modules.
  • Module dependencies: Automatic installation of prerequisite modules.
  • Rollback support: Uninstall or downgrade an installed module.
  • AI-assisted configuration: Suggest modules from a natural language project description.
  • Import/export: Share custom module configurations between projects.