The moment a team writes a custom FHIR validator or a domain-specific rule engine, the question comes up: how should the diagnostic output be shaped? The answer is almost always OperationOutcome — it is the spec's mechanism for reporting issues on any FHIR operation, and consuming clients already know how to read it. The site's OperationOutcome reader will accept your custom emissions the same as the canonical ones. For the wider FHIR framing, deeper FHIR walkthroughs has more.
Pick The Right Severity
fatal— the operation cannot proceed at allerror— a specific issue prevents the operation from succeeding for this entrywarning— the operation succeeded but here is something worth attentioninformation— the operation succeeded and here is context
Over-severity is worse than under-severity. A warning that surfaces as an error stops workloads that should have proceeded. A fatal that should have been an error creates unnecessary rollback. Match the severity to the actual behavior.
Reuse The Standard code Vocabulary
The standard vocabulary — structure, required, invalid, value, invariant, security, duplicate, not-supported, and the rest — covers the vast majority of custom cases. Reuse them. Consuming clients already dispatch on them; introducing a new code adds work everywhere.
For the enumeration and their intent, issue.code vocabulary and what it actually means is the reference.
Only Add Codes When The Standard Set Falls Short
Sometimes the domain rule does not fit any standard code. That is fine. Use business-rule as the closest match, and put the specific reason in details.coding with your own coding system. That way clients that recognise your codings can dispatch precisely, and clients that do not still fall back to the generic handling.
Do not introduce a new top-level code value. That path is closed by the spec; extending it breaks conformance.
Emit `expression` Always
Every custom outcome should carry an expression pointing at the offending element. FHIRPath is the language, dot-path form is the convention. Emit location too if you have older consumers, but do not skip expression.
For the mechanic, location vs expression: pointing at the offending element covers the split.
Diagnostics: Short, Actionable, Stable
- Short — one or two sentences
- Actionable — tells the reader what to do, not just what happened
- Stable — the same issue produces the same diagnostics string across runs
Long, verbose diagnostics are worse than short, precise ones. Stability matters because support teams string-match on diagnostics for triage, and drifting text breaks their tooling.
`details.text` For The Human Summary
details.text is the shorter human summary. Some UIs display it directly instead of diagnostics. Keep it brief — a single line — and consistent with the diagnostics narrative.
details.coding is where the stable machine-readable extension goes if you have one. Consumers that recognise the coding can dispatch on it; consumers that do not fall back to the base code + severity.
One Issue Per Distinct Problem
Do not aggregate multiple issues into one issue entry. If a resource has three missing required fields, emit three issue entries with three different expressions. Consumers can render them separately, count them, and route them individually.
Aggregation into diagnostic prose is a common shortcut that loses the per-issue routing. Do not take it.
Include `id` For Correlation
Every OperationOutcome can carry a Resource.id. For custom validators, generating a unique id per outcome makes correlation across log entries much easier. Support teams appreciate it. For the pattern, OperationOutcome patterns that help your support team is the deep dive.
The Short Version
Reuse standard severity and code. Add codes only in details.coding. Emit expression. Keep diagnostics short and stable. One issue per problem. Include an id for correlation. Custom OperationOutcomes that follow those rules are drop-in for any consumer.

Sources
- HL7 canonical OperationOutcome specification covering all - HL7 canonical OperationOutcome specification covering all issue fields