Canvas Manifest

Every plugin has a CANVAS_MANIFEST.json file at the root of its package. The manifest names the plugin, lists the handlers and applications Canvas loads, and declares the variables, URL permissions, and custom data namespace the plugin needs.

The manifest is validated against a JSON schema when you run canvas validate, canvas validate-manifest, or canvas install. Unknown top-level keys and unknown component types fail validation.

Where the manifest lives #

canvas init creates a project with the plugin package inside it. The manifest sits at the top of the package directory, next to the plugin’s code, and every path in the manifest (handler classes, icons, templates, the README) is relative to that directory.

my_plugin/                       # project directory
├── pyproject.toml
├── tests/
│   └── test_event_handlers.py
└── my_plugin/                   # plugin package
    ├── CANVAS_MANIFEST.json
    ├── README.md
    ├── __init__.py
    ├── assets/
    │   └── icon.png
    ├── handlers/
    │   ├── __init__.py
    │   └── event_handlers.py
    └── templates/
        └── intake_form.yml

A handler in my_plugin/handlers/event_handlers.py is referenced as my_plugin.handlers.event_handlers:ClassName, and the icon as assets/icon.png. Pass the package directory, the one that holds CANVAS_MANIFEST.json, to canvas validate and canvas install: canvas validate my_plugin/my_plugin from outside the project, or canvas validate my_plugin from inside it.

Canvas runs only the handlers and applications the manifest lists. A handler class in the package that the manifest doesn’t reference is never loaded, and canvas validate warns about each one it finds. Every other file in the package directory still ships with the plugin, so your handlers can import your own modules and read templates and assets without listing them in the manifest. See canvas install for the files left out of the package.

Basic structure #

{
  "sdk_version": "0.1.4",
  "plugin_version": "0.0.1",
  "name": "my_plugin",
  "description": "A description of what this plugin does",
  "components": {
    "handlers": [],
    "applications": [],
    "commands": [],
    "questionnaires": []
  },
  "variables": [],
  "tags": {},
  "references": [],
  "license": "",
  "diagram": false,
  "readme": "./README.md"
}

Top-level fields #

FieldTypeRequiredDescription
sdk_versionstringYesThe Canvas SDK version the plugin was built against.
plugin_versionstringYesThe plugin’s version. Must not be empty. Increase it on every install. See Versioning your plugin.
namestringYesThe plugin’s name. Must not be empty. Use the plugin’s package name in snake case.
descriptionstringYesWhat the plugin does.
componentsobjectYesThe handlers, applications, commands, and questionnaires the plugin provides. Must contain at least one component type. See Components.
tagsobjectYesCategorization tags. Can be empty ({}). See Tags.
licensestringYesA license identifier such as "MIT", or an empty string.
readmestring or booleanYesPath to the plugin’s README, or false.
variablesarrayNoConfiguration values and secrets the plugin reads at runtime. See Variables.
secretsarrayNoDeprecated. Use variables instead.
url_permissionsarrayNoExternal URLs the plugin’s iframes may load, and what each may do. See URL permissions.
originsobjectNoLegacy form of url_permissions.
custom_dataobjectNoThe custom data namespace the plugin uses. See Custom data.
referencesarray of stringsNoLinks to related documentation or resources.
diagramstring or booleanNoPath to an architecture or workflow diagram, or false.

Versioning your plugin #

Change plugin_version every time you install a new build, including builds sent only to a test instance. Canvas accepts a reinstall with an unchanged version, so the version is the only way to tell which build an instance is running. It appears in:

  • the Plugins list in the Canvas Admin
  • canvas list, as name@version
  • the log lines written when the plugin is installed and loaded, for example Successfully loaded plugin "my_plugin", version 1.4.0

Use semantic versioning (MAJOR.MINOR.PATCH):

ChangeBumpExample
A fix that doesn’t change behavior users rely onPATCH1.4.0 → 1.4.1
New functionality that leaves existing behavior in placeMINOR1.4.1 → 1.5.0
A change that breaks existing behavior, such as a renamed variable, a removed handler, or a changed custom data modelMAJOR1.5.0 → 2.0.0

Versioning with git #

If you keep your plugin in a git repository, tie each version to the history:

  • Bump the version in the commit or pull request that changes the code. Each merged change then carries its own version, and a reviewer can see what the new number ships.
  • Install only committed code. An install of uncommitted changes runs a build no commit describes, even when its version looks familiar.
  • Tag the commit you install (for example git tag v1.5.0). A version seen in the logs or the Admin then leads straight to the source that produced it, and git diff v1.4.1 v1.5.0 shows what changed between two installs.

