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, or canvas validate-manifest, and Canvas Platform checks it again on every canvas deploy push. 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
└── acme__my_plugin/             # plugin package
    ├── CANVAS_MANIFEST.json
    ├── OVERVIEW.md
    ├── README.md
    ├── __init__.py
    ├── assets/
    │   └── icon.png
    ├── handlers/
    │   ├── __init__.py
    │   └── event_handlers.py
    └── templates/
        └── intake_form.yml

A handler in acme__my_plugin/handlers/event_handlers.py is referenced as acme__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 deploy: canvas validate my_plugin/acme__my_plugin from outside the project, or canvas validate acme__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. canvas deploy ships the files committed to the plugin’s git repository, so .gitignore decides what is left out. See Plugin repository layout.

Basic structure #

{
  "sdk_version": "0.1.4",
  "plugin_version": "0.0.1",
  "name": "acme__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 deploy. See Versioning your plugin.
namestringYesThe plugin’s name, which is also its package folder name. For a plugin deployed through Canvas Platform it is publisher-prefixed, <org prefix>__<package> (for example acme__intake): lowercase letters, digits and underscores, starting with a letter on both sides of the double underscore. canvas login lists your organizations’ prefixes.
descriptionstringYesWhat the plugin does, in one sentence. The Canvas Platform catalog shows it under the plugin’s title and on its card.
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.
catalogobjectNoThe plugin’s listing in the Canvas Platform plugin catalog. See Catalog.
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 deploy a new build, including builds sent only to a test instance. Canvas accepts a redeploy 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 "acme__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 #

canvas deploy deploys a commit pushed to the plugin’s git repository on Canvas Platform, so every build on an instance maps to a commit. Tie each version to that 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.
  • Tag the commit you release (for example git tag v1.5.0, then git push origin v1.5.0). A version seen in the logs or the Admin then leads straight to the source that produced it, git diff v1.4.1 v1.5.0 shows what changed between two releases, and canvas deploy <package dir> --ref v1.5.0 --instance <instance> deploys exactly that tag.

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": "acme__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": "acme__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 per instance with canvas config set. See Managing Variables.

namespace_read_access_key and namespace_read_write_access_key are reserved for a plugin deployed through Canvas Platform: Canvas Platform supplies them to plugins entitled to a custom data namespace, and refuses a manifest that declares them.

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 with canvas config set.
{
  "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"
  }
}

Catalog #

catalog is the plugin’s listing in the Canvas Platform plugin catalog. Canvas Platform reads it from the manifest each time a push moves the plugin’s main branch, so the listing ships with the code it describes; pushes to other branches and tags leave the catalog as it is. It refuses a push whose listing breaks the rules below. canvas deploy --push-only updates the listing without deploying to an instance, and canvas validate checks it locally.

{
  "catalog": {
    "title": "Intake Summary",
    "kind": "plugin",
    "category": "Charting",
    "surfaces": ["Note"],
    "keywords": ["intake", "summary"],
    "screenshots": [
      {"path": "assets/summary.png", "caption": "In the note", "alt": "An intake summary in a note"}
    ],
    "pricing": [
      {"unit": "visit", "price_per_unit": "1.50"},
      {"unit": "token"}
    ],
    "vendors": [
      {"name": "Twilio", "relationship": "self_managed", "additional_fees": true}
    ],
    "icon": "assets/icon.png",
    "setup_guide": "setup_guide.md",
    "setup_instructions": "setup_instructions.md",
    "release_notes": {"kind": "fix", "title": "Summaries keep their line breaks"}
  }
}
FieldTypeRequiredDescription
titlestringYesThe name on the plugin’s card and page. At most 64 characters.
categorystringYesOne of Billing & RCM, Charting, Decision support, Interoperability, Labs & devices, Operations, Patient engagement, Population health, Prescribing, Scheduling.
surfacesarrayYesEvery place in Canvas the plugin’s work shows up, at least one and none repeated: Note, Chart app, Command, Background, Patient portal, Waffle.
kindstringNoagent for a plugin that calls a model and acts with latitude, or plugin for a deterministic one. Defaults to plugin.
agentobjectWhen kind is agentThe agent’s boundary: does, does_not, runs_when (text) and models (at least one model name). A plugin must leave it out.
keywordsarrayNoFree search terms, separate from Tags: lowercase letters, digits and hyphens, up to 32 characters each.
screenshotsarrayNoUp to eight images, in display order. Each has a path ending in .png, .jpg, .jpeg or .webp in lowercase, a required alt of at most 200 characters, and an optional caption of at most 40.
pricingarrayNoWhat Canvas charges, up to 10 components in the order the plugin’s page lists them. Each has a unit in the singular (at most 40 characters, named once) and a price_per_unit: a string of US dollars such as "5.75", with at most four decimal places. The token unit takes no price, because the inference gateway prices tokens by model. Leave pricing out for a plugin with no charge.
vendorsarrayNoThe third parties the plugin depends on, up to 10. Each has a name (at most 64 characters, named once), a relationship of canvas_managed (Canvas sets up the account) or self_managed (the customer does), and additional_fees: true when the vendor bills the customer directly for its own fees, separately from Canvas.
iconstringNoPath to the image shown beside the title on the plugin’s card, ending in .png, .jpg, .jpeg or .webp in lowercase.
setup_guidestringNoPath to a markdown file describing setup for the person deciding whether to install the plugin: what to have ready, the steps, how long it takes. The plugin’s page shows it under How setup works.
setup_instructionsstringNoPath to a file that Studio’s agent reads when an organization installs the plugin. It is not shown on the plugin’s page.
release_notesobjectNoWhat changed in this plugin_version: kind (fix, performance or breaking), title, and an optional body. Canvas Platform assembles the history from every pushed revision.

Every path is inside the package folder: no leading /, no .. anywhere in it, and no backslash. Unknown fields are refused at every level, and optional fields are left out rather than set to null.

Someone holding the Publisher role in the plugin’s organization can also edit the listing from the plugin’s page in the Canvas Platform catalog, including the description. Saving there is a commit to main with the listing changed and a patch bump of plugin_version, so pull before your next push. The edit is refused unless plugin_version on main is a plain MAJOR.MINOR.PATCH version, so a pre-release suffix such as 1.5.0-rc.1 blocks it.

An OVERVIEW.md file beside CANVAS_MANIFEST.json in the package folder is the plugin’s overview, and needs no field in catalog. When it is there, the plugin’s page shows it as rendered markdown beside the plugin’s pricing and setup. canvas init creates one to start from.

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 acme__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.