Skip to main content

canvas.yaml reference

Every canvas is one file in your tenant configuration repository:

canvases/{tenant}/{canvas-id}.canvas.yaml

Merging a change to this file makes it live within five minutes — no deployment, no Salesforce change set. This page documents every block. The example threads through a departure rooming list from the Meridian Travel Co. reference implementation.

Full shape

canvas:
id: rooming-list # URL-safe identifier; must match the filename
name: Rooming List # display name
version: "1.0.0"
description: Departure rooming list with per-hotel tables and send actions

connection:
tenant: meridian # owning tenant
environment: uat # default org; overridden by org resolution at runtime

queries: # named SOQL, executed in parallel
passengers: |
SELECT KaptioTravel__FirstName__c, KaptioTravel__LastName__c,
KaptioTravel__AllocationId__c,
KaptioTravel__Itinerary__r.KaptioTravel__Itinerary_No__c
FROM KaptioTravel__Passenger__c
WHERE KaptioTravel__Itinerary__r.Package_Departure__c = :recordId
AND KaptioTravel__Itinerary__r.KaptioTravel__Status__c = 'Booking Confirmed'
ORDER BY KaptioTravel__Itinerary__r.KaptioTravel__Itinerary_No__c
departure: |
SELECT Id, Name, KaptioTravel__Date__c, KaptioTravel__Package__r.Name
FROM KaptioTravel__PackageDeparture__c
WHERE Id = :recordId
LIMIT 1

data_sources: # optional REST sources alongside SOQL
weather:
provider: rest
url: "https://api.example.com/forecast?location={{recordId}}"
result_path: daily # optional dot-path into the JSON response

transform: | # JavaScript view-model precomputation (optional)
const byRoom = new Map();
for (const p of data.passengers) {
const key = p.KaptioTravel__Itinerary__r.KaptioTravel__Itinerary_No__c
+ '-' + (p.KaptioTravel__AllocationId__c || '');
if (!byRoom.has(key)) byRoom.set(key, []);
byRoom.get(key).push(p);
}
return {
rooms: [...byRoom.values()].map((travelers, i) => ({ no: i + 1, travelers })),
departure: data.departure[0] || null,
};

actions: # buttons the rendered HTML can trigger (optional)
compose_final:
type: compose_email
render_canvas: rooming-list-email-final
target_record_id: "{{formData.targetrecordid}}"
parameters:
recordId: "{{recordId}}"

template:
engine: handlebars # handlebars | html
style: slds # slds | standalone | minimal
html: |
<h2>Rooming List for {{departure.KaptioTravel__Package__r.Name}}</h2>
<table>
<tr><th>Room</th><th>Travelers</th></tr>
{{#each rooms}}
<tr>
<td>{{this.no}}</td>
<td>{{#each this.travelers}}{{this.KaptioTravel__FirstName__c}} {{this.KaptioTravel__LastName__c}}{{#unless @last}}, {{/unless}}{{/each}}</td>
</tr>
{{/each}}
</table>
<button class="slds-button slds-button_neutral"
data-action="compose_final" data-compose="true"
data-targetrecordid="{{departure.Id}}">Send Final Rooming List</button>

Block by block

canvas

FieldRequiredNotes
idyesLowercase alphanumeric with dashes. Forms the render URL.
nameyesShown as the page title and in tooling.
versionnoSemantic version string, defaults to 1.0.0.
descriptionnoFree text; use it to record what the canvas replaces or serves.

connection

Declares the owning tenant and a default Salesforce environment. At runtime, requests arriving through the org route (/canvas/org/{orgId}/...) resolve the environment from your blueprint's salesforce_environments, which overrides this default. The block exists so direct tenant-route requests and local tooling have a sensible fallback.

queries

A map of name → SOQL. All queries run in parallel, server-side, through the tenant's stored OAuth connection.

  • Bind variables:recordId (and any other parameter passed by the embedding component or an action) is substituted with proper escaping before execution. A request that leaves a bind unresolved is rejected with UNRESOLVED_QUERY_BINDS rather than silently querying wrong data.
  • Results — each query's rows appear in the template context under its name. Relationship fields arrive as nested objects, exactly as the Salesforce REST API returns them: record.KaptioTravel__Itinerary__r.Name.
  • Failure isolation — a failing query does not fail the canvas. Its name is recorded in _failedQueries (with the message in _queryErrors) and the rest of the data renders. Templates can surface this to the user.
  • A single query: (string) is also supported for one-query canvases; its rows land in records.

data_sources

Optional REST sources fetched alongside the SOQL, for enriching a canvas with non-Salesforce data. {{recordId}}, {{tenant}}, {{sfEnvironment}}, and platform URL placeholders are substituted into the url. result_path optionally selects a subtree of the JSON response. Failures degrade to an empty result, never a broken canvas.

transform

The body of a JavaScript function, executed server-side, receiving the full data object (all query results plus context keys). Its purpose is view-model precomputation: grouping, sorting, date formatting, flags, derived strings — so the Handlebars template stays presentation-only. Whatever object the transform returns is merged into the template context.

Context keys available to the transform:

KeyContents
data.<queryName>Rows for each named query
data._tenant, data._sfEnvironmentResolved connection context
data._configThe canvas's config: defaults merged with your blueprint's canvas_config overrides — the supported way to vary display options per tenant without forking the canvas
data._failedQueries, data._queryErrorsPer-query failure information
Request parametersrecordId and any other query parameters, exposed after the transform runs

Keep transforms deterministic and side-effect free. They have no network or filesystem access.

actions

Buttons in the rendered HTML trigger named actions. See Actions for the two action types (api_call, compose_email), parameter substitution, and button conventions.

template

FieldValuesNotes
enginehandlebars (default), htmlhtml renders the string verbatim.
styleslds (default), standalone, minimalslds wraps output with the Salesforce Lightning Design System stylesheet — use for record-page widgets. standalone emits your HTML with no wrapper — use for email bodies and externally embedded surfaces.
htmlstringThe template. Include a <style> block for widget-specific CSS.

Handlebars helpers available in templates include comparison (eq, ne, gt, lt, ifCond), logic (and, or, not), array (length, first, last, join, findByField), string (uppercase, truncate, contains), math (add, multiply), and formatting (formatDate, formatCurrency, formatNumber) helpers.

mockData

Optional records used as a fallback when a single-query canvas returns no rows — useful for demo tenants and previews. Leave it out for operational canvases, so an empty state renders honestly instead of showing sample data.