1. Templates
  2. Generate PDF

Templates

Generate PDF

POST
`/v1/templates/pdf/generate`

Generate custom PDFs with text, barcodes (Code128), QR codes, DataMatrix codes, and lines using explicit x,y positioning. This endpoint is useful for creating shipping labels, asset tags, inventory bin labels, and other custom documents.

Authentication

This endpoint requires authentication with an API key that has the templates:write scope.

Header Value
PX-API-KEY Your API key with templates:write scope
Content-Type application/json

Performance

PDF generation runs in a dedicated background service, so rendering a complex document with many elements does not tie up the API. The generated PDF is uploaded to cloud storage and a public URL is returned.

Request Body

size string
Page size. Options: A4, letter, 4x6, a6, custom. Default: A4
orientation string
Page orientation. Options: portrait, landscape. Default: portrait
width number
Custom width in points (required if size is custom). Minimum: 72
height number
Custom height in points (required if size is custom). Minimum: 72
filename string
Optional custom filename (without .pdf extension)
margin_top number
Top margin in points. Default: 18 (0.25 inch)
margin_bottom number
Bottom margin in points. Default: 18 (0.25 inch)
dpi integer
Rasterization density for raster elements (barcodes, QR, DataMatrix, images). Range: 72-600. Default: 216. See Raster Density (DPI)
raw array (required)
Array of elements to render (1-500 elements)

Page Sizes

Size Dimensions (points) Use Case
A4 595 x 842 Standard documents
letter 612 x 792 US Letter documents
4x6 288 x 432 Shipping labels
a6 298 x 420 Small labels
custom User-defined Custom dimensions required

Note: 72 points = 1 inch. Coordinates use top-left origin with Y increasing downward.

Raster Density (DPI)

The generated PDF is a vector document, so text and lines are resolution-independent and always render crisply at any zoom or print size. The dpi parameter controls only the raster elements - barcodes, QR codes, DataMatrix codes, and images.

One rule covers all of them: dpi sets the rasterization density for everything the server rasterizes - barcodes, QR/DataMatrix codes, and SVG. For bitmaps you supply yourself, dpi only ever trims excess pixels, and only when you pass it explicitly, so your original image data is never altered unless you ask.

Behavior Detail
Range 72 (screen) to 600 (high-quality print), integer only
Default 216 - unchanged from previous behavior, so omitting dpi produces identical barcode/QR/matrix output to before this parameter existed, and leaves supplied bitmaps at their native resolution
Barcodes / QR / DataMatrix Higher dpi renders the code at a higher pixel density before embedding, producing sharper edges for high-resolution printers and more reliable scanning at small sizes. Barcode density is quantized to integer multiples of 72 DPI (the underlying renderer uses an integer scale), so dpi is rounded to the nearest multiple of 72 for barcodes - e.g. any dpi from 180 to 251 renders barcodes identically (at 216 DPI)
SVG images Always rendered at the resolved dpi, like a barcode - there are no original pixels to preserve, so a higher dpi produces genuinely more detail rather than upscaling. Because of this, sending 216 and omitting dpi produce identical SVG output. Subject to a 5000-pixel-per-side raster ceiling, which can cap effective density on page-sized artwork - see Image Formats
Bitmap images (PNG/JPEG) Only resampled when dpi is explicitly provided - omit dpi and they keep their native resolution. When set, they are downsampled to match the requested dpi at their on-page size and are never upscaled, so a low dpi never adds blur - it only trims wasted pixels to shrink the file. An image already coarser than the requested dpi is embedded unchanged
Text / lines Not affected - vector content ignores dpi

Note: Supplying dpi at all - even 216, the value used when it is omitted - opts supplied bitmaps into resampling. This differs from omitting dpi, which leaves them at their native resolution. If you want bitmaps untouched, omit dpi rather than sending 216. This distinction applies to bitmaps only: barcode/QR/matrix and SVG output is identical either way.

Higher DPI values produce crisper raster elements at the cost of a larger output file. For most on-screen use the default (216) is ideal; raise it toward 300-600 for thermal/laser label printing, or lower it toward 72-150 to minimize file size when print sharpness is not a concern.

        const data = {
  size: "4x6",
  orientation: "portrait",
  dpi: 300, // sharper barcodes for thermal label printing
  raw: [
    { type: "code128", x: 20, y: 20, value: "1Z999AA10123456784", height: 50, width: 248 },
    { type: "qr", x: 100, y: 90, value: "https://track.example.com/1Z999AA10123456784", height: 80 },
  ],
};

      

Element Types

Text Element

