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.
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:
packages/shell/esm-app-shell(opens in a new tab) - Core framework (aggregated):
packages/framework/esm-framework(opens in a new tab) - Framework libraries (individual packages):
packages/framework/*(opens in a new tab) - Core frontend modules:
packages/apps/*(opens in a new tab)
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 dependenciessrc/index.ts- entry point that registers routes and extensionsroutes.json- static route metadata for the app shellconfig-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)
- The app shell boots and preloads the import map.
- It resolves module URLs and registers module bundles in the browser.
- It calls each module’s activator to register routes and extensions.
- 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.