Skip to content

FSH 101: Quick Start Guide for Polaris Developers

Welcome, Polaris developer! You're about to dive into FHIR Shorthand (FSH), the language we use to define our Polaris FHIR specifications. FSH makes it much easier to create and manage FHIR profiles and other artifacts compared to editing raw JSON. This guide will get you started with the basics.

Prerequisite: A general understanding of core FHIR concepts (Resources, Profiles, Elements). If you're new to FHIR, please read the "FHIR 101 for Polaris Developers" guide first.


What is FSH and Why Do We Use It?

  • FSH (FHIR Shorthand): A domain-specific language for defining FHIR conformance resources like Profiles, Extensions, ValueSets, and CodeSystems.
  • Concise & Readable: It's designed to be human-friendly and much less verbose than the equivalent FHIR JSON (StructureDefinition resources).
  • SUSHI: The command-line tool (compiler) that takes your FSH files (.fsh) as input and generates the official FHIR JSON StructureDefinition files. These JSON files are then used by the IG Publisher to build our Implementation Guide.
  • Polaris Standard: All Polaris FHIR specifications are authored in FSH.

Core FSH Syntax Elements (with Polaris Examples)

FSH files are plain text files, typically with a .fsh extension. Let's look at the common building blocks you'll see in Polaris FSH files (mostly found in src/fsh/resources/).

1. Comments

Just like in many programming languages:

// This is a single-line comment.

/*
This is a
multi-line comment.
*/
Polaris convention: The first line of every FSH file is // Polaris<ResourceType>.fsh Example: // PolarisCorePatient.fsh

2. Aliases

Aliases are shortcuts for long URLs or OIDs, making FSH more readable and maintainable. They are usually defined at the top of an FSH file or in a shared file like src/fsh/aliases.fsh.

  • Syntax: Alias: $<AliasName> = <URL or OID>
  • Polaris Convention: Alias names start with a $.

// Defines an alias for the SNOMED CT code system URL
Alias: $SCT = http://snomed.info/sct

// Defines an alias for the PolarisCorePatient profile's canonical URL
Alias: $PolarisCorePatient = https://fhir.apps.health/StructureDefinition/polaris-core-patient
Once defined, you use the alias (e.g., $SCT) instead of the full URL in your rules.

3. Defining a Profile

A Profile constrains a base FHIR Resource. This is the most common thing you'll see.

  • Syntax:

    Profile: <ProfileName>
    Parent: <BaseResourceName or ParentProfileName>
    Id: <profile-id-kebab-case>
    Title: "<Human Readable Title>"
    Description: "<Detailed description, often multi-line using triple quotes>"
    // Other metadata like ^url, ^status
    // Rules follow...
    

  • Polaris Example (PolarisCorePatient.fsh excerpt):

    Profile:        PolarisCorePatient
    Parent:         Patient // Based on the core Patient resource
    Id:             polaris-core-patient // kebab-case, used in URLs
    Title:          "Polaris Patient"
    Description:    "Generic Polaris core profile for patient demographic and administrative information..."
    * ^url = $PolarisCorePatient // Sets the canonical URL using an alias
    * ^status = #draft       // All Polaris profiles start as draft
    
    The * ^url = ... and * ^status = ... are "caret path" rules, setting metadata on the StructureDefinition itself.

4. Rules: Constraining Elements

Rules are the heart of FSH. They start with an asterisk * and specify constraints on elements.

  • Basic Structure: * <elementPath> <constraint1> <constraint2> ...

a. Cardinality Rules

Defines how many times an element can appear (min..max).

  • Syntax: * <elementPath> <min>..<max> (* means unbounded)
  • Polaris Examples:
    // Patient must have 1 or more identifiers
    * identifier 1..*
    
    // Patient can have 0 or 1 active status
    * active 0..1
    
    // Patient must have exactly one gender
    * gender 1..1
    

b. Data Type Rules (only)

Restricts an element to specific data type(s). Especially useful for choice elements (like onset[x]).

  • Syntax: * <elementPath> only <DataType1> or <DataType2> ...
  • Polaris Example (PolarisCoreObservation.fsh):
    // The effective[x] element can be either a dateTime or a Period
    * effective[x] only dateTime or Period
    

c. ValueSet Binding Rules (from)

Specifies that a coded element's value must come from a particular ValueSet.

  • Syntax: * <elementPath> from <ValueSetURI or $Alias> (<strength>)
    • Strength can be required, extensible, preferred, example. Polaris typically uses required or extensible.
  • Polaris Example (PolarisCorePatient.fsh):
    // The gender element must be a code from the administrative-gender ValueSet
    * gender from http://hl7.org/fhir/ValueSet/administrative-gender (required)
    

d. Fixed Value Rules (=)

Assigns a fixed value to an element. In a Profile, this means instances must have this value.

  • Syntax: * <elementPath> = <value>
    • For codes: * <elementPath> = #codeValue
    • For Codings: * <elementPath> = $SystemAlias#codeValue "<Display Text>"
  • Polaris Example (PolarisCoreMedicationRequest.fsh):
    // The intent element must always be 'order'
    * intent = #order (exactly) // (exactly) means no other sub-elements allowed
    

e. Must Support Flag (MS)

Indicates an element is "Must Support" (implementers must be able to process it).

  • Syntax: * <elementPath> MS
  • Often combined with cardinality: * identifier 1..* MS

f. Referencing Other Profiles (Reference())

Constrains a Reference element to point to resources conforming to specific profiles.

  • Syntax: * <elementPath> only Reference(<ProfileName1> or <ProfileName2>)
  • Polaris Example (PolarisCoreCondition.fsh):
    // The subject of a PolarisCoreCondition must be a PolarisCorePatient
    * subject only Reference(PolarisCorePatient)
    

5. Invariants

Custom validation rules expressed using FHIRPath.

  • Syntax:
    Invariant:     <invariant-id-kebab-case>
    Description:   "<Human-readable description>"
    Severity:      #error // or #warning
    Expression:    "<FHIRPath expression that evaluates to true if valid>"
    
  • Applied to a profile or element using an obeys rule: * obeys <invariant-id>
  • Polaris Example (common pattern):
    Invariant:     patient-has-polaris-id
    Description:   "Must have at least one identifier using Polaris canonical NamingSystem."
    Severity:      #error
    Expression:    "identifier.exists(system.matches('^https://fhir.apps.health/NamingSystem/[a-zA-Z0-9_]+-patient-identifier$'))"
    
    // In PolarisCorePatient.fsh:
    Profile: PolarisCorePatient
    // ...
    * obeys patient-has-polaris-id // Applies the invariant to the whole profile
    

6. Defining ValueSets

Specifies a set of allowed codes.

  • Syntax:
    ValueSet: <ValueSetName>
    Id: <valueset-id-kebab-case>
    Title: "<Human Readable Title>"
    Description: "<Detailed description>"
    * include codes from system <SystemURI or $Alias> // Includes all codes
    * include codes from system $SCT where concept is-a #<code> // Filtered include
    * $SCT#<code> "<Display>" // Includes a specific code
    
  • Polaris Example (PolarisCoreCondition.fsh):
    Alias: $VS_PolarisConditionCodes = https://fhir.apps.health/ValueSet/polaris-core-condition-codes
    
    ValueSet: PolarisConditionCodesVS
    Title: "Polaris Condition Codes"
    Description: "ValueSet for condition codes used in Polaris."
    * ^url = $VS_PolarisConditionCodes
    * ^status = #draft
    * include codes from system $SCT // Includes all SNOMED CT codes (very broad, usually more specific)
    

7. Defining Extensions

Creating new elements not present in base FHIR.

  • Syntax:
    Extension: <ExtensionName>
    Id: <extension-id-kebab-case>
    Title: "<Human Readable Title>"
    Description: "<Detailed description>"
    // Rules to define the extension's structure, often constraining value[x]
    * value[x] only <DataType>
    * value[x] from <ValueSetURI> (if coded)
    
  • Polaris Example (PolarisCoreAppState.fsh):
    Extension: AppStateStringValue
    Id:        app-state-string-value
    Title:     "App State String Value Extension"
    Description: "A simple extension to hold the value of a Polaris application-state entry as a string."
    * ^url = "https://fhir.apps.health/StructureDefinition/app-state-string-value" // Set canonical URL
    * valueString 1..1 MS // Extension holds a single, mandatory string value
    

How FSH Relates to JSON Examples

The FSH rules you define for a Profile directly dictate the expected structure and constraints for any JSON instance claiming to conform to that Profile.

Example:

If PolarisCorePatient.fsh has:

* identifier 1..* MS  // Must have at least one identifier
* gender 1..1         // Must have exactly one gender
* gender from http://hl7.org/fhir/ValueSet/administrative-gender (required)

Then PolarisCorePatient-example.json would look something like:

{
  "resourceType": "Patient",
  "meta": { // Indicates conformance
    "profile": ["https://fhir.apps.health/StructureDefinition/polaris-core-patient"]
  },
  "identifier": [ // Array, because 1..*
    {
      "system": "https://fhir.apps.health/NamingSystem/cedarbrook-clinic-patient-identifier",
      "value": "1234567890"
    }
    // ... potentially more identifiers
  ],
  "gender": "female", // Single value, from the specified ValueSet
  // ... other elements
}


Next Steps

  1. Explore src/fsh/resources/: Pick a resource (e.g., PolarisCoreObservation.fsh) and try to understand its rules.
  2. Compare with Examples: Look at the corresponding file in src/examples/ (e.g., PolarisCoreObservation-example.json) to see how the FSH translates.
  3. FSH School: For a more in-depth tutorial on FSH, visit fshschool.org.
  4. Polaris FHIR Modelling Guidelines: Refer to the detailed guidelines provided to Fire Blackwell (your persona) for specific Polaris conventions and best practices.

This quick start should give you a foothold. The best way to learn FSH is by reading and writing it within the context of the Polaris project!