zplCore.comzplCore.com

Documentation · zplCore Designer

zplCore Designer
the embeddable label editor.

The zplCore Designer is a WYSIWYG editor for ZPL labels that you run yourself and embed in your own application: design JSON and data in, design JSON, ZPL, PDF, PNG and SVG out. The canvas shows what the printer prints, because every preview is the rendered ZPL of the zplCore.com engine. The editor stores nothing and prints nothing – your application keeps the design and does the printing. This page covers the quick starts (NuGet, Docker, JavaScript, Web Component, Blazor, WPF, MAUI), all options, the render API, the JavaScript API, the design schema and operations.

Open the live editor Quick start Render API

01Overview

The designer consists of two parts that always run together: the editor UI (static files, an Angular app without external requests) and a stateless render API in .NET that renders previews, measures the printed geometry of each element for the canvas and converts images and SVGs. Both are delivered by one ASP.NET Core library; the container is that library with a minimal host.

DeliveryWhat you getFor whom
NuGet ZplCore.DesignerAddZplCoreDesigner() + MapZplCoreDesigner("/designer"): editor UI (embedded in the package) and render API in your ASP.NET Core app, behind your own authentication.NET teams that integrate the editor into their web app
Docker zplcore/designerReady-to-run container (distroless, non-root) with editor and API, configured by environment variables; Helm chartTeams without .NET, Kubernetes, embedding by iframe
JavaScript SDK zplcore-designer.jsZplCoreDesigner.mount(…): iframe embedding with a promise API, delivered by every designer host (and as npm package @zplcore/designer)Any web app (React, Vue, Angular, server-rendered)
Web Component <zplcore-designer>The same editor as a custom element (a slim wrapper around the SDK, no Angular runtime in your page)Pages that prefer an HTML tag to a script call
Blazor ZplCore.Designer.Blazor<ZplCoreDesigner @bind-Design="…" /> for Blazor Server and WebAssemblyBlazor apps
DesktopThe designer host on loopback + WebView2 (WPF, WinForms) or the MAUI WebViewDesktop applications

What is in it

  • Canvas with all element types: text, barcode (1D/2D, GS1, artistic codes), rectangle, line, circle/ellipse, image, printer-memory graphic, SVG icon, ZPL element, table, RFID.
  • Property panel, element list, groups, several pages, grid, rulers, snapping, zoom, undo/redo, clipboard, keyboard shortcuts.
  • Data binding with JSON records (from your app or edited in the editor), preview per record, hiding per record, expressions and number formats.
  • Previews: ZPL, PDF, PNG, SVG; ZPL analyser and linter; GS1 assistant, RFID fields, image → ZPL, SVG → ZPL, icon library.
  • 10 UI languages (en, de, zh, ja, es, fr, pt, it, ar, hi), light and dark, mm / inch / dots, 203 / 300 / 600 dpi.

What is deliberately not in it

  • Printing of any kind – your application sends the ZPL (for example with the zplCore SDK).
  • Storage, accounts, versions or teams – your application receives the JSON and stores it.
  • Data-source connectors – data comes as JSON records from your application.
  • An AI copilot and the old zplCloud design format { label, fields } (convert it first).

02Quick start: NuGet (ASP.NET Core)

dotnet add package ZplCore.Designer
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(/* your scheme */);
builder.Services.AddAuthorization();
builder.Services.AddZplCoreDesigner(o =>
{
    o.Fonts.AddDirectory("fonts");                  // optional: your TTF/OTF printer fonts (E:NAME.TTF)
    o.PrinterGraphics.AddDirectory("graphics");     // optional: your printer-memory graphics (E:NAME.GRF)
    o.Limits.MaxRecordsPerRequest = 500;
});

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapZplCoreDesigner("/designer")
   .RequireAuthorization("label-editors");          // your auth – the designer brings none of its own
app.Run();

The editor is now at /designer/, the API at /designer/api/…, the OpenAPI document at /designer/api/openapi/zplcore-designer.json and a health answer at /designer/healthz. MapZplCoreDesigner returns a convention builder: .RequireAuthorization(…) protects UI and API, .Api and .Ui address one part each (for example a public UI with a protected API).

