Saltar al contenido

Convert ZPL to PDF in PHP

To convert ZPL to PDF in PHP, POST the ZPL with cURL to the OpenZPL render endpoint with the header Accept: application/pdf and write the response to a file. Check the HTTP status before using the file: errors come back as text that starts with ERROR:.

Actualizado 2026-10-20 · Renderizados hechos con el motor de OpenZPL

Abrir en el visor

Lo que necesita

  • PHP 7.4 or later with the cURL extension (php -m | grep curl).
  • The ZPL, the label size in inches and the printer density (8dpmm = 203 dpi).

The minimal request

<?php
$zpl = file_get_contents('label.zpl');
$ch = curl_init('https://api.openzpl.com/v1/printers/8dpmm/labels/4x2/');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $zpl,
    CURLOPT_HTTPHEADER => ['Accept: application/pdf'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$pdf = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) {
    throw new RuntimeException($pdf);   // "ERROR: ..."
}
file_put_contents('label.pdf', $pdf);

Passing the ZPL as a string to CURLOPT_POSTFIELDS sends it as the raw body. Passing an array would send a form; if you want that, use ['file' => new CURLFile('label.zpl')], which the API also accepts.

Warehouse bin label with the location A12 03 in large type and a Code 39 barcode
etiqueta de 4 × 2 in a 203 dpi (8 dpmm) · 812 × 406 px · Abrir en el visor
^XA
^FO20,20^GB772,366,4^FS
^FO40,40^A0N,30,30^FDWAREHOUSE BIN^FS
^FO40,85^A0N,110,110^FDA12 03^FS
^BY3,3,110
^FO40,215^B3N,N,110,N,N^FDA1203^FS
^XZ

A function with retries and no memory spike

CURLOPT_FILE writes the body straight to disk, so a 50-page PDF never sits in a PHP string. A header callback picks up Retry-After and X-Total-Count as they arrive.

<?php
function zpl_to_pdf(string $zpl, string $path, int $dpmm = 8, string $size = '4x6',
                    ?string $apiKey = null, int $retries = 3): int
{
    $url = "https://api.openzpl.com/v1/printers/{$dpmm}dpmm/labels/{$size}/";
    $headers = ['Accept: application/pdf', 'Content-Type: text/plain; charset=utf-8'];
    if ($apiKey) {
        $headers[] = "X-API-Key: $apiKey";
    }
    for ($attempt = 0; ; $attempt++) {
        $out = fopen($path, 'wb');
        $meta = [];
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $zpl,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_FILE => $out,            // the body goes straight to disk
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$meta) {
                if (preg_match('/^(retry-after|x-total-count):\s*(\d+)/i', $line, $m)) {
                    $meta[strtolower($m[1])] = (int) $m[2];
                }
                return strlen($line);
            },
        ]);
        $ok = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        fclose($out);
        if ($ok === false) {
            throw new RuntimeException("Request failed: $error");
        }
        if (($status === 429 || $status === 503) && $attempt < $retries) {
            sleep($meta['retry-after'] ?? 1);
            continue;
        }
        if ($status !== 200) {
            $message = trim(file_get_contents($path));   // "ERROR: ..."
            unlink($path);
            throw new RuntimeException("HTTP $status: $message");
        }
        return $meta['x-total-count'] ?? 1;
    }
}

$count = zpl_to_pdf(file_get_contents('label.zpl'), 'label.pdf');
echo "label.pdf: $count label(s)\n";

Tested on PHP 8.5 with a four-label file: it printed label.pdf: 4 label(s). A 60-copy label threw HTTP 413: ERROR: The ZPL produces 60 labels; the maximum per request is 50 and left no half-written file behind. The code calls no curl_close(): it does nothing since PHP 8.0 and is deprecated in 8.5.

Serving the PDF to a browser

To send the label to the user instead of saving it, render it to a temporary file and stream it with header('Content-Type: application/pdf') and readfile(). Don't echo the cURL result before checking the status, or users download an error message named label.pdf.

Errores

EstadoLo que significaQué hacer
400Un parámetro en la URL es incorrecto, o los datos de un código de barras son inválidos.Lea el texto después de ERROR: y corrija la solicitud. No la reintente sin cambios.
404El ZPL no tiene etiqueta, o el índice de la etiqueta está fuera de rango. X-Total-Count indica cuántas etiquetas hay.Verifique que el ZPL tenga bloques completos de ^XA … ^XZ, o pida un índice por debajo de X-Total-Count.
406El encabezado Accept solicita un formato que la API no produce.Enviar image/png o application/pdf.
413El cuerpo supera 1 MB, o el ZPL produce más de 50 etiquetas.Divide el trabajo en varias solicitudes.
429Se alcanzó un límite de su plan: solicitudes por segundo o etiquetas por día UTC.Espere los segundos en Retry-After, luego reintente.
503La etiqueta tardó más de 10 segundos en renderizar, o el servicio está a capacidad.Intente de nuevo una vez después de Retry-After; si se repite, baje la densidad o divida la etiqueta.

Límites

  • Punto final: POST https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/ con el ZPL como cuerpo de la solicitud. La densidad es 6dpmm, 8dpmm, 12dpmm o 24dpmm; el tamaño está en pulgadas, hasta 15 por lado.
  • Accept: application/pdf sin un índice de etiqueta devuelve cada etiqueta, una por página. Agregue un índice (.../4x6/0/) para obtener una sola etiqueta.
  • Plan gratuito: sin clave, 3 solicitudes por segundo y 5,000 etiquetas por día por dirección IP. Cada etiqueta cuenta, se incluyen copias ^PQ y páginas PDF. Consulte precios.
  • Por solicitud: hasta 50 etiquetas y 1 MB de ZPL.

Preguntas

Can I use Guzzle instead of cURL?

Yes. Send the ZPL as the body option with the Accept: application/pdf header and use the sink option to write the response to a file. The API doesn't care which client you use.

Why does my PDF have question marks instead of accents?

Send the ZPL as UTF-8 and start the label with ^CI28. Without ^CI28 the printer, and the renderer, read the bytes in another character set.

Do I need an API key?

Not to start. Without a key the free limits apply per IP address. Add X-API-Key when you need your plan's limits.

Fuentes