> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mdcms.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Module System

> How modules extend server, CLI, and Studio functionality

MDCMS is built on a module system that organizes all server-side functionality into discrete, composable packages. Modules extend three surfaces -- the HTTP server, the CLI, and the Studio UI -- through a single, validated contract.

## Module Concept

A module is a self-contained unit that can contribute to one or more runtime surfaces:

| Surface    | What it provides                                                                                        |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| **Server** | HTTP route handlers mounted on the Elysia app, plus action catalog entries for discoverable operations. |
| **CLI**    | Action aliases, output formatters, and preflight hooks that extend the `mdcms` command.                 |
| **Studio** | UI extension points including routes, navigation items, slot widgets, field kinds, and editor nodes.    |

Modules declare a manifest with metadata (id, version, API version, dependencies) and one or more surface implementations.

## Module Package Type

Every module conforms to the `MdcmsModulePackage` type exported from `@mdcms/shared`:

```typescript theme={null}
type MdcmsModulePackage<App = unknown, AppDeps = unknown> = {
  manifest: ModuleManifest;
  server?: ServerSurface<App, AppDeps>;
  cli?: CliSurface;
};

type ModuleManifest = {
  id: string;
  version: string;
  apiVersion: "1";
  kind?: "domain" | "core";
  dependsOn?: string[];
  minCoreVersion?: string;
  maxCoreVersion?: string;
};

type ServerSurface<App, AppDeps> = {
  mount: (app: App, deps: AppDeps) => void;
  actions?: ActionCatalogItem[];
};

type CliSurface = {
  actionAliases?: CliActionAlias[];
  outputFormatters?: CliOutputFormatter[];
  preflightHooks?: CliPreflightHook[];
};
```

The `manifest.kind` field distinguishes infrastructure modules (`core`) from business-domain modules (`domain`). The `dependsOn` array declares inter-module dependencies that the loader validates at boot time.

## Loading Lifecycle

Modules are registered at compile time in `packages/modules/src/index.ts` and loaded during server startup through a three-phase process:

<Steps>
  <Step title="Build load report">
    `buildServerModuleLoadReport()` validates every registered module package
    against the Zod schema, checks manifest compatibility (API version, core
    version range), resolves the dependency graph, and produces a
    `ServerModuleLoadReport`.
  </Step>

  <Step title="Load modules">
    `loadServerModules()` reads from the `installedModules` compile-time
    registry and delegates to the build step. If any module fails validation or
    has unresolvable dependencies, the server refuses to start.
  </Step>

  <Step title="Mount modules">
    `mountLoadedServerModules()` iterates the loaded modules in dependency order
    and calls each module's `server.mount()`, passing the Elysia app instance
    and shared dependencies (database DAL, auth service, etc.).
  </Step>
</Steps>

```typescript theme={null}
// apps/server/src/lib/module-loader.ts (simplified)
const report = loadServerModules({ coreVersion: env.APP_VERSION, logger });
const actions = collectServerModuleActions(report);
mountLoadedServerModules(app, deps, report);
```

## Core Modules

MDCMS ships with two first-party modules:

<Accordion title="core.system">
  **Kind:** `core`

  Provides the foundational infrastructure surface: health checks (`/healthz`), Studio bootstrap endpoint, authentication routes, user management, API key management, RBAC grant management, and invite flows.
</Accordion>

<Accordion title="domain.content">
  **Kind:** `domain`

  Provides the content domain surface: document CRUD, schema registry sync, media upload and management, webhook dispatch, environment cloning/promotion, search, and content migration execution.
</Accordion>

Both modules implement `server` and `cli` surfaces. The module registry in `packages/modules/src/index.ts` sorts them deterministically by manifest id before exposing them to the loader.

## Action Catalog

Every server-side operation is registered as an **action** -- a discoverable, self-describing API endpoint. Actions are collected from all loaded modules and exposed through a unified catalog.