Authentication. The API requires an authenticated user of your app (your default authorization policy). Open access exists only explicitly with o.AllowAnonymous = true – for example behind your own proxy or for a public demo like the live editor.

03Quick start: Docker

docker run -d -p 8080:8080 --read-only --tmpfs /tmp \
  -e ZPLCORE_DESIGNER__AUTH__MODE=apikey \
  -e ZPLCORE_DESIGNER__AUTH__APIKEYS__0=<a key of at least 16 characters> \
  -e ZPLCORE_DESIGNER__ALLOWEDORIGINS__0=https://app.example.com \
  -v ./fonts:/fonts:ro -v ./graphics:/graphics:ro \
  zplcore/designer

The editor is at http://localhost:8080/designer/. The image is distroless (Azure Linux), runs as the non-root user app on port 8080 and works with a read-only root file system and a writable /tmp; it is built for linux/amd64 and linux/arm64. The container is configured with the section Designer as environment variables ZPLCORE_DESIGNER__… (double underscore = nesting, __0 = first list entry); every option of section 09 can be set this way, for example ZPLCORE_DESIGNER__LIMITS__MAXRECORDSPERREQUEST=200.

VariableMeaning
ZPLCORE_DESIGNER__PATHBASERoute prefix of editor and API, default /designer
ZPLCORE_DESIGNER__AUTH__MODERequired: apikey, jwt (OIDC) or none (explicitly, only behind your own proxy; the log warns)
…__AUTH__APIKEYS__0 … · …__AUTH__APIKEYHEADERAPI keys (at least 16 characters, from a secret) · header, default X-Api-Key
…__AUTH__AUTHORITY, __AUDIENCE, __REQUIREDROLE, __REQUIREHTTPSMETADATAJWT / OIDC (header Authorization: Bearer …)
…__ALLOWEDORIGINS__0 …Origins that may embed the editor (frame-ancestors) and call the API from the browser (CORS); empty = same origin only
…__LIMITS__*Protection limits, see Limits, incl. RATELIMIT__PERMITSPERWINDOW
…__FEATURES__*false switches an endpoint (and its tab) off
…__UI__BRANDING__*Title, logo, favicon, hidden tabs of the editor
OTEL_EXPORTER_OTLP_ENDPOINT (+ OTEL_*)OpenTelemetry export of metrics, traces and logs; unset = off
ASPNETCORE_FORWARDEDHEADERS_ENABLED=trueTrust X-Forwarded-* of your ingress (client IP for the rate limit)

Mounted folders (all optional, read-only is enough): /fonts printer-memory fonts, /graphics printer-memory graphics, /templates design templates (*.json), /icons your SVG icons, /svg-fonts SVG fonts and glyph sets, /graphic-fonts fonts released for graphic text (ZPLCORE_DESIGNER__FOLDERS__* for other paths). Liveness /healthz, readiness /readyz (renders a tiny label). The API needs the key or token; the static UI and {prefix}/healthz stay public.

Kubernetes: the Helm chart deploy/helm/zplcore-designer (API keys only from an existing secret, non-root, read-only root file system, probes, optional HPA and network policy):

kubectl create secret generic designer-api-keys --from-literal=key1=<key>
helm install designer deploy/helm/zplcore-designer \
  --set auth.apiKey.existingSecret=designer-api-keys \
  --set designer.allowedOrigins[0]=https://app.example.com

04Embedding with JavaScript (iframe)

Every designer host delivers the SDK at {baseUrl}/sdk/zplcore-designer.js (no dependencies, no CDN); with a bundler use the npm package @zplcore/designer. The SDK creates an iframe on the editor and talks to it with postMessage; the iframe isolates styles and scripts completely from your page.

