Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time
#    Copyright (C) 2019 pyTaxPrep
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    GNU General Public License for more details.
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <>.

A set of Python scripts to fill in common tax forms and schedules.

Supported Forms:

The filer is single and has moderately-complex taxes due to:
  - A combination of W2 and 1099-MISC income.
  - A sole proprietorship (Schedule C-EZ and SE). 
  - Money in bank accounts (1099-INT) and investment accounts (1099-DIV).
  - Estimated tax payments.
  - Contributions to a SEP IRA, rollovers from a SEP IRA, and a backdoor Roth conversion.
  - Deductions (medical expenses and donations).

With that in mind, the following forms have some amount of support:
  - Form 1040 (Schedules 1, 4, 5, A, B, CEZ, and SE)
  - SEP IRA Contribution Worksheet
  - Form 8606

The filer is _not_:
  - Filing jointly.
  - A homeowner.
  - Claiming any dependents.

(Do you fall into those categories? See "Contributing" below!)

Repository Layout:

    => templates/  - Unmodified PDF forms downloaded from the IRS.
    => forms/      - Python scripts for filling in various forms / schedules
    => filled/     - Directory where generated filled in forms get placed
    => keyfiles/   - Form metadata, one for each PDF form, mapping field
                     names to readable names
    => tables/     - Tax tables for 2018

    => data.json   - Fill in this file with finance information
    =>  - Run this script to actually fill in all the forms
    => requirements.txt - For Python virtualenv, if you want to set one up

Getting Started:

    Set up your environment. Create a new virtual environment
    (`virtualenv venv`), jump into it (`source venv/bin/activate`),
    and then install required packages (`pip install -r

    If you don't have Pip and/or Virtualenv, see

    At this point, you should be able to generate Mickey Mouse's tax
    forms by running `python`. The filled in forms will all
    go into `filled/`.

    Chances are you don't want Mickey Mouse's taxes ... you want your
    own taxes. Open up `data.json` and update the values to match your

    Some things to keep in mind:
      => Be careful not to commit an updated `data.json` file to a
         public repository.

      => If you're unsure what a field is used for, try searching for
         its use in the source code. That should tell you which
         schedules rely on the field.

      => If a field doesn't apply to you, make it empty instead of
         removing it entirely. (So, strings become empty strings, 
         numbers become 0, lists become empty lists, etc..)

      => As you change `data.json`, re-generate taxes to make sure
         they still generate.


    First, if you'd like to contribute, tinker around with the code
    base a bit. See if you can figure out how to control the value
    that goes in Form 1040 Line 12 B. Reading "Implementation Details"
    below might help.

    I'll happily entertain pull requests to add additional features
    or bug fixes.
      - Add more error checking / resilence to missing JSON values. (Easy)
      - Add support for joint filers. (Easy - Medium)

    I'll also entertain adding support for additional schedules /
    forms, including state forms.  This is a bit more work, depending
    on the number of fillable fields. And, anecdotally, I've found that
    state forms are less standardized than federal forms, which makes
    things more challenging.

    The main steps in adding a form are:
      - Adding the original form to templates.
      - Adding a new Python file to the form in forms. Like the
        other files, it should have `fill_in_form` and `build_data`
      - Add a keyfile for the form to keyfiles. Running `python
        forms/ [form PDF]` can help by identifying the field
        names; you'll need to create the readable names manually
      - If applicable, add any needed tables to tables.
      - Update `data.json` with examples for any fields your form needs.
      - Update `` to call into your new form.


    You're welcome to submit issues for bugs or requested features.  I
    can't promise I'll fix all (or even any) of them ... but don't let
    that stop you. Maybe someone else will be inspired and submit a
    pull request!

Implementation Details:

   Here's a high level overview of what's going on behind the scenes. has (among other things) a set of utilities for
   manipulating PDF files. There's functionality to iterate through
   all the fillable fields in a PDF and create an overlay with text
   where those fields appear. Then, the overlay gets combined with the
   original PDF to generate the filled in PDF.

   How does know what to put where? Each fillable field in a
   PDF has a name. The thing is, these names can be opaque, and they
   may not directly describe what the field actually holds. To make
   working with fields a bit easier, each PDF has a keyfile. The
   keyfile's only job is to map readable names into PDF field names
   and back.

   Each form has a build_data() function. This function returns a
   dictionary where the keys are readable PDF form names and the
   values are the data that should go in those fields. Then, the
   utility functions take care of creating an overlay that puts
   the text in the right spot.

   Separately, each form has a fill_in_form() function. This is the
   function that should actually generate the filled in PDF (by
   calling utils.write_fillable_pdf()). 

   Why separate out these two? This lets one form pull data from
   another form by running that form's build_data() function. So, a
   form's build_data() might get called many times, while you'll
   probably only call fill_in_form() once.

   To make this concrete, let's say you wanted to add in support
   for reporting "Tax Exempt Interest" (Form 1040 box 2A):

      - Looking at keyfiles/s1040.keys, 'tax_exempt_interest_dollars'
        and 'tax_exempt_interest_cents' look like the fields we're
        interested in.

      - Looking at forms/, those keys don't show up anywhere.
        That's why the box is blank. (For now.)

      - To fill it in, we need to add values for
        'tax_exempt_interest_dollars' and 'tax_exempt_interest_cents'
        to the dictionary that will be returned by the build_data()
        function in So, let's do that:

    utils.add_keyed_float(w2_income, 'wages_salaries_tips', data_dict)
+   data_dict['tax_exempt_interest_dollars'] = 50
+   data_dict['tax_exempt_interest_cents'] = 02

    data_dict['taxable_interest_dollars'], data_dict['taxable_interest_cents'] =\
        schedule_b['interest_total_dollars'], schedule_b['interest_total_cents']

      - There's a helper function that can make this easier. For
        instance, since we have keys with '_dollars' and '_cents', we
        can use utils.add_keyed_float():

        utils.add_keyed_float(50.02, 'tax_exempt_interest', data_dict)

      - Of course, you probably don't want to hard code in the value
        50.02. It might come from a supporting schedule, in which case
        you could call that schedule's build_data() function and pull
        the value out of the result. (That's what we do for
        taxable_interest, which gets computed and pulled from the
        schedule B.) Alternately, it might come from data.json (like
        what we do for reporting 1099_div's for qualified_dividends).

Change Log

1.2: Added support for signing and dating tax returns.

1.1: Added support for 1040-V, Qualified Business Income Deduction,
     Tax Worksheet, and Schedule 3. Fixed lots of small bugs.

1.0: Initial Release


Fills out forms for 2018 tax returns.







No releases published


No packages published