PDF Generation

PSPDFKit API allows you to create PDF documents from an HTML file. You do this by supplying a part containing the html key to the POST https://api.pspdfkit.com/build endpoint. Here’s a schema outlining all the available options:

Typescript
type Orientation = "landscape" | "portrait";
type PageSize =
  | "A0"
  | "A1"
  | "A2"
  | "A3"
  | "A4"
  | "A5"
  | "A6"
  | "A7"
  | "A8"
  | "Letter"
  | "Legal";

// Represents one part that was sent in the multipart request. Should be the
// `name` that was specified for the part.
type MultipartReference = string;

type HTMLPart = {
  html: MultipartReference, // The HTML file passed in the multipart request.
  assets?: Array<MultipartReference>, // All assets imported in the HTML. Reference the name passed in the multipart request.
  layout?: {
    orientation?: Orientation,
    size?: {
      width: number,
      height: number
    } | PageSize, // {width, height} in mm or page size preset.
    margin?: {
      // Margin sizes in mm.
      left: number,
      top: number,
      right: number,
      bottom: number
    }
  }
};

The only mandatory key for an HTML part is the html key pointing to the main HTML file to use for generating the PDF. All other keys are optional.

Referencing Assets

When designing an HTML page, it’s common to split the design into multiple files, such as an HTML file, a CSS file, fonts, and image files. The PDF generation command expects a flat directory structure, so any referenced assets have to reside next to the HTML file and not in subdirectories.

The following shows how you’d send a CSS file that’s referenced in the HTML file:

HTML
<!DOCTYPE html>
<head>
  <link rel="stylesheet" href="style.css" />
</head>
<html>
  <body>
    <h1>PDF Generation Header</h1>
    <img src="my-image.jpg">
  </body>
</html>
CSS
@font-face {
  font-family: "Open Sans";
  src: url("OpenSans-Regular.ttf") format("truetype");
}

h1 {
  font-size: xx-large;
  font-family: "Open Sans", sans-serif;
}

The request to create a PDF from these assets would look like this:

curl -X POST https://api.pspdfkit.com/build \
  -H "Authorization: Bearer your_api_key_here" \
  -o result.pdf \
  --fail \
  -F page.html=@/path/to/page.html \
  -F style.css=@/path/to/style.css \
  -F my-image.jpg=@/path/to/my-image.jpg \
  -F OpenSans-Regular.ttf=@/path/to/OpenSans-Regular.ttf \
  -F instructions='{
      "parts": [
        {
          "html": "page.html",
          "assets": [
            "style.css",
            "my-image.jpg",
            "OpenSans-Regular.ttf"
          ]
        }
      ]
    }'
package com.example.pspdfkit;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.json.JSONArray;
import org.json.JSONObject;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public final class PspdfkitApiExample {
  public static void main(final String[] args) throws IOException {
    final RequestBody body = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart(
        "page.html",
        "/path/to/page.html",
        RequestBody.create(
          new File("/path/to/page.html"),
          MediaType.parse("text/html")
        )
      )
      .addFormDataPart(
        "style.css",
        "/path/to/style.css",
        RequestBody.create(
          new File("/path/to/style.css"),
          MediaType.parse("text/css")
        )
      )
      .addFormDataPart(
        "my-image.jpg",
        "/path/to/my-image.jpg",
        RequestBody.create(
          new File("/path/to/my-image.jpg"),
          MediaType.parse("image/jpeg")
        )
      )
      .addFormDataPart(
        "OpenSans-Regular.ttf",
        "/path/to/OpenSans-Regular.ttf",
        RequestBody.create(
          new File("/path/to/OpenSans-Regular.ttf"),
          MediaType.parse("font/ttf")
        )
      )
      .addFormDataPart(
        "instructions",
        new JSONObject()
          .put("parts", new JSONArray()
            .put(new JSONObject()
              .put("html", "page.html")
              .put("assets", new JSONArray()
                .put("style.css")
                .put("my-image.jpg")
                .put("OpenSans-Regular.ttf")
              )
            )
          ).toString()
      )
      .build();

    final Request request = new Request.Builder()
      .url("https://api.pspdfkit.com/build")
      .method("POST", body)
      .addHeader("Authorization", "Bearer your_api_key_here")
      .build();

    final OkHttpClient client = new OkHttpClient()
      .newBuilder()
      .build();

    final Response response = client.newCall(request).execute();

    if (response.isSuccessful()) {
      Files.copy(
        response.body().byteStream(),
        FileSystems.getDefault().getPath("result.pdf"),
        StandardCopyOption.REPLACE_EXISTING
      );
    } else {
      // Handle the error
      throw new IOException(response.body().string());
    }
  }
}
using System;
using System.IO;
using System.Net;
using RestSharp;

namespace PspdfkitApiDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var client = new RestClient("https://api.pspdfkit.com/build");

      var request = new RestRequest(Method.POST)
        .AddHeader("Authorization", "Bearer your_api_key_here")
        .AddFile("page.html", "/path/to/page.html")
        .AddFile("style.css", "/path/to/style.css")
        .AddFile("my-image.jpg", "/path/to/my-image.jpg")
        .AddFile("OpenSans-Regular.ttf", "/path/to/OpenSans-Regular.ttf")
        .AddParameter("instructions", new JsonObject
        {
          ["parts"] = new JsonArray
          {
            new JsonObject
            {
              ["html"] = "page.html",
              ["assets"] = new JsonArray
              {
                "style.css",
                "my-image.jpg",
                "OpenSans-Regular.ttf"
              }
            }
          }
        }.ToString());

      request.AdvancedResponseWriter = (responseStream, response) =>
      {
        if (response.StatusCode == HttpStatusCode.OK)
        {
          using (responseStream)
          {
            using var outputFileWriter = File.OpenWrite("result.pdf");
            responseStream.CopyTo(outputFileWriter);
          }
        }
        else
        {
          var responseStreamReader = new StreamReader(responseStream);
          Console.Write(responseStreamReader.ReadToEnd());
        }
      };

      client.Execute(request);
    }
  }
}
const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

const formData = new FormData()
formData.append('instructions', JSON.stringify({
  parts: [
    {
      html: "page.html",
      assets: [
        "style.css",
        "my-image.jpg",
        "OpenSans-Regular.ttf"
      ]
    }
  ]
}))
formData.append('page.html', fs.createReadStream('/path/to/page.html'))
formData.append('style.css', fs.createReadStream('/path/to/style.css'))
formData.append('my-image.jpg', fs.createReadStream('/path/to/my-image.jpg'))
formData.append('OpenSans-Regular.ttf', fs.createReadStream('/path/to/OpenSans-Regular.ttf'))

;(async () => {
  try {
    const response = await axios.post('https://api.pspdfkit.com/build', formData, {
      headers: formData.getHeaders({
          'Authorization': 'Bearer your_api_key_here'
      }),
      responseType: "stream"
    })

    response.data.pipe(fs.createWriteStream("result.pdf"))
  } catch (e) {
    const errorString = await streamToString(e.response.data)
    console.log(errorString)
  }
})()

function streamToString(stream) {
  const chunks = []
  return new Promise((resolve, reject) => {
    stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)))
    stream.on("error", (err) => reject(err))
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
  })
}
import requests
import json

instructions = {
  'parts': [
    {
      'html': 'page.html',
      'assets': [
        'style.css',
        'my-image.jpg',
        'OpenSans-Regular.ttf'
      ]
    }
  ]
}

response = requests.request(
  'POST',
  'https://api.pspdfkit.com/build',
  headers = {
    'Authorization': 'Bearer your_api_key_here'
  },
  files = {
    'page.html': open('/path/to/page.html', 'rb'),
    'style.css': open('/path/to/style.css', 'rb'),
    'my-image.jpg': open('/path/to/my-image.jpg', 'rb'),
    'OpenSans-Regular.ttf': open('/path/to/OpenSans-Regular.ttf', 'rb')
  },
  data = {
    'instructions': json.dumps(instructions)
  },
  stream = True
)

if response.ok:
  with open('result.pdf', 'wb') as fd:
    for chunk in response.iter_content(chunk_size=8096):
      fd.write(chunk)
else:
  print(response.text)
  exit()
<?php

$FileHandle = fopen('result.pdf', 'w+');

$curl = curl_init();

$instructions = '{
  "parts": [
    {
      "html": "page.html",
      "assets": [
        "style.css",
        "my-image.jpg",
        "OpenSans-Regular.ttf"
      ]
    }
  ]
}';

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.pspdfkit.com/build',
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_POSTFIELDS => array(
    'instructions' => $instructions,
    'page.html' => new CURLFILE('/path/to/page.html'),
    'style.css' => new CURLFILE('/path/to/style.css'),
    'my-image.jpg' => new CURLFILE('/path/to/my-image.jpg'),
    'OpenSans-Regular.ttf' => new CURLFILE('/path/to/OpenSans-Regular.ttf')
  ),
  CURLOPT_HTTPHEADER => array(
    'Authorization: Bearer your_api_key_here'
  ),
  CURLOPT_FILE => $FileHandle,
));

