-
Notifications
You must be signed in to change notification settings - Fork 7
User Guide
The extension adds syntax highlighting, semantic (error) highlighting, and local execution for HL7 Clinical Quality Language (CQL) to Visual Studio Code
To install Visual Studio Code, visit https://code.visualstudio.com and follow the download instructions appropriate for your platform.
The cqframework.cql extension has been published to the VS Code Marketplace, so the installation is simple. Just search for "Clinical Quality Language" in the marketplace and install the extension:

The extension will be activated once you open any file with the .cql extension in the editor.
Note that the extension will download the CQL Language Server, which may take a while. During the download, the status is displayed in the lower-right corner of the VSCode environment.
In addition, this extension requires Java to be installed. It'll prompt you to install Java if required.
Download and unzip vscodeTest.zip to a local drive. In VSCode, use 'Open Folder', open the /vscodeTest directory. This should also open a panel on the left hand side called 'Explorer'. Select the /input/cql/HelloWorld.cql file, which should display on the right hand side. Select the CQL file on right, right mouse click, 'Execute CQL' option. This should cause a new HelloWorld.txt file to be generated, which should contain "HelloWorld=Your CQL extension works if you see this text in the output HelloWorld.TXT file". If you see that output being generated your CQL extension has been installed correctly and is up and working.
If not: please ensure you have the latest version of VSCode and Java. Please check your PATH environment variables. Should these not correctly refer to the \Java\javapath directory the CQL extension won't work.
The plugin provides syntax highlighting for CQL:

To see the result type of any expression, hover over the expression with the mouse:

Errors in the CQL are highlighted using a red underline. To see the error message, hover over the underlined CQL and the error message will display:

