Skip to content

Label Layouts


Introduction

Layouts control what will be printed on each label and how it is arranged.

Things to know:

  • Layouts can be static (each label is exactly the same) or dynamic (data is inserted into the label from the data list). Most commonly, layouts are a mix of both.
  • Layouts are responsive, meaning they will resize to fit the template dimensions. Usually, they'll be designed to fit specific labels.
  • Layouts are made up of one or more layout elements.

The entire label layout specification can be viewed here

Examples

Here is an example of a layout with a barcode in the top 75%, and text in the bottom 20%. Both the barcode and the text show a package label:

{
  "labelContentLayoutElements": [
    {
      "elementType": "CODE128_BARCODE",
      "valueTemplate": "{{package.label}}",
      "yStartFraction": 0.25
    },
    {
      "elementType": "TEXT",
      "valueTemplate": "{{package.label}}",
      "yEndFraction": 0.2
    }
  ]
}

Here is how this layout renders on a 3x1 thermal label:

Barcode/text layout rendered on 3x1 thermal label

Barcode/text layout rendered on 3x1 thermal label

Here is how this layout renders on an Avery 8160 label sheet:

Barcode/text layout rendered on Avery 8160

Barcode/text layout rendered on Avery 8160

Layout Elements

A layout element is a single element that appears somewhere on a label. Text, images, and barcodes are all examples of layout elements.

Each layout element has a specific elementType that describes how it will appear. Available element types:

elementType Description
TEXT A paragraph of text
TABLE A table made of rows/columns of cells, defined with HTML
IMAGE An image file (passed as base64)
CODE128_BARCODE A Code 128 barcode
CODE39_BARCODE A Code 39 barcode
QR_CODE A QR code
BOX A rectangular box used for borders or background fills
FRAGMENT Another label layout, embedded inside this one

Text

A TEXT layout element adds a paragraph of text.

Tables

A TABLE element adds multiple pieces of text formatted in rows and columns.

  • All text in a table has a consistent font size and spacing.
  • Table borders are not displayed by default.
  • Tables are defined with HTML.

Images

An IMAGE layout element adds an image file onto the label.

  • Images are centered in the layout element and fit to the available space.
  • Image data is passed as base64 and accessed via filename (see value templates).

Barcodes

The CODE128_BARCODE and CODE39_BARCODE elements display a scannable 1D barcode that encodes a piece of text.

  • The barcode will fill the layout element area horizontally and vertically.
  • The barcode encodes whatever is produced by the value template.
  • Use CODE128_BARCODE for Metrc tags and most general-purpose use. CODE39_BARCODE is available for systems that specifically require Code 39.

QR Codes

A QR_CODE element displays a scannable QR code that encodes a piece of text.

  • QR codes are centered in the layout element and fit to the available space, with padding.
  • The QR code encodes whatever is produced by the value template.

Boxes

