Label Templates
Label templates drive the printable outputs in Albert, such as inventory lot barcode labels, batch task labels, and formula reports. A label template is a Mustache HTML file stored for your tenant, plus a template record that names it, types it, and holds its page options.
This page covers the end-to-end SDK workflow and, most importantly, the rules for authoring the template HTML file itself.
End-to-end workflow
Create a template and print a Lot label
from pathlib import Path
from albert import Albert
from albert.resources.label_templates import LabelTemplate, LabelTemplateType
client = Albert.from_client_credentials()
# 1) Create the template, uploading its HTML file
template = LabelTemplate(
name="3x1 Inventory Label",
type=LabelTemplateType.INVENTORY,
template_file="3x1-inventory-label.html",
metadata={
"width": "3in",
"height": "1in",
"margin": {"top": "0mm", "bottom": "0mm", "left": "0mm", "right": "0mm"},
},
)
created = client.label_templates.create(
label_template=template,
template_html=Path("3x1-inventory-label.html").read_text(),
)
# 2) Generate a label PDF for a Lot
url = client.label_templates.generate_label_pdf(
inventory_lot_number_id="LOTB1234",
template_id=created.id,
)
# url is a short-lived link to the rendered PDF
Where the HTML file lives
You never upload to S3 yourself. create(template_html=...) sends the HTML through the template API, which stores it for your tenant under the template_file name; that name is the file's identity. There is no URL at create time: when a label is printed, the platform resolves the template record's template_file into the stored file's URL (returned as payload.template.body) and the renderer fetches it server-side.
Two consequences of the name-is-identity model:
- Uploading a file with the same
template_filename replaces the stored file for the whole tenant, so pick distinct names unless replacement is what you want. update()can repoint a template record to a differenttemplate_filename, but the only way to upload file content iscreate(). To revise a template's HTML, create a new template with the corrected file (or re-upload under the same file name and delete the extra record).
To inspect what data a template will receive (or to render manually), use get_print_payload:
Inspect the render payload
Anatomy of a template file
A template file is a complete, ordinary HTML document with placeholders where the label's data goes. The placeholder syntax is Mustache: {{info.inventoryName}} inserts a value, {{#info.Symbols}}...{{/info.Symbols}} repeats a block for each item in a list, and {{{info.lotNumber}}} (triple braces) inserts a value without HTML escaping. That is all the syntax there is; if you can write HTML and CSS, you can write a label template. At print time the placeholders are filled in and the page is printed to PDF by headless Chrome.
The placeholder names are not arbitrary: they are the fields of the print payload assembled for your template's type. The full list per type is in Data available to each template type below, and get_print_payload(type=...) returns the exact data (payload.data["labels"]) your template will be rendered with.
The general shape is:
<html>
<head>
<meta charset="UTF-8">
<!-- DO NOT MODIFY the below commented line, API (template/print) is consuming it -->
<!--metadata:{
"width": "3in",
"height": "1in",
"margin": {"top": "0mm", "bottom": "0mm", "left": "0mm", "right": "0mm"},
"renderBackgroundImage": true
}-->
<style>
/* Inline CSS. Fixed sizing is typical: 1in = 96px. */
body { width: 100%; overflow: hidden; }
</style>
</head>
<body>
{{#labels}}
<!-- One block per printed entity -->
<div class="label-container">
...
</div>
{{/labels}}
</body>
</html>
Key rules:
- Wrap the body in
{{#labels}} ... {{/labels}}. The render data is{"labels": [...]}with one entry per printed entity, and each entry's fields live underinfo. - The
<!--metadata:{...}-->comment must contain valid JSON. It is parsed out of the file and merged over the template record'smetadata; the merged object supplies the PDF page options (width,height,margin, and so on). - The page renders in headless Chrome and waits for network idle, so externally hosted fonts, stylesheets, and images load before printing. Inline
<script>tags also run before printing, so DOM post-processing is supported. - Size the page explicitly. Set
width/heightin the metadata (or template recordmetadata) and size your containers to match, withoverflow: hiddento prevent spill. When printing multiple entities in one PDF, usepage-break-before: always/page-break-after: alwayson the per-label container.
The metadata block
The metadata block carries two kinds of keys: PDF page options consumed by the renderer, and label-assembly settings consumed while gathering the data. The same keys can also be set on the template record's metadata when creating via the SDK; the file block wins where both define a key.
Page options (the renderer reads exactly these and ignores anything else; see PDFOptions):
| Key | Purpose |
|---|---|
width, height |
Page size as CSS lengths (e.g. "3in", "1in"). Both must be set to apply. |
format |
Named paper size (e.g. "A4", "Letter") as an alternative to width/height. |
margin |
Object with top/bottom/left/right. |
landscape |
Render in landscape orientation when true. |
renderBackgroundImage |
Print CSS background colors and images when true. |
hideHeaderFromFirstPage |
Skip the header on page 1 (title-page pattern) when true. |
Label-assembly settings:
| Key | Purpose |
|---|---|
hasBlackPictos |
Use the black (no-line) pictogram set. |
useGhsOfficialPictos |
Use official GHS pictogram images. |
numberOfPictos |
Cap how many hazard pictograms are passed to the template. |
header, footer |
File names of separate header/footer HTML files. |
languageCode |
Language for regulatory precautionary phrases (default EN). |
templateId |
Informational name for the file. |
Headers, footers, and page numbers
For multi-page outputs (reports, certificates), a template can have separate header and footer HTML files. Reference them in the metadata block ("header", "footer") or upload them via create(header_html=..., footer_html=...), with their file names in the template record's metadata.
Header and footer templates repeat on every page and support special classes that the renderer fills in:
<div style="font-size: 8px; width: 100%; text-align: right; padding-right: 1cm;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
Available classes: pageNumber, totalPages, date, title. Set hideHeaderFromFirstPage: true in the options to keep the first page clean while headers appear on subsequent pages. Size the header/footer with inline styles and leave room for them via the page margin.
Data available to each template type
Each entry in {{#labels}} exposes its fields under info (except propertytaskreport, noted below). All types also receive {{info.currentUser.name}} and {{info.currentUser.email}} (the printing user). Whatever the tables below say, get_print_payload(type=...) is always the ground truth for what a given template receives.
inventory (Lot barcode labels)
Print with inventory_lot_number_id (one or more Lot IDs).
| Field | Contents |
|---|---|
{{info.albertId}} |
The parent Inventory ID. |
{{info.inventoryName}} |
Inventory item name. |
{{info.alias}} |
Inventory alias. |
{{{info.lotNumber}}} |
Barcode image URL for the lot. Use inside <img src="...">. |
{{{info.lotNumberQrCode}}} |
QR code image as a data: URI. Use inside <img src="...">. |
{{info.vendorLotNumber}} |
Manufacturer lot number. |
{{info.Supplier}} / {{info.manufacturer}} |
Supplier/manufacturer company name. |
{{info.owner}} |
Lot owner name. |
{{info.lotCreationDate}} |
Lot creation date. |
{{info.expirationDate}} |
Lot expiration date. |
{{info.locationAddress}} |
Location address. |
{{info.sublocation}} |
Location and storage location names combined. |
{{info.inventoryCategory}} |
Parent category (e.g. RawMaterials). |
{{info.rsnNumber}}, {{info.idh}} |
Values from inventory metadata where present. |
{{info.logo}} |
Tenant logo image URL. |
{{#info.Symbols}} |
Loop of hazard pictogram image URLs. |
{{info.AdditionalInfo.InventoryInfo...}} |
The full Inventory object, including Metadata.<customField>. |
{{info.AdditionalInfo.LotInfo...}} |
The full Lot object (e.g. StorageLocation.name, manufacturerLotNumber). |
{{info.AdditionalInfo.RegulatoryInfo...}} |
Regulatory data for the item (hazard statements, precautionary phrases). |
batch (Product/Formula lot labels)
Print with albert_id (the formula Inventory ID) and task_id (the batch task). One entry total.
| Field | Contents |
|---|---|
{{info.albertId}} / {{info.formulaID}} |
Formula Inventory ID (prefix stripped). |
{{info.formulaName}} |
Formula name. |
{{info.lotId}} |
Batch lot display ID (e.g. B123-4). |
{{info.lotNumber}} |
Batch lot number prefix as text (no barcode image for this type). |
{{info.taskID}}, {{info.taskName}} |
Batch task ID and name. |
{{info.batchWeight}} |
Batch size. |
{{info.batchStartDate}} |
Task start date. |
{{info.batchLocation.name}} |
Task location. |
{{info.taskOwner}} |
Assigned user name. |
{{info.taskTags}} |
Task tags. |
{{info.Project.id}}, {{info.Project.name}}, {{info.Project.Technology}} |
Parent project details. |
{{#info.hasQcTasks}} / {{#info.QCResultsTable}} |
QC task rows, each with info.taskId, info.barcodeId, info.property, info.target, info.qcResult, info.qcResultStatus, info.ParameterGroups. |
{{info.Created...}}, {{info.Updated...}} |
Template audit info. |
{{info.logo}}, {{info.backgroundImageUrl}} |
Tenant logo and background image URLs. |
property (Property task interval labels)
Print with task_id and albert_id; optionally lot_id and block_id. One entry per inventory x block x interval, so these templates are usually printed as one small label per page.
| Field | Contents |
|---|---|
{{{info.lotNumber}}} |
Barcode image URL encoding task-inventory-block-interval. |
{{{info.lotNumberQrCode}}} |
QR code image of the same code, as a data: URI. |
{{info.intervalId}} |
Human-readable interval name. |
{{info.blockId}}, {{info.intervalRow}} |
Block and interval row IDs. |
{{info.taskID}}, {{info.taskName}} |
Property task ID and name. |
{{info.albertId}}, {{info.inventoryName}}, {{info.alias}}, {{info.lotId}} |
The inventory/lot the label is for. |
{{info.propertyName}} / {{info.Datatemplate}} |
The data template (property) being measured. |
{{info.startDate}}, {{info.dueDate}}, {{info.priority}}, {{info.state}}, {{info.status}}, {{info.result}}, {{info.target}} |
Task fields. |
{{info.AssignedTo.name}}, {{info.Location...}}, {{info.Tags}}, {{info.Workflow...}} |
Task assignment details. |
{{info.ParameterGroups...}}, {{info.Notes}}, {{info.Tasks}}, {{info.History}} |
Workflow parameter groups, task notes, related lot tasks, task history. |
{{info.Created.date}} |
Task creation date (formatted). |
{{#info.Symbols}} |
Loop of hazard pictogram image URLs. |
{{info.formulaIngredient}} |
Unpacked formula ingredients, for formula inventories. |
{{info.AdditionalInfo...}} |
InventoryInfo, LotInfo, projectInfo, and TaskMetadata objects. |
{{info.logo}} |
Tenant logo image URL. |
Data template names honor the x-alb-language request header for localization.
propertytaskreport (Property task reports)
Print with task_id and albert_id. One entry, and uniquely its fields are top-level (no info wrapper): {{task...}} (the full task), {{taskHistory}}, {{propertyDataResult}} (processed property data by block/interval), {{wflPGData}} (workflow parameter groups), {{projectDetails...}}, and {{taskNotes}}.
batchtemplate (Batch task documents, e.g. CoA)
Print with task_id. One entry.
| Field | Contents |
|---|---|
{{info.overview.taskId}}, {{info.overview.taskName}} |
Batch task ID and name. |
{{{info.overview.taskIdBarcode}}} |
Barcode image of the task ID, as a data: URI. |
{{#info.overview.Product}} |
Loop of produced formulas: id, name, lotNumber, barcodeId. |
{{info.overview.Created...}}, {{info.overview.AssignedTo...}} |
Task audit and assignee. |
{{#info.tableJson}} |
Batch usage tables (raw materials and amounts), grouped per numberOfProducts from the metadata block. |
{{info.workflows...}} |
The task's final workflow, including parameter groups. |
{{info.projectDetails...}} |
Parent project, including Metadata.<customField>. |
{{info.AdditionalInfo.TaskInfo...}} |
The full task object, including Metadata.<customField>. |
{{info.AdditionalInfo.TaskNotes}} |
Task notes. |
This type pairs naturally with {{manualFields.X}} placeholders and a metadata schema block for operator-entered values (see below).
generaltasklabel (General task labels)
Print with task_id. One entry whose info is the full task object, enriched with:
| Field | Contents |
|---|---|
{{info.displayId}} |
Task ID with the prefix stripped. |
{{info.name}}, {{info.status}}, and all other task fields |
The task record itself. |
{{info.ProjectInfo...}} |
The full parent project. |
{{#info.Inventories}} |
Loop of task inventories, each with displayId, inventoryInfo (full inventory record, Symbols resolved to pictogram URLs) and lotInfo (full lot record, plus RecentTransferDate). |
batchlabel and formulareport (direct generation)
These two types skip the print-payload step entirely; the platform assembles the data and renders the PDF in one call, returning a finished URL:
url = client.label_templates.get_batch_label_url(task_id="TAS1234")
url = client.label_templates.get_formula_report_url(
formula_id="INVA1234-001", template_id="TMP123"
)
The GHS batch label (batchlabel) is rendered by the platform's document generator with computed hazard data (signal word, hazard and precautionary statements, pictograms); its template is not a tenant label template file. The formula report uses a formulareport tenant template with internally assembled composition and results data.
Mustache patterns that matter
Use triple braces for image sources. Barcode URLs and QR data: URIs contain characters that double braces would HTML-escape into broken links:
<img src="{{{info.lotNumber}}}" width="100" height="40">
<img src="{{{info.lotNumberQrCode}}}" width="90" height="90">
The barcode image itself is a fixed-format Code 128 PNG with the human-readable text always included; style it by sizing, rotating, or cropping the <img> in CSS (only the text size is tunable server-side, via the generate_barcode endpoint's text_size).
Loop arrays with sections. Inside a loop, {{.}} is the current item:
Use inverted sections for fallbacks. For example, show the storage location, falling back to the location:
{{#info.AdditionalInfo.LotInfo.StorageLocation.name}}
{{info.AdditionalInfo.LotInfo.StorageLocation.name}}
{{/info.AdditionalInfo.LotInfo.StorageLocation.name}}
{{^info.AdditionalInfo.LotInfo.StorageLocation.name}}
{{info.AdditionalInfo.LotInfo.Location.name}}
{{/info.AdditionalInfo.LotInfo.StorageLocation.name}}
Reach custom fields through AdditionalInfo. Tenant custom fields on the Inventory are available at {{info.AdditionalInfo.InventoryInfo.Metadata.<fieldName>}}.
Manual fields. Placeholders of the form {{manualFields.<Name>}} are detected in the file and surfaced so the user is prompted for values before printing. They are returned in LabelPrintPayload.manual_fields.
Complete example: 3x1in inventory lot label
A functional starting point (plain black-and-white, barcode plus QR plus pictograms):
<html>
<head>
<meta charset="UTF-8">
<!-- DO NOT MODIFY the below commented line, API (template/print) is consuming it -->
<!--metadata:{
"width": "3in",
"height": "1in",
"margin": {"top": "0mm", "bottom": "0mm", "left": "0mm", "right": "0mm"},
"renderBackgroundImage": true
}-->
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; overflow: hidden; }
.label-container {
display: grid;
grid-template-columns: 180px 108px;
width: 288px; /* 3in */
height: 96px; /* 1in */
overflow: hidden;
padding: 4px;
page-break-after: always;
}
.name { font-size: 10px; font-weight: 700; }
.field { font-size: 8px; }
.field b { font-weight: 700; }
.pictos img { width: 14px; height: 14px; }
</style>
</head>
<body>
{{#labels}}
<div class="label-container">
<div>
<div class="name">{{info.inventoryName}}</div>
<div class="field"><b>ID:</b> {{info.albertId}}</div>
<div class="field"><b>Mfr Lot #:</b> {{info.vendorLotNumber}}</div>
<div class="field"><b>Location:</b> {{info.sublocation}}</div>
<div class="field"><b>Exp:</b> {{info.expirationDate}}</div>
<div class="pictos">
{{#info.Symbols}}<img src="{{.}}">{{/info.Symbols}}
</div>
</div>
<div>
<img src="{{{info.lotNumber}}}" width="100" height="40">
<img src="{{{info.lotNumberQrCode}}}" width="45" height="45">
</div>
</div>
{{/labels}}
</body>
</html>