Add value-sets, dimensions, types and conversions
Before a node can declare a coded, quantity or object port, the terminology,
dimension or datatype it points at has to exist. This recipe walks the four
vocabulary contribution points that back those ports — value-sets, dimensions,
types and conversions — using one consistent example: an estimated GFR result.
Everything below lives under contributes: in your plugin.yaml. For the full field
list and semantics of every section see the plugin manifest reference;
for the shape of the type: blocks see the TypeRef reference.
If you don't have a plugin skeleton yet, scaffold one first.
Before you start
The host applies contributions in a fixed order: value-sets → dimensions → types → conversions → nodes → adapters → agents → eventSources → eventBindings. So a value-set or dimension is always registered before the type that uses it, and a type before the node or conversion that references it. Author each section in the same order and forward references resolve on their own.
A malformed contribution — or one referencing an unknown value-set, dimension or type — is logged and skipped; the rest of the plugin still loads. A port whose type can't be resolved makes its node get skipped too, so keep the identifiers below in sync.
Add a value-set
A value-set is the terminology behind a coded type. Enumerate its concepts, or mark it
external: true when a terminology server resolves the codes at runtime.
contributes:
valueSets:
- id: renal-stage
system: urn:acme:renal-stage
doc: KDIGO chronic-kidney-disease stage.
concepts:
- { code: G1, display: "G1 — normal or high (≥ 90)" }
- { code: G2, display: "G2 — mildly decreased" }
- { code: G3, display: "G3 — moderately decreased" }
- { code: G3a, display: "G3a", parent: G3 } # optional hierarchy
idis the identifier a{ kind: coded, valueSet: <id> }type points at.systemis the code-system URI.docis required;concepts[].parentis optional and only records hierarchy.- Set
external: trueand leaveconcepts: []for a server-resolved terminology (SNOMED CT, LOINC, …). An enumerated value-set codegens to a literal union of its codes; an external one falls back tostring.
For the ops side — pointing an instance at a terminology server — see Configure terminology.
Add a dimension
A dimension gives quantity types their UCUM unit checking: a canonical unit plus the
factor from each accepted unit to it.
contributes:
dimensions:
- name: filtration-rate
label: Glomerular filtration rate
canonical: "mL/min/{1.73_m2}"
units:
- { symbol: "mL/min/{1.73_m2}", toCanonical: 1 }
nameis the identifier a{ kind: quantity, dimension: <name> }type points at.canonicalis the reference unit; everyunits[].toCanonicalis the multiplier that converts that unit's values into the canonical one (the canonical unit itself is1).- List every unit you want accepted on ports of this dimension.
Add a type
A type is an explicit descriptor: each field carries a TypeRef, a required flag and an
optional doc. layer is datatype (reusable building blocks) or resource (clinical
resources). This is where the value-set and dimension above get consumed.
contributes:
types:
- name: EgfrResult
doc: Estimated GFR (value + equation + stage).
layer: resource
fields:
value: { type: { kind: quantity, dimension: filtration-rate }, required: true }
equation: { type: { kind: primitive, name: String }, required: true }
stage: { type: { kind: coded, valueSet: renal-stage }, required: true }
nameis what an{ kind: object, name: <name> }port references.docis required on the type;fields.<field>.docis optional.- A type owned by a
dependsOnplugin is referenced by name and resolved across the dependency closure — codegen imports it from the owning package, you don't redeclare it.
See the TypeRef reference for every kind (primitive,
coded, quantity, object, ref, list, any), and
the type system for why datatypes and
resources are layered this way.
Add a conversion
A registered conversion powers the transform.convert node (and the editor's auto-insert
when two wired ports need bridging). from/to are type names — a registered type or a
primitive — and expr is a JEXL expression evaluated
over value, the input.
contributes:
conversions:
- from: EgfrResult
to: Decimal
label: eGFR → number
expr: value.value.value
Here the input is an EgfrResult, so value.value is its quantity field and
value.value.value is the numeric measure. Register one conversion per from/to pair
you want the platform to offer.
Verify
Validate the manifest — this catches an unresolved value-set/dimension/type reference before you ship:
meridian-plugin validateRegenerate the typed client so
EgfrResult, therenal-stagecode union and the rest surface in TypeScript — see Generate types:meridian-plugin generateBuild and bundle with
meridian-plugin build(esbuild), then publish to the registry when ready.
Next steps
- Reference the new types from typed ports in Implement a node behavior.
- Localize the labels and docs you added in Translate a plugin.
- Map these types onto FHIR resources: FHIR mapping (
apps/ehr-lab/docs/resources/reference/fhir-mapping.md). - Background on how the registries fit together: type system registries and the plugins concept.