DEV Community

PDFops
PDFops

Posted on Originally published at pdfops.dev

A working example: filling a PDF form from JavaScript, the way the API actually behaves

Most "fill a PDF in JavaScript" snippets are one fetch call with field names that already match the template. Real forms don't work that way. A signup page collects applicant_name and email, someone renames a field in the PDF template six months later, and the fetch call that used to work now fails with a vague error, or worse, silently writes nothing. Here is a working example that discovers the real field names first, handles a typo the way the API actually reports it, and fills a real PDF.

Ask the PDF what fields it has

Start by asking the PDF what fields it actually has, rather than guessing from the template design file. A POST to /api/inspect with the template returns the field names, types, and current values as JSON:

const form = new FormData();
form.append("pdf", templateFile); // a Blob/File of the PDF template

const res = await fetch("https://pdfops.dev/api/inspect", {
  method: "POST",
  body: form,
});
const { fields } = await res.json();
console.log(fields.map((f) => f.name));
Enter fullscreen mode Exit fullscreen mode

Running that against a sample rental application returns eight text fields: applicant_name, email_addr, dob, addr_line1, city, postal_code, phone_no, and notes. Those are the exact keys the fill call needs, read straight from the PDF rather than copied from a spec someone wrote down once.

Fill it with one fetch call, no PDF library

Filling is the same shape: a FormData with the template and a JSON string of field values, posted to /api/fill-form. No PDF library runs in this code at all. The browser or Node's fetch and FormData do the whole job, because the PDF engine lives on the server side of the call.

const fields = {
  applicant_name: "Jamie Oyelaran",
  email_addr: "jamie@example.com",
  dob: "1990-04-12",
  addr_line1: "44 Birch Lane",
  city: "Austin",
  postal_code: "73301",
  phone_no: "512-555-0199",
  notes: "Referred by a current tenant",
};

const form = new FormData();
form.append("pdf", templateFile);
form.append("fields", JSON.stringify(fields));

const res = await fetch("https://pdfops.dev/api/fill-form", {
  method: "POST",
  body: form,
});
const filledPdf = await res.arrayBuffer(); // the filled PDF, ready to save or stream back
Enter fullscreen mode Exit fullscreen mode

What a typo actually looks like

Send applicant_nam instead of applicant_name (one dropped letter, the kind of typo that survives code review) and the API answers with a 400 and this body:

{
  "error": "unknown_field",
  "details": "no field named \"applicant_nam\" (did you mean \"applicant_name\", \"addr_line1\"?)"
}
Enter fullscreen mode Exit fullscreen mode

That suggestion comes from the field names /api/inspect already returned, matched against what was sent. A caller who logs this response, rather than swallowing the error, finds the typo in one read instead of diffing the template by hand.

Fix the field name and the same call returns a PDF: the applicant's name, email, date of birth, address, and notes all written into the template's existing fields, with the original layout untouched (the API re-renders field text in Helvetica, so a form built with a different font will not keep it).

Try it

Both calls above ran keyless against the live API, capped at 100 requests per IP per month, enough to build and test an integration end to end. A free key from POST /api/signup raises that to 250 requests a month under X-API-Key and works with the exact same two endpoints, no code change beyond adding the header.

Top comments (0)