The Problem
The extension displayExpression system currently evaluates JavaScript expressions to control extension visibility. However, the available expression context is limited to:
-
session — user, roles, privileges, sessionLocation
-
slotState — custom state passed via
ExtensionSlotprops
This limitation means that location tags, visit type, and encounter context cannot be accessed directly within expressions. Instead, they must be manually passed through every <ExtensionSlot state={{...}}> mounting point across the codebase, leading to significant prop-drilling.
Examples of currently unsupported expressions
"displayExpression": "visit.visitType.display === 'Inpatient'"
"displayExpression": "isLocationTagged('ICU')"
"displayExpression": "encounter.encounterType === 'ANC'"
This limitation represents a structural blocker for the GSoC 2026 project:
“Dynamic EHR: Custom screens based on user roles, locations, and other values.”
What I Have Already Built
To address related gaps, I have implemented the following groundwork:
-
(feat) O3-5522 — Added
hasRole()andhasPrivilege()helpers to the expression context (feat) O3-5522: Add hasRole and hasPrivilege helpers to extension display expression context by sohamdhande · Pull Request #1682 · openmrs/openmrs-esm-core · GitHub -
(fix) O3-5523 — Introduced deep equality check on
ExtensionSlotstate prop to prevent cascading re-renders (fix) O3-5523: Add deep equality check on ExtensionSlot state prop to prevent cascading re-renders by sohamdhande · Pull Request #1687 · openmrs/openmrs-esm-core · GitHub -
(chore) O3-5524 — Memoized AST parsing in the expression evaluator for performance improvements (chore) O3-5524: Memoize AST parsing in extension display expression evaluator by sohamdhande · Pull Request #1691 · openmrs/openmrs-esm-core · GitHub
Proposed Solution
I propose introducing a GlobalExpressionContextStore using a lightweight Zustand store that allows applications to write shared runtime context:
setExpressionContext('visit', currentVisit);
setExpressionContext('location', sessionLocation);
setExpressionContext('encounter', currentEncounter);
The evaluator in getAssignedExtensionsFromSlotData() would automatically merge this global context with:
-
existing session
-
existing slotState
This eliminates the need for prop-drilling.
Resulting capability
Implementers could then write expressions like:
"displayExpression": "visit.visitType.display === 'Inpatient'"
"displayExpression": "isLocationTagged('ICU')"
"displayExpression": "hasRole('Nurse') && visit != null"
Questions for Mentors
-
Architecture
Is a global context store the right pattern, or is there a preferred approach for sharing runtime context across the extension system?
-
Placement
Should this implementation live in:
-
esm-extensions, or -
esm-react-utils?
-
-
Security Considerations
Are there any security or sandboxing concerns with exposing visit/encounter data to arbitrary expressions?
-
Existing Patterns
Is there an existing global state pattern in the codebase that applications can write to and that I should align with?
cc: @jayasanka , @dkayiwa , @dennis , @ibacher , @bawanthathilan