```typescript theme={null}
type ActionCatalogItem = {
  id: string; // e.g. "content.documents.list"
  kind: "command" | "query"; // write vs read
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
  path: string; // e.g. "/api/v1/content"
  permissions: string[]; // required scopes
  studio?: StudioActionMeta; // visibility, surface, label, form hints
  cli?: CliActionMeta; // visibility, alias, input mode
  requestSchema?: JsonSchema; // JSON Schema for request body
  responseSchema?: JsonSchema; // JSON Schema for response body
};
```

The catalog is queryable at runtime:

* **`GET /api/v1/actions`** -- Returns all actions visible to the current principal.
* **`GET /api/v1/actions/:id`** -- Returns a single action by id.

Actions support visibility policies that filter the catalog per-request based on the caller's authentication context and permissions.

<Tip>
  The CLI uses the action catalog to dynamically discover available commands.
  Run `mdcms actions` to see all actions available for your current credentials.
</Tip>

## Studio Extensibility Surfaces

The Studio component exposes several extension points that modules can populate:

| Surface           | Description                                                           |
| ----------------- | --------------------------------------------------------------------- |
| `routes`          | Additional React Router routes mounted inside the Studio shell.       |
| `navItems`        | Navigation entries added to the Studio sidebar.                       |
| `slotWidgets`     | Widgets rendered in named layout slots across Studio pages.           |
| `fieldKinds`      | Custom form field renderers for schema-driven frontmatter editing.    |
| `editorNodes`     | Custom TipTap/ProseMirror nodes for the MDX editor.                   |
| `actionOverrides` | Replace or wrap default action behaviors (e.g., custom publish flow). |
| `settingsPanels`  | Additional panels on the Studio settings page.                        |

Standard slot IDs used across Studio pages:

| Slot ID                         | Location                                      |
| ------------------------------- | --------------------------------------------- |
| `dashboard.main`                | Main content area of the dashboard page.      |
| `content.list.toolbar`          | Toolbar above the content list table.         |
| `content.editor.header.actions` | Action buttons in the document editor header. |
| `content.editor.sidebar`        | Sidebar panels in the document editor.        |
| `settings.general`              | General settings section.                     |

<Note>
  In v1, Studio extensibility surfaces are defined but only consumed by
  first-party modules. The contract is stable and documented for future
  third-party use.
</Note>

## v1 Limitation

The current module system supports **first-party modules only**. All modules are compiled into the server binary at build time via the `packages/modules/` registry. There is no dynamic plugin loading, no remote module resolution, and no third-party module marketplace.

The module contract (`MdcmsModulePackage`, `ModuleManifest`, compatibility checks) is designed with future extensibility in mind, but v1 restricts the surface to modules maintained within the MDCMS monorepo.

## Multi-Tenancy Model

MDCMS implements a two-level isolation hierarchy: **projects** and **environments**.

### Project Isolation

A project is the top-level tenant boundary. Each project owns:

* Its own **schema** (content types and field definitions)
* Its own **content** (documents and versions)
* Its own **environments** (independent content spaces)
* Its own **media** (uploaded files in S3)
* Its own **webhooks** (event subscriptions)
* Its own **users and API keys** (scoped access control)

Projects are identified by slug (e.g., `marketing-site`) and are completely isolated from one another at the database level.

### Environment Isolation

Within a project, each environment maintains:

* **Independent documents** -- Editing content in `staging` does not affect `production`.
* **Independent versions** -- Publish history is per-environment.
* **Schema overlays** -- Environments that extend another inherit the base schema and can add or modify fields.

### Target Routing

Every scoped API request must include explicit targeting headers:

```
X-MDCMS-Project: marketing-site
X-MDCMS-Environment: production
```

The server's target routing guard validates these headers before the request reaches any route handler. Routes that require project+environment context (content, schema, media, webhooks, search) reject requests missing either header with a `400` error. Routes that require only project context (environments API) require `X-MDCMS-Project` alone.

<Warning>
  API keys include a `contextAllowlist` that restricts which project/environment
  pairs they can access. A key scoped to `production` cannot be used to read
  `staging` content, even if it has `content:read` permission.
</Warning>