type string (required)
Set to "text"
x number (required)
X coordinate in points from left edge
y number (required)
Y coordinate in points from top edge
value string
Text content to display
height number
Font size in points. Default: 12
width number
Maximum text width (for clipping). Default: page width
font_family string
Font family: inter (sans-serif, default) or mono (Fira Mono, monospace)
font_weight string
Font weight: light, regular (default), bold, black
align string
Text alignment: left (default), center, right. For center/right, x is the alignment anchor point
color string
Hex color code (e.g., #FF0000). Default: #000000
inverted boolean
When true, draws a filled rectangle using color behind the text and renders text in text_color. Default: false
text_color string
Hex color code for text when inverted is true. Default: #FFFFFF. Ignored when inverted is false
padding number
Padding in points around text when inverted is true. Default: 4. Ignored when inverted is false
angle number
Rotation angle in degrees. Default: 0

Code128 Barcode

type string (required)
Set to "code128"
x number (required)
X coordinate in points
y number (required)
Y coordinate in points
value string (required)
Barcode value to encode
height number
Barcode height in points. Default: 40
width number
Barcode width in points
angle number
Rotation angle in degrees. Default: 0

QR Code

type string (required)
Set to "qr"
x number (required)
X coordinate in points
y number (required)
Y coordinate in points
value string (required)
Data to encode (URL, text, etc.)
height number
QR code size in points. Default: 50
width number
QR code width (if different from height)
angle number
Rotation angle in degrees. Default: 0

DataMatrix Code

type string (required)
Set to "matrix"
x number (required)
X coordinate in points
y number (required)
Y coordinate in points
value string (required)
Data to encode
height number
DataMatrix size in points. Default: 50
width number
DataMatrix width (if different from height)
angle number
Rotation angle in degrees. Default: 0

Line

type string (required)
Set to "line"
x number (required)
Starting X coordinate in points
y number (required)
Starting Y coordinate in points
height number
Line thickness in points. Default: 1
width number
Line length in points. Default: 100
color string
Hex color code. Default: #000000
angle number
Line angle in degrees (0 = horizontal). Default: 0

Image

type string (required)
Set to "image"
x number (required)
X coordinate in points from left edge
y number (required)
Y coordinate in points from top edge
value string (required)
Image URL, base64-encoded image data, or "org_logo" to use the organization's logo. An image fetched by URL may be up to 10 MB, any format. See Image Formats
height number
Image height in points, max 5000 (reported per element in errors). Default: 12. For SVG, omit it to derive the height from the image's aspect ratio
width number
Image width in points, max 5000 (reported per element in errors). Default: page width
angle number
Rotation angle in degrees. Default: 0

Image Formats

PNG, JPEG, and SVG are supported. The format is detected from the data itself, so no extra field is needed.

SVG may be supplied in any of these forms:

Form Example
HTTPS URL https://cdn.example.com/logo.svg
base64 data URI data:image/svg+xml;base64,PHN2ZyB4bWxucz0...
bare base64 PHN2ZyB4bWxucz0...

Because a PDF cannot hold an SVG directly, SVG input is rasterized to a bitmap before embedding, at the element's on-page size scaled to dpi, so it stays crisp at print size - up to the raster ceiling below.

Sizing. An SVG is drawn into the element's width x height box. Omit height and it is derived from the SVG's own aspect ratio, so a logo sent with only a width keeps its proportions; omit both and the SVG's intrinsic size is used. A box larger than the page is scaled down to fit the page dimensions, preserving its aspect ratio. (x and y are not adjusted, so an SVG positioned near the edge can still overflow.)

Limits. Every limit below reports as a per-element entry in errors while the rest of the PDF still renders - no limit fails the whole request.

Limit Value
Image element box 5000 x 5000 points
SVG source document 2 MB
Image fetched by URL (any format) 10 MB
Raster ceiling per SVG element 5000 pixels per side
Rasterization budget per request 40 million pixels across all distinct SVG elements
Rendering time budget per request 25 seconds of SVG rasterization

Repeating the same image is free. The usual case - one logo on every label of a sheet - is rasterized and embedded once and then reused, so it costs the budget once no matter how many elements draw it, and the output PDF carries a single copy of the bitmap. This applies to PNG and JPEG as well as SVG.

Distinct artwork per element is what consumes the budget, so page-sized SVGs are limited in number. Approximate ceilings for full-bleed artwork, one distinct document per element:

Page and density Distinct elements before a budget error
4x6 label at dpi 216 ~35
4x6 label at dpi 600 ~4
A4 at dpi 216 ~8
A4 at dpi 600 ~2
120x40pt logo at dpi 600 ~100

The raster ceiling can cap effective density below the dpi you request for page-sized SVG. A full-page A4 element at dpi: 600 would need 7016 pixels on its long edge; capped at 5000, it renders at roughly 433 effective dpi. Label-sized artwork is unaffected - a 4x6 label at dpi: 600 needs 3600 pixels and gets the full density.

SVG documents containing entity declarations (<!ENTITY>, or a <!DOCTYPE> with an internal subset) are rejected, because expanding them can consume unbounded CPU; a plain <!DOCTYPE svg PUBLIC ...> with no internal subset is fine.

Font Families

Family Description Weights Available
inter Inter (Sans-serif) - Default light, regular, bold, black
mono Fira Mono (Monospace) regular (all weights map to regular)

Use mono for tracking numbers, SKUs, and other codes where character alignment matters.

Text Alignment

Align Description
left Text starts at x position (default behavior)
center Text is centered at x position - useful for centering on page (x = page_width/2)
right Text ends at x position - useful for right-aligned columns

Note: When using center or right alignment, the x coordinate becomes the anchor point for alignment rather than the text starting position.

Example Request - Shipping Label

        const data = {
  size: "4x6",
  orientation: "portrait",
  raw: [
    {
      type: "text",
      x: 20,
      y: 20,
      value: "SHIP TO:",
      height: 10,
      font_family: "inter",
      font_weight: "bold"
    },
    {
      type: "text",
      x: 20,
      y: 35,
      value: "John Smith",
      height: 14,
      font_family: "inter",
      font_weight: "black"
    },
    {
      type: "text",
      x: 20,
      y: 52,
      value: "123 Main Street",
      height: 12,
      font_family: "inter"
    },
    {
      type: "text",
      x: 20,
      y: 67,
      value: "New York, NY 10001",
      height: 12,
      font_family: "inter"
    },
    {
      type: "line",
      x: 20,
      y: 90,
      height: 1,
      width: 248
    },
    {
      type: "text",
      x: 20,
      y: 110,
      value: "1Z999AA10123456784",
      height: 10,
      font_family: "mono"
    },
    {
      type: "code128",
      x: 20,
      y: 125,
      value: "1Z999AA10123456784",
      height: 50,
      width: 248
    },
    {
      type: "qr",
      x: 188,
      y: 200,
      value: "https://track.example.com/1Z999AA10123456784",
      height: 80
    }
  ]
};

const res = await fetch("https://api.packagex.io/v1/templates/pdf/generate", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(data),
}).then((res) => res.json());

