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-src only. The igs/base-r4 and igs/base-r5 directories are generated output: every preprocessing run deletes and recreates their content, so changes made there are lost.

Prerequisites

ToolUsed for
Java 11 or laterrunning the IG publisher
Node.js (provides npx)rendering the liquid templates during preprocessing
SUSHI (npm install -g fsh-sushi)compiling the FSH files
Jekyllgenerating the IG pages
curl, bashthe 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

PathDescription
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.jsonThe 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.jarThe 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

ScriptDescription
_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.shPublishes the generated IGs to the base and base-r5 repositories.
igs/base-<rx>/_build.shThe 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:

VariableR4R5
isR4 / isR5true / falsefalse / true
fhirVersion4.0.15.0.0
r-code / R-coder4 / R4r5 / 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:

  1. Run ./_preprocessMultiVersion.sh to generate the version-specific IGs.
  2. Make sure a publisher.jar is available, downloading it to igs/publisher.jar if needed. All generated IGs share that single copy; a publisher.jar in the input-cache of an individual IG takes precedence over it.
  3. Build each IG by running ./_build.sh build in 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:

  1. From the root directory, run:
    ./_preprocessMultiVersion.sh
    
  2. The generated IGs will be found in the igs/base-r4 and igs/base-r5 directories.
  3. In each of those directories run ./_build.sh to 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 the output, template and temp directories

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.jar from fhir-ig-publisher to igs/publisher.jar, the single copy shared by all generated IGs.
  • scripts: downloads _build.sh and _build.bat from ig-publisher-scripts into the ig-src directory. Run ./_preprocessMultiVersion.sh afterwards 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.