<div id="editor" style="height: 720px"></div>
<script src="https://labels.example.com/designer/sdk/zplcore-designer.js"></script>   <!-- global ZplCoreDesigner -->
<script type="module">
  const editor = await ZplCoreDesigner.mount('#editor', {
    baseUrl: 'https://labels.example.com/designer/',
    design,                                   // design document { label, editor, records } or only the label JSON
    data: [{ name: 'Anna', ean: '9506000134352' }],
    locale: 'en', theme: 'auto', unit: 'mm',
    requestHeaders: { 'X-Api-Key': key },     // container with API key; travels by postMessage, never in a URL
    onChange: (doc) => save(doc)              // user edits only, debounced, incl. records
  });
  const zpl = await editor.getZpl();          // the same ZPL the server prints
</script>
// with a bundler
import { mount } from '@zplcore/designer';
const editor = await mount('#editor', { baseUrl: '/designer/' });

When the editor runs on another origin than your app, list your origin in AllowedOrigins of the designer host; across origins load the classic script (the ES module from another origin would need CORS). Ready-made wrappers for React 19, Vue 3 and Angular are in the SDK's examples/. All options, methods and events: section 11.

05Embedding as a Web Component

<script src="/designer/sdk/zplcore-designer.js"></script>   <!-- defines <zplcore-designer> -->
<zplcore-designer base-url="/designer/" locale="en" theme="auto" unit="mm"
                  features="-gs1,-rfid" style="display: block; height: 720px"></zplcore-designer>
<script type="module">
  const el = document.querySelector('zplcore-designer');
  el.design = doc;                  // or the attribute design='{"label":…}'
  el.data = rows;
  el.addEventListener('zplcore-change', (e) => save(e.detail.design));
  const zpl = await el.getZpl();
</script>

With a bundler: import '@zplcore/designer/element'. The element is a slim wrapper around the iframe SDK (Shadow DOM, the iframe is the part frame): attributes base-url, design, locale, theme, unit, tab, features, read-only, autosave, allowed-origins, timeout, title; properties design, data, requestHeaders; events (bubbling, composed) zplcore-ready, zplcore-change, zplcore-selection-change, zplcore-validation, zplcore-error; all SDK methods on the element. It works in every framework that renders custom elements.

06Blazor

dotnet add package ZplCore.Designer.Blazor
<ZplCoreDesigner @ref="editor" @bind-Design="design" Data="rows" BaseUrl="/designer"
                 Locale="en" Theme="DesignerTheme.Auto" ReadOnly="false" Height="720px"
                 OnChange="OnChange" OnSelectionChange="OnSelection" />

@code {
    ZplCoreDesigner? editor;
    JsonObject? design;                  // DesignDocument { schemaVersion, label, editor, records }
    List<object> rows = [new Dictionary<string, object?> { ["name"] = "Anna", ["ean"] = "9506000134352" }];

    async Task Export()
    {
        string zpl = await editor!.GetZplAsync();
        byte[] pdf = await editor.GetPdfAsync();
    }
}
// same host (Blazor Server or a hosted WebAssembly app)
builder.Services.AddZplCoreDesigner();
app.MapZplCoreDesigner("/designer").RequireAuthorization();   // your auth

The component wraps the JavaScript SDK for Blazor Server and WebAssembly (interactive render modes; nothing happens during prerendering). The editor is served by a ZplCore.Designer host in the same app or by the container: BaseUrl="https://labels.example.com/designer", list your app in ZPLCORE_DESIGNER__ALLOWEDORIGINS__0 and pass the key with RequestHeaders. Designs, ZPL and PDFs come back as byte streams, so the SignalR message limit of Blazor Server does not apply.

ParameterMeaning
BaseUrl (required)URL of the editor (MapZplCoreDesigner prefix or container URL)
Design / DesignChangedDesign document, two-way with @bind-Design
DataRecords for the binding (a new collection instance is sent)
Locale, Theme, ReadOnlyChangeable at any time
Unit, Tab, Features, Autosave, RequestHeaders, AllowedOrigins, SdkUrlRead when mounting
OnChange, OnSelectionChange, OnValidation, OnReady, OnErrorEvents (OnValidation switches the validation on)
Height, Class, Title, MaxResultBytesHost element, iframe title, largest result read back (64 MB)

Methods: GetDesignAsync, SetDesignAsync, SetDataAsync, GetZplAsync, GetPdfAsync, GetPngAsync, GetSelectionAsync, ValidateAsync, UndoAsync, RedoAsync, SetLocaleAsync, SetThemeAsync, SetRequestHeadersAsync.

07WPF / WinForms with WebView2

A desktop app starts the designer in its own process (NuGet ZplCore.Designer, Kestrel on a random 127.0.0.1 port) and shows it in WebView2. A small bridge page mounts the editor with the JavaScript SDK and forwards commands and events between C# (CoreWebView2.PostWebMessageAsString / WebMessageReceived) and the editor. The API answers only requests with a random per-process token, so other programs on the machine cannot use the port; nothing leaves the machine.

// samples/DesignerWpf: host on a random loopback port, API only with a per-process token
var host = await LocalDesignerHost.StartAsync(Path.Combine(AppContext.BaseDirectory, "bridge"));
await Web.EnsureCoreWebView2Async();
var core = Web.CoreWebView2;
var bridge = new DesignerBridge(json => { core.PostWebMessageAsString(json); return Task.CompletedTask; });
core.WebMessageReceived += (_, e) => bridge.OnMessage(e.TryGetWebMessageAsString());
core.Navigate(host.BridgePageUrl);                 // the bridge page mounts the editor with the JS SDK
await bridge.InitAsync(host.DesignerUrl, design, data, "en", headers: host.Headers);

string zpl = await bridge.GetZplAsync();
JsonObject? doc = await bridge.GetDesignAsync();

The sample samples/DesignerWpf (shared parts in samples/designer-embed: bridge page, DesignerBridge, LocalDesignerHost, record import from JSON or CSV) opens and saves designs, loads records, exports ZPL and PDF and switches undo/redo and the language. It needs the WebView2 runtime.

08.NET MAUI

A HybridWebView shows the same bridge page; commands and events go through DesignerBridge. On Windows the designer host runs inside the app (LocalDesignerHost); on Android, iOS and Mac Catalyst the app uses a designer host or the container on your network – allow the page origin there:

ZPLCORE_DESIGNER__ALLOWEDORIGINS__0=https://0.0.0.1   # Android
ZPLCORE_DESIGNER__ALLOWEDORIGINS__1=app://0.0.0.1     # iOS / Mac Catalyst

The sample samples/DesignerMaui needs the MAUI workloads (dotnet workload install maui-windows maui-android) and is not part of the solution. The canvas is designed for mouse and pen; on touch devices the editor works with limitations.

09Options and configuration

AddZplCoreDesigner(o => …) configures ZplCoreDesignerOptions; the container binds the same object from the section Designer.

OptionDefaultMeaning
PathBase/designerPrefix used by MapZplCoreDesigner() without argument
AllowedOriginsemptyOrigins that may frame the editor and call the API cross-origin; empty = same origin only
AllowAnonymousfalseOpen the API without an authenticated user (explicitly only)
Fonts.AddDirectory(dir, "E")–Printer-memory fonts (TTF/OTF) as E:NAME.TTF; Fonts.Providers for own IFontProviders
Fonts.AddGraphicDirectory(dir)–Fonts released for graphic text (they must be installed on the server too); system fonts are never served
PrinterGraphics.AddDirectory(dir) / .ProvidernonePrinter-memory graphics (^XG) for canvas and render
Icons.AddDirectory(dir) / .ProvidernoneYour SVG icons next to the built-in library
Templates.AddDirectory(dir) / .ProvidernoneDesign templates (id.json, translations id.de.json)
SvgFonts.AddDirectory(dir)noneSVG fonts and glyph sets for the fonts tab
Limitssee belowProtection limits
Features.*all trueZpl, Png, Pdf, Svg, Preview, ItemInk, ZplElement, Fonts, PrinterGraphics, ImageConvert, SvgConvert, SvgOptimize, Icons, Templates, ZplAnalyse, ZplLint, SvgFonts, ZplImport, BarcodeGenerate, BarcodeReadability, OpenApi, Ui – false does not map the endpoint (404) and hides its tab
Ui.Branding–Title, PlatformTitle, Logo, LogoAlt, Favicon, HideTabs, HideHeaderLinks
Ui.FileProviderembedded UIServe a customised build of the editor UI

Limits

