Skip to Content

Developing frontend modules

After setting up a frontend module locally, you’ll want to start making changes to it. This guide will walk you through the steps you need to follow when doing local development on a frontend module. We’ll cover how to install dependencies, run frontend modules locally, write tests, push your changes to GitHub, maintain dependencies and troubleshoot common issues.

Installing dependencies

Most frontend repos pin their package manager in package.json and, for Yarn Berry repos, in .yarnrc.yml. Use the Yarn version declared by the repo, then cd into your local copy of the frontend module you want to work on and run yarn (which is short for yarn install). For example, if you want to work on the Patient Chart  frontend module, you’d run:

cd openmrs-esm-patient-chart yarn

Running frontend modules locally

Non-monorepos

Some of our projects are not monorepos. For example, the Form Builder  is a standalone project. To run the Form Builder locally, you’d cd into the project directory and run yarn start. For example:

cd openmrs-esm-form-builder yarn start

You shouldn’t need to scope commands by their target packages in non-monorepos.

Monorepos

Most of our frontend modules are part of monorepos. For example, the Patient Chart  monorepo consists of several frontend modules that handle patient-related concerns. You’ll typically want to run a specific frontend module within a monorepo. To do that, run yarn start --sources and pass in the relative path to your target frontend module. For example, to run just the Vitals  frontend module, you’d run:

yarn start --sources packages/esm-patient-vitals-app

Where the argument following --sources is the relative path to the frontend module you want to run.

If the workspace can resolve packages by name, you can also use --packages instead of filesystem paths:

yarn start --packages @openmrs/esm-patient-vitals-app

It is possible to run multiple frontend modules simultaneously. For example, if you’re making changes to the Order Basket , you’ll likely want to test those changes against the Medications and Orders frontend modules. To do that, you’d run:

yarn start --sources packages/esm-patient-medications-app packages/esm-patient-orders-app

This should fire up a bundler dev server running local copies of the Medications and Orders frontend modules. Current React modules use Rspack, while a few legacy modules may still use Webpack. Any local changes should propagate to the dev server in your browser.

By default, the dev server will run on port 8080. If you wish to run the server on a different port, you can pass a port number as the argument to the --port flag. For example, to run a dev server for the Vitals frontend module on port 7000, you’d run:

yarn start --sources 'packages/esm-patient-vitals-app' --port=7000

This will start the app shell at http://localhost:7000/openmrs/spa and serve the module on the next available port (typically http://localhost:7001).

openmrs tooling deep dive

yarn start is a shorthand for running the openmrs CLI . More specifically, it’s aliased to run openmrs develop, which serves the app shell and runs it against a given OpenMRS backend. It starts a local dev server on port 8080 and proxies requests to the default backend, which is hosted at https://dev3.openmrs.org . This dev server runs local copies of the frontend modules you specify and overrides both their import map entries and their routes registry entries. Other frontend modules come from the import map and routes registry exposed by the selected backend, dev3 by default. With openmrs develop, you can also:

  • Specify an alternative backend server (other than the community dev3 server) and proxy requests to it. For example, to run the Vitals app and proxy requests to the O3 QA dev environment’s backend, you can run:

    yarn start --sources packages/esm-patient-vitals-app --backend=https://test3.openmrs.org
  • Specify an alternative target server path (defaults to /openmrs/spa).

  • Specify an alternative API URL (defaults to /openmrs/).

  • Specify a custom import map or routes registry.

To learn more about the develop command, read through its implementation .

Using import map overrides

O3 determines where to load the code for frontend modules from based on the import map provided to the app shell. The import map is a JSON object that maps module specifiers to URLs. You can find the import map for your O3 distro by navigating to /openmrs/spa/importmap.json in your browser. The app shell also reads /openmrs/spa/routes.registry.json to discover each module’s route and extension metadata.

The Devtools  frontend module provides an interface for overriding import map entries. When you override a module URL, Devtools also adds a matching route-map override that points at routes.json next to the local bundle. You can leverage this when doing local development to load copies of your frontend modules from your local dev server. For example, if you fire up a local development server running the app shell, it’s possible to override multiple frontend modules from multiple projects at the same time. This is useful when you’re working on a feature that spans multiple frontend modules.

Import map and route overrides don’t work on the dev3 server because of security restrictions. We’ll work on loosening these restrictions in the future. For now, you can only use these overrides on a local dev server.