const pdfUrl = res.data.url; // URL to the generated PDF
const filename = res.data.filename; // e.g., "pdf_abc123.pdf"

      

Example Request - Asset Tag with Font Families

        const data = {
  size: "custom",
  width: 144,
  height: 72,
  orientation: "portrait",
  raw: [
    {
      type: "text",
      x: 5,
      y: 8,
      value: "ASSET TAG",
      height: 6,
      font_family: "inter",
      font_weight: "bold"
    },
    {
      type: "text",
      x: 5,
      y: 50,
      value: "AST-001234",
      height: 8,
      font_family: "mono"
    },
    {
      type: "code128",
      x: 5,
      y: 18,
      value: "AST-001234",
      height: 25,
      width: 100
    },
    {
      type: "matrix",
      x: 110,
      y: 18,
      value: "AST-001234",
      height: 30,
      width: 30
    }
  ]
};

const res = await fetch("https://api.packagex.io/v1/templates/pdf/generate", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(data),
}).then((res) => res.json());

      

Response

        {
  "data": {
    "url": "https://storage.googleapis.com/bucket/org_123/pdf/pdf_abc123.pdf",
    "filename": "pdf_abc123.pdf"
  },
  "message": "PDF generated successfully"
}

      

Partial Success Response

If some elements fail to render but the PDF is still generated:

        {
  "data": {
    "url": "https://storage.googleapis.com/bucket/org_123/pdf/pdf_abc123.pdf",
    "filename": "pdf_abc123.pdf",
    "errors": [
      {
        "index": 2,
        "type": "code128",
        "error": "Invalid barcode value"
      }
    ]
  },
  "message": "PDF generated with 4/5 elements rendered successfully"
}

      

Error Responses

Empty Raw Array (400)

        {
  "errors": [
    {
      "message": "Array must contain at least 1 element(s)",
      "path": ["body", "raw"]
    }
  ]
}

      

Custom Size Missing Dimensions (400)

        {
  "errors": [
    {
      "message": "width and height are required when size is 'custom'"
    }
  ]
}

      

Margins Exceed Page Height (400)

