← All posts

2026-10-23

Automating Label Printing with Python Field Scripts

Automating Label Printing with Python Field Scripts
pdf labels barcodes label merge browser based graphical layout editor google sheets addin microsoft excel addon electronic shelf labels esl barcode3 labels label editor

Automating Label Printing with Python Field Scripts

The label is almost right every time. The date is stale, the serial number lost its leading zeros, and the total needs a check digit before the barcode will scan. So someone opens the label template in the designer, edits three fields, hits print, and then does the exact same thing tomorrow morning.

That edit does not belong in a template. It belongs in code that runs at the moment of printing. RSJ LPSNG lets you attach a Python script block directly to a label field, so the transformation happens inside the template — no preprocessing step, no “fix the CSV first” ritual between your ERP and the printer.

This article is about field-level automation: what a field script is, how to wire it up once, how to drive it from the web service API, and how to keep it from quietly breaking your production line.

Two adjacent topics are covered elsewhere: content that changes per order is in Building Order-Specific Labeling on Top of a Label Printing API, and data-driven labels from spreadsheets are in How to Create Labels from Excel Data Files and Lookup Tables. Here we stay on the field.

Why Automate Label Printing with Python Field Scripts?

Field scripts run inside the label template. The Python block is attached to a field and gets access to that field’s current value, which it can modify before the label is rendered. That single design decision changes the pipeline:

  • No external preprocessing. You don’t need a script that rewrites your input data before submitting it. The data goes in as it is; the label fixes what needs fixing.
  • Consistency across clients. The script travels with the label. Whether the job comes from the web UI, the web service API, or the LPSNG Player, the same field gets the same treatment.
  • The logic is reviewable. It lives next to the field it affects, not in a cron job on someone’s laptop.

The tasks that justify this are boring and repetitive:

Task Manual approach Field script
Print timestamp Type today’s date into the field Format the value at render time
Serial numbers Pad by hand, miss one, reprint Normalize to a fixed width
Check digits Compute in a spreadsheet Calculate from the field value
Derived labels Copy a value from another field Reference the source field

None of these are hard. That’s the point — they’re too small to deserve a service, and too frequent to keep doing by hand.

One-Time Setup: Preparing Your Label and Script

Setup is a one-time job per field. You need the label studio, not a local Python environment.

  1. Open the label you want to automate in the LPSNG label studio and select the field. Pick a field that maps to a text or barcode element.
  2. Attach a Python script block to that field through the field script API. The script is stored and managed by the platform alongside the label — there is no interpreter to install on the print client.
  3. Use the script context. The block sees the field’s current value and returns a modified one before printing. The exact context variables are defined in the Python API reference — read it once, then copy the pattern.
  4. Save the label. From here on, every job that renders this label runs the script.

A minimal block that normalizes a serial number:

# Field: serial
# Runs before rendering. Input: the value passed in the print job.
raw = str(value).strip()

if raw.isdigit():
    value = f"SN-{int(raw):06d}"
else:
    value = raw.upper()

And one that stamps the print time rather than trusting the data:

# Field: printed_at
from datetime import datetime

value = datetime.now().strftime("%Y-%m-%d %H:%M")

The value of doing this in a managed platform rather than in your own code is that the script is part of the label: it is versioned with it, reused across jobs, and cannot drift out of sync with the template.

Reuse is the real payoff. Once a script is correct, attach the same block to every field that needs the same transformation — across labels, across printers. Ten label templates that all print serial numbers the same way are ten templates that all fail the same way, and one place to fix it.

Building the Automated Pipeline: From Data to Print

With the script attached, the pipeline is: authenticate, submit a print job, let the label do the work.

Authenticate once

LPSNG’s web service interface uses OAuth2 with a simplified registration protocol that connects an external system using a single URL. Do the registration once, store the token in your secret store, and keep it out of your repository:

export LPSNG_BASE_URL="https://lpsng.rsj.de"   # host of your edition
export LPSNG_TOKEN="<token from the OAuth2 registration>"

Submit a job

The web service API accepts print jobs, renders single labels as PDF or PNG, and supports direct printing. The exact endpoint paths and payload schema come from the web service interface documentation — the snippet below shows the shape of the call, not a substitute for the reference:

import os
import requests

BASE = os.environ["LPSNG_BASE_URL"]
TOKEN = os.environ["LPSNG_TOKEN"]

job = {
    # Payload keys are illustrative — use the schema from the
    # web service interface documentation.
    "label": "shipping-label",
    "printer": "warehouse-zebra-01",
    "data": {
        "order_id": "A-10442",
        "serial": "42",          # the field script turns this into SN-000042
        "printed_at": "",        # left empty on purpose
    },
}

resp = requests.post(
    f"{BASE}/<print-endpoint-from-the-docs>",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json=job,
    timeout=30,
)
resp.raise_for_status()
print(resp.json())   # the response gives you the reference to poll

Note what the caller does not do: it does not format the serial, and it does not fill in printed_at. Those are the label’s job now.

