Generate Cover Letter from a Template

Generate cover letter on the fly

Have you ever wondered, “how the heck am I going to craft 100 customized cover letters if every one of the 100 online applications asks to provide a cover letter?” 

You can do what I did, which is going through the process of studying about the company, tailoring the company vision and job description into the cover letter, and…eventually get a computer-generated rejection email, ON A WEEKEND!

Or better yet, write a simple computer program that helps you generate one in seconds!

Don’t get me wrong, this is not to say you shouldn’t focus on crafting few versions on a well thought-out cover letters. It could very well be my cover letter really just sucks in comparison, so no one (system) gives a crap. But for those of you who face difficulties getting your online application picked up like me, you might want to try this out.

In this post, I will walk through step-by-step on how to generate cover letters in Python. Of course, we would still need a well-written cover letter template to produce great results.

  • Install Python 3.6.8. Instructions for MacOS/Windows/Linux installation can be found here.
  • Install required Python packages that are needed to run the code. I use the package “python-docx” to read and write Microsoft Word .docx files since it already provides a really nice set of API’s (Application Programming interface) for our needs.
  • Copy and paste the below block of lines in a text editor (eg. notepad for Windows or TextEdit for OSX) and save it as “requirement.txt”. Alternatively, you can download requirement.txt from my Github repository. Save this file anywhere, but remember this path for later.
  • Open command line application (Command prompt for Windows or Terminal for OSX), and type
pip install -r <path-to-requirement.txt>

where the <path-to-requirement.txt> is the path of the requirement.txt saved in step 2.
For example:

On Windows

pip install -r C:\Users\requirement.txt

On Mac

pip install -r /user/local/requirement.txt

Let’s get down to the nitty-gritty

generate cover letter template

Import(ant) modules

Now that we have all the logistics sorted out, it’s time to code! First, we import some important Python modules for our program:

from docx import Document
import argparse
import os
import datetime

All hail the argsparse, king of inputs!

Next, in order to make our Python program versatile, we need to make our program capable of reading different inputs. “available_args” contains a dictionary that manages a data structure of keys as shorten form of the full keyword to be used across docx document and Python program. The corresponding values of those keys represent the full keyword themselves. Once the dictionary is defined, we simply go through the items in the dictionary and add arguments using parser.add_argument() method provided in the argsparse module:

available_args = {
    'jp': 'job_position',
    'cn': 'company_name'
parser = argparse.ArgumentParser(description='Input')
for short_name, long_name in available_args.items():
    parser.add_argument(f'-{short_name}', f'--{long_name}', 
                        dest=f'{long_name}', help=f'Desire {long_name}')

args = parser.parse_args()

set_para_data method

For the meat of the program, we define two methods to help us process the content of our docx document. The first one, set_para_data, we want this method to retain and inherit all of the document styles in our original docx document. Note that I used a small trick to copy the source and destination formats by using iterating all the required attribute names. For more information on how to use setattr and getattr (both are Python built-in functions), see the Python Doc on Built-in Functions.

def set_para_data(out_doc, in_para, modified_text):
    out_para = out_doc.add_paragraph(modified_text)
    para_format_attr = [
        'alignment', 'first_line_indent', 'keep_together', 
        'keep_with_next', 'left_indent', 'line_spacing', 
        'line_spacing_rule', 'page_break_before', 'right_indent', 
        'space_after', 'space_before', 'widow_control'
    for attr in para_format_attr:
        Lazy way to copy all attributes from source to destination. 
        Instead of writing out line-by-line assignment of each attribute, 
        we use python built-in methods getattr to get the value of an 
        attribute by name and setattr to set the attribute by name. Find 
        out more more at
        setattr(out_para.paragraph_format, attr, 
                getattr(in_para.paragraph_format, attr)
        ) =
    out_para.text = modified_text
    return out_para

generate method

The generate method helps us with doing things that are not document specific (leave that for set_para_data). Here we have the usual suspect of creating a output folder for our generated document and getting a timestamp for our output document name.

Then per each paragraph from the input document, we finally replace all the keywords enclosed in {} using standard Python string.format() method. Note that we access arguments by name in which we defined and parsed earlier.

def generate(args):
    company_name, job_position = args.company_name, args.job_position
    output_path = os.path.join(os.getcwd(), "output")
    current_time ="%m.%d.%H.%M")
    document = Document("cl_template.docx")
    if not os.path.exists(output_path):
    out_doc = Document()
    for para in document.paragraphs:
        modified_text = para.text.format(**args.__dict__)
        out_para = set_para_data(out_doc, para, modified_text)"{output_path}/{company_name}_cover_letter\
    print("Generated Successfully :)")

Finally, the one who does it all


But wait! Don’t forget about the cover letter template.

Now the most important part! Don’t forget to supply the program with a cover letter with replaceable keywords tags. Take a look at the cover letter sample template (cl_template.docx) for more details.

cat sleeping
Take a well deserved break 🙂

And that’s it! I hope this simple piece of code will help alleviate the job submission process for some of you out there, and hopefully also inspire some of you who are learning Python to accelerate repetitive tasks. If you like to see the entire code, feel free to checkout my Github repository @

Need more help? Try my obligation-free consulting service to get more personalized help for your own unique case!

Start the discussion

Leave a Reply

Your email address will not be published. Required fields are marked *