Skip to main content

Generate Word, Excel and PowerPoint documents from Python using ETCH

Design a template in Office, combine it with data from Python and produce a finished Word, Excel or PowerPoint document

Updated 8 min readMarkdown
Part of seriesETCH Python Client
Generate Word, Excel and PowerPoint documents from Python using ETCH

Using ETCH with Python to generate Word, Excel and PowerPoint documents is very straightforward. In this post we'll walk through the initial installation and some simple examples.

Everything covered here is also available in our examples repository, so you can clone it and run each one yourself.

Before you start

You need Python 3.9 or later, and an ETCH account. The free plan is enough for everything in this post. From the dashboard you need two things:

  • a package token, from the Integrations page, so your package manager can download the client;
  • an API key, from the API keys page, which your code uses to call ETCH.

The client is published via the ETCH package registry rather than PyPI. With uv, make a project and tell it where etch-client comes from:

uv init etch-hello
cd etch-hello

Add this to the end of pyproject.toml:

[[tool.uv.index]]
name = "etch"
url = "https://packages.get-etch.app/pypi/simple/"
explicit = true

[tool.uv.sources]
etch-client = { index = "etch" }

explicit = true means uv fetches only etch-client from the ETCH registry and everything else from PyPI as usual. Then give uv your token and install:

export UV_INDEX_ETCH_USERNAME=__token__
export UV_INDEX_ETCH_PASSWORD=<your package token>
uv add etch-client

The token stays in your environment and isn't written to any file in the project. If you use pip, or Windows, the install guide has the commands.

Finally, set your API key:

export ETCH_API_KEY=<your API key>

Your first document

Here's the whole program for the first example. It reads the data from a JSON file and renders an invoice template:

import json
from pathlib import Path

from etch import ETCHClient

data = json.loads(Path("invoice.json").read_text(encoding="utf-8"))

with ETCHClient() as client:  # reads ETCH_API_KEY
    result = client.process({"template": "invoice.docx", "data": data})

result.save_to_file(Path(f"invoice-{data['number']}.docx"))

Run it with uv run render.py, and you get a Word document. On the left is the template, an ordinary Word file; on the right is what came back:

Two invoices side by side. On the left, "The template (invoice.docx, in Word)": a teal-branded invoice whose fields are tags such as {{seller.name}}, {{number}}, {{customer.name}}, a line items row starting {{#each items}}{{description}}, and {{total | currency "$"}}. On the right, "What ETCH produced (from invoice.json)": the same invoice filled in for Kwame Placeholder, number DEMO-0001, with three line items and a total of $2,419.20.

Each tag names a field in the data. {{customer.name}} becomes "Kwame Placeholder", and {{unitPrice | currency "$"}} formats a number as money. The line items row starts with {{#each items}} and ends with {{/each}}, so it's repeated once for each item. The fonts, colours and spacing all come from the template, so if someone wants the invoice to look different, they change the Word file and the code stays as it is.

The data doesn't have to come from a file, so you can build it from a database query or an API response or a URL etc.

Let's add a picture into the data, as a data: URI. The client has a helper that makes one from a file:

from etch import ETCHClient, image_from_file

data["logo"] = image_from_file(Path("images/logo.png"))

In the template, {{#image logo width="56"}} marks where it goes and how wide it is. Each customer's documents could carry their own logo this way, from the same template.

The invoice template with the tag {{#image logo width="56"}} at the top, beside the finished invoice with the teal Mei Example Design logo in its place.

QR codes and barcodes

You can generate QR codes and barcodes from the data too. The shipping label example uses three tags:

{{#barcode type="code128" width="340" height="80" shipment.tracking}}
{{#qr shipment.trackingUrl size="110"}}
{{#barcode type="ean13" width="190" height="60" ean}}

The first draws the tracking number as a Code 128 barcode, the second a QR code of the tracking page's address, and the third, inside the packing slip's loop, an EAN-13 barcode for each product. The Python code is the same as for the invoice. Only the template and the data have changed.

A shipping label and packing slip. On the left, the template: tags for the sender, recipient and postcode, {{#barcode type="code128" width="340" height="80" shipment.tracking}}, {{#qr shipment.trackingUrl size="110"}}, and a packing slip row with an EAN-13 barcode tag. On the right, the finished label: NEXT DAY, 1.4 kg, Example Client Ltd at ZZ99 2BB, a Code 128 barcode of SMP-QX-0815001, a QR code, and a packing slip with a barcode for each of three products.

Excel

To generate a workbook, use an Excel template.

The sales workbook has a summary row with {{#each regions}} at the start and {{/each}} at the end, which becomes one row per region. The total underneath is an ordinary Excel formula, =SUM(C5:C5), and ETCH stretches it to cover the new rows. The chart beside it was drawn in Excel over that same row, and its ranges grow in the same way, so it shows a bar for every region. A second sheet, marked with {{#sheet-each regions}}, is copied once per region and named after the region.

Two versions of the Summary sheet, one above the other. Above, the template: "Monthly sales" with {{company}}, {{period}} under it, a table with one row of tags, {{#each regions}}{{name}}, {{length orders}} and {{sum orders "amount"}}{{/each}}, a Total row, and an empty bar chart titled "Sales by region" whose only category is {{#each regions}}{{name}}. Below, what ETCH produced: the table lists East (3 orders, £3,755.00), North (2, £2,190.00), South (2, £2,090.50) and West (3, £3,885.00), with a total of 10 orders and £11,920.50, and the chart has an ochre bar for each region.

The numbers become native Excel numbers, so the formulas and number formats in the template still work with them.

PowerPoint

The project update deck has three slides in the template, and the finished deck has five. The middle slide starts with {{#slide-each projects}}, so ETCH makes a copy of it for each project, and the milestones on each slide come from a loop of their own.

On the left, one template slide from project-update.pptx: {{#slide-each projects}} above {{name}}, a large {{progress | percentage}} at the top right, a card with {{owner}}, the due date and a grey pill reading {{#fill statusColor}}{{status}}, and a milestones loop. On the right, the three slides ETCH made from it: Website refresh (70.0%, a green "On track" pill), Customer portal (35.0%, a red "At risk" pill and "See the speaker notes") and Brand guidelines (90.0%, green), each with its own milestones.

The status colour comes from the data. Each project has a statusColor such as C0392B, and the pill's text starts with {{#fill statusColor}}, which fills the shape with that colour. The same goes for the table cells on the summary slide.

The chart on the summary slide is one I drew and styled in PowerPoint with placeholder figures. Its Alt Text holds {{#chart projects x="name" y="progress"}}, so ETCH replaces the placeholder bars with one per project and keeps the colours and labels I gave it. The tag is easy to miss when you open the template, because it isn't on the slide. To see it, right-click the chart and choose View Alt Text.

The summary slide's chart open in PowerPoint, with three placeholder bars, Project A, B and C, at 50% each. Beside it, PowerPoint's Alt Text pane, whose text box reads {{#chart projects x="name" y="progress"}} and, on the next line, Progress by project.

Anything after the tag is kept as the finished chart's Alt Text, so in the deck ETCH produces, a screen reader still hears "Progress by project".

The summary slide. On the left, the template: a table row loop with a grey status cell holding {{#fill statusColor}}{{status}}, and a bar chart with three placeholder bars, Project A, B and C, all at 50%. On the right, the finished slide: three projects with green, red and green status cells, and the chart showing Website refresh at 70%, Customer portal at 35% and Brand guidelines at 90%.

PDF, and multiple documents at once

You can easily change the output format to PDF by adding output_format to the request:

from etch import ETCHClient, OutputFormat

pdf = client.process({
    "template": "invoice.docx",
    "data": data,
    "output_format": OutputFormat.PDF,
})

And when you have multiple records, such as a month of invoices, you can send them in one request. The batch template starts with {{#document-each invoices filename="invoice-{{number}}.docx"}}, and render_batch returns one document per invoice:

with ETCHClient() as client:
    result = client.render_batch({"template": "invoice-batch.docx", "data": data})

print(f"Rendered {result.succeeded} of {result.total} invoices")
result.save_all(Path("output/invoices"))
result.save_zip(Path("output/invoices.zip"))

If one record has a problem, it fails in isolation, and result.failures tells you which one failed. The other documents still come back.

Where to go next

The Python examples have 28 of these, each with a README. They cover conditionals, formatters, helpers that add up and rank figures in the template, reusable blocks, stored templates, error handling and running several renders in parallel. The Python video builds the invoice template in Word from scratch and renders it from Python, with uv and with pip.

You can sign up for the free plan, which gives you 200 documents a month and includes the client libraries. The same examples exist for JavaScript and Java, and those posts are coming next.

No comments yet. Be the first to comment.

ETCH Python Client

Part 1 of 1

Integrating ETCH with the Python Client Library