Example: Running the Implementer tools and Primary navigation frontend modules against the app shell

Run the app shell

cd openmrs-esm-core yarn run:shell

This should fire up a dev server on port 8080 (assuming you have no other dev server running).

Run the implementer tools app

In a different terminal window, run:

cd openmrs-esm-core yarn start --sources packages/apps/esm-implementer-tools-app --port=7000

This starts the app shell on port 7000 and serves the Implementer tools bundle from the next available port, usually 7001. To see the actual bundled JavaScript code for the app, navigate to http://localhost:7001/openmrs-esm-implementer-tools-app.js  in your browser. This URL is the entry point for the app shell to load the implementer tools app. It’s important that the URL points to the correct location of the bundled JavaScript code. Often, you might want to specify a different port number to ensure that the URL points to the correct location rather than the default port or whatever port the tooling assigns.

Port numbers

By default, yarn start starts the app shell on port 8080 and serves each local module bundle from the next available ports. For example, with the app shell on http://localhost:8080/openmrs/spa, a Form Builder bundle is usually available at http://localhost:8081/openmrs-esm-form-builder-app.js . The name of this URL corresponds to the name used in the browser  entry in the package.json file of the frontend module you’re running. If you pass a custom app shell port such as --port=7000, open the app shell at http://localhost:7000/openmrs/spa and use the next port, usually 7001, for the bundled JavaScript URL.

Note that if you’re running multiple frontend modules simultaneously using yarn start, the app shell will run on port 8080 and the frontend modules will run on the next available ports. That is, the packages will run on port 8081, 8082, and so on, if available. However, if you’re running each frontend module individually with a custom --port, the module will typically be served on the next available port after the one you chose.

Run the primary navigation app

In a different terminal window, run:

cd openmrs-esm-core yarn start --sources packages/apps/esm-primary-navigation-app --port=9000

Consolidate everything

On the dev server running the app shell, log in and navigate to the home page. We want to make sure that import map overrides are enabled. Type this command in the browser devtools console and press enter:

localStorage.setItem('openmrs:devtools', 'true')

Reload the page. You should see a new icon in the bottom right corner of the screen. Click on it to launch the import map overrides panel. You can now override frontend modules in your distribution with locally running copies. Add an override for the Implementer tools app by searching for the app in the Search modules input field. You should see the app in the search results. Type the URL to the bundled JavaScript code for the app in the URL input field.

For the Implementer tools app on port 7000, the URL would be http://localhost:7001/openmrs-esm-implementer-tools-app.js . Click the Apply button to add the override. Next, do the same for the Primary navigation app. The URL for the Primary navigation app on port 9000 would be http://localhost:9001/openmrs-esm-primary-navigation-app.js . Click the Apply button to add the override. If the module has already been loaded, or if you changed its routes.json, reload the page so the app shell reads the new import map and route-map snapshot. After reload, the Devtools icon should turn red to signify that overrides are in place.

You should now see your changes to the Implementer tools and Primary navigation apps propagate to the app shell. You can now test out your changes in the context of the app shell.

Running esm-patient-common-lib locally

The esm-patient-common-lib is a library of common components and utilities used primarily by the patient chart in O3. You might want to make changes to the library and test those changes against a local dev server. To achieve that, you can do the following:

  • Open the package.json file for the frontend module you want to work on. Look for the esm-patient-common-lib dependency in the peerDependencies section. Remove that entry and run yarn to install dependencies.

  • Run your local dev server using yarn start. For example, for the labs app, run:

    yarn start --sources 'packages/esm-patient-labs-app'
  • Try making a change to a library, e.g. adding a console.log to a file such as DashboardExtension.tsx. Your dev server should reload and you should see your console.log printed in the browser console once you navigate to the patient chart.

  • Once you’re done making changes, you can revert the changes you made to the package.json file and run yarn to install dependencies again.

Running the app shell locally

Running individual modules with yarn start is usually enough to get a local dev server running. One notable departure, however, is running the app shell. You’ll need to run the shell directly if you want to:

  • Make changes to the styleguide frontend module and test them out locally.

  • Test out changes to the following applications:

    • devtools - used for setting up import map and route overrides.
    • implementer tools - used for configuring extensions and UI components on the fly.
    • login - the reference application login page.
    • offline tools - the offline tools dashboard.
    • primary navigation - the navigation menu.