To tell test builds apart without spending release numbers, add a pre-release suffix such as 1.5.0-rc.1 or 1.5.0-dev.3, and drop the suffix for the build you release.

Components #

The components object groups everything Canvas loads from your plugin. Each key holds a list, and the object must contain at least one key.

KeyHolds
handlersClasses that respond to Canvas events and serve plugin APIs.
applicationsApplications that appear in the Canvas UI.
commandsCustom commands the plugin adds to notes.
questionnairesQuestionnaire templates Canvas creates on install.

Handlers #

Handlers respond to Canvas events: event handlers, SimpleAPI endpoints, CronTasks, action buttons, embedded applications, and the rest of the handler types.

protocols is deprecated in favor of handlers. Canvas still loads classes listed under protocols, so existing plugins keep working, but new plugins should use handlers. To migrate, rename the key. The entries inside it stay the same.

FieldTypeRequiredDescription
classstringYesImport path to the handler class, in package.module:ClassName form.
descriptionstringYesWhat the handler does.
metaobjectNoHandler metadata. Used by clinical quality measure protocols.
data_accessobjectNoAccepted by the schema with event, read, and write keys, but not enforced. A handler’s data access is not limited by this field.
{
  "components": {
    "handlers": [
      {
        "class": "my_plugin.handlers.patient_sync:PatientSync",
        "description": "Sends patient updates to an external system"
      }
    ]
  }
}

Applications #

Applications add entry points to the Canvas UI. See Applications for where each one appears and the handler that runs when a user opens it.

FieldTypeRequiredDescription
classstringYesImport path to the Application subclass, in package.module:ClassName form.
namestringYesDisplay name. Up to 32 characters.
descriptionstringYesWhat the application does. Up to 256 characters.
iconstringYesPath to an image inside the plugin package, or an https:// URL to one. Rendered at 48 × 48 px.
scopestringYesWhere the application appears. See Application Scopes for the values.
menu_positionstringNo"top" or "bottom". Defaults to "top".
menu_orderintegerNoOrder of the application within its menu position. Lower numbers appear first.
show_in_panelbooleanNoShows the application alongside the panel buttons instead of in the app drawer. Defaults to false.
panel_priorityintegerNoOrder of the application among panel applications.
{
  "components": {
    "applications": [
      {
        "class": "my_plugin.applications.risk_calculator:RiskCalculator",
        "name": "Risk Calculator",
        "description": "Calculates cardiovascular risk for the open patient",
        "icon": "assets/risk_calculator.png",
        "scope": "patient_specific",
        "show_in_panel": true,
        "panel_priority": 100
      }
    ]
  }
}

Embedded applications (note applications, scheduling applications, and docked applications) are declared under handlers, not applications, and take no scope or icon. See Embedded Applications.

Commands #

The commands list declares custom commands: commands with plugin-rendered HTML content that can be added to a note.

FieldTypeRequiredDescription
namestringYesUnique name for the command.
schema_keystringYesIdentifier the CustomCommand effect uses to refer to this command. Must be unique across every plugin installed on the instance, or installation fails.
labelstringNoLabel shown in the Canvas UI.
sectionstringNoNote section the command belongs to: subjective, objective, assessment, plan, procedures, history, or internal.
{
  "components": {
    "commands": [
      {
        "name": "RiskAssessment",
        "label": "Risk Assessment",
        "schema_key": "myPluginRiskAssessment",
        "section": "assessment"
      }
    ]
  }
}

Questionnaires #

The questionnaires list points at YAML templates in the plugin package. Canvas creates each questionnaire when the plugin is installed. See Questionnaires for the template format.

FieldTypeRequiredDescription
templatestringYesPath to the questionnaire’s YAML template inside the plugin package.
{
  "components": {
    "questionnaires": [
      {
        "template": "templates/intake_form.yml"
      }
    ]
  }
}

Content, effects, and views #

content, effects, and views are accepted for compatibility and have no effect. canvas init adds them as empty lists, and you can remove them.

Variables #

variables declares the configuration values and secrets a plugin reads from self.secrets at runtime. Values are set at install time with canvas install --variable or --secret, or later in the Admin UI. See Managing Variables.

FieldTypeRequiredDescription
namestringYesThe key the value is read under in self.secrets.
sensitivebooleanNoWhen true, the value is hidden in the Admin UI and CLI listings. Defaults to false.
defaultstringNoAccepted by the schema for non-sensitive variables only. Canvas does not pre-fill the variable with it, so set the value at install time.
{
  "variables": [
    {"name": "API_TOKEN", "sensitive": true},
    {"name": "API_BASE_URL"}
  ]
}

The older secrets field is a list of names, each treated as a non-sensitive variable. canvas validate-manifest prints a deprecation warning when a manifest uses it.

{
  "secrets": ["API_TOKEN"]
}

URL permissions #

A LaunchModalEffect or PortalWidget only loads URLs and external scripts listed in url_permissions. Each entry names a URL and the iframe capabilities it is granted.

FieldTypeRequiredDescription
urlstringYesThe URL to allow, in CSP host-source format.
permissionsarray of stringsYesCapabilities granted to the URL. Can be empty.
PermissionGrants
SCRIPTSJavaScript execution
ALLOW_SAME_ORIGINSame-origin access
MICROPHONEMicrophone access
CAMERACamera access
CLIPBOARD_READReading from the clipboard
CLIPBOARD_WRITEWriting to the clipboard
{
  "url_permissions": [
    {
      "url": "https://example.com",
      "permissions": ["SCRIPTS", "ALLOW_SAME_ORIGIN", "MICROPHONE"]
    }
  ]
}

See Additional Configuration on the layout effects page for when each permission is needed.

Origins (legacy) #

origins is the older form of url_permissions. A manifest can use one or the other, not both. Canvas converts each urls entry to a URL with no permissions and each scripts entry to a URL with SCRIPTS.

{
  "origins": {
    "urls": ["https://example.com"],
    "scripts": ["https://cdn.example.com"]
  }
}

Custom data #

custom_data declares the namespace a plugin stores custom data in, and whether it can write to it. See the Custom Data Quick Start.

FieldTypeRequiredDescription
namespacestringYesNamespace name in org__name form: lowercase letters, digits, and underscores, with a double underscore between the two parts. Up to 63 characters.
accessstringYes"read" for read-only access, or "read_write".
{
  "custom_data": {
    "namespace": "my_org__my_plugin",
    "access": "read_write"
  }
}

Tags #

Tags categorize a plugin. Unrecognized tag categories and values produce warnings during validation rather than errors.

CategoryAllowed values
patient_sourcing_and_intakesymptom_triage, coverage_capture
interaction_modes_and_utilizationsupply_policies, demand_policies, auto_followup
contentpatient_intake
diagnostic_range_and_inputsNone yet. Use an empty list.
pricing_and_paymentsNone yet. Use an empty list.
care_team_compositionNone yet. Use an empty list.
interventions_and_safetyNone yet. Use an empty list.
{
  "tags": {
    "patient_sourcing_and_intake": ["symptom_triage"]
  }
}

Complete example #

A plugin with a patient chart application, the SimpleAPI handler that serves it, a custom command, and a sensitive variable:

{
  "sdk_version": "0.1.4",
  "plugin_version": "1.0.0",
  "name": "risk_calculator",
  "description": "Calculates clinical risk scores for patients",
  "url_permissions": [
    {
      "url": "https://risk.example.com",
      "permissions": ["SCRIPTS", "ALLOW_SAME_ORIGIN"]
    }
  ],
  "components": {
    "applications": [
      {
        "class": "risk_calculator.applications.calculator:RiskCalculatorApp",
        "name": "Risk Calculator",
        "description": "Calculates cardiovascular risk for the open patient",
        "icon": "assets/risk_calculator.png",
        "scope": "patient_specific",
        "show_in_panel": true,
        "panel_priority": 100
      }
    ],
    "handlers": [
      {
        "class": "risk_calculator.handlers.api:RiskCalculatorAPI",
        "description": "Serves the calculator page and its data"
      }
    ],
    "commands": [
      {
        "name": "RiskAssessment",
        "label": "Risk Assessment",
        "schema_key": "riskCalculatorAssessment",
        "section": "assessment"
      }
    ]
  },
  "variables": [
    {"name": "RISK_API_TOKEN", "sensitive": true}
  ],
  "tags": {},
  "references": [],
  "license": "MIT",
  "diagram": false,
  "readme": "./README.md"
}

Validation #

Validate a manifest with the Canvas CLI:

canvas validate my_plugin

canvas validate checks the manifest against the schema and then loads every handler in the plugin sandbox, which catches disallowed imports and other errors that only appear on the instance. canvas validate-manifest my_plugin runs the manifest checks alone.