The interesting case: computing a value

The transformation that pays for itself fastest is a check digit, because a wrong one means a scanner rejects the label at the packing station. The script computes it from the value the job passed in:

# Field: ean
# Compute the mod-10 check digit for a 12-digit payload.
digits = [int(c) for c in str(value) if c.isdigit()]

if len(digits) == 12:
    checksum = (10 - (sum(digits[::2]) + 3 * sum(digits[1::2])) % 10) % 10
    value = "".join(map(str, digits)) + str(checksum)
else:
    # Defensive: don't render an invalid barcode silently.
    value = "INVALID"

Because the script modifies the field value before the barcode is rendered, the barcode content reflects the computed result. You don’t need a separate barcode-generation step.

The hard way, for contrast

The alternative is to compute these values upstream: a preprocessor that reads your order data, rewrites every field, and only then calls the API. It works, until the preprocessor and the label template disagree — one of them pads to six digits, the other to eight — and nobody notices until a pallet ships with unreadable serials. Keeping the logic inside the template means there is exactly one definition of “correct” per field.

If you need to run the same label logic somewhere without a browser, the LPSNG Player is a standalone command-line print engine that takes a package plus data and produces PDF, PNG, JSON, a direct print, or an ESL update. Same label, same scripts, different execution context.

Monitoring and Handling Failures in Automated Printing

An automated pipeline that fails silently is worse than a manual one, because nobody is looking at the label.

Watch the queue. The web UI has a job queue, and the web service API lets you check job status. Poll the reference you got back from the submission, with backoff, and alert when a job sits in the same state too long:

import time

def wait_for_job(session, base, job_ref, timeout=120, interval=5):
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        status = session.get(
            f"{base}/<job-status-endpoint-from-the-docs>/{job_ref}",
            timeout=15,
        ).json()
        if status.get("state") in ("done", "failed"):
            return status
        time.sleep(interval)
    raise TimeoutError(f"job {job_ref} did not finish in {timeout}s")

The published interface documents job submission, single-label rendering, direct print, and status — it does not describe webhooks, so polling is the pattern to build on. If you need push notifications, treat that as a gap to solve on your side.

Fail defensively inside the script. Decide what a bad input should do before it happens. A script that renders INVALID into a barcode field is doing you a favor compared to one that raises and takes the whole job down — or one that raises, gets swallowed, and prints the unformatted value. Check the field script API documentation for how the platform reports a failing block, and write your scripts so the failure mode is the one you chose.

Dry-run with rendered output. The web service API can render a single label as PDF or PNG. Render before you print. It’s the cheapest smoke test you have: feed the script a known input, look at the output, and only then point the pipeline at the printer.

Best Practices for Python Field Scripts

  • One transformation per script. A block that formats a date and looks up a customer name and recalculates a total is a block nobody will debug at 2 a.m.
  • Be idempotent. Reprints happen — a jammed label, a rejected carton, an operator who hits print twice. If SN-000042 goes through the script again, it should come out as SN-000042, not SN-SN-000042. Check whether the value already has the expected shape before transforming it.
  • Version-control the scripts outside LPSNG. Keep the label definitions and their script blocks in Git with a short note on why each exists. The platform stores and runs them; it doesn’t replace code review.
  • Never hardcode data. No fixed customer numbers, no fixed dates, no printer-specific branch logic. Everything should come from the field value or from data passed in the print job.
  • Keep it lightweight. Avoid long-running operations. Field scripts run in the print path, and a slow script is a slow label.
  • Test with real oddities. Empty strings, leading zeros, values that are already formatted, values that are far too long. That’s where field scripts break.

FAQ

Can Python field scripts access external APIs or databases?

The field script API is designed to access and modify field values within the label context. For external data, you would typically pass it in the print job data. Check the LPSNG documentation for any limitations on external calls.

What Python version and libraries are available in field scripts?

The exact Python environment is specified in the LPSNG field script API documentation. It is recommended to use standard library features and keep scripts lightweight.

How do field scripts work with barcodes?

Scripts can modify the field value before the barcode is rendered, allowing you to compute check digits, format data, or apply transformations that affect the barcode content.

Is there a limit on script execution time?

Field scripts should be efficient. While specific limits are not detailed in the provided information, it’s best practice to avoid long-running operations. Refer to LPSNG documentation for any constraints.

Conclusion

The pattern is simple, and that’s why it holds up: attach the transformation to the field once, authenticate once, submit jobs forever. The manual edit disappears, and the definition of a correct label lives in exactly one place — the template — instead of in whoever happened to be at the printer this morning.

Start with one field. Pick the one your team edits most often, write ten lines of Python, render a PDF, and compare it against a known-good label. Then do the next one.

Next Generation Label Printing System is the managed platform that runs these scripts — the label studio, the OAuth2 web service interface, the job queue, and the Player — without a local Python environment to maintain. You can see the full picture at lpsng.rsj.de.

Related posts

EU label: AI-generated content