To run the app shell, you’ll need to cd into the esm-core monorepo and run:

yarn run:shell

This command will fire up a dev server at http://localhost:8080/openmrs/spa. You can then navigate to the app shell at http://localhost:8080/openmrs/spa/shell. From there, you can navigate to the applications you want to test out.

Because these applications reside in the apps directory of the esm-core monorepo, running them within the context of the app shell makes it a lot easier to test out changes. Something to note when using this approach, however, is that for your changes to propagate to your local server, you might need to rebuild your frontend module. To do this, run:

yarn turbo build

This command should run the build script in all of the constituent frontend modules whose code you’ve changed. Once the build is complete, you should see your changes reflected in the dev server in your browser.

How do I know where to make changes?

There is no simple correlation between frontend modules and pages and content. The first step to figuring out where to make changes is to identify the frontend module you want to work on. For example, if you want to make changes to the patient chart, you’d work on the patient-chart frontend module. If you want to make changes to the patient search, you’d work on the patient-search frontend module. If you want to make changes to the login page, you’d work on the login frontend module. And so on. There are a few ways to figure out which frontend module you need to work on:

  • You can inspect an element in the browser and look at its class name. You can then search through your IDE for that class name. This should point you to the specific bit of code in the frontend module you need to work on.
  • You could search for specific strings in the codebase. For example, if you see the string Vitals history in the UI, you could search for that string in your IDE. This should point you to the frontend module that renders that string (the vitals frontend module, in this case).
  • You could also use the UI editor in the implementer tools to see which extensions and extension slots get rendered in the UI. You can then search for those extensions and extension slots in your IDE and make changes to them. This is a bit more involved, but it’s a good way to get a sense of the structure of the UI and frontend modules it contains.
  • Go through the list of key repositories and their descriptions. There aren’t very many and they’re typically domain-specific, so it’s likely you can narrow down what you’re trying to work on just by looking at the list.

There are no hard and fast rules for figuring out where to make changes. The best approach is to try out different things and see what works for you. The more you work on the codebase, the more familiar you’ll become with it and the easier it’ll be to figure out where to make changes. If you have done all of the things above and still haven’t found the code you’re looking for, feel free to ask in the #openmrs-helpme channel  on Slack.

Working on a ticket (practical flow)

If you’re picking up a ticket and want to get to done quickly, this flow usually works:

  1. Find the right repo/module using the techniques above.
  2. Run the module locally and reproduce the issue or verify the requirement.
  3. Make the change and keep the coding conventions in mind.
  4. Verify locally (UI check + relevant tests).
  5. Update translations if you add or change user‑facing strings.
  6. Open a PR with a clear summary and screenshots if the UI changed.

Common blockers to watch for:

  • Backend access/auth: You’ll need a running OpenMRS backend (dev3 or local). If login fails, confirm you’re using valid credentials for that environment.
  • Import map and route overrides: If a change doesn’t show up, check Devtools overrides and hard refresh so the app shell reads the latest import map and routes registry.
  • Expected URLs: The app shell runs at /openmrs/spa and your local module may be served from a different port.
  • Feature flags: If you’re not seeing a new UI, check whether it’s behind a feature flag in the Implementer Tools’ feature flag menu.

Data fetching and mutations

Most frontend module work eventually touches backend data. Prefer the O3 framework utilities and the local repo’s existing resource hooks:

  • Use openmrsFetch with SWR hooks instead of calling fetch directly. openmrsFetch resolves OpenMRS-relative URLs, sets the usual JSON and OpenMRS REST request headers, parses responses, and handles errors.
  • Use framework base URLs such as restBaseUrl and fhirBaseUrl rather than hard-coding /ws/rest/v1 or FHIR paths throughout components.
  • Keep data access in resource files or custom hooks, then return the SWR state (data, error, isLoading or isValidating, and mutate) to components.
  • After a successful mutation, update the relevant SWR cache with mutate so the UI does not need a full reload.

For examples, see the Retrieve and post data recipe and the Data fetching, Loading states, and Mutations and effects coding conventions.

How can I develop against a restricted environment?

In general, you can develop against another environment using the --backend flag. If the other environment is guarded, e.g., by an IP or network restriction then this is something you need to take care of on your local machine. In case the guarded environment is restricted via some SSO mechanism using a cookie you could use the --add-cookie flag to achieve this. As an example, look at the access for a development server from the ICRC:

yarn start --backend "https://emr-v2.test.icrc.org/" --add-cookie "MRHSession=1234..."

The cookie must be obtained by you and strongly depends on the backend used. Use npx openmrs start with the same flags when you only need to launch the app shell outside a frontend module repo.

Should I write tests?

Yes, absolutely. Current React modules generally use Vitest  and React Testing Library  for component tests, though some older repos may still use Jest . Check the module’s package.json to see which test runner is configured. See the unit and integration testing guide and the end-to-end testing guide for more on how to write tests. Consider adding E2E tests for high-value workflows. Reading nearby tests in the codebase is usually the fastest way to match the repo’s conventions.

Pushing your changes to GitHub

Once you’re done working on a feature or fix, you’ll want to push your changes to GitHub and file a PR. To do this, commit your changes. We have guidelines for writing commit messages in our contributing guide.

Creating a commit should trigger some pre-commit hooks. Typically, part of the work these hooks do involves running yarn run extract-translations. This task fires up turborepo and runs the extract-translations task in each package in the monorepo. It extracts translation keys and strings and updates the default locale’s translations file, usually en.json in the translations directory of your frontend module. Commit any generated translation changes with the code that introduced them. After the pre-commit hooks run, you then want to push your changes to GitHub. Doing so will trigger a pre-push hook that will typically run the following tasks in parallel using turborepo:

  • typescript - checks your application’s types.
  • lint - runs ESLint, usually with warnings treated as failures.
  • test - runs tests (Jest or Vitest, depending on the repo).

If any of these tasks fail, the push should fail. The terminal should display the error message(s) that caused the push to fail. You’ll need to fix the error(s) and push again. Once the push succeeds, you can go to the GitHub repo related to your package and file a pull request.

Maintaining dependencies

Maintaining up-to-date dependencies is important to leverage the latest features, performance improvements and security patches. Most current frontend repos use Yarn 4, but always check the packageManager field in package.json because older repos may differ. Yarn provides an interactive tool to help you keep dependencies up to date. To use it, run the following command:

yarn upgrade-interactive

This command lists the dependencies of your project that are eligible for updates. You can navigate the list using your keyboard’s arrow keys. In general, avoid major version updates unless you are intentionally handling breaking changes. Keep OpenMRS dev tooling dependencies such as openmrs and @openmrs/esm-framework pinned to next in devDependencies, but preserve the supported semver ranges already used in peerDependencies. Everything else should generally be OK to update to the latest compatible version. Once you’ve selected the dependencies you want to update, press Enter, then test the app, run the relevant tests, and make sure the app builds correctly.

Troubleshooting

If you’re working off of a fork, your branch might miss the latest changes from main. Fetch the upstream repository, then update your branch from upstream/main using the repo’s normal flow. For example:

git fetch upstream git merge upstream/main

I’m getting an Error: ENOSPC: System limit for number of file watchers reached error

If you’re running Linux, you may see the following error the first time you run a dev server: Error: ENOSPC: System limit for number of file watchers reached. If that happens, you need to increase the system limit for the number of file watchers. See this StackOverflow answer .

I see errors about missing APIs when I run the app

If your dev server is throwing errors about missing APIs or functions, it’s likely because you’re running outdated versions of the framework or core tooling. This means that new functionality was likely added to Core  that your local dev server doesn’t have. New pre-release versions of the openmrs  CLI and @openmrs/esm-framework  get published each time a pull request is merged in Core. To ensure you have the latest versions of these packages, run:

yarn up openmrs@next @openmrs/esm-framework@next

This command might change the versions specified in your manifest file (package.json). If it does, keep OpenMRS dev tooling dependencies such as openmrs and @openmrs/esm-framework pinned to next before you commit. Peer dependency ranges should stay aligned with the ranges already used by the repo. You might need to run yarn after adjusting the manifest so the lockfile reflects the newest published packages. Be sure to review the package.json diff before committing. If the only manifest changes are temporary next resolutions, some contributors use an alias like this:

alias bump-core-tooling="yarn up openmrs@next @openmrs/esm-framework@next && git restore package.json && yarn"

Only use that alias when you have no intentional package.json edits to preserve.

I’m getting a ERR_OSSL_EVP_UNSUPPORTED error when running yarn run build

