Streamlit to PDF: how to build & distribute PDF reports

Niko Nelissen
Peliqan.io
Published in
4 min readJun 18, 2024

Streamlit is an amazing toolkit in Python to build data-centric interactive apps with data visualisations such as charts, tables etc. In this article we’ll explore how you can generate PDF files from your Streamlit app, how you can allow users to download those PDF files and how you can distribute them by email. In short, we’ll discuss how you can build and distribute PDF reports from your Streamlit apps.

We will be using Peliqan.io in our examples. Peliqan.io offers a low-code Python runtime with Streamlit built-in, as well as ETL pipelines and SQL. You can use Peliqan for free for 14 days. We will also show how to run this code outside of Peliqan.

Option 1: Print to PDF

Unfortunately it is not possible to simply convert all the output of your Streamlit app directly into a PDF file. However, we can add a button in Streamlit that opens the Print dialogue. The user can select “Print to PDF” or “Save as PDF” to download the current Streamlit output as a PDF file:

import streamlit as st
import streamlit.components.v1 as components

# Streamlit output goes here
st.title("Sales report")
st.bar_chart(data)

show_print_button ="""
<script>
function print_page(obj) {
obj.style.display = "none";
parent.window.print();
}
</script>
<button onclick="print_page(this)">
Print page (choose 'Save as PDF' in print dialogue)
</button>
"""
components.html(show_print_button)

Note that you cannot use the standard way of showing HTML in Streamlit (st.markdown), because that will strip the onclick property from the button. The following code will not work in Streamlit:

# Does not work in Streamlit
st.markdown('<a onclick="window.print();">Click me</a>', unsafe_allow_html = True)

Option 2: Generate a PDF file

As an alternative to using the Print dialogue from the browser, we can generate a PDF file from our Streamlit code, and allow the user to download the PDF file. It only takes two lines of code in Peliqan.io:

report_html = "<h1>Sales report</h1>"
pq.download_pdf_button(report_html, file_name = "report.pdf", label = "Download PDF")

Obviously in reality you would build up a proper HTML string with text, layout and styling (using CSS) and e.g. tables with data.

Here’s the same example, outside of Peliqan.io:

import pdfkit, os
import streamlit as st

report_html = "<h1>Sales report</h1>"

file_name = 'report.pdf'
pdfkit.from_string(html, file_name)
with open(file_name, "rb") as pdf_file:
st.download_button(
'Download PDF',
data = pdf_file,
file_name = file_name,
mime = 'application/octet-stream')
os.remove(file_name)

We use pdfkit to convert the HTML to a PDF file. We write to a temporary file and make that file available for download using a Streamlit download button. At the end we remove the temporary file.

Generate a PDF file with charts

In proper PDF reports, you will want to include charts and other data vizualizations. We use PyPlot to create charts. This allows us to create charts that we show in our interactive Streamlit apps, and the same charts can be included in a PDF file.

Here’s an example in Peliqan.io with some dummy data, where we create a bar chart and add it to the PDF report:

import matplotlib.pyplot as plt
import base64
import io

# Dummy data
fruits = ['apple', 'blueberry', 'cherry', 'orange']
counts = [40, 100, 30, 55]
bar_labels = ['green', 'blue', 'red', 'orange']
bar_colors = ['tab:green', 'tab:blue', 'tab:red', 'tab:orange']

# Create chart with PyPlot
fig, ax = plt.subplots()
ax.bar(fruits, counts, label=bar_labels, color=bar_colors)
ax.set_ylabel('fruit supply')
ax.set_title('Fruit supply by kind and color')
ax.legend(title='Fruit color')

# Show chart using Streamlit
st.pyplot(fig)

# Convert chart to jpg image (base64 encoded)
stringIObytes = io.BytesIO()
plt.savefig(stringIObytes, format='jpg')
stringIObytes.seek(0)
base64_jpg = base64.b64encode(stringIObytes.read()).decode()

# Create HTML for report
img_html = '<img src="data:image/png;base64, ' + base64_jpg + '" width=100%>'
html = "<h1>Fruit supply report</h1>" + img_html

# Show download button in Peliqan which will convert HTML to PDF first
pq.download_pdf_button(html, file_name='report.pdf', label='Download PDF report')

This is what the downloaded PDF file will look like:

PDF report generated in Streamlit

Outside of Peliqan, replace the last line above with the following code, to show a download button in Streamlit. We convert the HTML to PDF using pdfkit and provide that PDF file to st.download_button:

import pdfkit, os
file_name = 'report.pdf'
pdfkit.from_string(html, file_name)
with open(file_name, "rb") as pdf_file:
st.download_button(
'Download PDF',
data = pdf_file,
file_name = file_name,
mime = 'application/octet-stream')
os.remove(file_name)

Distribute PDF reports by email

Finally, here’s an example in Peliqan where we generate the same PDF report with a chart and we send it as an attachment by email using Postmark:

import matplotlib.pyplot as plt
import base64
import io
import os
import pdfkit

# Dummy data
fruits = ['apple', 'blueberry', 'cherry', 'orange']
counts = [40, 100, 30, 55]
bar_labels = ['green', 'blue', 'red', 'orange']
bar_colors = ['tab:green', 'tab:blue', 'tab:red', 'tab:orange']

# Create chart with PyPlot
fig, ax = plt.subplots()
ax.bar(fruits, counts, label=bar_labels, color=bar_colors)
ax.set_ylabel('fruit supply')
ax.set_title('Fruit supply by kind and color')
ax.legend(title='Fruit color')

# Convert chart to jpg image (base64 encoded)
stringIObytes = io.BytesIO()
plt.savefig(stringIObytes, format='jpg')
stringIObytes.seek(0)
base64_jpg = base64.b64encode(stringIObytes.read()).decode()

# Create HTML for report
img_html = '<img src="data:image/png;base64, ' + base64_jpg + '" width=100%>'
html = "<h1>Fruit supply report</h1>" + img_html

# Write HTML to temporary PDF file and read as base64 string
file_name = 'report.pdf'
pdfkit.from_string(html, file_name)
with open(file_name, "rb") as f:
pdf_base64 = base64.b64encode(f.read())

# Send PDF as email attachment
email_server = pq.connect('Postmark')
email = {
"from": "no-reply@acme.com",
"to": "john@acme.com",
"subject": "Your report",
"text": "See PDF file attached.",
"html": "See PDF file attached.",
"attachment_name": "report.pdf",
"attachment_content_base64": pdf_base64.decode("ascii"),
"attachment_contenttype": "application/octet-stream"
}
result = email_server.add('email_with_attachment', email)
st.json(result) # for testing only, show result of sending email

os.remove(file_name)

Conclusion

By using PyPlot in Streamlit to build data visualizations, we can use the same charts in PDF reports that we make available for download or that we distribute by email. Peliqan.io is an all-in-one low-code platform with Streamlit built-in, that makes it easy to build and run Streamlit apps including generating and distribution of PDF reports at scale.

--

--

Niko Nelissen
Peliqan.io

Founder of Peliqan.io — all-in-one data platform, combine SQL and low-code Python for data activation of your data, connect to any SaaS source, DB and DW.