Returned when margin_top + margin_bottom is greater than or equal to the page height, leaving no printable area. margin_top and margin_bottom each default to 18pt (0.25 inch), so short custom pages fit by default; this error occurs when the supplied margins are too large for the page.

        {
  "status": 400,
  "message": "Top and bottom margins (margin_top 40 + margin_bottom 40 = 80pt) leave no printable area on a 72pt-tall page. margin_top and margin_bottom each default to 18pt when omitted - pass smaller margin_top and margin_bottom (e.g. 0) in the request to fit this page height."
}

      

Invalid Color Format (400)

        {
  "errors": [
    {
      "message": "Invalid color format. Use hex code like #FF0000",
      "path": ["body", "raw", 0, "color"]
    }
  ]
}

      

Invalid Font Family (400)

        {
  "errors": [
    {
      "message": "Invalid enum value. Expected 'inter' | 'mono'",
      "path": ["body", "raw", 0, "font_family"]
    }
  ]
}

      

Too Many Elements (400)

        {
  "errors": [
    {
      "message": "Array must contain at most 500 element(s)",
      "path": ["body", "raw"]
    }
  ]
}

      

Example Request - Inventory Bin Label

        const data = {
  size: "custom",
  width: 216,  // 3 inches
  height: 72,  // 1 inch
  orientation: "portrait",
  raw: [
    {
      type: "text",
      x: 10,
      y: 15,
      value: "BIN A-01-03",
      height: 14,
      font_family: "inter",
      font_weight: "black"
    },
    {
      type: "code128",
      x: 10,
      y: 35,
      value: "A-01-03",
      height: 25,
      width: 120
    },
    {
      type: "text",
      x: 140,
      y: 42,
      value: "A-01-03",
      height: 12,
      font_family: "mono"
    }
  ]
};

const res = await fetch("https://api.packagex.io/v1/templates/pdf/generate", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(data),
}).then((res) => res.json());

      

Example Request - Text Alignment

        const data = {
  size: "A4",
  orientation: "portrait",
  raw: [
    // Left aligned (default) - text starts at x
    {
      type: "text",
      x: 50,
      y: 50,
      value: "Left Aligned",
      height: 18,
      font_family: "inter",
      align: "left"
    },
    // Center aligned - text centered at x (page center = 297.5)
    {
      type: "text",
      x: 297,
      y: 80,
      value: "Center Aligned",
      height: 18,
      font_family: "inter",
      font_weight: "bold",
      align: "center"
    },
    // Right aligned - text ends at x (right margin = 545)
    {
      type: "text",
      x: 545,
      y: 110,
      value: "Right Aligned",
      height: 18,
      font_family: "inter",
      align: "right"
    },
    // Horizontal line to show alignment
    {
      type: "line",
      x: 50,
      y: 130,
      height: 1,
      width: 495
    }
  ]
};

const res = await fetch("https://api.packagex.io/v1/templates/pdf/generate", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(data),
}).then((res) => res.json());

      

Example Request - Rotated Label Elements

        const data = {
  size: "4x6",
  orientation: "landscape",
  raw: [
    {
      type: "text",
      x: 50,
      y: 100,
      value: "FRAGILE",
      height: 24,
      font_family: "inter",
      font_weight: "black",
      color: "#FF0000",
      angle: -15
    },
    {
      type: "line",
      x: 20,
      y: 150,
      height: 2,
      width: 200,
      color: "#FF0000"
    },
    {
      type: "text",
      x: 50,
      y: 180,
      value: "HANDLE WITH CARE",
      height: 16,
      font_family: "inter",
      font_weight: "bold"
    }
  ]
};

const res = await fetch("https://api.packagex.io/v1/templates/pdf/generate", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(data),
}).then((res) => res.json());

      

Best Practices

Coordinate System

  • All coordinates are in points (72 points = 1 inch)
  • The origin (0, 0) is at the top-left corner
  • X increases to the right, Y increases downward
  • Elements are positioned by their top-left corner

Element Ordering

Elements are rendered in the order they appear in the raw array. Later elements will appear on top of earlier ones if they overlap.

Font Selection

  • Use inter (default) for general text - it has excellent readability at all sizes
  • Use mono (Fira Mono) for tracking numbers, SKUs, and codes where character alignment is important

Barcode Best Practices

  • Ensure barcode values contain only valid characters for the barcode type
  • Code128 supports alphanumeric characters
  • QR codes can encode URLs, text, or binary data
  • DataMatrix codes are ideal for small spaces with high data density

Performance Tips

  • Keep the number of elements under 100 for optimal performance
  • Use the smallest appropriate page size for your use case
  • Pre-validate barcode values before sending to avoid partial success responses