Title: Issue with Streamlit Form Submission and Download Button (PDF Output)

Hello everyone,

I’m currently building a form using Streamlit. After the user fills out all the fields and clicks the submit button, I want to generate a downloadable file in PDF format.

However, I’m having trouble implementing this properly. I’m not sure about the correct way to:

  • Handle form submission

  • Generate the file (PDF)

  • Display a download button after submission

I also encountered issues like:

  • NameError: submitted is not defined

  • Problems placing the download button inside or outside the form

  • Errors when trying to save or generate files in a deployed app (Streamlit Cloud)

My goal is:

:backhand_index_pointing_right: User fills out the form

:backhand_index_pointing_right: Clicks submit

:backhand_index_pointing_right: A download button appears

:backhand_index_pointing_right: The form data is exported as a PDF file

Could anyone guide me on the best practice for this workflow?

Thank you so much!

import os

from datetime import date

import datetime

import streamlit as st

st.set_page_config(page_title=“FPE MEMBER”, layout=“wide”)

st.title(“FPE FORMS”, text_alignment=“center”)

with st.container():

st.subheader("Personal Information")

with st.form(“fpe_form”):

col1, col2, col3, col4 = st.columns(4)

with col1:

lastname = st.text_input(

“Lastname”,

        )

with col2:

firstname = st.text_input(“Firstname”)

with col3:

middlename = st.text_input(“Middlename”)

with col4:

suffix = st.text_input(“Suffix”)

    (

col8,

col9,

col10,

col11,

    ) = st.columns(4)

with col8:

dob = st.date_input(

“Date of Birth”, min_value=date(1930, 1, 1), max_value=date.today()

        )

with col9:

birthplace = st.text_input(“Birthplace”)

with col10:

civilstatus = st.selectbox(

label=“Civil Status”,

options=[

“Annulled”,

“Co-Habitation”,

“Married”,

“Separated”,

“Single”,

“Widower”,

            \],

        )

with col11:

maidenname = st.text_input(“Maidenname”)

    (

col5,

col6,

col7,

col12,

    ) = st.columns(4)

with col5:

sex = st.radio(“Gender”, [“Male”, “Female”])

with col6:

alcohol = st.radio(“Alcohol Intake”, [“YES”, “NO”])

with col7:

cigarettes = st.radio(“Use Cigarettes”, [“YES”, “NO”])

with col12:

employmentstatus = st.radio(

“Employment Status”,

            \["Employed", "Unemployed", "Retire", "Student", "Unknown"\],

        )

    (col13, col14, col15, col16) = st.columns(4)

with col13:

bloodtype = st.radio(

“bloodtype”,

            \["A+", "A-", "AB+", "AB-", "B+", "B-", "O+", "O-"\],

        )

with col14:

familymember = st.text_input(“Family Member”)

with col15:

fourpsmember = st.text_input(“4ps member”)

with col16:

pwd = st.text_input(“PWD”)

    (col18, col19, col20, col21) = st.columns(4)

with col18:

philhealthnumber = st.text_input(“Philhealth Number”)

with col19:

yakapregistered = st.selectbox(

label=“YAKAP Registered?”,

options=[“yes”, “no”],

        )

with col20:

natureofvisit = st.selectbox(

label=“Nature of Visit”,

options=[“New Consultation”, “Follow up Visit”],

        )

with col21:

age = st.number_input(“Age”, min_value=0, max_value=120, step=1)

with st.container():

        st.subheader("Vital Signs")

    (

col22,

col23,

    ) = st.columns(2)

with col22:

height = st.number_input(

“Height (cm)”, min_value=30.0, max_value=250.0, step=0.1

        )

with col23:

weight = st.number_input(

“Weight (kg)”, min_value=1.0, max_value=300.0, step=0.1

        )

    (

col24,

col25,

col26,

    ) = st.columns(3)

with col24:

systolic = st.number_input(

“Systolic (mmHg)”, min_value=50, max_value=250, step=1

        )

with col25:

diastolic = st.number_input(

“Diastolic (mmHg)”, min_value=30, max_value=150, step=1

        )

with col26:

respiratory_rate = st.number_input(

“RR (bpm)”, min_value=5, max_value=60, step=1

        )

    medicalhistory = st.multiselect(

“Medical History”,

        \[

“Allergy”,

“Pneumonia”,

“Asthma”,

“UTI”,

“TB”,

“Cancer”,

“Diabetes Melitus”,

“Hyperlipidemia”,

“Epilepsy/Seizure Disorder”,

“Cerebrovasscular Disease”,

“Hepatits”,

“Mental Illness”,

“Pectic Ulcer”,

“Thyroid Disease”,

“Pulomnary Tuberculosis”,

“Others”,

“Extrapulmonary Tuberculosis”,

“Coronary Artery Disease”,

“Hepatits”,

“Emphysema”,

        \],

    )

    Remarks = st.text_area("REMARKS")

    Surgical_Operation = st.text_area("Description of Surgical Operation:")

    submitted = st.form_submit_button("Submit")

    content = f"""

Lastname: {lastname}

Firstname: {firstname}

Middlename: {middlename}

Suffix: {suffix}

Date of Birth: {dob}

Birthplace: {birthplace}

Civil Status: {civilstatus}

Maiden Name: {maidenname}

Gender: {sex}

Alcohol Intake: {alcohol}

Cigarettes: {cigarettes}

Employment Status: {employmentstatus}

Blood Type: {bloodtype}

Family Member: {familymember}

4ps member: {fourpsmember}

PWD: {pwd}

Philhealth Number: {philhealthnumber}

YAKAP Registered: {yakapregistered}

Nature of Visit: {natureofvisit}

Age: {age}

-– VITAL SIGNS —

Height: {height}

Weight: {weight}

Blood Pressure: {systolic}/{diastolic}

Respiratory Rate: {respiratory_rate}

-– MEDICAL —

Medical History: {medicalhistory}

Remarks: {Remarks}

Surgical Operation: {Surgical_Operation}

“”"

st.success("Submitted! Download below 👇")

st.success("Form submitted! Click below to download.")

if submitted:

    content = f"Name: {firstname} {lastname}"

    st.success("Form Submitted")

st.download_button(

label=“:inbox_tray: Download Form”,

data=content,

file_name=f"{lastname}_{firstname}.txt",

mime=“text/plain”,

)

st.rerun()

Welcome to the Streamlit community! :balloon: You’re on the right track with your form, and your question is a common one for folks building data collection apps. Let’s break down the best practice for your workflow:

1. Handle form submission:
Use st.form and st.form_submit_button to batch user input and only process data after the user clicks “Submit”. Place all your input widgets inside the form context. The variable submitted will be True only after the user submits the form—make sure any logic that depends on form data (like generating files) is inside an if submitted: block, and outside the with st.form(…) context. See st.form documentation.

2. Generate the PDF file:
Streamlit doesn’t natively generate PDFs, but you can use libraries like FPDF or ReportLab. After submission, use the form data to create a PDF in memory (not on disk, to avoid file system issues on Streamlit Cloud). Save the PDF to a BytesIO buffer.

3. Display a download button after submission:
After generating the PDF, use st.download_button to let the user download the file. Place this button inside the if submitted: block, outside the form. Don’t put st.download_button inside the form—Streamlit doesn’t allow it (see docs).

Example (using FPDF):

import streamlit as st
from fpdf import FPDF
from io import BytesIO

with st.form("fpe_form"):
    firstname = st.text_input("Firstname")
    lastname = st.text_input("Lastname")
    # ... add other fields ...
    submitted = st.form_submit_button("Submit")

if submitted:
    # Generate PDF in memory
    pdf = FPDF()
    pdf.add_page()
    pdf.set_font("Arial", size=12)
    pdf.cell(200, 10, txt=f"Name: {firstname} {lastname}", ln=1)
    # ... add more fields ...
    pdf_buffer = BytesIO()
    pdf.output(pdf_buffer)
    pdf_buffer.seek(0)
    st.success("Form submitted! Click below to download.")
    st.download_button(
        label="Download PDF",
        data=pdf_buffer,
        file_name=f"{lastname}_{firstname}.pdf",
        mime="application/pdf"
    )

This approach avoids file system issues and ensures the download button only appears after submission. For more details, see this forum example and official docs.

Sources: