A REST service that renders labels from declarative YAML templates. It produces a single label as PNG (for continuous-roll printers) or a sheet of labels as PDF (for pre-cut label sheets), by generating Typst source on the fly and compiling it in-process.
docker run -p 8080:8080 ghcr.io/pfa230/labeler:edgecargo run # serves on 0.0.0.0:$PORT (default 8080)A React + TypeScript SPA in ui/ (Vite, Tailwind). The backend serves its build at /.
npm --prefix ui install # once
npm --prefix ui run dev # Vite dev server (proxies /api to cargo run on :8080)
npm --prefix ui run build # build to ui/dist (then `cargo run` serves it at /)In production the binary serves ui/dist; the Docker multi-stage build bundles the UI (see Deployment
below).
Run the whole thing with Docker:
docker compose up -d --build # serves on http://localhost:${HOST_PORT:-8080}See docs/DEPLOY.md for configuration, persistent volumes and backups, and CUPS/IPP
printing setup.
YAML templates are loaded from templates/ at startup; an invalid template stops the service from
starting. Starter templates: avery5163 (US Letter sheet); the Brother continuous-tape set
brother_12mm / brother_18mm / brother_24mm (text only) plus brother_18mm_qr / brother_24mm_qr
(QR + text); and homebox-qr, a Homebox asset label whose QR links to an item, demonstrating variable
interpolation.
All routes are under /api (the root is reserved for the web UI); unknown /api/* → 404 NotFound.
GET /api/health→{ "status": "ok" }GET /api/templates→ list of template summariesGET /api/templates/{id}→ detailed template schemaGET /api/templates/{id}/source→ raw stored template YAMLPOST /api/render/label→ rendered PNG/PDF for a single template (preview / one-off)POST /api/batch→ render/print a batch (single → ZIP or per-label jobs, sheet → paginated PDF or job)GET /api/openapi.json→ OpenAPI documentGET /api/docs/→ Swagger UI
scripts/render_avery_horizontal.sh posts a sample request to a running server and writes a PDF. All
/api routes require authentication (ADR-0017), so export LABELER_API_TOKEN (create one in the UI
under Settings) before running it; the script sends it as Authorization: Bearer $LABELER_API_TOKEN.
All errors are JSON with a stable schema:
{
"error": {
"code": "TemplateNotFound",
"message": "No template with id 'xyz' was found",
"details": { "template": "xyz" }
}
}cargo fmt
cargo clippy --all-targets --all-features
cargo testCONTRIBUTING.md has the contributor workflow. The full API and template spec is in
docs/SPEC.md; design decisions are recorded as ADRs.