Custom Command
Introduction #
The CustomCommand class allows plugins to create custom commands with HTML-rendered content that can be inserted into patient charts. Custom commands are designed for displaying read-only content and do not support user input or interactive forms.
Important: Custom commands must be configured in the plugin’s CANVAS_MANIFEST.json file under the commands array before they can be used.
Parameters #
| Name | Type | Required | Description |
|---|---|---|---|
note_uuid | string | true | The externally exposable id of the note in which to insert the command. |
command_uuid | string | true | The externally exposable id of the command which is being referenced. |
schema_key | string | true | Identifier for data binding. Must match the schema_key in your manifest configuration and must be unique across every plugin installed on the instance. |
content | string | true | HTML content for display in the chart. |
print_content | string | false | HTML content for print version (recommended for optimal print output). |
Manifest Configuration #
Custom commands must be declared in your CANVAS_MANIFEST.json:
{
"components": {
"commands": [
{
"name": "RiskAssessment",
"label": "Risk Assessment",
"schema_key": "riskAssessment",
"section": "assessment"
}
]
}
}
Manifest Fields #
- name: Unique name for the command
- label: User-friendly label displayed in Canvas UI
- schema_key: Identifier for the command. Must be unique across every plugin installed on the Canvas instance — if another installed plugin already declares the same
schema_key, installation will be rejected. CustomCommand instances must use this value. - section: Chart section where command appears:
subjective,objective,assessment,plan,procedures,history, orinternal
Note: schema_key values must be unique across all plugins installed on the instance, not just within a single plugin. If you install a plugin whose manifest declares a schema_key already owned by another installed plugin, the installation fails with a clear validation error instead of silently overwriting the existing command. Choose a distinctive schema_key — for example, prefixing it with your plugin’s name — to avoid collisions.
Basic Usage #
Step 1: Create HTML Templates #
Create a template file for your command content (e.g., templates/risk_assessment.html):
<!DOCTYPE html>
<html>
<head>
<style>
.risk-assessment {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding: 16px;
}
.risk-header {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
}
.risk-item {
padding: 8px;
margin-bottom: 8px;
background: #f7fafc;
border-radius: 4px;
}
.risk-level {
font-weight: 600;
color: #c53030;
}
</style>
</head>
<body>
<div class="risk-assessment">
<div class="risk-header">Risk Assessment</div>
<div class="risk-item">
<span class="risk-level">Cardiovascular Risk: High</span>
<p>Hypertension, family history of heart disease</p>
</div>
<div class="risk-item">
<span class="risk-level">Falls Risk: Moderate</span>
<p>Age over 65, history of dizziness</p>
</div>
</div>
</body>
</html>
Create a simpler print version (e.g., templates/risk_assessment_print.html):
<!DOCTYPE html>
<html>
<head>
<style>
.risk-assessment-print {
font-family: Arial, sans-serif;
font-size: 11px;
.section {
margin-bottom: 8px;
}
.label {
font-weight: bold;
}
}
</style>
</head>
<body class="risk-assessment-print">
<h3>Risk Assessment</h3>
<div class="section">
<span class="label">Cardiovascular Risk:</span> High - Hypertension, family history
</div>
<div class="section">
<span class="label">Falls Risk:</span> Moderate - Age over 65, history of dizziness
</div>
</body>
</html>
Step 2: Use Templates in Your Command #
from canvas_sdk.commands.commands.custom_command import CustomCommand
from canvas_sdk.templates import render_to_string
import uuid
command = CustomCommand(
schema_key="riskAssessment",
content=render_to_string("templates/risk_assessment.html"),
print_content=render_to_string("templates/risk_assessment_print.html")
)
command.command_uuid = str(uuid.uuid4())
command.note_uuid = "rk786p"
effect = command.originate()
Extended CustomCommand Class (For Reusability) #
Create a subclass with a predefined schema_key:
from canvas_sdk.commands.commands.custom_command import CustomCommand
from canvas_sdk.templates import render_to_string
import uuid
class RiskAssessmentCommand(CustomCommand):
"""Custom command for risk assessment."""
class Meta:
schema_key = "riskAssessment"
# Usage
command = RiskAssessmentCommand(
content=render_to_string("templates/risk_assessment.html"),
print_content=render_to_string("templates/risk_assessment_print.html")
)
command.command_uuid = str(uuid.uuid4())
command.note_uuid = "rk786p"
effect = command.originate()
Methods #
originate() #
Returns an Effect that originates a new command in the note body.
Example:
from canvas_sdk.commands.commands.custom_command import CustomCommand
from canvas_sdk.templates import render_to_string
import uuid
command = CustomCommand(
schema_key="riskAssessment",
content=render_to_string("templates/risk_assessment.html"),
print_content=render_to_string("templates/risk_assessment_print.html")
)
command.command_uuid = str(uuid.uuid4())
command.note_uuid = "rk786p"
effect = command.originate()
Content vs Print Content #
Custom commands support two versions of content:
Display Content (content) #
- Rendered in the Canvas UI when viewing the chart
- Can include rich styling and complex layouts
Print Content (print_content) #
- Rendered when printing the chart or generating PDFs
- Should be simpler and more compact
Best Practice: Always provide both versions for the best user experience.
Collapsible Content #
The content frame stops responding to clicks once the command is committed or the note is locked, so a toggle inside content can’t be opened in a finished note. To show a long, read-only record without it taking up the note, attach it with set_custom_html inside a <details> element. Custom HTML renders under the command’s title in its own frame. That frame keeps responding to clicks after the command is committed or the note is locked, and it grows when the provider opens the <details> element.
- Set the custom HTML while the command is still staged: return
set_custom_htmlin the same list asoriginate(), before anycommit(). contentis still required. Keep it to a short summary; the custom HTML appears above it.- Custom HTML isn’t printed, so put the full record in
print_content.
from canvas_sdk.commands.commands.custom_command import CustomCommand
from canvas_sdk.templates import render_to_string
import uuid
full_record = render_to_string("templates/intake_record.html")
command = CustomCommand(
schema_key="intakeRecord",
content="<p>Intake submitted. 3 answers flagged for review.</p>",
print_content=full_record
)
command.command_uuid = str(uuid.uuid4())
command.note_uuid = "rk786p"
effects = [
command.originate(),
command.set_custom_html(
f"<details><summary>Show full intake</summary>{full_record}</details>"
),
]
Limitations #
- Custom commands are read-only and cannot capture user input
- Interactive elements (forms, buttons, input fields) are not supported, and the
contentframe stops responding to clicks once the command is committed or the note is locked. To let providers expand long content, see Collapsible Content. - Commands must be configured in the manifest before use
- The
schema_keymust be unique across every plugin installed on the Canvas instance. Installing a plugin whoseschema_keyis already owned by another installed plugin will fail with a validation error.