HL7 Tables Guide

HL7 tables are easy to underestimate because they look like small lookup lists. In real interfaces, they are where a lot of meaning lives. A single value in a table can decide whether an ADT message is an admit, transfer, discharge, cancellation, merge, order update, result status, document event, appointment change, billing classification, route of administration, specimen condition, or ACK outcome.

This page is not another list of every table. The searchable list on the left already does that better, and the fuller all tables list is there when you want definitions, table type, and the HL7 Soup ${HL7:####} shortcut for tables that exist in the built-in HL7 table set. This is the practical guide: how to read a table, when to trust the standard values, when local values are normal, and what to document before a sender and receiver start pretending they agree.

HL7 tables are vocabulary, not database tables

An HL7 table is a named set of codes used by a field or datatype component. Sometimes the code is tiny, such as F in table 0001 Administrative Sex. Sometimes the table is a large catalogue of message events, such as table 0003 Event Type. Sometimes the table is not a list of values at all in your implementation, but a pointer to a local master file, external terminology, or site-maintained set of codes.

The mistake is treating the table number as the whole answer. The table number tells you where the vocabulary comes from. The field context tells you what the value means. F in a sex field, F in a status field, and F as a local result flag are not interchangeable just because the visible code is one character long.

The standard-side ownership split is reflected in the table detail pages: user-defined tables are local or site-defined, HL7-defined tables affect message interpretation, external tables come from other terminology owners, and obsolete tables should be treated with caution. Vocabulary profiling is the act of narrowing the allowed values for coded data elements such as ID, IS, CE, CWE, and CNE. In plain interface work, that means this: do not just say "uses table 0123"; say exactly which values are allowed, which code system they belong to, and what the receiver will do with each one.

Integration lookup tables are mappings, not HL7 tables

It is easy to blur the names because everyone says "table" when they are tired. An HL7 table is a vocabulary: it defines or points to the values a field can carry. In HL7 Soup, built-in HL7 tables can be referenced with the shorter ${HL7:####} form, such as ${HL7:0001}. Generic data tables are a broader feature and use the separate ${DataTable:path.field} style when you need a lookup data source for test values, sample generation, or anonymized data.

An Integration Soup lookup table is different again: it is a mapping asset used to convert one representation into another. For example, a lookup table might convert a sender's male into the receiving system's M, or convert an incoming F into the display value female. That mapping may be informed by HL7 table knowledge, generic data-table rows, or a receiver contract, but the lookup table itself is not the HL7 table. Its job is transformation: source value in, agreed target value out, with the mapping kept visible enough that support can explain why a code changed.

The four table situations you keep meeting

Situation What it means in practice Implementation advice
HL7-defined The values are part of the standard, because changing their meaning changes message interpretation. Examples include 0003 Event Type, 0076 Message Type, and many status-style tables. Do not redefine a standard code locally. If you need local behavior, document a local extension and make sure both sides know it is local.
User-defined The standard gives a table number, but the actual values are often site, region, profile, or application specific. Some user-defined tables include suggested values. Expect local variation. The interface agreement must list the real values, not just the HL7 table number.
External The code system is maintained outside HL7, such as LOINC, SNOMED CT, ICD, CVX, UCUM, or a national code set. Send the coding system and version where the datatype supports it. Do not silently translate external codes into local labels unless the mapping is governed.
Local/private The code only has meaning inside a trading partner agreement, an application, a facility, or an interface engine mapping. Name it honestly. Use a local coding-system identifier, keep the mapping table under change control, and avoid pretending it is a standard vocabulary.

The datatype changes how you should read the table

The table and the datatype work together. If you only look at the table number, you miss half the contract.

ID is the classic "coded value for an HL7-defined table" datatype. It usually means the value should be drawn from a legal set and there is not room for a coding system component beside it. That is why an ID field is unforgiving: if the receiver does not recognize the code, there is nowhere else in the field to explain what you meant.

IS is the old "coded value for user-defined tables" datatype. In real feeds it often carries things like local location codes, patient class values, financial class values, source codes, and workflow codes. Treat IS fields as implementation territory. They can be perfectly valid HL7 while still being completely unknown to the receiver until you map them.

CE, CWE, and CNE are safer for rich coded content because they can carry the identifier, display text, coding system, alternate coding, and in later forms coding-system versions. That matters for lab tests, observations, diagnoses, procedures, organisms, medication codes, vaccine codes, document types, and any value where "the code" is only meaningful when you know which vocabulary produced it.

Table 0396 Coding System is the bridge people forget. It is where values such as LN for LOINC, SCT for SNOMED CT, RXNORM, CVX, UCUM, and HL7nnnn are identified in many v2-coded fields. If you see a CE/CWE value like 718-7^Hemoglobin^LN, the table for the field is not enough; the coding-system component tells you the code belongs to LOINC.

Message type, event type, and structure are related but not the same

The tables that cause the most confusion are the message tables because they sit right in MSH. In a modern message, MSH-9 Message Type is normally a composite: message code, trigger event, and message structure. Those pieces are related, but they answer different questions.

Part Example What it answers
0076 Message Type ADT, ORM, ORU, SIU, MDM What family of message is this?
0003 Event Type A01, A08, R01, S12, T02 What business event caused the message?
Message structure ADT_A01, ORU_R01, SIU_S12 Which segment layout should the receiver parse?

The event is not just decoration. ADT^A01^ADT_A01 and ADT^A08^ADT_A01 may share a structure, but they do not mean the same operational thing. A01 is an admit or visit notification; A08 is an update. A receiver may route them differently, update different audit trails, or reject one if the patient state does not make sense. That is why local Z events need care. They are sometimes unavoidable, but they are also a promise that every receiver in the chain has been taught what that event means.

Best practices that save pain

  • Document the allowed values beside the field. Do not leave implementers to chase table numbers through a PDF when the interface only allows six of the possible values.
  • Preserve the code, not just the description. Descriptions change, are translated, or get shortened in application screens. The code is what the receiving logic usually branches on.
  • Do not reuse standard codes for local meanings. If F means final in one standard table, do not make it mean "flagged for follow-up" in a local table without a distinct coding-system context.
  • Keep local extensions obvious. Local event codes, local location codes, local result flags, and local order statuses should be named, documented, and versioned.
  • Validate at the boundary. Catch unsupported codes before they reach the clinical or billing application. A clean ACK with a useful ERR segment is better than a silent mapping failure.
  • Know which values are workflow decisions. Event type, order control, order status, result status, patient class, appointment status, document status, and acknowledgment code often drive behavior. Treat them as logic, not labels.
  • Do not flatten external terminologies into HL7 table numbers. LOINC, SNOMED CT, ICD, RxNorm, CVX, UCUM, and local catalogues need their coding-system identity to travel with the code when the datatype supports it.

A practical mapping workflow

When I am building or reviewing an interface, I usually start with the fields that have tables and ask four questions:

  1. Is this table HL7-defined, user-defined, external, or local?
  2. Does the datatype give me only a code, or does it also carry text and coding-system detail?
  3. Which values will the receiver actually accept, and what does each one do?
  4. What should happen when the sender sends an unknown, deprecated, blank, or locally extended value?

That last question is where many interfaces become expensive. If the receiver silently maps an unknown order status to "active", or treats an unknown result status as final, you may not discover the problem until someone is relying on bad workflow state. If the receiver rejects with an ACK and a useful ERR, support can fix the source or mapping quickly.

In HL7 Soup Web, open a representative message and inspect the fields that carry table-driven values: MSH-9, PV1-2, ORC-1, OBR-25, OBX-11, MSA-1, and any local fields your profile depends on. In Integration Soup, keep those mappings explicit: one table-driven decision per mapping rule, with unsupported values handled deliberately rather than falling through to a default.

Common traps

Trap Why it hurts Better habit
Using descriptions as keys Descriptions vary by version, vendor, implementation guide, language, and screen label. Route and map from the code plus coding system.
Assuming user-defined means optional A user-defined table can still be mandatory in a local profile or receiver contract. Treat "user-defined" as "must be defined by this implementation".
Inventing local values in standard tables Receivers may parse the message as standard HL7 and make the wrong workflow decision. Use agreed local extension values and document them in the interface spec.
Dropping alternate coding External vocabularies often need local and standard codes side by side during migration. Use CE/CWE alternate components when both sides need both identities.
Letting table mappings drift Codes added in a source system can start arriving before the receiver is ready. Version table mappings, test new codes, and monitor ACK errors after releases.

What to do next

Pick one real interface and make a short table-value inventory. Start with message type and event type, then work through the fields whose values drive routing, patient state, order state, result state, document state, appointment state, billing behavior, or validation. For each field, write down the table number, datatype, allowed values, coding system, owner, local extensions, and error behavior.

That small inventory is often more useful than a giant mapping spreadsheet, because it tells you which codes are harmless labels and which ones are operational switches. The searchable table pages and the all tables list can help you inspect the standard side, but the real interface is finished only when the sender's values, receiver behavior, and support process all agree.

More Integration Soup HL7 reference articles