Skip to main content

Getting Started with LiquidJS Templates

Updated today

1. Introduction

SKULabs templates use LiquidJS — a powerful templating language that gives you full control over the layout and content of your printable documents (packing slips, invoices, pick lists, and more).

Why LiquidJS templates are better than V1:

  • Live preview — See changes instantly as you type in the editor

  • AI-powered Template Assistant — Describe what you want in plain English and the assistant writes the code for you

  • Custom tags — Purpose-built tags for tables, barcodes, layouts, store logos, and more

  • More flexible — Full support for conditionals, loops, filters, and HTML/CSS styling

New accounts use LiquidJS templates by default. If you're on an older account, you can switch from the template editor.


2. Getting Started

  1. Navigate to Store > Templates

  2. Select a template type (e.g., Packing Slip)

  3. The editor opens with two panels:

    • Left side — Code editor where you write your template

    • Right side — Live preview that updates as you type


3. Understanding Variables

Variables are data fields available in your template. They represent real order data (customer name, SKU, price, etc.) that get filled in when the template renders.

Syntax:

  • Output a value: {{ variableName }}

  • Nested access: {{ customer.name }}, {{ origin.address }}

4. How to View Available Variables (Data Model)

If you're not sure what variables are available or what path to use:

  1. In the template editor, click the three-dot menu (next to the Save button)

  2. Select "View data model"

  3. A dialog opens showing the complete JSON structure of sample data

  4. Browse the tree to find the exact variable path you need

  5. Use this as your reference when writing template code

For example, if you see customer > name in the data model, use {{ customer.name }} in your template.


5. Using Liquid Syntax

Output a value

{{ orderNumber }} {{ customer.name }}

Conditionals

{% if customer.company %}   {{ customer.company }} {% elsif customer.name %}   {{ customer.name }} {% else %}   No customer info {% endif %}

Loops

{% for item in lines %}   {{ item.name }} - {{ item.sku }} {% endfor %}

Assign a variable

{% assign myVar = "Hello" %} {% assign shippedItems = 0 %}

Default filter

Use default to provide a fallback when a value is empty:

{{ origin.company | default: origin.name }}

6. Custom Filters

formatDate

Formats a date value using format tokens.

{{ currentDate | formatDate: 'Mon DD YYYY' }} {{ orderDate | formatDate: 'MM/DD/YYYY' }} {{ newShipments[0].time | formatDate: 'Mon DD YYYY HH:mm' }}

{{ currentDate | date: "%a %b %d %H:%M" }}

Available tokens:

Token

Output

Example

YYYY

Four-digit year

2025

YY

Two-digit year

25

Mon

Abbreviated month

Jan, Feb, Aug

MM

Month, 2 digits

01, 08, 12

M

Month, no leading zero

1, 8, 12

DD

Day, 2 digits

01, 15, 31

D

Day, no leading zero

1, 15, 31

HH

Hours, 2 digits

00, 09, 23

H

Hours, no leading zero

0, 9, 23

mm

Minutes, 2 digits

00, 05, 59

m

Minutes, no leading zero

0, 5, 59

ss

Seconds, 2 digits

00, 05, 59

s

Seconds, no leading zero

0, 5, 59

%a

Abbreviated weekday

Mon, Tue, Wed

%b

Abbreviated month

Jan, Feb, Mar

%d

Day of the month (zero-padded)

01-31

%H

Hour (24-hour format)

00-23

%M

Minutes

00-59

currency

Formats a number as currency.

{{ item.price | currency: 'USD' }} {{ orderSummary.total | currency: orderSummary.currency }}

where_exp

Filters a collection based on an expression. Useful for showing only certain items.

{% assign shippedLines = lines | where_exp: "item", "item.shipped > 0" %} {% assign unshippedLines = lines | where_exp: "item", "item.remaining > 0" %}

7. Custom Tags (SKULabs-Specific)

These are special tags built specifically for SKULabs templates. They handle layout, tables, barcodes, and other common document elements.

PackingSlip — Document wrapper

Wraps the entire packing slip content. Required as the outermost tag.

{% PackingSlip %}   ... your template content ... {% endPackingSlip %}

Options:

  • Custom page size: {% PackingSlip width: 4in; height: 6in %}

  • Landscape orientation: {% PackingSlip landscape %}

FlexRow / FlexColumn — Layout

Create horizontal rows with columns inside them. Use this to place content side by side.

{% FlexRow %}   {% FlexColumn %}     Left-side content   {% endFlexColumn %}    {% FlexColumn text-right %}     Right-aligned content   {% endFlexColumn %} {% endFlexRow %}

FlexColumn options: text-left, text-right, text-center

Table — Dynamic data table

Renders a table by iterating over a collection (like lines).

{% Table item in lines %}   
{% Headers[Name, SKU, Ordered, Shipped] %}
{% Column %}
{{ item.name }}
{% Column %}
{{ item.sku }}
{% Column %}
{{ item.ordered }}
{% Column %}
{{ item.shipped }}
{% KitItems %}
{% endTable %}

