How to Populate Fillable PDF's with Python

I recently was working on a small Python project, and one requirement was to populate a PDF form based on some set of data.

Easy right? Not so much.

After browsing the internet for a bit, I came across posts like these:

… but was left wholly unsatisfied. I didn’t really want to use ReportLab or fdfgen (even though they look incredibly powerful), and conceptualizing reliable overlays left my head spinning (remember, I’m just a data engineer).

After digging into how PDF’s are represented in pdfrw, the following method seemed to be the easiest way to populate fillable PDF’s. The code is simple: it just reads in a template pdf and creates a new pdf with the designated fillable fields populated. Enjoy!

Preparing the fillable pdf template

The first step is to take an existing fillable PDF and modify it. For our purposes, I’ve simply grabbed an invoice template from Square and opened it with Adobe Acrobat DC. It will look something like this after clicking Prepare Form:

Fillable PDF

Once the programmatically-populated fields are renamed (snake-case in this example), the file can be saved. This file becomes the template, so simply save it as invoice_template.pdf. These snake-cased fields should be named clearly, as values will be populated via a Python dict.

You can view and download the file I’m using here.

Populating fields, with a single function

Once the template has been created, populating the fields and generating a new file is pretty straight-forward. pdfrw is the only necessary Python dependency we’ll need, and it can be installed (in a virtualenv, hopefully) via pip install pdfrw.

The code below is what handles populating the fillable fields, and there’s only one thing to note: data_dict keys must correspond to the fillable field names from the template.

#! /usr/bin/python

import os
import pdfrw

INVOICE_TEMPLATE_PATH = 'invoice_template.pdf'
INVOICE_OUTPUT_PATH = 'invoice.pdf'

ANNOT_KEY = '/Annots'
SUBTYPE_KEY = '/Subtype'

def write_fillable_pdf(input_pdf_path, output_pdf_path, data_dict):
    template_pdf = pdfrw.PdfReader(input_pdf_path)
    annotations = template_pdf.pages[0][ANNOT_KEY]
    for annotation in annotations:
        if annotation[SUBTYPE_KEY] == WIDGET_SUBTYPE_KEY:
            if annotation[ANNOT_FIELD_KEY]:
                key = annotation[ANNOT_FIELD_KEY][1:-1]
                if key in data_dict.keys():
    pdfrw.PdfWriter().write(output_pdf_path, template_pdf)

data_dict = {
   'business_name_1': 'Bostata',
   'customer_name': '',
   'customer_email': '[email protected]',
   'invoice_number': '102394',
   'send_date': '2018-02-13',
   'due_date': '2018-03-13',
   'note_contents': 'Thank you for your business, Joe',
   'item_1': 'Data consulting services',
   'item_1_quantity': '10 hours',
   'item_1_price': '$200/hr',
   'item_1_amount': '$2000',
   'subtotal': '$2000',
   'tax': '0',
   'discounts': '0',
   'total': '$2000',
   'business_name_2': 'Bostata LLC',
   'business_email_address': '[email protected]',
   'business_phone_number': '(617) 930-4294'

if __name__ == '__main__':
    write_fillable_pdf(INVOICE_TEMPLATE_PATH, INVOICE_OUTPUT_PATH, data_dict)

Provided that the code lives in, running it is easy:

(system) Jacob:fillable_pdf jacobthomas$ ls
(system) Jacob:fillable_pdf jacobthomas$ python
(system) Jacob:fillable_pdf jacobthomas$

The final product will be titled invoice.pdf in this case, and the result looks pretty good:

Populated Invoice


This method of populating fillable PDF’s has come in handy multiple times since I initially dug into pdfrw, and I hope it helps you as well. For more information about how the library and how PdfReader and PdfWriter work, please visit the documentation here: Cheers!


Read more posts by this author.

Boston, Ma