CQL syntax cheat sheet
The Clinical Quality Language (CQL) syntax you meet most often when reading rules, grouped into a quick reference: each item gives the form, a one-line meaning and a short example. All examples belong to one diabetes follow-up library on the FHIR R4 data model.
New to CQL? Start with Reading the workbench’s CQL, which walks through one NHI clause from start to finish; this page is for looking things up while you read a rule. For unfamiliar terms, see the glossary.
File structure
A CQL file is a library. It opens with a series of declarations, by convention in the order below, followed by the defines. This header is shared by every example on the page: the examples in later sections can be pasted straight after it.
library DiabetesCare version '1.0.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1' called FHIRHelpers
codesystem "LOINC": 'http://loinc.org'
valueset "Diabetes": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.103.12.1001'
code "HbA1c": '4548-4' from "LOINC" display 'Hemoglobin A1c/Hemoglobin.total in Blood'
code "eGFR": '62238-1' from "LOINC"
// Measurement year 2026
parameter "Measurement Period" Interval<DateTime>
default Interval[@2026-01-01T00:00:00.000, @2027-01-01T00:00:00.000)
context Patientlibrary DiabetesCare version '1.0.0'- The library’s name and version. Other libraries name these when they include it.
using FHIR version '4.0.1'- The data model. FHIR 4.0.1 is FHIR R4; it decides which resources a retrieve can return and which elements they have.
include FHIRHelpers version '4.0.1' called FHIRHelpers- Includes another library; the name after called is the alias used in this file. FHIRHelpers converts FHIR data types to CQL types, so nearly every library that reads FHIR data includes it.
codesystem "LOINC": 'http://loinc.org'- A code system, identified by its canonical URI after the colon.
valueset "Diabetes": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.103.12.1001'- A value set: a list of codes, named by its canonical URL. This one is the diabetes value set in the US Value Set Authority Center (VSAC).
code "HbA1c": '4548-4' from "LOINC" display 'Hemoglobin A1c/Hemoglobin.total in Blood'- A single code: the code and its code system. display is a human-readable label and can be left out, as the eGFR line does.
// Measurement year 2026- A comment: // runs to the end of the line, and /* */ wraps several lines. Comments don’t affect the result.
parameter "Measurement Period" Interval<DateTime> default Interval[@2026-01-01T00:00:00.000, @2027-01-01T00:00:00.000)- A parameter: supplied from outside at run time, falling back to the default. Here it is the whole of 2026; the closing parenthesis leaves 2027-01-01 out.
context Patient- Every define after this line is evaluated for one patient, and retrieves only return that patient’s data.
Note: double quotes hold names, like "HbA1c", and names may contain spaces; single quotes hold text, like 'http://loinc.org'.
Spec: Author’s Guide: DeclarationsUsing CQL with FHIRFHIRHelpers
define and retrieve
define gives an expression a name; a retrieve is how CQL fetches data, and it returns a list.
define "Name": expressionNames an expression so other expressions can refer to it by that name. The second define uses exists to check whether the first returned anything.
define "Diabetes Conditions": [Condition: "Diabetes"] define "Has Diabetes": exists "Diabetes Conditions"[Type: "value set"]This patient’s Conditions (diagnoses) whose code is in the Diabetes value set.
[Condition: "Diabetes"][Type: "code"]Filters by a single code: Observations coded LOINC 4548-4, that is, HbA1c results.
[Observation: "HbA1c"][Type]No filter: every Encounter (visit) for this patient.
[Encounter][Type: element in "value set"]Names the element to match on. Without it, the resource’s primary code element is used, which is code for both Condition and Observation, so this line returns the same as the first form.
[Condition: code in "Diabetes"]
A retrieve doesn’t filter by status: cancelled and entered-in-error records come back too. Filter them out yourself with a where clause, as the next section shows.
Queries
Add an alias after a list and you have a query; the alias stands for the current item. Clauses follow in this order: with or without, where, return, sort.
source aliasO is the alias. A query with only an alias returns the whole list unchanged; the clauses below usually follow.
[Observation: "HbA1c"] Owhere conditionKeeps only items for which the condition is true; false and null are both dropped. In FHIR, an Observation’s effective can be a point in time or a period.
as dateTimetakes the point in time; for a period it gives null, so that item is dropped.define "HbA1c In Period": [Observation: "HbA1c"] O where O.status in { 'final', 'amended', 'corrected' } and (O.effective as dateTime) during "Measurement Period"with source alias such that conditionKeeps items that have at least one related match: visits during which an HbA1c was taken.
define "Visits With HbA1c": [Encounter] E with [Observation: "HbA1c"] O such that (O.effective as dateTime) during E.periodwithout source alias such that conditionThe opposite: keeps items with no related match at all, here visits with no HbA1c taken during them.
define "Visits Without HbA1c": [Encounter] E without [Observation: "HbA1c"] O such that (O.effective as dateTime) during E.periodreturn expressionDecides what each item becomes;
Tuplebuilds a new structure with named fields. Tuple fields aren’t converted automatically, so this callsFHIRHelpers.ToDateTimeto make takenAt a CQL DateTime. In comparisons, FHIRHelpers converts for you.define "HbA1c Results": "HbA1c In Period" O return Tuple { takenAt: FHIRHelpers.ToDateTime(O.effective as dateTime), result: O.value as Quantity } sort by takenAtsort by element ascsort by element descSorts ascending (the default) or descending. sort by takes element names directly, without the alias;
start of periodis when the visit started.define "Encounters Latest First": [Encounter] E sort by start of period desc
Spec: Author’s Guide: Queries
Types and literals
Every value in CQL has a type. These are the types you meet most often in rules, and how to write them.
BooleanTrue or false. When data is missing it is null; see Three-valued logic and null.
true falseIntegerA whole number.
42DecimalA decimal number.
7.5StringText, in single quotes. Double quotes hold names, not text.
'final'DateA date, starting with @.
@2026-03-15DateTimeA date and time separated by T; +08:00 is Taiwan’s time zone.
@2026-03-15T08:30:00.000+08:00TimeA time of day only, starting with @T.
@T08:30QuantityA number with a unit. Units use UCUM codes in single quotes; lengths of time use words such as years, months and days, without quotes.
7 '%' 30 'mL/min/{1.73_m2}' 90 daysIntervalA range. Square brackets include the boundary and parentheses exclude it:
Interval[18, 65)is 18 or more and under 65.Interval[18, 65) Interval[@2026-01-01, @2026-12-31]ListA list, in curly braces. Retrieves and queries also return lists.
{ 'final', 'amended', 'corrected' }TupleA structure with named fields, each written as name: value.
Tuple { takenAt: @2026-03-15, result: 6.8 '%' }CodeA single code: the code and its code system, optionally with a display.
Code '4548-4' from "LOINC" display 'Hemoglobin A1c/Hemoglobin.total in Blood'ConceptA set of codes that mean the same thing, such as two LOINC codes for the same test. A FHIR CodeableConcept becomes a Concept in CQL.
Concept { Code '4548-4' from "LOINC", Code '17856-6' from "LOINC" } display 'Hemoglobin A1c'
Lists and value sets
Retrieves and queries both return lists. These operations answer “is there one”, “how many” and “which one”.
element in listlist contains elementWhether the list holds the element. in and contains are the same test with the sides swapped.
'final' in { 'final', 'amended', 'corrected' } // true { 'final', 'amended', 'corrected' } contains 'preliminary' // falsevalue in intervalWhether the value falls within the range: 18 to 75, both ends included. contains works for intervals too.
AgeInYears() in Interval[18, 75]code in "value set"Whether a code is in a value set. C.code is a FHIR CodeableConcept, and it counts if any of its codes is in the value set, so this returns the same as
[Condition: "Diabetes"].define "Diabetes By Code": [Condition] C where C.code in "Diabetes"exists listTrue if the list has at least one item. An empty list and null both give false, never null.
exists "HbA1c In Period"Count(list)How many items the list has, not counting nulls. Here: at least two HbA1c results in the measurement period.
Count("HbA1c In Period") >= 2First(list)Last(list)The first or last item, in the list’s current order; null if the list is empty. HbA1c Results is already sorted by takenAt from oldest to newest, so Last is the most recent.
define "Latest HbA1c": Last("HbA1c Results")
Spec: CQL Reference: List OperatorsCQL Reference: Aggregate Functions
Comparison and logic
Comparison, logic and null checks. For how each behaves when a value is null, see the last section on this page.
= != < <= > >=Comparison. Quantities need compatible units. If either side is null, for example when there is no HbA1c result, the comparison is null.
define "HbA1c Controlled": "Latest HbA1c".result < 7 '%'~ !~Equivalence. Text is compared ignoring case; codes are compared on code system and code only, ignoring display; two nulls are equivalent, so ~ never returns null.
'Final' ~ 'final' // true 'Final' = 'final' // falseand or notAnd, or, not. For how they treat null, see Three-valued logic and null.
define "Adult With Diabetes": "Has Diabetes" and AgeInYears() >= 18A implies B“If A, then B”: when A is false the result is true; when A is true the result is B. Here: a patient with diabetes should have an HbA1c in the measurement period.
define "HbA1c Checked If Diabetic": "Has Diabetes" implies exists "HbA1c In Period"X is nullX is not nullTests for null. The result is always true or false.
"Latest HbA1c" is nullX is trueX is falseHolds only when the value is exactly true (or false); null gives false. For why this matters, see The result and is true.
"HbA1c Controlled" is trueif condition then A else BA null condition takes the else branch. So test is true and is false first; the final else is then the missing-data case.
define "HbA1c Status": if "HbA1c Controlled" is true then 'controlled' else if "HbA1c Controlled" is false then 'uncontrolled' else 'no-result'
Spec: CQL Reference: Comparison OperatorsCQL Reference: Logical OperatorsDeveloper’s Guide: Conditional Expressions
Dates and times
Comparing points and periods in time, and date arithmetic. The most common rule is “this happened during the measurement period”.
X during intervalFalls entirely within the interval. When the left side is a point in time, the point is within the interval, as in
(O.effective as dateTime) during "Measurement Period"under Queries.define "Encounters In Period": [Encounter] E where E.period during "Measurement Period"X before YX after YEarlier or later. start of and end of pick an interval’s start or end: here, HbA1c results before the measurement period began.
define "HbA1c Before Period": [Observation: "HbA1c"] O where (O.effective as dateTime) before start of "Measurement Period"X 3 months or less before YAdds a length of time: within the 3 months before Y; or more means at least 3 months before. Here, HbA1c results in the last 3 months of the measurement period.
define "HbA1c Near Period End": [Observation: "HbA1c"] O where (O.effective as dateTime) 3 months or less before end of "Measurement Period"X overlaps YThe two periods share some time: a hospital stay over the new year counts toward 2026.
define "Encounters Overlapping Period": [Encounter] E where E.period overlaps "Measurement Period"X starts during YX ends during YLooks only at the moment a period starts or ends: visits that ended during the measurement period.
define "Encounters Ending In Period": [Encounter] E where E.period ends during "Measurement Period"start of intervalend of intervalAn interval’s start and end. The measurement period’s upper bound is open, so end of returns the millisecond before 2027-01-01.
start of "Measurement Period" // @2026-01-01T00:00:00.000 end of "Measurement Period" // @2026-12-31T23:59:59.999years between A and BHow many whole units passed between two times; the unit can be years, months, days and so on.
years between @1960-05-01 and @2026-10-28 // 66 days between @2026-03-01 and @2026-03-15 // 14difference in years between A and BHow many unit boundaries were crossed. 31 December to the next day is one day: zero whole years, but one year boundary crossed, so 1.
years between @2025-12-31 and @2026-01-01 // 0 difference in years between @2025-12-31 and @2026-01-01 // 1date + quantitydate - quantityAdds or subtracts time. Today() is the day of evaluation, so Today() - 90 days is 90 days ago.
@2026-01-15 + 3 months // @2026-04-15 Today() - 90 daysduration in days of intervalThe length of an interval: 1 March to 4 March is 3 days.
duration in days of Interval[@2026-03-01, @2026-03-04] // 3AgeInYears()AgeInYearsAt(time)The patient’s age from the Patient’s birthDate, or null if it is missing. AgeInYears() uses the day of evaluation; AgeInYearsAt uses the time you give, so the result doesn’t depend on when it runs. Quality measures usually use the latter.
AgeInYears() >= 18 AgeInYearsAt(end of "Measurement Period") >= 18
Spec: Author’s Guide: Timing and Interval OperatorsCQL Reference: Date and Time OperatorsCQL Reference: Clinical Operators
Functions
When the same check is needed in many places, write it as a function. Functions live in a library like defines, and other libraries can use them.
define function "Name"(argument Type): expressionArguments need a type; Observation here is the FHIR Observation. Call it by putting the value in parentheses. To state the return type, add
returns Booleanafter the parentheses.define function "Is Final"(obs Observation): obs.status in { 'final', 'amended', 'corrected' } define "Final HbA1c": [Observation: "HbA1c"] O where "Is Final"(O)define fluent function name(argument Type): expressionWith fluent, the function can be called with a dot after a value: O.isFinal() is the same as isFinal(O). Fluent functions arrived in CQL 1.5.
define fluent function isFinal(obs Observation): obs.status in { 'final', 'amended', 'corrected' } define "Final eGFR": [Observation: "eGFR"] O where O.isFinal()alias.function(arguments)Calls a function from an included library, prefixed with the alias given after called.
define "HbA1c Dates": "HbA1c In Period" O return FHIRHelpers.ToDateTime(O.effective as dateTime)
Three-valued logic and null
CQL uses null for “unknown”, such as a lab test that isn’t in the record. Null propagates: most operations return null if any input is null. A few always return true or false, and those are what you use to handle missing data.
| Expression | Result | Why |
|---|---|---|
true and null | null | It depends on the null side, so it can’t be decided. |
false and null | false | One side already fails, so the other doesn’t matter. |
true or null | true | One side already holds. |
not null | null | The opposite of unknown is still unknown. |
null = 7 | null | A comparison with null is null. |
null ~ 7 | false | Equivalence only returns true or false. |
null is null | true | Null tests never return null. |
null is true | false | Only true holds. |
Count({ 1, null, 3 }) | 2 | Count skips nulls. |
Coalesce(null, 'unknown') | 'unknown' | Returns the first value that isn’t null. |
if null then 'yes' else 'no' | 'no' | A null condition takes the else branch. |
Coalesce and if turn “unknown” into an answer: Coalesce("HbA1c Controlled", false) counts a patient with no HbA1c result as poorly controlled. When deciding whether criteria are met, separate the two settled outcomes with is true and is false first, and treat the rest as missing data. See The result and is true.
The full truth tables for and, or and not are in Three-valued logic, and the scenario simulator lets you switch conditions and watch the result change.
Spec: CQL Reference: Nullological OperatorsDeveloper’s Guide: Nullological Operators