base-multi-versions
August 7, 2026 · View on GitHub
Multi FHIR version sources for the base / core HL7 FHIR IG.
This repository holds the source files for the base / core HL7 FHIR IG. More information on the IG, meeting minutes and telco details can be found on HL7 confluence.
The current builds of the implementation guide can be found at the following URLs:
Feedback on the specification is tracked using HL7 Jira.
Multi-version approach
This repository contains the source files to build and deploy a multi-FHIR version of the HL7 Europe Base and Core FHIR IG.
┌──────────────────────────────────────┐
│ │
│ github.com/hl7-eu/base-multi-versions│
│ │
└───────────────┬──────────────────────┘
│
│ ┌──────────────────────────┐
├───────► github.com/hl7-eu/base │
│ └──────────────────────────┘
│
│ ┌─────────────────────────────┐
└───────► github.com/hl7-eu/base-r5 │
└─────────────────────────────┘
The content of this repository is processed and generates the content of the base and base-r5 repositories. The content of these repositories is not edited directly but generated based on the content of this repository.
This approach has been chosen as it allows both versions of the specification to be generated from a single codebase and still allows the R4 and R5 versions to be build using the FHIR autobuilder.
The version-specific IGs are generated by a preprocessing step: the sources are copied from ig-src to one directory per FHIR version, and files whose name contains .liquid. are rendered with LiquidJS, using the variables from the context-<Rx>.json file of that version.
ig-src/ + context-R4.json ──preprocessing──► igs/base-r4/ ──IG publisher──► igs/base-r4/output/
ig-src/ + context-R5.json ──preprocessing──► igs/base-r5/ ──IG publisher──► igs/base-r5/output/
Edit the sources in
ig-srconly. Theigs/base-r4andigs/base-r5directories are generated output: every preprocessing run deletes and recreates their content, so changes made there are lost.
Prerequisites
| Tool | Used for |
|---|---|
| Java 11 or later | running the IG publisher |
Node.js (provides npx) | rendering the liquid templates during preprocessing |
SUSHI (npm install -g fsh-sushi) | compiling the FSH files |
| Jekyll | generating the IG pages |
curl, bash | the scripts in this repository |
The IG publisher itself is not part of the repository, it is downloaded on demand — see Updating the build tooling.
Quick start
# 1. edit the sources in ig-src/
# 2. generate both FHIR versions and build them with the IG publisher
./_preProcessAndCheckAll.sh
# 3. open the results
# igs/base-r4/output/index.html
# igs/base-r5/output/index.html
# 4. once the result is fine, publish it to the base and base-r5 repositories
./_commitToMainRepos.sh
A full build of both versions takes a while. To work on a single FHIR version, pass its version number: ./_preProcessAndCheckAll.sh 4.0.1 or ./_preProcessAndCheckAll.sh 5.0.0.
Repository structure
| Path | Description |
|---|---|
ig-src/ | The IG sources — this is where all edits are made. Contains input/ (FSH files, page content, images), the ig-template, and the liquid templates for sushi-config.yaml, ig.ini and publication-request.json. Everything the base and base-r5 repositories should contain lives here, including their .gitignore, LICENSE and README.liquid.md. |
context-R4.json, context-R5.json | The variables used to render the liquid templates, one file per FHIR version. |
igs/base-r4/, igs/base-r5/ | Generated, version-specific IGs, including the build results in their output/ directory. Not edited by hand. |
igs/publisher.jar | The IG publisher, shared by all generated IGs. Downloaded on demand. |
subigs/ | Working copies of the base and base-r5 repositories, used by _commitToMainRepos.sh. Cloned on demand. |
Scripts
| Script | Description |
|---|---|
_preProcessAndCheckAll.sh [4.0.1|5.0.0] | Preprocessing plus a full IG publisher build of the generated IGs. The usual entry point. |
_preprocessMultiVersion.sh [4.0.1|5.0.0] | Preprocessing only: generates igs/base-r4 and igs/base-r5 from ig-src. |
_updateBuildTools.sh [scripts|publisher] | Downloads the IG publisher and the HL7 build scripts. |
_commitToMainRepos.sh | Publishes the generated IGs to the base and base-r5 repositories. |
igs/base-<rx>/_build.sh | The HL7 build script, used to build a single generated IG. |
Without an argument, the scripts that take a FHIR version process both versions.
Writing version-specific content
Any file in ig-src whose name contains .liquid. is rendered during preprocessing and written without the .liquid part — sushi-config.liquid.yaml becomes sushi-config.yaml, condition-eu-core.liquid.fsh becomes condition-eu-core.fsh. Files without .liquid. in their name are copied unchanged, so only files that actually differ between FHIR versions need to be templates.
The variables available in the templates are defined in context-R4.json and context-R5.json:
| Variable | R4 | R5 |
|---|---|---|
isR4 / isR5 | true / false | false / true |
fhirVersion | 4.0.1 | 5.0.0 |
r-code / R-code | r4 / R4 | r5 / R5 |
R4 / R5 | "" / //R5 | //R4 / "" |
There are two main patterns to write version-specific content.
The first pattern uses the {% if isR4 %} and {% endif %} statements as is depicted in the example below.
* status = #final
{% if isR5 %}
* version = "1.0.0" // invented - not there in the report
{% endif %}
{% if isR4 %}
* extension[version].valueString = "1.0.0"
{% endif %}
In the R4 version of the shorthand file only the R4 part will be present, the R5 version will only have the R5 alternative.
This approach has the advantage that indentation remains in place, but the disadvantage that line numbers change. An alternative approach is illustrated below.
* status = #final
{{R5}}* version = "1.0.0" // invented - not there in the report
{{R4}}* extension[version].valueString = "1.0.0"
In the R4 version, {{R4}} is replaced by "" and {{R5}} by "//R5".
In the R5 version, {{R4}} is replaced by "//R4" and {{R5}} by "".
This keeps the different sections clearly marked and preserves line numbers, at the cost of indentation alignment.
The main files on which this process is typically used are sushi-config.yaml, ig.ini and fsh files. FHIR release specific pages are generated using the standard variables made available by the IG-publisher, mainly site.data.fhir.version.
Building the IGs
The simplest way is to run preprocessing and the IG publisher builds in one step. From the root directory, run:
./_preProcessAndCheckAll.sh # both FHIR versions
./_preProcessAndCheckAll.sh 4.0.1 # only R4
./_preProcessAndCheckAll.sh 5.0.0 # only R5
This will:
- Run
./_preprocessMultiVersion.shto generate the version-specific IGs. - Make sure a
publisher.jaris available, downloading it toigs/publisher.jarif needed. All generated IGs share that single copy; apublisher.jarin theinput-cacheof an individual IG takes precedence over it. - Build each IG by running
./_build.sh buildin its directory.
The build results are written to igs/base-r4/output/ and igs/base-r5/output/; open the index.html in that directory to review them. Build errors and warnings are collected in qa.html.
Alternatively the steps can be run separately, which is useful when only one of them needs to be repeated:
- From the root directory, run:
./_preprocessMultiVersion.sh - The generated IGs will be found in the
igs/base-r4andigs/base-r5directories. - In each of those directories run
./_build.shto build the IG. The script offers a menu, or can be called directly with an argument:./_build.sh build– build the IG./_build.sh notx– build without a terminology server (faster, but no terminology validation)./_build.sh nosushi– build without running SUSHI./_build.sh clean– remove theoutput,templateandtempdirectories
Updating the build tooling
Both the IG publisher and the HL7 build scripts are updated with:
./_updateBuildTools.sh # both
./_updateBuildTools.sh scripts # only _build.sh / _build.bat
./_updateBuildTools.sh publisher # only publisher.jar
- publisher: downloads the latest
publisher.jarfrom fhir-ig-publisher toigs/publisher.jar, the single copy shared by all generated IGs. - scripts: downloads
_build.shand_build.batfrom ig-publisher-scripts into theig-srcdirectory. Run./_preprocessMultiVersion.shafterwards to propagate them to the generated IGs.
The update option built into _build.sh is deliberately not used for either: it is interactive, it always puts the jar in the input-cache of a single IG instead of the shared location, and updating the scripts in igs/base-r4 / igs/base-r5 has no effect, as those directories are overwritten on every preprocessing run.
Publishing to the base and base-r5 repositories
Once the changes have been checked and the results can be committed, run:
./_commitToMainRepos.sh
For each FHIR version this will clone the target repository to subigs/ (or reuse an existing working copy), check out the branch with the same name as the current branch of this repository, creating it if needed, copy the contents of igs/base-r4 / igs/base-r5 into it, and commit and push the result. The commit message of the last commit in this repository is reused, with a reference to the source commit.
Continuous integration
Every push runs .github/workflows/deploy-to-repos.yml, which uses the same scripts as a local build — see .github/workflows/README.md for details. It builds both FHIR versions with _preProcessAndCheckAll.sh, one job per version, and only if both succeed it syncs them to the base and base-r5 repositories, into a branch of the same name as the branch that was pushed.
Validation is deliberately not split into a second workflow: a pull request from a branch of this repository raises both a push and a pull_request event, which would build every IG twice.