If you’re running or building the form-entry package, an Angular wrapper around the AMPATH Form Engine, with a Node/OpenSSL combination that rejects legacy hash algorithms, you may see the following error:

Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:71:19) at Object.createHash (node:crypto:133:10) at BulkUpdateDecorator.hashFactory (/path/to/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/util/createHash.js:145:18) at BulkUpdateDecorator.update (/path/to/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/util/createHash.js:46:50) at RawSource.updateHash (/path/to/openmrs-esm-patient-chart/node_modules/webpack-sources/lib/RawSource.js:77:8) at NormalModule._initBuildHash (/path/to/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:880:17) at handleParseResult (/path/to/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:946:10) at /path/to/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:1040:4 at processResult (/path/to/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:755:11) at /path/to/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:819:5 { opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ], library: 'digital envelope routines', reason: 'unsupported', code: 'ERR_OSSL_EVP_UNSUPPORTED' }

To get around this, run:

export NODE_OPTIONS=--openssl-legacy-provider

I’m getting errors after upgrading @carbon/react

You might see the following errors in your terminal when running a local dev server after upgrading @carbon/react:

ERROR in src/components/search-history/search-history.component.tsx:8:3 TS2305: Module '"@carbon/react"' has no exported member 'ModalHeader'. ERROR in src/components/search-history/search-history.component.tsx:9:3 TS2305: Module '"@carbon/react"' has no exported member 'Pagination'.

Do not add a blanket declare module "@carbon/react"; workaround. Modern @carbon/react ships its own types, and a blanket ambient declaration can hide real type errors. Check the installed Carbon version, review the Carbon release notes for renamed or removed exports, update imports to the supported component names, and reinstall dependencies so TypeScript resolves the package’s bundled types.

I’m getting merge conflicts in my yarn.lock file

If the conflict is only in the generated yarn.lock file and your package manifests are correct, regenerate the lockfile from your branch:

git restore --source=HEAD -- yarn.lock yarn

Do not use this if you intentionally need to preserve incoming lockfile changes without regenerating them.

The verify job is failing on GitHub Actions for my PR

You likely have a lint or typescript error in your code. ESLint is setup to fail when it flags warnings. Run yarn turbo lint locally to see the specific lint error in your IDE. For TypeScript errors, you should see the error in the CI log. If not, run yarn turbo typescript locally.

I’m getting 502 errors from attempting to fetch the session when running a local dev server

If you’re running a local dev server and you’re getting 502 errors from attempting to fetch the session, that’s likely because the dev3 server (which your local dev server proxies to) is down. Check that you can log in to dev3  first. If you cannot log in, then you’ll need to wait for the server to come back up.

I’m using @openmrs/esm-patient-common-lib and getting “No workspace named ’…’ has been registered”

If you’re getting this error, you most likely need to add @openmrs/esm-patient-common-lib to the peerDependencies list in your package.json. Also, make sure that you have @openmrs/esm-patient-common-lib: next listed under your devDependencies.

I’m getting an Oops! An unhandled promise rejection occurred error when loading my dev server

If you see an Unhandled promise rejection error when running a dev server or a production instance of O3, it likely means that you have a module that is not getting loaded correctly. The devtools console in your browser will likely contain an error message about the module that is not getting loaded. Typically, you’ll want to start by checking that you don’t have any stale import map or route overrides set up in Devtools. To fix the error, you will likely need to open the implementer tools panel and click the Reset all overrides button. Once you’ve done that, reload the page. The error should go away. Alternatively, you could also look through your browser’s local storage and delete any overrides that you find there. Remember to reload the page after doing so.

I’m running into memory issues when building Core

When testing changes to Core by running yarn run:shell from the esm-core directory, you’ll typically want to run yarn turbo build to ensure that you are running the latest changes. You might run into errors like the following when running the frontend build:

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

To increase the memory limit for the Node.js process, run:

export NODE_OPTIONS=--max-old-space-size=8192

This command increases the memory limit for the Node.js process to 8GB. Note that increasing the memory limit can impact system performance. You should only increase the memory limit when necessary.

Alternatively, cleaning generated files can help. Use this as a last resort and preview what would be removed first:

git clean -fdxn

The actual cleanup command removes untracked and ignored files and directories from your working directory, including build artifacts, node_modules directories, and other temporary files that can sometimes cause build issues. It does not reset tracked files, but it can still delete useful local-only files.

git clean -fdx

Only run git clean -fdx after confirming the dry run does not include anything important.

Last updated on