The Mirth Connect JavaScript transformer is where message data is read, transformed, and routed. It runs on Mozilla Rhino — a Java-native JavaScript engine — and gives you access to the parsed HL7 message via the msg E4X object, cross-destination variables via channelMap, and a persistent cache via globalMap. This guide covers every object, pattern, and pitfall. For channel architecture context, see our channel design patterns guide.
What JavaScript Engine Does Mirth Connect Use?
Mirth runs Mozilla Rhino — not Node.js, not V8. In Mirth 4.x, Rhino supports ES5 with selective ES6 additions: const, let, and arrow functions work. Promise, async/await, and fetch() do not. The most important Rhino-specific feature is E4X (ECMAScript for XML) — a Rhino extension enabling dot-notation XML access. All the msg['PID']['PID.3'] patterns in Mirth code are E4X, not standard JavaScript. This syntax does not exist in Node.js and will not run outside Rhino.
The .toString() Rule: The Most Common Mirth Bug
E4X field access returns a Java String object, not a JavaScript primitive. The strict equality operator === checks both value and type — so Java String and JavaScript string never match, even with identical content. Always call .toString() before any comparison, string operation, or logic check:
// WRONG — Java String vs JS string type mismatch, always false
if (msg['PID']['PID.3']['CX.1'] === '12345') { }
// CORRECT — .toString() converts to JavaScript primitive
var mrn = msg['PID']['PID.3']['CX.1'].toString();
if (mrn === '12345') { } // works correctly
// Safe accessor — returns '' instead of E4X node on absent field
function safeGet(xmlNode) {
try { return xmlNode.toString(); } catch(e) { return ''; }
}The safeGet() function prevents the runtime exception that occurs when .toString() is called on an absent E4X node — the most common cause of the errors listed in our top 10 Mirth failures guide.
Reading HL7 v2 Messages: Key Field Access Patterns
// Patient demographics
var lastName = msg['PID']['PID.5']['XPN.1']['FN.1'].toString();
var firstName = msg['PID']['PID.5']['XPN.2'].toString();
var dob = msg['PID']['PID.7'].toString();
var patClass = msg['PV1']['PV1.2'].toString(); // I=Inpatient, O=Outpatient
var attending = msg['PV1']['PV1.7']['XCN.1'].toString();
// Iterate repeating PID.3 (multiple MRNs)
for each (var pid3 in msg['PID']['PID.3']) {
var mrn = pid3['CX.1'].toString();
var oid = pid3['CX.4']['HD.2'].toString();
channelMap.put('mrn_' + oid, mrn);
}
// Iterate OBX lab result segments
for each (var obx in msg['OBX']) {
var loincCode = obx['OBX.3']['CWE.1'].toString();
var value = obx['OBX.5'].toString();
var abnFlag = obx['OBX.8'].toString();
}For comprehensive OBX iteration including critical value detection, see our ORU lab results routing guide.
Context Objects: All Five Explained
msg — The parsed incoming message as E4X XML (HL7 v2) or XML object (C-CDA/FHIR). channelMap — Per-message variables that persist from source transformer through all destination transformers, then clear. Use for routing decisions and extracted patient identifiers. globalMap — Channel-lifetime persistent cache. Load lookup tables once at channel startup (in the Deploy script), read in every transformer. This is the most impactful performance optimization available — eliminates per-message database queries for static data. connectorMessage — Raw message bytes (getRawData()) and connector metadata. Use for audit log entries that require the original input. responseMap — Control outbound ACK/NAK for MLLP channels: responseMap.put('RESPONSE', ResponseFactory.getSuccessResponse('AA')).
Code Templates: Shared Function Libraries
Code templates are Mirth's mechanism for sharing JavaScript utility functions across all channels without copy-pasting. Functions defined in a code template are automatically available in every transformer in every channel. Organize by single responsibility — CI/CD deployments with MirthSync version-control each template separately:
// Code Template: DateUtils
function hl7DateToISO(hl7date) {
var d = hl7date.toString().replace(/[^0-9]/g, '');
if (d.length < 8) return null;
var iso = d.substring(0,4) + '-' + d.substring(4,6) + '-' + d.substring(6,8);
if (d.length >= 12)
iso += 'T' + d.substring(8,10) + ':' + d.substring(10,12) + ':00Z';
return iso;
}
// Code Template: FHIRBuilder
function buildFHIRPatient(mrn, oidRoot, lastName, firstName, dob, gender) {
return {
resourceType: 'Patient',
identifier: [{ system: 'urn:oid:' + oidRoot, value: mrn }],
name: [{ family: lastName, given: [firstName] }],
birthDate: hl7DateToISO(dob).substring(0,10),
gender: gender === 'M' ? 'male' : gender === 'F' ? 'female' : 'unknown'
};
}Database Lookups: Safe Pattern With Connection Pooling
function lookupLoincCode(localCode, facility) {
var cacheKey = facility + ':' + localCode;
var cached = globalMap.get('loinc_' + cacheKey);
if (cached) return cached; // Serve from cache
var db = DatabaseConnectionFactory.createDatabaseConnection(
'org.postgresql.Driver', 'jdbc:postgresql://db:5432/mirth_ref', 'user', 'pass'
);
try {
var rs = db.executeCachedQuery(
"SELECT loinc_code FROM loinc_map WHERE local_code = '" +
localCode.replace("'","''") + "' AND facility = '" + facility + "'"
);
if (rs.next()) {
var loinc = rs.getString('loinc_code');
globalMap.put('loinc_' + cacheKey, loinc);
return loinc;
}
} finally {
db.close(); // ALWAYS in finally — never rely on GC
}
return localCode;
}The finally block is not optional — unclosed JDBC connections accumulate under load and cause channel failures. For high-volume channels, pre-load all lookup data into globalMap at startup rather than querying per message. Full caching strategies are in our Mirth performance tuning guide.
Structured Error Handling Pattern
try {
var mrn = msg['PID']['PID.3']['CX.1'].toString();
if (!mrn || mrn.trim() === '')
throw new Error('Required PID.3 (MRN) is empty');
channelMap.put('mrn', mrn);
channelMap.put('processing_status', 'ok');
logger.info('Processing MRN: ' + mrn);
} catch (e) {
logger.error('Transform failed: ' + e.message);
channelMap.put('processing_status', 'error');
channelMap.put('error_message', e.message);
}Destination filters check channelMap.get('processing_status') !== 'error' before routing. Error-flagged messages route to a dedicated error destination that writes to a monitoring queue. See our production monitoring guide for the complete error queue setup.



