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 #

NameTypeRequiredDescription
note_uuidstringtrueThe externally exposable id of the note in which to insert the command.
command_uuidstringtrueThe externally exposable id of the command which is being referenced.
schema_keystringtrueIdentifier for data binding. Must match the schema_key in your manifest configuration and must be unique across every plugin installed on the instance.
contentstringtrueHTML content for display in the chart.
print_contentstringfalseHTML 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, or internal

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_html in the same list as originate(), before any commit().
  • content is 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 content frame 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_key must be unique across every plugin installed on the Canvas instance. Installing a plugin whose schema_key is already owned by another installed plugin will fail with a validation error.