A BOX element draws a rectangular box inside the layout element area. It does not use a value template.

  • boxStrokeColor sets the border color (hex, default #000000).
  • boxStrokeWidth sets the border width in points (default 0.5).
  • boxStrokeAlpha sets the border opacity (0.01.0, default 1.0).
  • boxBackgroundColor sets the fill color (hex). Omit to disable fill.
  • boxBackgroundAlpha sets the fill opacity (0.01.0, default 1.0).

Fragments

A FRAGMENT element draws another layout inside its rectangle, so a block you use on many labels is written once instead of copied into each one. See Reusing Layouts With Fragments.

Position and Sizing

Layout elements are positioned as rectangles inside the printable area. The boundaries of the rectangle are specified as fractions of the label's printable width and height. This allows the same layout to be reused across labels of different sizes.

Layout elements use the following values to define size and position:

  • xStartFraction - left edge (default 0)
  • xEndFraction - right edge (default 1)
  • yStartFraction - bottom edge (default 0)
  • yEndFraction - top edge (default 1)

Values are decimals between 0 and 1 (for example, 0.05 = 5% of the total dimension).

Note: The default start and end values fill the entire printable area. In the earlier example, only one fraction is defined for each layout element — the rest fall back to defaults.

Aspect Ratio Limits

By default, layouts will fill the available template width and height. You can define a minimum and maximum aspect ratio for the layout, which will limit how much it will stretch.

Layouts use two values to control this:

  • minAspectRatio
  • maxAspectRatio

Note: If T3 detects a minimum or maximum aspect ratio is reached, it will center the layout inside the template.

Reusing Layouts With Fragments

Most labels share blocks: a state warning panel, a barcode with its human-readable tag underneath, a facility footer. A fragment lets you write that block once and embed it in as many layouts as you like.

A fragment is not a special kind of object — it is an ordinary label layout. Anything you can print on its own can be embedded in another layout, and the layout you embed is exactly what GET /v2/labels/label-content-layouts returns.

Fragments work like images: send a map of named layouts alongside your request, then reference one by name from a FRAGMENT element.

{
  "labelContentLayoutConfig": {
    "labelContentLayoutElements": [
      {
        "elementType": "CODE128_BARCODE",
        "valueTemplate": "{{ package.label }}",
        "yStartFraction": 0.4
      },
      {
        "elementType": "FRAGMENT",
        "fragmentKey": "state_warning",
        "yStartFraction": 0,
        "yEndFraction": 0.35
      }
    ]
  },
  "fragments": {
    "state_warning": {
      "labelContentLayoutElements": [
        {
          "elementType": "TEXT",
          "valueTemplate": "{{ t3.text.poison_control_center_phone }}",
          "paragraphFontSize": 6,
          "yStartFraction": 0.5
        },
        {
          "elementType": "BOX",
          "boxStrokeWidth": 1
        }
      ]
    }
  },
  "renderingOptions": { "renderingVersion": 2 }
}

Positioning inside a fragment

A fragment fills its element's rectangle, and its own elements are positioned relative to that rectangle — not to the whole label. In the example above, the fragment occupies the bottom 35% of the label, so the TEXT at yStartFraction: 0.5 sits halfway up the fragment, not halfway up the label.

This is what makes a fragment portable: move the FRAGMENT element and everything inside it moves with it.

Aspect ratio

A fragment honors its own minAspectRatio and maxAspectRatio inside its rectangle, exactly as a layout does inside a label — so a block designed to be wide is not squeezed into a tall box. If the rectangle falls outside the fragment's limits, the fragment is centered within it.

The defaults are wide (0.1 and 10), so an ordinary layout used as a fragment will simply fill its rectangle. Set the limits on the fragment when its proportions matter.

Font sizes do not scale

paragraphFontSize is measured in points, so it is not affected by the size of the fragment's rectangle. A fragment written with 10pt text renders at 10pt whether it fills the label or sits in a narrow strip.

This is worth planning around, because barcodes, QR codes and images do scale to fit their element. Put text that needs to fit its box on paragraphTextResizeStrategy: SHRINK_TEXT (see Responsive Sizing).

Data

A fragment sees exactly the same data as the layout that embeds it, so {{ package.label }} means the same thing in both. There is no separate data list for a fragment.

Nesting and limits

A fragment can embed other fragments, up to five levels deep. Every fragment goes in the same flat fragments map no matter how deeply it is nested.

Requests are rejected with a 400 if:

  • A fragmentKey is not in the fragments map. The error lists the available keys and suggests near matches.
  • A fragment embeds itself, directly or through another fragment.
  • Fragments are nested more than five levels deep.
  • Fragments expand a single label past 500 elements.
  • renderingOptions.renderingVersion is not 2. Fragments require version 2.

All of these are detected before anything is drawn, so a rejected request never produces a partial PDF.

Textual Elements

Textual elements (TEXT, TABLE) have options to control how the text appears: size, font, spacing, positioning, etc.

Text Formatting

Textual elements can control font size, font family, and paragraph spacing.

  • paragraphFontSize controls the font size. Default is 6.
  • paragraphSpacing controls the paragraph spacing. Default is 6. The font size cannot exceed the spacing.
  • paragraphFontName controls the font family. Supported fonts:
  • Times-Roman
  • Times-Bold
  • Times-Italic
  • Times-BoldItalic
  • Helvetica (default)
  • Helvetica-Bold
  • Helvetica-Oblique
  • Helvetica-BoldOblique
  • Courier
  • Courier-Bold
  • Courier-Oblique
  • Courier-BoldOblique
  • Symbol
  • ZapfDingbats

Text Alignment

Textual elements can control how text is aligned inside the element boundaries — or, in the case of tables, inside each cell.

  • horizontalParagraphAlignment controls horizontal alignment: LEFT, CENTER (default), RIGHT
  • verticalParagraphAlignment controls vertical alignment: TOP, CENTER (default), BOTTOM

Responsive Sizing

Textual elements also allow you to specify how the text behaves when it overflows the layout element boundaries, using paragraphTextResizeStrategy:

  • ALLOW_OVERFLOW (default) displays text at its initial font size, even if it overflows the layout element boundaries.
  • TRUNCATE_TEXT preserves the initial font size and removes letters from the end until the remaining text fits. An ellipsis (...) is appended to indicate truncation.
  • SHRINK_TEXT preserves the initial text length but progressively reduces the font size until the text fits.

Note: TRUNCATE_TEXT and SHRINK_TEXT are best-effort — they will only shorten or shrink the text to a certain point before giving up.

Element Values

A value is the data that appears inside a layout element: paragraph text, table contents, image data, or barcode contents. This data is produced by a value template.

Value Templates

The value template is a string used to specify text, HTML, or images that will appear inside the element. Different element types use the value template differently, and some do not use it at all.

Each value template is processed before being rendered onto the label.

Template Processing

All value templates are processed with Jinja before being added to the label.

  • TEXT uses the processed value as the text displayed inside the element.
  • Example: Hello, my name is {{ user.name }}
  • TABLE uses the processed value as the HTML table displayed inside the element.
  • Example: <table><tr><td><b>First Name</b></td><td><b>Last Name</b></td></tr><tr><td>{{ user.firstName }}</td><td>{{ user.lastName }}</td></tr></table>
  • IMAGE uses the processed value as a reference to a base64-encoded image.
  • CODE128_BARCODE / CODE39_BARCODE use the processed value as the barcode's scan value.
  • QR_CODE uses the processed value as the QR code's scan value.
  • BOX does not use a value template.

Jinja Templating

Value templates use Jinja syntax. The most important feature is variable interpolation with double curly braces:

{{ package.label }}

When the label PDF is generated, T3 processes each value template against the data available for that label. Variables are drawn from these namespaces:

  • Per-label data: each entry in your data list. For example, if your data list contains {"package": {"label": "1A44..."}}, then {{ package.label }} renders 1A44....
  • Shared data: values passed via commonContentData are merged into the top level of every label's data, so a commonContentData of {"facilityContactInfo": {...}} is read as {{ facilityContactInfo.phoneNumber }} — not {{ common.facilityContactInfo.phoneNumber }}. A key you set per label wins over the shared value of the same name.
  • images.*: base64 image data you supplied, accessed by filename. For example, {{ images['logo.png'] }}.
  • t3.*: built-in text and images provided by T3. For example, {{ t3.images['t3_logo.png'] }} or {{ t3.text.poison_control_center_phone }}. Call GET /v2/labels/globals for the full list.
  • data.*: an alias for that label's entire data entry, so {{ data.package.label }} and {{ package.label }} are equivalent. Useful when a key name would otherwise be ambiguous.

Jinja also supports conditionals, loops, and filters, which can be useful for labels that conditionally display information:

{% if package.isOnHold %}ON HOLD{% endif %}
{{ package.item.name | upper }}

Missing Values

By default, an expression that cannot be resolved renders as an empty string rather than failing. {{ package.lot }} on a package with no lot key simply prints nothing.

That is forgiving when a field is genuinely optional, but it also means a typo produces a silently blank label. To supply an explicit fallback:

{{ package.lot | default('N/A') }}

default only covers keys that are missing. To also cover keys that are present but null, pass true as a second argument:

{{ package.packagedDate | default('N/A', true) }}

To guard an optional key in a conditional, prefer is defined over a bare truthiness test:

{% if package.isOnHold is defined and package.isOnHold %}ON HOLD{% endif %}

Catching Blank Output While Authoring

Set strictTemplates in your rendering options to turn every silently-blank expression into an error:

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

With strictTemplates enabled, an undefined variable, a missing key, or a null value all return a 400 LABEL_TEMPLATE_ERROR naming the element, the line, and what the data actually held. The default filter and is defined shown above continue to work, so they remain the way to say "blank here is intentional."

It defaults to false, so existing layouts are unaffected. Turning it on while building a layout is the fastest way to find typos; leaving it off in production keeps optional fields forgiving.

If a template fails, T3 returns a 400 Bad Request reporting every problem it found across your layout and data, not just the first. See Handling Errors in the tutorial for a worked example.


Next Steps