---
title: Adding an application
description: The full recipe for a new application module - scaffold, register, mount, enable, and build the first feature.
---

This guide stands up a whole new application module (`packages/applications/<app>`), registers it with the platform, mounts it in the web app, and enables it for an organization. It assumes the platform is running (see [Running the platform](/guides/running-the-platform)) and that you know the [anatomy](/applications/anatomy-of-an-app).

### The five steps

```mermaid
flowchart LR
    A["1. Scaffold the package"] --> B["2. Register + install"]
    B --> C["3. Mount in the web app"]
    C --> D["4. Enable for an org"]
    D --> E["5. Build the first feature"]
```

### Step 1 - scaffold the package

Create `packages/applications/<app>/` modeled on retail:

```txt
packages/applications/<app>/
├── app.config.ts
├── package.json
├── tsconfig.json
├── project.json
├── vitest.config.ts
└── src/
    ├── index.ts
    ├── application.tsx
    ├── queryClient.ts
    ├── components/
    ├── features/
    └── routes/
```

Write `app.config.ts` with `defineAppConfig` (see [Configuration](/platform/configuration)) - the metadata the registry will store:

```ts
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 a mountable root component (`application.tsx`) that computes the path relative to its mount prefix and renders a shell with the matching page - exactly like `RetailApplication`. Export it, the config, and the query client from `index.ts`.

### Step 2 - install and register

```bash
pnpm install
pnpm db:register-apps
```

`register-apps.ts` reads the new `app.config.ts`, validates it, and upserts the application into `platform.applications`. Confirm with:

```bash
pnpm nx run @africaos/<app>:typecheck
```

### Step 3 - mount in the web app

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

```tsx
"<app>": lazy(() =>
  import("@africaos/<app>").then((module) => ({
    default: module.XApplication,
  }))
),
```

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

### Step 4 - enable it for an organization

The registry row alone does not grant access. Enable the application for the organizations you care about:

- In the seed: add an `organization_applications` row in `infra/database/seeds/development.sql` and run `pnpm db:seed`.
- At runtime: `RegistryService.setEnabled(organizationId, "<app>", true)` or `OrganizationService.enableApplication`.

The launcher lists only enabled applications, so an enabled org sees the card.

### Step 5 - build the first feature

Follow [Adding a feature](/guides/adding-a-feature) to build the first domain slice, and list it in `app.config.ts` `features` with a `path` so it appears in the application's navigation.

### What you inherit

Because the application runs through the platform, you get authentication, tenancy, authorization (including the automatic `platform.applications.access` gate), the shell, and the data-driven launcher with no further work.

### Verification checklist

- [ ] `app.config.ts` registers without warnings (`pnpm db:register-apps`).
- [ ] `pnpm nx run @africaos/<app>:typecheck` passes.
- [ ] The lazy mount resolves in `app-registry.tsx`.
- [ ] The application renders at `/:organization/<app>` once enabled.
- [ ] Server functions scope to the active organization and guard with `requirePermission`.
- [ ] `pnpm test` passes for the new package.