LimitDefault
MaxRequestBodyBytes16 MB
MaxRecordsPerRequest500
MaxItemsPerDesign2,000
MaxInkItemsPerRequest · MaxInkBatchRequests · MaxInkBatchItems60 · 50 · 1,000
MaxImageBytes · MaxImagePixels10 MB · 40 MP
MaxSvgBytes · MaxGraphicDots2 MB · 16 M dots
MaxZplElementBytes · MaxLabelMm1 MB · 1,000 mm
MaxConcurrentRenders · MaxQueuedRenders · QueueTimeoutCPU count · 4 × CPU count · 10 s
RenderTimeout30 s
ItemInkCacheEntries20,000
RateLimit.PermitsPerWindow · RateLimit.Window0 (off) · 1 min, per client IP

Extension points

Interfaces you register in DI or pass in the options: IFontProvider, IPrinterGraphicsProvider, IIconProvider, ITemplateProvider (content), IDesignerAuthorization (allow or refuse an operation per request), IRenderObserver (logging, metrics) and IDesignerLicenseValidator (licence check).

Configuration of the editor UI

The UI reads, later sources winning: {prefix}/config.json of the server (features, embedding origins, branding – no secrets) → window.__ZPLCORE_DESIGNER__ of a host page → the query string for display options only (locale, theme, unit, tab, autosave=0, features=-images,-fonts). The API base, request headers and origins never come from the URL. autosave (default on) keeps a draft in the browser's local storage under the prefix zplcore.designer.; switch it off when your app stores the design.

10Render API reference

All routes are relative to {prefix}/api (e.g. /designer/api). Requests and responses are UTF-8 JSON with camelCase names; every request is self-contained, nothing is stored. Responses carry X-ZplCore-Designer-Api: 1. The editor uses this API; your backend can call it directly (with the host's authentication or the container's API key).

Design rendering

{
  "document": { "label": { "unit": "Mm", "width": 60, "height": 40, "items": [
    { "type": "text", "name": "name", "x": 3, "y": 3, "dataField": "name", "fontSize": 14 } ] } },
  "records": [ { "name": "Anna" }, { "name": "Ben" } ]
}
RouteBodyResult
POST /zpldocument, records?{ "zpl": "^XA…^XZ" }, one format per record
POST /render/pngdocument, record? or records? (first)image/png, one label
POST /render/pdfdocument, records?application/pdf, one page per record
POST /render/svgdocument, records? (first)image/svg+xml (vector)
POST /render/previewas pngimage/png – print image for the canvas overlay

document is a design document (or { "label": … }); the label is read strictly (unknown properties → 400). Values come from records/record, not from document.records. Every image is the rendered /zpl result: preview = print.

curl -X POST https://labels.example.com/designer/api/zpl \
  -H "X-Api-Key: $DESIGNER_API_KEY" -H "Content-Type: application/json" \
  -d @request.json

Canvas helpers

RoutePurpose
POST /item-ink · POST /item-ink/batchPrinted geometry of each element in dots (x, y, width, height, for linear barcodes also bars*) – keeps the canvas congruent with the print; cached on the server
POST /zpl-elementPreview of a ZPL element: { zpl, dpi, widthMm, heightMm } → { empty, x, y, width, height, png }
GET /fonts{ graphic: [families], printer: [{ name, family }] }
GET /fonts/font0 · /fonts/mono · /fonts/printer?name=E:X.TTFFont files for the canvas
GET /printer-graphics · /printer-graphics/png?name=E:LOGO.GRFPrinter-memory graphics of the host
POST /barcode/readabilityRead-back check of artistic barcodes (findings AR001–AR010)

Conversion and analysis

RoutePurpose
POST /image/convertImage (base64 PNG/JPEG/GIF/BMP) → grf and zpl, with crop, rotate, flip, size and blackness; stateless, no upload
POST /svg/convert · POST /svg/optimizeSVG → ^GFA / optimised SVG (scripts, event handlers, external references removed)
POST /zpl/analyse · POST /zpl/lintZPL analyser (commands, print time) and linter (issues with fix)
POST /zpl/design · POST /zpl/decodeZPL → design document (import) · bytes of a ZPL file → text
POST /barcode/generateSingle barcode as ZPL + PNG (generator tab)
GET /icons · /icons/{id} · /svg-fonts/{path} · /templates · /templates/{id}/design · /templates/{id}/thumbContent of the host

Errors

{ "error": "The request body is larger than 16,777,216 bytes.", "code": "too_large" }
HTTPcodeWhen
400invalid_request · render_failedUnreadable or invalid request · the design could not be rendered
401 / 403forbidden · license_invalidAuthentication of the host / container, authorization hook, licence check
404not_foundUnknown font, graphic, icon, template or route, or endpoint switched off
413too_largeAbove a limit (the message names it)
415unsupported_media_typeImage format other than PNG/JPEG/GIF/BMP
429rate_limitedRate limit; Retry-After header
503 · 504busy · timeoutAll render slots busy (Retry-After) · render longer than RenderTimeout

Server options for the UI: GET {prefix}/config.json; health: GET {prefix}/healthz → { status, version, buildTimeUtc }. The complete OpenAPI description is served at {prefix}/api/openapi/zplcore-designer.json by your own host.

11JavaScript API

ZplCoreDesigner.mount(target, options)

Creates the editor in target (element or selector) and resolves with an editor object once the editor answers.

OptionDefaultMeaning
baseUrl– (required)URL of the editor, e.g. /designer/ or https://labels.example.com/designer/
designstart designDesign document, label JSON or JSON text
datadesign.recordsRecords for the binding
locale · theme · unit · tabstored / browser · auto · mm · –10 languages · light / dark / auto · mm / inch · start tab (pdf, zpl, data …)
featuresall onSwitch parts off: { gs1: false }, ['-gs1'] or '-gs1,-rfid' – gs1 rfid images icons fonts zplElement jsonTab testData templates pdfPreview svgPreview pngPreview zplTab zplAnalyser zplLinter zplToDesign dpiCalc generator serverStatus commandPalette fileMenu
readOnlyfalseView, preview and export only
autosavefalseKeep a draft in the browser (your app saves through onChange)
requestHeaders–Headers of the editor's API calls (object or async function), e.g. { 'X-Api-Key': … } or Authorization: Bearer …
allowedOriginsorigin of baseUrlOrigins whose messages are accepted
timeout · readyTimeout30,000 msTime limit of a command (each call also accepts { timeout }) · until the editor is ready
title, className–iframe title (accessibility) and CSS class
onChange · onSelectionChange · onValidation · onReady · onError–Events: user edits (debounced, complete design incl. records; not your own setDesign/setData) · { names, items } · server linter result after each change · ready · { code, message }

Methods (all return promises)

MethodResult
getDesign() · setDesign(doc, records?)Design document ({ label, editor, … }) · load a design (document or label JSON)
setData(records)Replace the records
getZpl({ records? })ZPL for all (or the given) records – the same ZPL the server prints
getPdf({ records? }) · getPng({ record: 0 })Blob of the PDF / of one label as PNG
undo() · redo()History
setLocale(l) · setTheme(t) · setReadOnly(on) · setRequestHeaders(h)Language, colour scheme, read-only mode, new token
getSelection() · validate(){ names, items } · { valid, errorCount, warningCount, infoCount, issues }
ping() · whenReady() · on(event, fn) / off(event, fn)Liveness, readiness, events after mounting
destroy()Remove the iframe and all listeners

All methods return promises; failures reject with ZplCoreDesignerError (code: timeout, command_failed, ready_timeout, destroyed, invalid_options …). createDesigner() returns the instance at once (commands wait until it is ready).

The postMessage protocol

Without the SDK you can talk to the iframe directly (protocol version 2). Host → editor: { source: 'zplcore-host', type, id, payload }; editor → host: { source: 'zplcore', type: 'result', id, payload } and the events ready and change (debounced, { design, records }). Commands: ping, getDesign, setDesign { design, records? }, setData { records }, getZpl { records? }, getPdf, getPng { record? } (both { mimeType, base64 }), undo, redo, setLocale { locale }, setTheme { theme }. The first valid sender fixes the host origin (or the allow-list AllowedOrigins); answers go only to that origin, never to *. The SDK additionally uses a start handshake (?embed=sdk: the editor sends hello, the SDK answers init with headers, read-only mode, design and records before the first API call) and the commands setReadOnly, setRequestHeaders, getSelection, setValidation, validate with the events selectionChange, validation and error. The live editor on this site uses exactly this protocol.

12Design document schema

A design is a JSON document with five parts. Only label is required; the editor writes only values that differ from their default.

{
  "schemaVersion": 4,
  "label":   { "unit": "Mm", "width": 100, "height": 50, "items": [ … ] },
  "editor":  { "version": 1, "dpi": 203, "items": { "ean": { "locked": true } }, "groups": [] },
  "records": [ { "name": "Anna", "metadata": { "promo": { "isRendered": false } } } ],
  "uiSettings": { "zoom": 120, "tab": "pdf" }
}
PartContentAffects the print
labelThe zplCore label model (label.schema.json, schema version 4) – the same JSON the SDK reads with Label.FromJsonyes
editorState of the designer, own version editor.version (missing = 1)partly (see below)
recordsSample records; field names are the binding names; reserved metadata[itemName].isRendered = false hides an element for that recordyes (hiding)
uiSettingsOptional UI state: vsplit, split, theme, zoom, tab, gridSize, snapDistanceno

The editor part

PropertyType · defaultMeaning
versioninteger · 1Version of the editor part
dpiinteger · 203Resolution of the preview; fallback when no printer dpi is given – affects the ZPL
debugLabelboolean · falseDebug label (frame, centre cross, 10 mm marks, timestamp) – affects the ZPL
cutAfterLastLabelMode"delayed" | "cutter"How label.cutAfterLastLabel is printed – affects the ZPL
grid{ enabled }Grid on/off (further properties allowed)
items[name]objectPer element (key = label.items[].name, case-sensitive): locked, groupId, gs1 (elements[{ai, value}], encoding bracket / encoded / digitalLink, domain), textFit (auto / clip / wrap / shrink), rfidLockApply and rfidPermaLockApply (affect the ZPL)
groups[{ id, name }]Groups of the element list

Compatibility promise

  • Within a schema version only additive changes: new optional properties whose default keeps the previous print and behaviour.
  • Renaming, removing, a new type, meaning or default ⇒ a new version (label.schemaVersion or editor.version) with a migration step; every version reads all older ones.
  • Unknown properties in editor are kept and written back, so an older host never damages a newer design. At API boundaries the label is read strictly.

13Security and operations

  • No user management of its own. NuGet: your authentication and policies. Container: apikey, jwt (OIDC) or explicitly none.
  • Embedding: frame-ancestors and CORS from AllowedOrigins; postMessage answers only to the fixed host origin.
  • Inputs: size limits per request, render slots with queue and time limit, optional rate limit per IP; SVGs are sanitised (no scripts, no external references), the server never fetches URLs, ZPL elements are only rendered.
  • No storage, no telemetry: the server keeps nothing between requests (only caches of measured geometry); no phone-home, no CDN – the UI loads every file from your host.
  • Scaling: stateless, so any number of instances without sticky sessions. Native dependencies (SkiaSharp, HarfBuzz, PDFium, ZXing) support Linux x64/arm64 (glibc), Windows and macOS.
  • Monitoring: health {prefix}/healthz (container also /healthz, /readyz), metrics meter ZplCore.Designer (zplcore.designer.requests, zplcore.designer.duration, zplcore.designer.records), OpenTelemetry export (OTEL_EXPORTER_OTLP_ENDPOINT) and structured JSON logs in the container.
  • Container: distroless base (Azure Linux), non-root user app, read-only root file system with a writable /tmp; Helm chart with dropped capabilities, seccomp and optional network policy.
  • Privacy: designs and records stay in your application; the editor's draft lives only in the user's browser and can be switched off (autosave=0).

14Licensing

The zplCore Designer is part of the commercial zplCore.com product family. Licence model and prices are not published yet; contact us for a trial version or an offer. The live editor on this site is free to try with demo limits.