$response = curl_exec($curl);

curl_close($curl);

fclose($FileHandle);

Please note that JavaScript assets currently aren’t supported.

Assets passed in the multipart request must match the name used to reference the file in HTML. For example, if you have an image block, <img src="my-image.jpg">, the data representing the image in the multipart request should have the name my-image.jpg.

Page Layout

The layout object, which is part of the HTML part, allows for customization of the PDF page layout and dimensions. All figures in this object are in reference to millimeters, and all pages will take on this configuration.

Fillable Forms

PSPDFKit API can also generate PDFs containing fillable form fields from HTML.

Because not all HTML form fields map directly to PDF forms, a subset of form fields is supported. The following table shows how the HTML input element types map to PDF form types.

input type PDF Form Field
text Text box
password Text box where all characters are replaced with *
radio Radio button
checkbox Checkbox
select Combo box

All other input types aren’t supported and won’t be converted to PDF form fields.

Form Values

All form values and radio buttons/checkboxes that are checked in HTML will be carried over to the form field values in the generated PDF.

Headers and Footers

PSPDFKit API can add custom header and footer sections that will repeat across all pages in the resulting PDF. You can also dynamically display the current page number and page count, as well as style it as desired.

Adding a Header

PSPDFKit API will look for a container element using pspdfkit-header as its ID, defined in your template, and it’ll repeat it at the top of each of the generated pages:

HTML
<div id="pspdfkit-header">
    <div class="header-columns">
        <div class="logotype">
            <img class="logo" src="logo.svg">
            <p>ACME Inc.</p>
        </div>

        <div>
            <p>Always improving.</p>
        </div>
    </div>
</div>

Before generating the PDF, the following CSS will be added before any style sheet already defined in the template. We don’t recommend overriding those properties, as it might break the header position:

CSS
#pspdfkit-header {
    display: flow-root;
}

The display: flow-root; declaration defines a new block formatting context and suppresses margin collapsing so that any margin defined inside the header is preserved entirely across all pages in the resulting PDF.

Please note that the element with pspdfkit-header as its ID must be the first child of the document’s body, if specified.

Adding a Footer

To add a footer that you want repeated across all pages of the resulting PDF, you have to define a container element with the pspdfkit-footer ID. It must be the last child of the document’s body:

HTML
<div id="pspdfkit-footer">
    <div class="footer-columns">
        <span>10/18/2021</span>
        <span>Page {{ pageNumber }} of {{ pageCount }}</span>
    </div>
</div>

Before generating the PDF, the following CSS will be added before any style sheet already defined in the template. We don’t recommend overriding these properties, as it might break the footer position:

CSS
#pspdfkit-footer {
    display: flow-root;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    box-sizing: border-box;
    width: 100%;
}

The display: flow-root; declaration defines a new block formatting context and suppresses margin collapsing so that any margin defined inside the footer is preserved entirely across all pages in the resulting PDF.

Displaying the Current Page Number and Page Count

To display the current page number as part of the header and/or the footer, you can use the special {{ pageNumber }} token as a placeholder to tell PSPDFKit where to display the value on each page. Similarly, for the total page count, use {{ pageCount }}:

HTML
<p>Page {{ pageNumber }} of <strong>{{ pageCount }}</strong></p>

Customizing the Header and Footer

When specifying a header and footer, you can make use of any HTML and CSS as usual, and all styles added to the main page will affect the header and footer just as expected with regular DOM elements.

You can add images, use custom fonts, set a background, change font size, etc.

To set a gap between the header and the content, you can use regular CSS techniques, such as specifying a margin-bottom declaration to the #pspdfkit-header selector via CSS.

Similarly, you can also use CSS to set a gap between the content on each page and the footer — for instance, via the margin-top property in the #pspdfkit-footer selector.

Please note that the margins that are set to the page layout as part of the generation configuration will affect the space between the edges of the generated pages and the header and footer respectively.

Sample Templates

We have a collection of sample templates available for you to try. Please feel free to download them and modify them as needed.