Web-based label editor and PDF batch generator for non-technical office users. Self-hosted, multilingual (PL + EN), runs on a single Docker host behind Tailscale.
The editor open on a Zebra 2Γ1β³ template β text, dynamic
{{placeholders}}, an EAN-128 barcode, all positioned in millimetres. Right panel shows the per-object inspector with Lock + Print-in-PDF toggles, font, size, alignment.
- Online label editor β Konva-powered drag-and-drop canvas (text, text blocks with auto-fit, rectangles, lines, images, barcodes, dynamic
{{column}}fields). - Lockable + non-printable objects β pin a logo so it can't be moved; drop a scan of a pre-printed sheet as a layout reference that stays out of the final PDF.
- Layer order + alignment + distribute β z-stack controls (front/back/forward/backward), 6 page-relative + 6 selection-relative align operations, equal distribution.
- Duplicate fast β Alt+drag a selected object to clone it under the cursor; Ctrl/Cmd+D to duplicate in place. Multi-select supported.
- Barcodes β EAN-13, EAN-14, GTIN, Code 128, GS1-128, QR (with checksum validation).
- Series generation β upload a CSV, Excel, or SQLite file, map placeholders to columns (or write a custom SELECT), optionally filter rows, get a single PDF with one label per row. Up to 1,000 labels per batch.
- ZPL / ZPL II round-trip β paste an existing ZPL label, adjust element positions on the canvas, and export native ZPL back out (copy to clipboard or download
.zpl) for use in external software. Single-brace{VARIABLES},^PQ{NoLabel}quantity, resident fonts (E:ARIxxx.TTF) and native QR are preserved 1:1; a batch mode fills{{column}}values from a dataset. DPI is auto-detected from^PW/^LL. - TSPL export (TSC / desktop Toshiba) β export the same canvas as TSPL/TSPL2 (
SIZE/GAP/DIRECTION/CLSβ¦PRINT) for TSC and desktop-Toshiba printers: text, barcodes (Code 128, GS1-128, EAN-13, QR), rectangles, lines and tables. Copy to clipboard or download a.txt; warnings flag anything approximated (bitmap fonts) or skipped (images). - Print preview β hit π Preview to render the single-label PDF and see it embedded in the app (with any text-overflow warnings) before committing to a download β the same generation is reused, so preview + download is one action.
- Editable label size β resize a template (width Γ height in mm, with presets) at any time from the editor; undoable and persisted, so a label never has to be recreated just to change its dimensions.
- Date placeholders β
{{date+14d}},{{date+3m:YYYY-MM-DD}}compute best-before / production dates at generation time (PDF and ZPL alike), with a live preview chip in the editor and month-end-safe arithmetic. - Direct printing via the local connector β a single-binary Go agent (
connector/) polls the server's print queue with a per-device Bearer token and forwards ZPL to label printers over RAW TCP 9100 (Zebra / JetDirect compatible). Register devices on the Devices page, then hit π¨ Print in the editor; job status (queued β printing β done/error) reports back live. Pure Go, zero cgo β prebuilt binaries ship for Windows, macOS (Intel + Apple Silicon) and Linux (amd64/arm64/arm, incl. Raspberry Pi) with launchd / systemd / service setup in the connector README. An Android connector core (connector/mobilecore/) is in progress. - Virtual printer capture β the same agent can pose as a network printer (JetDirect listener): anything another application prints to it lands in the web app's Inbox, one click away from becoming an editable template β the escape hatch from legacy label software. On Windows via the ZDesigner driver + a Standard TCP/IP port; on macOS / Linux via a CUPS raw queue (one-command
connector/install-capture-cups.sh). - Template import / export β every template is a single self-contained
.blg-template.json(size, objects, embedded images). Cross-instance portable; partial import lets you skip objects + override the size. - Multilingual UI + in-app docs β Polish + English from day one, with HELP + FAQ rendered inside the app.
- Roles β admin / editor / viewer; admin manages users + temporary password resets.
- Label formats β A4, A5, A6, common Zebra sizes (4Γ6", 4Γ4", 3Γ2", 2Γ1"), plus arbitrary custom mm.
First screen after signing in. Two cards point straight at the user's job: open the template editor, or read the in-app guide. Sidebar exposes Dashboard / Templates / Help / Administration β Users.
Every template you (or a shared admin) own. Search filters by name; each tile shows the size + version + last-modified date. Hover a tile to reveal Export (β¬) and Delete (β). Top-right pair: Import (from a
.blg-template.jsonfile) and New template (pick a format).
The "left side" of the mail-merge flow. Any CSV / Excel / SQLite with column headers works. Here: 60 products with
SKU,ProductName,Ingredients,BatchNo,BestBefore,EAN-128β feeds straight into a template that uses{{SKU}},{{ProductName}}etc.
Step 1 of the 4-step wizard. Accept any of:
.csv,.xls,.xlsx(max 10 MB / 1,000 rows) or.db/.sqlite/.sqlite3(max 50 MB).
When the uploaded file is a SQLite database the wizard detects every user-visible table (sorted by row count β biggest first) and lets you either pick one or expand "Show advanced" to write a
SELECTwithWHERE/JOIN/UPPER(...)etc. Read-only connection; single-statement validator blocksINSERT/UPDATE/DELETE/DROP/ATTACH/PRAGMA; auto-LIMIT 1000.
Step 2 detects every
{{name}}placeholder in the template and auto-maps it to a same-named column. Mismatched names are pickable from a dropdown β the wizard refuses to move on until every placeholder has a source.
The fallback dropdown showing every column the source carries. Works the same whether the source was CSV, Excel, or a SQLite query.
One PDF, one page per row. Each
{{placeholder}}is replaced by the row's value β hereVictoria Sponge Cakeis row 5 of 60, with its own ingredients, batch number, and GS1-128 barcode. Long ingredients lists wrap inside the text block; the renderer reports a warning if anything doesn't fit.
π Download a sample PDF β (50 Zebra 2Γ1β³ labels, 668 KB) β generated from the products dataset above, ready to inspect or print.
Every template exports to a single
.blg-template.json(size + objects + embedded images, all base64). The import modal lets you rename the new template, override the size, and uncheck specific objects to bring in β the{{β¦}}chip flags objects that carry dynamic placeholders. Duplicate images (detected by SHA-256) prompt a reuse-vs-copy choice.
Paste an existing ZPL label, leave DPI on Auto-detect (or force 203 / 300), and hit Analyze: the elements become editable canvas objects, the dialog reports the detected DPI + object count and warns when the content spills past the label (a wrong-DPI signal). Import keeps the label size you already set; single-brace
{VARIABLES},^PQ{NoLabel}, resident fonts (E:ARIxxx.TTF) and native QR survive the round-trip.
A live native-ZPL preview of the current label with Copy and Download .zpl. Template mode keeps the variables intact for your external system; batch mode substitutes
{{column}}values from a dataset and emits one^XAβ¦^XZblock per row.
The size button (π WΓH) in the toolbar opens a dialog to change the label's width Γ height in millimetres, with one-click presets (40Γ100, 50Γ30, 100Γ150, 105Γ148, 210Γ297). The change is undoable and saved to the template β object positions stay put.
The π Preview toolbar button renders the single-label PDF and embeds it right in the app β check the layout (and any wrapping warnings) before you download. The same generation is reused, so preview + download counts as one action in the history.
Export the current label as TSPL/TSPL2 for TSC and desktop-Toshiba printers: pick the density, preview the generated commands (
SIZE/GAP/DIRECTION/CLSβ¦
The connector ships as prebuilt binaries for Windows, macOS (Intel + Apple Silicon) and Linux (amd64/arm64/arm, incl. Raspberry Pi) β see
connector/README.md. Virtual-printer capture works on macOS / Linux through a CUPS raw queue created byconnector/install-capture-cups.sh, routing another app's ZPL into the web app's Inbox.
| Layer | Technology |
|---|---|
| Frontend | React 18 + TypeScript + Vite + Konva + TailwindCSS + react-router 7 + react-i18next + react-markdown |
| Backend | Python 3.12 + Flask 3 + uv + SQLAlchemy 2 + Alembic + ReportLab + python-barcode + qrcode + pandas + Pillow + pdfplumber |
| Database | PostgreSQL 16 (production) Β· SQLite (test fixture) |
| Cache + sessions + job queue | Redis 7 |
| Infrastructure | Docker + Docker Compose + nginx |
| Deployment | Linux host fronted by Tailscale Serve |
| Tests | pytest (backend) + tsc + eslint (frontend); Go go test for the connector |
# Clone
git clone https://github.com/AmigoUK/BarcodeLabelGen.git
cd BarcodeLabelGen
# Build + start (Postgres + Redis + Flask + nginx)
docker compose up -d
# First-time DB migration (auto-runs on web container start; explicit form below)
docker compose exec web alembic upgrade head
# Bootstrap an admin account (replace email + password)
docker compose exec web flask create-admin --email you@example.com --password 'change-me-now-please'
# Open in your browser
open http://127.0.0.1:18003The service binds to 127.0.0.1:18003 only β to expose it to your tailnet (and get a free *.ts.net HTTPS cert):
tailscale serve --bg --https=18003 http://127.0.0.1:18003
# β https://<your-machine>.<tailnet>.ts.net:18003docker compose -f compose.dev.yml up
# Vite dev server + Flask debug, proxied through nginx.- π
docs/HELP.pl.md/docs/HELP.en.mdβ full user guide (onboarding, every feature, troubleshooting). Same content is rendered in-app at/help. - β
docs/FAQ.pl.md/docs/FAQ.en.mdβ common questions + error message recovery. - π
docs/PROJECT.mdβ original MVP specification (PL).
BarcodeLabelGen/
βββ backend/
β βββ app/
β β βββ models/ # SQLAlchemy models
β β βββ routes/ # Flask blueprints (REST endpoints)
β β βββ schemas/ # Pydantic request/response models
β β βββ services/ # Business logic (PDF render, batch, datasets, β¦)
β β βββ factory.py # Flask app factory
β βββ alembic/ # DB migrations (0001β¦0006)
β βββ tests/ # pytest β 203 tests
β βββ pyproject.toml
βββ frontend/
β βββ src/
β β βββ components/ # Shared UI (Modal, Button, ImportTemplateModal, β¦)
β β βββ editor/ # Konva-based label editor (Canvas, panels, store)
β β βββ hooks/ # React-Query data hooks
β β βββ pages/ # Route components (Dashboard, Templates, Editor, Help, β¦)
β β βββ i18n/locales/ # PL + EN
β β βββ lib/ # api client, csrf, download helper
β βββ package.json
βββ docs/
β βββ HELP.{pl,en}.md # User guide (rendered in-app)
β βββ FAQ.{pl,en}.md # FAQ (rendered in-app)
β βββ PROJECT.md # MVP specification
β βββ screenshots/ # Images referenced from this README
βββ compose.yml # Production docker-compose
βββ README.md # β you are here
β Production on a Tailscale-only host.
- Backend: 203 / 203 tests passing.
- QA harness (PDF render geometry checks): all formats β .
- Frontend: typecheck + lint + build clean; bundle 153 KB (gzipped 47 KB) main + lazy chunks for editor (Konva) and help (react-markdown).
dev@attv.uk Β· Project & Development: Tomasz 'Amigo' Lewandowski Β· www.attv.uk Β· GitHub
GPL-3.0 β see LICENSE.