Warnings in the CQL (i.e. messages that indicate something may be wrong or unexpected with the CQL but doesn't necessarily mean an error) are highlighted using a yellow underline. Again, to see the warning message, hover over the underlined CQL and the warning message will display:

The plugin supports execution of CQL. The user can run either all test cases or have the ability to select one or more individual test cases, or run mulitple libraries at the same time.
Note: CQL commands require an active connection to the CQL Language Server (CQL-LS). On initial start-up, commands will not appear in the context menus or command palette until the CQL-LS connection is established.
The CQL-LS status is displayed in the lower right-hand corner of the VSCode status bar. Hovering over it shows a tooltip with the CQL-LS status and version, if available.

With an open CQL file, right-click anywhere in the CQL editor and select Execute CQL or Execute CQL - Select Test Cases from the context-menu:
Note: The plugin relies on the name of the library, so the filename and CQL library declaration name must match. In the above example, the file is named
Simple.cqland the library name isSimple.

The Execute CQL - Select Test Cases option will open a quick-pick that wil allow the user to select one or more test cases.

Note: If a MeasureReport is defined for the test case, the cqfm-testCaseDescription will be used for quickpick detail.
Running multiple libraries is accessible from the VSCode Command Palette.

-
Open the Command Palette: Use the keyboard shortcut
Ctrl+Shift+P(Windows/Linux) orCmd+Shift+P(Mac). You can also open it from the top menu by selectingView > Command Palette. -
Search for the CQL Execute commands: Type
CQLin the command palette input box. -
Select the command: Choose the
CQL: ExecuteorCQL: Execute CQL - Select Test Casesoption that appears in the dropdown list.

User can select one or more libraries. The libraries will processed sequentially and will execute all test cases.
During execution, progress will shown in a notifcation window.


Executing the CQL will open a new editor on a file with the library name and a .txt extension, logging the results of the evaluation. Every top-level expression in the library is evaluated and the results are added to the evaluation log:
In addition, any messages generated by the execution are output to the log.
Note that not all messages are errors, the evaluation may result in information, warning, and other messages that don't necessarily mean the evaluation failed. Check for the keyword
ERROR:at the beginning of the message to make sure an error has occurred, rather than just a warning or informational message produced by the evaluation.
The plugin also supports vocabulary and data access as part of execution, see the Adding Vocabulary and Adding Test Data topics below for more information on these features.
By default all test cases found in a measure specific test folder will be executed. There is an option to exclude specific test cases.
To exclude test cases, create a config.json file in the input\tests folder.

The measure test results file will include information about any excluded test cases.

CQL is translated into a machine-friendly format called Expression Logical Model or ELM. Most engines that run CQL are processing ELM to perform the actual evaluation. To see the ELM output, in either XML or JSON, for a given CQL file, right-click anywhere in the CQL editor and select View ELM - XML or View ELM - JSON:

The plugin will open a new editor with the ELM output using the selected format, example is XML:

This feature is useful mainly as a tool for helping understand what an engine is doing or when debugging an unexpected result or error.
The translation and execution capabilities in the plugin expect CQL files to be in the following directories, by convention:
<PROJECT>/input/cql
<PROJECT>/input/tests
<PROJECT>/input/tests/<cql-library-name>
<PROJECT>/input/tests/<cql-library-name>/<patient-id>
<PROJECT>/input/tests/<cql-library-name>/<patient-id>/<resource-type-name>/<resource files> // flexible structure
<PROJECT>/input/vocabulary/codesystem
<PROJECT>/input/vocabulary/valuesetWithin the tests folder, there is a folder for each CQL library, by name (note that the name of the file must match the name of the library in order for the evaluator to properly execute the CQL).
Note also that the evaluator is a separate subsystem from the translator, so it will read whatever is current on disk, so be sure to save before executing.
Within the library folder, there is a folder for each "test case", in the form of a Patient (the execution only supports patient context execution at this point). The folder must have the same id as the patient (that's how the evaluator knows what the patient id is).
Within each test case folder are the resources for that specific test case. The resource files can be provided either directly in this folder, or they can be organized into folders by resource type name. Whether they are in the test folder or in subfolders, resources can be provided as bundles (included nested bundles), or as separate files, and in either XML or JSON format. If a Patient is provided, the id element of the Patient resource must match the name of the test case folder. Resource file names are expected to follow the convention <id>.(json|xml) (when in a resource-specific folder) or <ResourceType>-<id>.(json|xml), for example Patient-123.json.
NOTE: As of the 0.7.3 release of the VSCode plugin, test case resources can no longer be provided as bundles, each resource must be in a separate file in order for it to be recognized by the plugin. This change was made to accommodate future capabilities for supporting test case authoring and updating, making it easier for the plugin to identify where resource contents should be updated.
Refer to the Adding a Test Case topic below for more information.
NOTE: The plugin will actually search any subfolders of the
input/testsfolder, looking for the first match of a folder with the library name being tested. This allows for flexible organization of tests to match intent, for example, there may belibrarytests organized under a library folder, andmeasuretests organized under a measure folder. However, when this feature is used, the plugin will use the first folder it finds that matches the library name, regardless of how nested the folder structure is.
The plugin can make use of an options file to support setting compiler and run-time options for the plugin. These options are read from a file named cql-options.json in the input/cql folder. Here is an example cql-options file with typical defaults for common options:
{
"options":[
"EnableAnnotations",
"EnableLocators",
"DisableListDemotion",
"DisableListPromotion"
],
"formats":[
"XML",
"JSON"
],
"validateUnits":true,
"verifyOnly":false,
"errorLevel":"Info",
"signatureLevel":"None",
"analyzeDataRequirements":false,
"collapseDataRequirements":true
}For a complete description of the available options, refer to the Usage topic in the CQL-to-ELM translator documentation.
To support collaboration among multiple authors, Visual Studio Code has built-in support for using Github. To use Github, you will need a Github account, it's free and can be associated with your favorite identity provider if you want. Once you have a github account, go to the Source Control tab in Visual Studio Code and select "Clone a repository":

In the popup that displays select Clone from github:

Type in the Github organization and repository you want to clone (e.g. cqframework/cqf-ccc), then select the repository in the dropdown:

Choose a folder locally where you want to clone the repository and click Select a Repository Destination:

VSCode will prompt you whether you want to open the newly cloned repository:

VSCode will then prompt for whether you trust the authors of the repository. Select Yes, I trust the authors. You can optionally select the checkbox to Trust the authors of all files in the parent folder, that way other repositories you clone from this same organization will already be trusted.

You are now taken to the main VSCode folder explorer interface. On the left-hand side you will see a listing of the files and folders in the repository:

In the explorer view, navigate to the input/cql folder to see all the CQL files in the repository. Click on the CQL file you want to open:

Make some changes to the CQL library (in this case we've added a comment to the expression). Then save the changes and the editor will indicate where the changed lines are in the file, and the explorer will indicate that the file is modified:

These changes are only in your local clone of the repository at this point, so they are visible only to you.
To commit these changes back to the github repository, select the Source Control icon on the left side of VSCode:

Provide a message describing the changes, and select the + icon next to the list of changes to Stage All Changes:

Once the changes you want to commit back to the repository are staged, pull down the arrow on the Commit button and select Commit and Push:

This will commit the changes directly back to the main branch of the repository.
NOTE: This process requires write access to the repository. Contact the repository administrator to ensure you have privileges to write.
NOTE: Many repositories require the use of a Pull Request to commit changes to the main branch of the repository. There is an extension (github.vscode-pull-request-github) that enables the use of pull requests in VSCode.
To pull changes from the github repository (i.e. get the latest changes that other users have committed), click the Source Control icon on the left side of VSCode, and open the Source Control menu by clicking the ellipses. On the Source Control menu, select Pull:

The plugin can use locally available value sets in the form of FHIR ValueSet resources. The plugin looks in the input/vocabulary/valueset folder (and any sub-folders recursively) and loads any files it finds and tries to interpret them as FHIR ValueSet resources. This means that the value set files can be FHIR Bundles or FHIR ValueSet resources in both XML and JSON format.
For example, given the following declaration in a CQL library:
valueset "Active Condition": 'http://fhir.org/guides/cqf/common/ValueSet/active-condition'The plugin will look in the input/vocabulary folder for a FHIR ValueSet resource with this url:
{
"resourceType" : "ValueSet",
"id" : "active-condition",
...
"url" : "http://fhir.org/guides/cqf/common/ValueSet/active-condition",
"version" : "4.0.1",
"name" : "CQFActiveCondition",
"title" : "CQF Active Condition",
...Note that the
iddoesn't matter, only theurlelement is used to find the value set.
If the value set is a simple extensional value set (i.e. it is defined only in terms of a list of specific codes from a code system) then the plugin will compute the expansion. When this happens, a WARNING is issued by the evaluator indicating that the plugin performed the expansion and it may not be correct (because it does not use the code system to compute the expansion so it may include codes that should not be included based on the expansion rules that a full terminology server would follow).
...
"status" : "active",
"experimental" : false,
"date" : "2019-07-21",
"publisher" : "Alphora",
...
"compose" : {
"include" : [
{
"system" : "http://terminology.hl7.org/CodeSystem/condition-clinical",
"concept" : [
{
"code" : "active",
"display" : "Active"
},
{
"code" : "recurrence",
"display" : "Recurrence"
},
{
"code" : "relapse",
"display" : "Relapse"
}
]
}
]
}
}Best practice is to use ValueSet resources which have an expansion element already computed, rather than relying on the plugin to perform the computation. ValueSets which are more complex than simple extensional definitions will not be expanded by the plugin and must have an expansion element present in order to be used.
The structure of CQL projects is an extension to the FHIR IG publisher project structure, it is best practice to use an external sub-folder to contain value sets that are copied from other locations and not actually defined as part of the content IG. Future enhancements to the plugin will be able to make use of value sets distributed in dependent IGs, but for now, all terminology dependencies need to be copied locally in order for the plugin to make use of them.
Once you have added any required terminology, use the same process above for committing those changes back to the repository.
The plugin is also capable of making use of locally available FHIR resources for test data. The plugin looks in the input/tests folder (and any sub-folders, recursively) for a folder with the same name as the library being tested. For example, when running the ColorectalCancerElements library, the plugin will look in the input/cql/library/ColorectalCancerElements folder.
The evaluation log lists the directory that the plugin is using to find test data:
Executing CQL...
CQL path: <PROJECT>\input\cql
Data path: <PROJECT>\input\tests\library\ColorectalCancerElements
Terminology path: <PROJECT>\cqf-ccc\input\vocabulary\valuesetThe plugin supports defining any number of Test Cases, where each test case is a separate folder in the test folder. In this case, the plugin finds two folders:
- denom-EXM130
- numer-EXM130
Each of these folders contains JSON files with the FHIR resources representing the test case. The name of the test case is the name of the folder and is used as the patient id for the test case. This means that all the FHIR data in the folder must use this patient id:
{
"resourceType": "Patient",
"id": "denom-EXM130",
...
}{
"resourceType": "Encounter",
"id": "denom-EXM130-1",
"subject": {
"reference": "Patient/denom-EXM130"
},
...
}To create a new test case, add a new folder and place the resource files corresponding to the test case in the folder. As with the terminology, the plugin will search for any files in the folder and any sub-folders, recursively, attempting to load any files and interpret them as FHIR resources, so the files can be in XML or JSON format and can be either Bundles or individual resources, and organized in whatever folder structure makes the most sense for your team.
As with changes to the CQL and Vocabulary, once you have made changes and want to commit them back to the repository, follow the same process for committing and pushing changes described above.
Files can be named by Id only or Type and Id.
input\tests\library\PatientAndCoverage\minimal-patient-example\minimal-coverage-example.json
input\tests\library\PatientAndCoverage\minimal-patient-example\minimal-organization-example.json
input\tests\library\PatientAndCoverage\minimal-patient-example\minimal-patient-example.jsoninput\tests\library\PatientAndCoverage\minimal-patient-example\Coverage-mpe.json
input\tests\library\PatientAndCoverage\minimal-patient-example\Organization-mpe.json
input\tests\library\PatientAndCoverage\minimal-patient-example\Patient-mpe.jsonNOTE: Mixing
Id onlyandType and Idfile naming conventions is not allowed. If mixed mode is used the extension won't return results.
Once you have defined your test cases, right click anywhere in the library and select Execute CQL to run the library. The plugin will find the test cases and run the library for each test case, outputting the result of evaluating each expression definition in the library to the evaluation log:
Executing CQL...
CQL path: c:\Users\Bryn\Documents\Src\Github\CQFramework\cqf-ccc\input\cql
Data path: c:\Users\Bryn\Documents\Src\Github\CQFramework\cqf-ccc\input\tests\library\ColorectalCancerElements
Terminology path: c:\Users\Bryn\Documents\Src\Github\CQFramework\cqf-ccc\input\vocabulary\valueset
Patient=Patient(id=denom-EXM130)
AsOf=2023-09-28T11:59:30.162
...
Has Appropriate Colorectal Cancer Screening=false
Has History of Appropriate Colorectal Cancer Screening=false
Patient=Patient(id=numer-EXM130)
AsOf=2023-09-28T11:59:30.624
...
Has Appropriate Colorectal Cancer Screening=true
Has History of Appropriate Colorectal Cancer Screening=falseNOTE: The plugin reads what is on disk, so make sure to save any changes in the editor before running your tests
For step-by-step manual acceptance testing of all extension features — including server startup, syntax highlighting, diagnostics, hover, formatting, ELM output, CQL execution, the call graph, and the CQL debugger — see the UAT Script.
The most common error is Error: spawn /undefined/bin/java ENOENT, which indicates that JAVA_HOME is not set or accessible to the extension.
| Manager | Setup Instructions |
|---|---|
| SDKMAN! | Usually manages JAVA_HOME automatically. Ensure you have run sdk use java <version>. |
| jenv | Add jenv add ~/.jenv/plugins/java/set-java-home.zsh (or .bash) to your shell profile. |
| asdf | Ensure the Java plugin is set to export the environment variable: echo "java_home_export = yes" >> ~/.asdfrc. |
| mise | Ensure java_home = true is set in your .mise.toml or your shell is correctly evaluating mise activate. |
CQL libraries often declare parameters (e.g., Measurement Period) that must be supplied at evaluation time. The extension will support a three-tier parameter configuration system that lets you set parameter values globally, override them per library, and override them again per individual test case.
Parameters are defined in input/tests/config.json — the same file already used for Test Case Exclusion. The extension provides full IntelliSense (auto-complete and validation) for this file via a bundled JSON Schema.
| Tier | Key | Description |
|---|---|---|
global |
— | Applies to every library and every test case |
libraries |
library name | Overrides global for a specific library |
testCases |
library name → patient UUID | Overrides global and library for one specific test case |
When the same parameter name appears at multiple tiers, the highest-priority value wins.
In this example:
- All libraries receive
Measurement Period = Interval[@2024-01-01, @2024-12-31] -
MyQualityMeasureadditionally receivesProduct Line = HMO - The specific test case
a1b2c3d4-...overridesProduct LinewithMedicaid
Each parameter entry requires three fields:
| Field | Description |
|---|---|
name |
Must match the CQL parameter declaration exactly (case-sensitive) |
type |
One of the supported CQL types (see below) |
value |
String representation of the value |
type value |
CQL type | Notes |
|---|---|---|
String |
String |
Passed as-is |
Integer |
Integer |
Parsed to a whole number |
Decimal |
Decimal |
Parsed to a decimal number |
Boolean |
Boolean |
true or false (case-sensitive) |
DateTime |
DateTime |
CQL literal, e.g. @2024-01-01T00:00:00.000Z
|
Date |
Date |
CQL literal, e.g. @2024-01-01
|
Time |
Time |
CQL literal, e.g. @T12:00:00
|
Quantity |
Quantity |
CQL literal, e.g. 5 'mg'
|
Interval<DateTime> |
Interval<DateTime> |
CQL literal, e.g. Interval[@2024-01-01T00:00:00.000Z, @2025-01-01T00:00:00.000Z)
|
Interval<Date> |
Interval<Date> |
CQL literal, e.g. Interval[@2024-01-01, @2025-01-01)
|
Note: All types are parsed and converted to their native CQL runtime types by the language server before being passed to the engine. If a value cannot be parsed (e.g.
"abc"for anInteger, or a malformed interval literal), a warning is logged and the string value is passed through unchanged.
Each test case produces a TestCaseResult-<patientId>.json file in input/tests/results/<LibraryName>/. The file records which parameters were supplied via config.json and which fell back to the default value declared in the CQL library:
{
"parameters": [
{ "name": "Measurement Period", "type": "Interval<DateTime>", "value": "Interval[@2026-01-01T00:00:00.000Z, @2027-01-01T00:00:00.000Z)", "source": "config-global" },
{ "name": "Product Line", "type": "String", "value": "HMO", "source": "config-library" },
{ "name": "Some Default Param", "value": "42", "source": "default" }
],
"results": [...],
"errors": []
}parameters is a single unified list covering all parameters for the evaluation. Use the source field to understand where each value came from:
source |
Meaning |
|---|---|
"config-global" |
Supplied in the top-level global array of config.json
|
"config-library" |
Supplied in libraries.<LibraryName> of config.json
|
"config-test-case" |
Supplied in testCases.<LibraryName>.<patientUUID> (highest priority) |
"default" |
Not in config.json — fell back to the CQL-declared default expression |
Config-supplied entries appear first; CQL-default entries are appended. Default entries have no type field (the CQL type is not returned by the engine).
If a parameter appears in defaultParameters, you may want to add it to config.json to make the value explicit and version-controlled.
Test case keys in the testCases section are the patient UUID directory names found under input/tests/<LibraryName>/. For example, if your test case folder is:
input/tests/MyQualityMeasure/a1b2c3d4-e5f6-7890-abcd-ef1234567890/
then the key is a1b2c3d4-e5f6-7890-abcd-ef1234567890.
CQL execution can produce results in two formats: individual (default) and flat. The format is selected by the cql.execute.resultFormat VS Code setting (workspace or user scope), and can also be overridden per-project in config.json.
Each test case produces a separate JSON file written to:
input/tests/results/<LibraryName>/TestCaseResult-<patientId>.json
This format is well suited for programmatic consumption and for tracking per-test-case results over time, since each file is independently diff-able and can be committed to source control.
When executing a single test case, the result file opens automatically in the editor. When executing multiple test cases at once, a notification appears after all files are written.
Example result file:
{
"executedAt": "2026-04-09T14:22:00.000Z",
"libraryName": "MyQualityMeasure",
"testCaseName": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"testCaseDescription": "Patient meets denominator criteria",
"parameters": [
{ "name": "Measurement Period", "type": "Interval<DateTime>", "value": "Interval[@2024-01-01, @2024-12-31]", "source": "config-global" }
],
"results": [
{ "name": "Initial Population", "value": "true" },
{ "name": "Denominator", "value": "true" },
{ "name": "Numerator", "value": "false" }
],
"errors": []
}| Field | Description |
|---|---|
executedAt |
ISO 8601 timestamp of when execution started |
libraryName |
Name of the CQL library |
testCaseName |
Patient UUID (folder name), or null when no test data was found |
testCaseDescription |
The cqfm-testCaseDescription from the MeasureReport, if present |
parameters |
All parameters for this evaluation — see Test Case Parameters for the source field values |
results |
One entry per top-level define expression (excluding Error) |
errors |
Any Error-named expression values from the evaluation |
All test cases for a library are written to a single text file:
input/tests/results/<LibraryName>.txt
The file opens automatically in the editor after each run. This format matches the output style from earlier versions of the extension and is useful for quick, human-readable inspection.
Set the format globally in VS Code settings:
{
"cql.execute.resultFormat": "individual"
}Override it per project in input/tests/config.json:
{
"testCasesToExclude": [],
"resultFormat": "flat"
}The config.json setting takes precedence over the VS Code setting. When running multiple libraries via the Command Palette (CQL: Execute - Select Libraries), the individual format is always used regardless of either setting.
When the language server compiles a CQL file it must resolve every include statement to an
actual .cql file. This section explains how that resolution works and how you can configure
it per project.
The language server searches for included libraries in priority order:
-
Local
.cqlfiles — files on disk in your project (highest priority) -
npm packages — libraries distributed via FHIR IG npm packages in
~/.fhir/packages -
Bundled FHIRHelpers — built-in copies of
FHIRHelpersat selected versions (lowest priority)
For local file search, the language server performs a breadth-first search (BFS) starting
from the directory of the requesting library. It searches shallower directories before deeper
ones, so a file in input/cql/ beats a file in input/cql/subfolder/.
Tier 1 — the directory of the requesting library itself.
Tier 2 — the project's input/cql/ root (always enabled; skipped if tier 1 already is
input/cql/). This means a library in input/cql/measures/ can include a shared library
stored at input/cql/FHIRHelpers-4.0.1.cql without any configuration.
Configure how the language server matches versions in filenames using libraryResolution
in input/tests/config.jsonc:
| Mode | Behavior |
|---|---|
patch-flexible (default) |
Same major.minor required; accepts any patch version >= the requested patch. FHIRHelpers-4.0.1.cql satisfies include FHIRHelpers version '4.0.0'. |
strict |
Exact match only. FHIRHelpers-4.0.1.cql does not satisfy version '4.0.0'. |
{
"libraryResolution": "strict"
}The recommended way to share libraries across projects in a multi-project workspace is
namespace-qualified includes. The namespace is the package ID from the project's ig.ini:
include com.smiledigitalhealth.common.helper100 version '1.0.000'The language server reads each workspace project's ig.ini at startup, extracts the
packageId (e.g. com.smiledigitalhealth.common) and canonical URL, and registers them as
CQL namespaces. When a namespace-qualified include arrives, the language server routes it
directly to the matching project's input/cql/ without searching other tiers.
Requirement: A project must have an
ig.inifile to participate in cross-project resolution. Projects withoutig.iniare invisible to other projects.
By default, unqualified includes (e.g. include FHIRHelpers) are only searched within the
requesting project (tiers 1 and 2). Cross-project search for unqualified includes is
disabled by default to avoid accidental resolution from a sibling project when the
intended source is an npm package.
To enable it:
{
"unqualifiedCrossProjectSearch": true
}When enabled, the search expands to tier 3: other workspace projects' input/cql/ directories
(projects with ig.ini only). Control the search order and exclusions:
{
"unqualifiedCrossProjectSearch": true,
"projectSearchOrder": ["shared-libs", "common-terminology"],
"projectSearchExclude": ["reference-only"]
}-
projectSearchOrder— workspace folder names to search first; unlisted projects are searched alphabetically after listed ones. -
projectSearchExclude— workspace folder names to skip entirely (e.g. reference-only projects that should not contribute libraries).
All library resolution keys in input/tests/config.jsonc:
{
// Test case exclusions and parameters (existing keys) ...
// Library resolution (new in v0.9.4)
"libraryResolution": "patch-flexible", // "strict" | "patch-flexible" (default)
"unqualifiedCrossProjectSearch": false, // true to enable tier 3 search (default: false)
"projectSearchOrder": ["shared-libs"], // workspace folder names; unlisted → alphabetical
"projectSearchExclude": ["ref-only"] // excluded from tier 3 cross-project search
}The plugin supports interactive step-through debugging of CQL libraries using the VS Code Debug Adapter Protocol (DAP). Breakpoints can be set on any expression definition, and the Variables panel shows the result of each expression as it is evaluated.
The quickest way to start debugging is via the Debug CQL File button (▷ with a bug icon) that appears in the editor title bar whenever a .cql file is open. Clicking it launches the debugger using the active file.
Alternatively, press F5 or use Run > Start Debugging to launch via a launch.json configuration (see below).
Click in the gutter (left margin of the editor) on any line containing an expression definition to set a breakpoint. The debugger will pause before evaluating that expression and display its result in the Variables panel.
Note: Breakpoints on non-expression lines (e.g.,
librarydeclarations,usingstatements) are shown as unverified and are not hit during evaluation.
The standard VS Code step controls are available:
- Continue (F5) — resume evaluation to the next breakpoint
- Step Over (F10) / Step Into (F11) / Step Out (Shift+F11) — all advance to the next breakpoint (CQL's functional evaluation model does not support sub-expression stepping)
The debugger is configured via a .vscode/launch.json file in the workspace. When no launch.json exists, VS Code generates a default one the first time you press F5 or click the debug button.
The required configuration looks like this:
{
"version": "0.2.0",
"configurations": [
{
"type": "cql",
"request": "launch",
"name": "Evaluate CQL",
"libraryUri": "${fileDirname}",
"cqlFileUri": "${file}",
"libraryName": "${fileBasenameNoExtension}"
}
]
}| Field | Required | Description |
|---|---|---|
libraryUri |
Yes | URI to the directory containing the .cql files (use ${fileDirname}) |
libraryName |
Yes | CQL library name — must match the library declaration in the file (use ${fileBasenameNoExtension}) |
cqlFileUri |
No | URI to the specific .cql file being debugged (use ${file}; derived from libraryUri/libraryName if omitted) |
fhirVersion |
No | FHIR version to use (default: R4) |
terminologyUri |
No | URI to the directory containing FHIR ValueSet resources (defaults to input/vocabulary/valueset) |
modelUri |
No | URI to the directory containing patient test data (defaults to input/tests) |
context |
No | Evaluation context (default: Patient) |
contextValue |
No | Patient ID to use as the evaluation context |
Migrating an existing
launch.json: Configurations created before version 0.7.13 may have"libraryUri": "${file}"(pointing to the file rather than its directory). Update this to"libraryUri": "${fileDirname}"and add"cqlFileUri": "${file}"to ensure the debugger can correctly resolve library dependencies.
When the debugger is paused at a breakpoint the Variables panel shows three scopes:
| Scope | Content |
|---|---|
| Arguments | Parameter values passed to the current function (only shown when paused at a define function) |
| Expressions | Results of all define expressions evaluated so far in this session |
| Context | The evaluation context, e.g. Patient = <patient-id>
|
-
Step controls are coarse. Continue (F5), Step Over (F10), Step Into (F11), and Step Out (Shift+F11) all advance to the next breakpoint. CQL's functional evaluation model does not support stepping into individual sub-expressions within a
definebody. - Results are pre-computed. The engine runs all expressions to completion before the replay begins, so the Variables panel always shows the final result of an expression. This matches what you would see from Execute CQL and is correct for the vast majority of cases.
- No live data editing. Patient test data is loaded from disk at session start. To evaluate with different data, edit the FHIR resource files on disk and restart the debug session.
- Single-library scope. Only expressions defined in the library being debugged appear as breakpointable. Expressions in included libraries are evaluated but cannot be paused on.
- No conditional breakpoints. All breakpoints pause unconditionally regardless of any condition entered in VS Code's breakpoint editor.
The CQL debugger evaluates expressions against patient test data. At least one test case folder (a patient directory under input/tests/<LibraryName>/) must exist for the debug session to produce results. If no test data is found, expressions will evaluate to null.
See CQL Project Structure and Adding Test Data for details on how to set up test cases.
{ "testCasesToExclude": [], "parameters": [ { "name": "Measurement Period", "type": "Interval<DateTime>", "value": "Interval[@2024-01-01, @2024-12-31]" }, { "library": "MyQualityMeasure", "parameters": [ { "name": "Product Line", "type": "String", "value": "HMO" } ], "testCases": { "a1b2c3d4-e5f6-7890-abcd-ef1234567890": [ { "name": "Product Line", "type": "String", "value": "Medicaid" } ] } } ] }