Table options:

  • Filter: {% Table item in lines filter: item.shipped > 0 %} — only show rows matching the condition

  • Sort: {% Table item in lines sort: item.name %} or {% Table item in lines sort: item.price:desc %}

  • Offset: {% Table item in lines offset: 2 %} — skip the first 2 items

  • Limit: {% Table item in lines limit: 5 %} — show only 5 items

You can combine options: {% Table item in lines filter: item.remaining > 0 sort: item.name limit: 10 %}

Headers — Table header row

Defines the column headers for a Table. Place inside a Table tag.

{% Headers[Product, SKU, Qty, Price] %}

Column — Table column content

Separates column content inside a Table. Each {% Column %} starts a new column. Supports inline styles:

{% Column style="text-align: center" %}

Barcode — Generate a barcode

Renders a barcode from a variable value.

{% Barcode orderNumber %} {% Barcode item.barcode width: 2; height: 30 %} {% Barcode orderNumber show_text: true; font_size: 20 %}

Options (separated by semicolons):

  • width — Bar width (default: 2)

  • height — Bar height in pixels (default: 25)

  • show_text — Show the value as text below the barcode (true/false)

  • font_size — Text font size when show_text is true

  • scale — Scale factor (default: 1)

StoreLogo — Render store logo

Renders the store's logo image. If no logo is set, it renders the store name as a heading instead.

{% StoreLogo %}

Bin — Bin and batch number

Renders the bin number and batch/group number in a bordered box. Only renders if both values exist.

{% Bin %}

KitItems — Kit sub-items

Place inside a Table tag. If the current line item is a kit, this renders a sub-table showing the kit's component items with their name, SKU, ordered, and shipped quantities.

{% KitItems %}

TrackingButton — Tracking link

Renders a clickable "Track package" button that links to the shipment's tracking URL.

{% TrackingButton newShipments[0] %}

8. Template Assistant (AI)

The Template Assistant is located at the top of the editor. It lets you modify your template using natural language — just describe what you want.

How to use it

  1. Type your request in the assistant input at the top of the editor

  2. The assistant modifies your template code and you see the result in the live preview

  3. If you don't like the change, type "undo" or "revert last" to go back one step

  4. Follow-up questions work — the assistant remembers the conversation context

Tips for effective prompts

Be specific about variable names. The assistant knows the data model, so referencing exact field names gets better results.

Prompt

Why it works

"Add item.barcode as a new column in the products table"

Specific variable + clear placement

"Show customer.phone below the customer name"

Specific variable + relative position

"Add a column showing item.price formatted as currency"

Specific variable + formatting

"Hide lines where item.shipped is 0"

Specific condition using a real field

Prompt

Why it's less effective

"Add the product code"

Ambiguous — could be SKU, barcode, or lineId

"Make it look better"

Too vague — what specifically should change?

How to find the right variable name

  1. Open View data model from the three-dot menu

  2. Browse the JSON tree to find the field you want

  3. Use the exact path in your prompt (e.g., item.namingDetails.variant)


9. Walkthrough: Default Packing Slip Template

Section-by-section breakdown

1. Document wrapper
{% PackingSlip %} ... {% endPackingSlip %} — Wraps everything. Sets up the page with default letter size (8.5" x 11").

2. Header row
{% FlexRow %} creates a two-column header:

  • Left column: Store logo ({% StoreLogo %}), current date formatted as "Mon DD YYYY", and the order number prefixed with #

  • Right column (right-aligned): A barcode of the order number with visible text, plus the bin/batch number box

3. Address row
Another {% FlexRow %} with two columns:

  • Left: Store/origin info — company name (falling back to warehouse name via | default:), phone, email, and full address. Each field is wrapped in {% if %} so it only shows when present.

  • Right: Customer info — company, name (only if different from company), phone, email, and full address.

4. Shipment details
Two lines showing the shipped date (from the first shipment in newShipments) and the requested shipping method.

5. Products table
{% Table item in lines %} iterates over all order line items with four columns: Name, SKU, Ordered, and Shipped. The {% KitItems %} tag at the end adds a sub-table for kit components when applicable.

6. Customer notes
Shows customer notes at the bottom, but only if the value is not empty (using {% if customerNotes != '' %}).


10. Tips and Best Practices

  • Always check the live preview as you edit — it updates in real time so you'll catch issues immediately

  • Use "View data model" to find the exact variable path before writing template code

  • Use conditionals to handle missing data — wrap optional fields in {% if %} blocks so they don't show blank lines:

    {% if customer.phone %}Phone: {{ customer.phone }}<br>{% endif %}
  • The Template Assistant works best with specific variable names — open the data model first, find the field path, then use it in your prompt

  • Use | default: to provide fallback values:

    {{ origin.company | default: origin.name }}
  • Use | currency: for prices to get proper formatting:

    {{ item.price | currency: 'USD' }}
  • Use | formatDate: for dates instead of showing raw timestamps:

    {{ orderDate | formatDate: 'Mon DD, YYYY' }}
  • Combine Table options to show exactly the data you need:

    {% Table item in lines filter: item.remaining > 0 sort: item.name %}
Did this answer your question?