Docs
Core concepts

Core concepts

This section explains how the core pieces of O3 fit together. By the end, you should understand how O3 starts up and how modules are loaded and configured. The diagram below shows the main building blocks and how they relate.

O3 architecture

Diagram notes (for clarity): The “distribution manifest” refers to the distro config spa-assemble-config.json, which produces the import map. Modules load at runtime from URLs in that import map (often a CDN or a local dev server), not directly from the npm registry. “Webpack dev server (proxy)” represents the local dev proxy; many packages now use Rspack, but the idea is the same.

The major pieces in the O3 frontend architecture are:

  • The app shell - the base layer that coordinates startup and runtime behavior.
  • Frontend modules - composable UI building blocks.
  • The import map - a JSON file that tells the app shell what modules to load and where to load them from.
  • The core framework - shared libraries and APIs used by frontend modules.

Where this lives in code

If you want to see these pieces in the reference implementation, the openmrs-esm-core (opens in a new tab) monorepo groups them as follows:

App shell

The app shell is the host application that boots O3 and coordinates loading and runtime services. Introduced in RFC-26 (opens in a new tab), it handles:

  • Startup: prepares index.html, loads the import map, and wires module routes/activators.
  • Runtime services: defines breakpoints, subscriptions (modals, toasts, inline notifications), and offline support.
  • Platform wiring: initializes global state and sets up the configuration and extension systems.

Learn more: App shell deep dive

Frontend modules

Frontend modules are the units of functionality that make up the O3 UI. Think of each module as a mini‑app that owns a slice of the interface (for example, the patient chart or the primary navigation).

O3 treats these modules as microfrontends (popularized by single-spa (opens in a new tab)). The app shell loads modules from the import map and calls each module’s activator (entry point) to register routes and extensions. Modules export lifecycle functions that follow the interface defined in RFC-26 (opens in a new tab). In most distros, that import map is generated from spa-assemble-config.json. Extensions are plug‑in UI blocks that modules register into named slots.

Anatomy essentials (files you’ll touch most often):

  • package.json - module metadata, scripts, and dependencies
  • src/index.ts - entry point that registers routes and extensions
  • routes.json - static route metadata for the app shell
  • config-schema.ts - where the module’s configuration properties live

Frontend modules are loaded on demand by the app shell when they’re needed, which improves initial load performance.

Frontend modules can live in standalone repositories or in domain-focused monorepos when multiple modules share a domain. For example, patient-management modules (registration, queues, appointments, bed management, etc.) are grouped in the openmrs-esm-patient-management (opens in a new tab) monorepo.

You control which modules are included in a distro by editing the distro’s spa-assemble-config.json file. To add your own module, start from the template app repo (opens in a new tab), publish it to npm under your own namespace, then add an entry for it in spa-assemble-config.json.

Next:

Import map

An import map is a browser specification for mapping module names to the URLs they should load from. Introduced in RFC-4 (opens in a new tab), it tells the app shell exactly where to fetch each frontend module. The app shell preloads the import map on startup and then uses it to resolve and load module bundles.

In practice, import maps are generated and served by a distro (not the app shell). For example, the import map for the O3 community reference application (opens in a new tab) is served at /openmrs/spa/importmap.json, and its source lives in the OpenMRS distro config repository (opens in a new tab). Most distros follow the same approach.

To view your distro's import map in the browser, navigate to /openmrs/spa/importmap.json (or your distro’s SPA base path), where you'll see something like:

{
  "imports": {
    "@openmrs/esm-home-app": "./openmrs-esm-home-app-4.1.1-pre.211/openmrs-esm-home-app.js",
    "@openmrs/esm-login-app": "./openmrs-esm-login-app-4.3.2-pre.671/openmrs-esm-login-app.js",
    "@openmrs/esm-primary-navigation-app": "./openmrs-esm-primary-navigation-app-4.3.2-pre.671/openmrs-esm-primary-navigation-app.js",
    "@openmrs/esm-patient-chart-app": "./openmrs-esm-patient-chart-app-4.3.1-pre.1352/openmrs-esm-patient-chart-app.js"
    // ... more modules
  }
}

The keys in this object are module names (unique identifiers), and the values are the relative paths to the frontend modules. The app shell reads this map at runtime and registers each module in the browser. Most modules are Module Federation bundles, and some legacy modules are loaded as plain SystemJS scripts. See the App shell deep dive for the loading mechanics.

Learn more: Configuration system

How O3 loads modules (quick mental model)

  1. The app shell boots and preloads the import map.
  2. It resolves module URLs and registers module bundles in the browser.
  3. It calls each module’s activator to register routes and extensions.
  4. The router mounts modules as users navigate.

If you need to change…

  • Which modules load (and versions) → edit the distro’s spa-assemble-config.json (import map source).
  • How modules load globally → app shell behavior/configuration.
  • What a module does → the module’s own repo (UI, routes, extensions, config).

Core framework

The O3 framework is packaged as a JavaScript library in the NPM registry (opens in a new tab). Most app code imports from @openmrs/esm-framework to use these services:

  • Platform services: configuration, routes, state, extensions, and feature flags.
  • UX and navigation: navigation, breadcrumbs, and offline support.
  • Developer utilities: API helpers, globals, and general utilities.

Learn more: Framework API reference

Next steps

Ready to build? Continue to Frontend modules to see how modules are structured and loaded.