Skip to content
Africa OS Knowledgebase
Esc
navigateopen⌘Jpreview
On this page

Building an application

The step-by-step recipe for adding a new application module to the platform - package, config, shell, feature, routes, registry, and mount.

This guide walks through adding a new application module, end to end. It assumes you have the anatomy and the retail reference in mind - the goal is to mirror retail.

What you are creating

A host-agnostic package in packages/applications/<app> that:

  1. Self-describes through app.config.ts.
  2. Exports a mountable root component.
  3. Ships its domain as feature folders.
  4. Registers with the platform registry.
  5. Mounts in the web app.

Step 1 - scaffold the package

Create packages/applications/<app>/ with the standard files, modeled on retail:

packages/applications/<app>/
├── app.config.ts
├── package.json          @africaos/<app>, "private": true
├── tsconfig.json         extends "@africaos/typescript-config/..."
├── project.json
├── vitest.config.ts
└── src/
    ├── index.ts
    ├── application.tsx
    ├── queryClient.ts
    ├── components/
    ├── features/
    └── routes/

Write package.json

Depend on the shell, ui, config, database, permissions, request-context, logger, and the TanStack packages, all as workspace:*:

{
  "name": "@africaos/<app>",
  "private": true,
  "dependencies": {
    "@africaos/config": "workspace:*",
    "@africaos/database": "workspace:*",
    "@africaos/logger": "workspace:*",
    "@africaos/permissions": "workspace:*",
    "@africaos/request-context": "workspace:*",
    "@africaos/shell": "workspace:*",
    "@africaos/ui": "workspace:*",
    "@tanstack/react-query": "^5.101.0",
    "@tanstack/react-router": "latest",
    "@tanstack/react-start": "latest",
    "effect": "4.0.0-beta.103",
    "react": "^19.2.0",
    "zod": "4"
  }
}

Write app.config.ts

Declare the application’s identity and feature surface with defineAppConfig (see Configuration for the contract):

import { defineAppConfig } from "@africaos/config";

export default defineAppConfig({
  id: "<app>",
  name: "<App> OS",
  icon: "package",
  color: "#2563eb",
  description: "Your vertical's purpose",
  route: "/",
  capabilities: { offline: false, desktop: true, mobile: false },
  features: [],
});

Write the mountable root

Export a <XApplication organizationSlug={...} /> component that computes the path relative to its mount prefix and renders the shell with the matching page - exactly like RetailApplication.

Expose the public surface

In src/index.ts, export the root component, the config, the query client, the route tree, and anything consumers need.

Step 2 - install and register

pnpm install
pnpm db:register-apps

The registry upserts the application from app.config.ts. Verify it with:

pnpm nx run @africaos/<app>:typecheck

Step 3 - mount it in the web app

Add one lazy entry to apps/web/src/features/applications/app-registry.tsx:

import { lazy, type ComponentType } from "react";

export const applicationMounts: Record<string, ComponentType<ApplicationMountProps>> = {
  // ...existing
  "<app>": lazy(() =>
    import("@africaos/<app>").then((module) => ({
      default: module.XApplication,
    }))
  ),
};

The shell’s $application route will now render the application at /:organization/<app> whenever it is enabled for the organization and the user holds platform.applications.access.

Step 4 - enable it for an organization

The registry row alone does not enable the application for any organization. Enable it in the seed data (infra/database/seeds/development.sql) or through OrganizationService.enableApplication / RegistryService.setEnabled. The launcher only lists enabled applications.

Step 5 - build a feature

Mirror the retail products feature to add your first domain slice. The full recipe is on Adding a feature.

What you get for free

Because your application runs through the platform, you inherit:

  • Authentication - every server function authenticates via the request runtime.
  • Tenancy - CurrentOrganization and the orgWhere/orgScope helpers keep data isolated.
  • Authorization - requirePermission guards every domain action; the platform.applications.access gate runs automatically.
  • The shell - sidebar chrome and navigation from @africaos/shell or your own RetailShell-style wrapper.
  • The launcher - the dashboard renders your application from registry metadata, with no launcher code changes.

Verification checklist

  • app.config.ts passes defineAppConfig and registers without warnings.
  • pnpm nx run @africaos/<app>:typecheck passes.
  • The lazy mount resolves in app-registry.tsx.
  • The application appears in the launcher once enabled for an org.
  • Server functions scope to the active organization and guard with requirePermission.
  • pnpm test passes for the new package.

Next steps

Last updated on August 18, 2026