Skip to content

Generating Label PDFs


Introduction

T3 label PDFs are generated using several different sources that control how the labels will appear:

Templates
Layouts
Data List
Default Data
Built-In Data
Images
Settings


Templates

A template defines the overall layout and physical structure of the label, including its dimensions, margins, padding, and page arrangement such as rows, columns, and spacing.

Learn more about label templates


Layouts

Layouts control what will be printed on each label and how it is arranged. When you are configuring a label, this is where most of the effort will be dedicated.

Learn more about layouts and layout elements


Data List

The data list controls how many unique labels are generated, and what data is inserted into each label.

Each item in the data list represents one label. If you wanted to print a set of 5 labels that didn't need any special data into each, you would just create an empty list of length 5:

[
  {},
  {},
  {},
  {},
  {}
]

If you wanted to insert two values, say firstName and lastName into each label, you could specify that as follows:

[
  {
    "firstName": "Mike",
    "lastName": "Smith"
  },
  {
    "firstName": "Jane",
    "lastName": "Jones"
  }
]

Package data returned from the T3 API is structured as follows:

[
  {
    "package": {
      "hostname": "ca.metrc.com",
      "label": "1A4400001234000000005678",
      "item": {
        "name": "Bubba Kush",
        ...
      },
      "isInTransit": false,
      "isOnHold": false,
      "isOnRecall": false,
      ...
    }
  },
  {
    "package": {
      "hostname": "ca.metrc.com",
      "label": "1A4400001234000000005679",
      "item": {
        "name": "1g Wedding Cake Preroll - 5pk",
        ...
      },
      "isInTransit": false,
      "isOnHold": false,
      "isOnRecall": false,
      ...
    }
  }
]

How to add these values to a label is covered in Injecting Values


Default Data

Default data is used in two situations: - You want to inject a value into all labels, but don't want to repeat it in every data list entry. (Example: facility address, shared between labels) - Items in your data list are missing values, and you want to fall back to a specific value

Default data is passed in the commonContentData field. Its keys are merged into the top level of every label's data, so they are read exactly as if you had repeated them in each data list entry — a commonContentData of {"facilityAddress": "123 Main St"} is used as {{ facilityAddress }}.

A key set on an individual data list entry takes precedence over the shared value of the same name, which is what makes the fallback case work.

Default data is usually not needed.


Built-In Data

Built-in data is provided by T3 out of the box. It includes text and images that are commonly used by cannabis businesses, and is accessed under the t3.* namespace.

Examples: - State-specific warning images (e.g. {{ t3.images['ca_cannabis_warning.png'] }}) - State-specific warning disclosures (e.g. {{ t3.text.ca_cannabis_warning_disclosure }}) - Generic cannabis images - Poison Control Center contact information ({{ t3.text.poison_control_center_phone }})

You don't have to manage any of these yourself — they're available in every label generation request.


Images

Images can be inserted into labels and rendered using an IMAGE layout element.

The following is an example of how image data might be passed:

{
  "logo.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAO..."
}

This image can now be used as follows in a layout element:

{
  "elementType": "IMAGE",
  "valueTemplate": "{{ images['logo.png'] }}"
}

Fragments

fragments works the same way as images, but for whole layouts instead of image data: a map of named layouts, sent alongside your request and referenced by name. Use it to share a block — a warning panel, a barcode with its tag beneath it — across many labels instead of copying it into each one.

A fragment is an ordinary label layout, so a layout returned by GET /v2/labels/label-content-layouts can be passed here unchanged:

{
  "state_warning": {
    "labelContentLayoutElements": [
      { "elementType": "TEXT", "valueTemplate": "{{ t3.text.poison_control_center_phone }}" }
    ]
  }
}

Draw it with a FRAGMENT layout element, which sets where on the label it goes:

{
  "elementType": "FRAGMENT",
  "fragmentKey": "state_warning",
  "yStartFraction": 0,
  "yEndFraction": 0.35
}

The fragment's own elements are positioned relative to that rectangle. Fragments require renderingVersion 2.

See Reusing Layouts With Fragments for nesting, aspect ratio, and the limits that apply.


Settings

Label settings configure how your labels will appear. Settings used most often:

  • How many copies of each label should be printed
  • Reversing the print order
  • Rotating your layout sideways by 90 degrees
  • Enabling/disabling watermark, for trial/error that doesn't count against your monthly quota
  • Enabling/disabling debug, for drawing boundaries around each layout element
  • Enabling strictTemplates, so expressions that would render blank fail loudly instead

strictTemplates is worth calling out because it changes how mistakes surface. Off (the default), an expression T3 cannot resolve renders as an empty string — forgiving for optional fields, but a typo produces a blank label with no warning. On, the same expression returns a 400 naming the element and line:

{
  "renderingOptions": {
    "renderingVersion": 2,
    "strictTemplates": true
  }
}

Turn it on while building a layout to catch typos; leave it off in production so optional fields stay forgiving. See Missing Values for the default filter and other ways to say that blank output is intentional.


Injecting Values

Most layout elements support a value template — a string that tells T3 what to render inside the element. Value templates are processed with Jinja, so you can interpolate values from your data, use conditionals, and apply filters.

Interpolating a value:

{{ package.label }}

Values can come from these namespaces:

  • Per-label data — each entry in your labelContentDataList. If the entry is {"package": {"label": "1A44..."}}, {{ package.label }} renders 1A44....
  • Shared data — values from Default Data, merged into the top level of every label. A commonContentData key facilityAddress is read as {{ facilityAddress }}.
  • images.* — base64-encoded images you supplied, accessed by filename. For example, {{ images['logo.png'] }}.
  • t3.*Built-In Data provided by T3.
  • data.* — an alias for the whole per-label entry, so {{ data.package.label }} matches {{ package.label }}.

Conditionals:

{% if package.isOnHold %}ON HOLD{% endif %}

Filters:

{{ package.item.name | upper }}

For a deeper walkthrough, see the tutorial's section on injecting values.


Arranging and Styling Text

TEXT and TABLE elements support a range of formatting options:

  • Font: paragraphFontName (e.g. Helvetica, Times-Bold, Courier), paragraphFontSize, paragraphSpacing.
  • Alignment: horizontalParagraphAlignment (LEFT, CENTER, RIGHT), verticalParagraphAlignment (TOP, CENTER, BOTTOM).
  • Overflow handling: paragraphTextResizeStrategy:
  • ALLOW_OVERFLOW (default) — text overflows the element.
  • SHRINK_TEXT — font size is reduced until the text fits.
  • TRUNCATE_TEXT — letters are removed from the end until the text fits; an ellipsis is appended.

Tables are defined with HTML and share the same font settings as TEXT elements. A basic example:

<table>
  <tr><td><b>THC</b></td><td>{{ package.metadata.totalThc }}</td></tr>
  <tr><td><b>CBD</b></td><td>{{ package.metadata.totalCbd }}</td></tr>
</table>

For the full list of options, see Label Layouts → Textual Elements.


Next Steps