A typed client for Epic's Active Guidelines (AGL) platform. Call do() to run
actions, listen for events, and read session details — the handshake, tokens,
and message plumbing are handled for you.
It handles:
- Detecting whether the app is running inside Epic
- Communicating with the Epic backend
- Triggering and queuing actions in Hyperspace
- Subscribing to and handling AGL events, including print requests
- App state and navigation history
- Clean teardown of the persistent message listener
- Getting Started
- Configuration
- Usage
- Parameters
- Methods
- Queuing
- Error Handling
- Subscriptions
- State Management
- Navigation History
- Teardown
- Migrating from 1.x
AGL.js is not affiliated with or endorsed by Epic Systems Corporation. It is
intended solely for use by organizations with active Epic licenses. Please
ensure compliance with your Epic agreements before using or distributing this
library.
Install via npm or yarn:
npm install agljs
# or
yarn add agljsimport AGL from 'agljs'
const agl = new AGL()
if (await agl.active) console.log(agl.details.availableActions)import AGL from 'agljs'
const agl = new AGL()
if (await agl.active) console.log(agl.details.availableActions)const AGL = require('agljs')
const agl = new AGL()
agl.active.then(active => active && console.log(agl.details.availableActions))Download agl.min.js from the
releases page, or load it from a CDN such as
jsDelivr:
The script tag exposes the class as the global AGL:
<!-- local: <script src="path/to/agl.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/agljs/js/agl.min.js"></script>
<script>
const agl = new AGL();
agl.active.then(function(active) {
if (active) console.log(agl.details.availableActions);
});
</script>The AGL constructor accepts an optional configuration object with the
following optional parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
debug |
boolean |
Enables debug logging for troubleshooting | false |
timeout |
number |
Timeout (in milliseconds) before failure for action responses | 2000 |
targetOrigin |
string |
Restricts inbound/outbound messages to a specific Epic origin | '*' |
subscribe |
object |
An object containing events to subscribe to during the handshake | {} |
onError |
function |
Callback for handling errors | null |
onNavigate |
function |
Callback for navigation events (e.g., back/forward) | null |
onReload |
function |
Callback for reload (history package) events | null |
onAGLEvent |
function |
Callback for custom AGL events | null |
onSubscribed |
function |
Callback for handling subscription results | null |
onPrint |
function |
Callback fired when Epic requests a print | null |
Debugging tip: set
debug: truein the constructor as your first step. Because the handshake runs at construction, only a constructor-timedebugflag captures it — this is the most useful log for diagnosing initialization.
Note:
subscribeandtargetOriginare fixed at first construction. Re-constructingAGLupdates onlydebug,timeout, and callbacks.
const agl = new AGL({
debug: true,
timeout: 5000,
subscribe: {
"Epic.Common.RequestToCloseApp": { PauseDuration: 300 },
},
onError: (error) => console.error('Error:', error),
onNavigate: (event) => console.log('Navigation event:', event.direction),
onReload: (payload) => console.log('Reload payload:', payload),
onPrint: () => window.print(),
});All actions must wait for the AGL handshake to complete and confirm the app is
running inside Epic, via if (await agl.active).
Example:
const agl = new AGL();
if (await agl.active) {
agl.do('Epic.Clinical.Informatics.Web.SaveState', { state: 'example' })
.then((success) => console.log('State saved:', success))
.catch((err) => console.error('Error saving state:', err));
}Example:
const agl = new AGL();
if (!await agl.active) throw new Error('AGL is inactive! Not running in Epic.');
agl.on('navigate', (event) => {
console.log('User navigated:', event.direction);
});Always returns a Promise<boolean> that resolves to whether AGL is active and
initialized. await it before performing actions:
if (await agl.active) console.log('AGL is initialized and ready.');Provides information from Epic about the current AGL context. Properties include:
| Property | Type | Description |
|---|---|---|
availableActions |
string[] |
List of actions supported in the current context |
actionsNotRequiringUserConfirmation |
string[] |
Actions Epic will run without a confirmation prompt |
currentState |
string | null |
State restored from the handshake (see below) |
interfaceVersion |
string | null |
Version of the AGL JavaScript interface |
readOnly |
boolean |
Indicates if the AGL context is read-only |
token |
string | null |
Token required for posting messages to Epic |
Example:
console.log('Token:', agl.details.token);
console.log('Available actions:', agl.details.availableActions.join(', '));Enables or disables debug logging dynamically
Example:
agl.debug = true; // Enable debug loggingExecutes an action within AGL.
| Parameter | Type | Description |
|---|---|---|
action |
string |
The name of the action to perform |
args |
object |
Optional Arguments to pass with the action |
haltOnError |
boolean |
Optional Stops the queue if this action fails, preventing subsequent actions from running |
do() automatically waits for the handshake to finish before running, so you
do not need to guard every call with await agl.active. It resolves false if
AGL never initializes (e.g. not running in Epic).
Example: Basic Action
agl.do('SaveState', { state: 'example' });For convenience, actions without a full namespace (i.e., actions without a
dot) are automatically prefixed with Epic.Clinical.Informatics.Web.
To override this behavior, include the full namespace directly:
Example: Full Namespace Action
if (await agl.active) {
agl.do('Custom.Namespace.Action', { someArg: 'value' });
}Registers a callback for specific AGL events.
| Parameter | Type | Description |
|---|---|---|
eventName |
string |
The name of the event to listen for |
callback |
function |
The callback function to execute when the event occurs |
| Event Name | Example Response |
|---|---|
error |
{ message: 'Error description', details: ['Detail 1', ...] } |
navigate |
{ direction: 'Back' } |
reload |
The raw historyPackage payload from Epic (see note below) |
aglEvent |
{ name: 'Event name', args: { arg1: ... } } |
subscribed |
{ EventName: 'Event name', SubscriptionSuccess: boolean } |
print |
(no arguments) — fired when Epic requests a print |
reloadpayload: Epic delivers the navigation-history package underhistoryPackage. Its inner shape is passed through unchanged as aRecord<string, unknown>; inspect it against your Epic build (enabledebug) rather than assuming key names. This is distinct from state restoration — see State Management.
Example:
agl.on('navigate', (event) => {
console.log('User navigated:', event.direction);
});Example: Chaining listeners
agl
.on('navigate', (event) => console.log('User navigated:', event.direction))
.on('reload', (payload) => console.log('History package:', payload));Actions are added to a queue and executed sequentially to avoid race
conditions.
By default, the queue continues processing even if an action fails.
However, setting haltOnError in do to true stops the queue when that
action fails, ensuring dependent actions are not executed. A timeout is treated
as a lost connection: it tears the session down entirely (see
Teardown).
Example:
agl.do('SaveState', { state: 'example' }, true) // Stop queue if SaveState fails
.catch((error) => console.error('SaveState failed:', error));
agl.do('CloseActivity', null) // Will only execute if SaveState succeeds
.catch((error) => console.error('CloseActivity failed:', error));Without an error handler, a failed action rejects with an Error. With a
handler, the callback receives the structured error and the action resolves
false. If the handler throws, the action rejects with that error.
You can override internal error handling by providing a custom onError
callback in the configuration.
Example: During instantiation
const agl = new AGL({
onError: (error) => {
console.error('Custom error handler:', error.message);
},
});Example: After instantiation
agl.on('error', (error) => {
console.error('Custom error handler:', error.message);
});You can specify subscriptions during initialization using the subscribe
property. Subscriptions allow your application to register for specific events,
such as changes in patient demographics or requests to close the app.
To confirm that each subscription was successfully processed during the
handshake, you can define an onSubscribed callback. This callback receives
the subscription results, allowing you to verify whether your subscriptions
were accepted by Epic.
Example:
const agl = new AGL({
subscribe: {
"Epic.Patient.Demographics.Updated": { IncludeHistory: true },
"Epic.Common.RequestToCloseApp": { PauseDuration: 200 }
},
onSubscribed: (results) => {
console.log('Subscription handshake completed:', results);
},
});After subscriptions are successfully established, your application can handle the events triggered by those subscriptions using the aglEvent handler.
Example:
agl.on('aglEvent', (event) => {
if (event.name === 'Epic.Patient.Demographics.Updated') {
console.log('Patient demographics updated:', event.args);
}
if (event.name === 'Epic.Common.RequestToCloseApp') {
agl.do('Epic.Common.CloseApp', {
CanClose: true
});
}
});AGL.js supports saving and restoring app state during transitions or
hibernation.
To save the current state of your application, use the SaveState action.
Example: Simple string
agl.do('SaveState', { state: 'dashboard' });Example: Complex state
agl.do('SaveState', {
state: JSON.stringify({ tab: 'overview', filters: { active: true } })
});Epic returns the previously saved state on the handshake, exposed as
details.currentState. Read it once AGL is active — it is null when no state
was saved:
Example:
const agl = new AGL();
if (await agl.active && agl.details.currentState) {
const restored = JSON.parse(agl.details.currentState);
console.log('Restored state:', restored);
activatePage(restored.tab, restored.filters);
}State restoration (
SaveState→details.currentState) is separate from thereloadevent, which carries Epic's navigation-history package. Do not usereloadto restore saved state.
The library supports managing navigation history, tracking user interactions with Back/Forward buttons, and maintaining app-specific navigation states.
Use onNavigate to track Back/Forward button clicks.
Example:
agl.on('navigate', ({ direction }) => {
console.log(`Navigated: ${direction}`);
// Handle navigation here
});Save custom navigation states using SaveHistoryState. AGL.js will persist
these states for the current session.
Example:
agl.do('Epic.Clinical.Informatics.Web.SaveHistoryState', {
state: JSON.stringify({ tab: 'dashboard', filters: { active: true } })
});Disable Back/Forward buttons in unsupported contexts.
Example:
agl.do('Epic.Clinical.Informatics.Web.SetEnabledHistBtns', {
Back: false,
Forward: false
});AGL.js installs one persistent message listener for the session. Call
destroy() to remove it, settle any outstanding actions as false, and
release the singleton. A later new AGL() then performs a fresh handshake.
Example:
agl.destroy();The instance is also disposable, so using cleans it up automatically:
{
using agl = new AGL();
await agl.active;
await agl.do('SaveState', { state: 'example' });
} // destroy() runs here| Change | 1.x | 2.0 |
|---|---|---|
active |
Returned boolean after init |
Always returns Promise<boolean> — always await it |
reload payload |
{ state, fromHibernation } (unverified) |
Raw historyPackage object |
| State restore | Documented via reload |
Read details.currentState after await agl.active |
| CommonJS | require('agljs').default |
require('agljs') |
| Script-tag global | window.agl |
window.AGL |
| (none) | onPrint / on('print', ...) |
|
| Teardown | (none) | destroy() / [Symbol.dispose] |
| Runtime | ES2022 | ES2024 with Symbol.dispose |
2.0 targets ES2024 and requires Symbol.dispose (modern Chromium/Edge).
Fork, branch, and open a pull request.
Kopimi. Free to use, modify, and share. If the code is used, borrowed, modified, or incorporated into a commercial product or service, attribution is required.