PDFs in Anvil

In Anvil, generating a PDF document is easy and we only need to use Python. Any Anvil Form can be made into a PDF with just a single function call! We can render an Anvil Form as a PDF by turning it into a Media Object. This means we can easily download our PDF, attach it to an email or store it in a Data Table.

In this tutorial, we’ll see how easy it is to generate, download and email PDF documents using Python in Anvil. We’ll build a simple web app where users can register for an event and download their ticket as a PDF. Follow along with the tutorial, or clone and try out the finished app:

A demo of the finished app.

The finished app

If you’re completely new to Anvil, you may want to complete the Feedback Form tutorial before beginning this tutorial.

Step 1 - Design your PDF

First, let’s design the Form that we want to convert into a PDF ticket. Add a new Blank Panel Form to the app and rename it ‘Ticket’. I am using the Rally light colour scheme.

You can design your Form however you like. To make the Form look like a ticket, I added a Card and a Text component with a secondary-coloured background for the event name ‘SUMMER MUSIC FESTIVAL’. Inside the Card, I placed a Column Panel with some static ticket details and an Image for the QR code. I also added two Text components to display the user’s name (name_label) and the date of the event (date_label):

Adding text labels to Ticket Form

Add Text labels for user name and event date

Change the __init__ of the Ticket Form so that it takes strings called name and date as arguments and displays them on the labels you just created. Switch to Code View in the Form Editor and change the __init__ function of the Ticket class so it looks something like this:

class Ticket(TicketTemplate):
  def __init__(self, name, date, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)
    self.name_label.text = name
    self.date_label.text = date

Step 2 - Create the user interface

Let’s now build a UI so that users can generate and download the ticket as a PDF. We’ll design a simple registration page for users to put in their name and email address and choose the date of the event they will attend.

Switch to Form1 in the App Browser. We need two TextBoxes so that users can enter their name and email address, and two Text components to label these fields. We’ll name the TextBoxes name_box and email_box. We’ll also add a DropDown named date_dropdown, so that users can choose the date of the event.

Adding input fields to registration form

Add input fields to registration form

Populate the items property of the DropDown with a few dates. Each item in the DropDown should be on a separate line. You can also include a placeholder that says ‘choose date’.

Add date options to the DropDown menu

Add date options to the DropDown menu

Finally, add a Button called register_button, so users can register and download their PDF ticket. Let’s add a click event for this Button that just displays the Ticket Form, so we can see what it will look like when it’s downloaded.

  def register_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    name = self.name_box.text
    date = self.date_dropdown.selected_value
    open_form('Ticket', name, date)

Now, run the app! Fill in a name, select a date and click REGISTER to see what the ticket looks like.

Generate ticket with user details

Viewing the ticket with user details


Step 3 - Render and download the PDF

Our ticket is looking good! Now let’s turn it into a PDF. Rather than displaying the Ticket form in the browser, we’ll define a server function to render it as a PDF document.

We can only generate a PDF from Server code, so we’ll first need to add a Server Module to our app. We define a function which calls anvil.pdf.render_form(). This method returns a Media object, which means we can pass it back to the browser, download it or send it as an email attachment. We also need to decorate our function with @anvil.server.callable so we can call the function from client code.

Here’s what our server code looks like:

import anvil.pdf

@anvil.server.callable
def create_pdf(name, date):
    pdf = anvil.pdf.render_form('Ticket', name, date)
    return pdf

Let’s call this function from the registration form then download the generated PDF. We use anvil.server.call() to call our server function and generate the PDF, and anvil.media.download() to save it. The click event should now look like this:

  def register_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    name = self.name_box.text
    date = self.date_dropdown.selected_value

    pdf = anvil.server.call('create_pdf', name, date)
    anvil.media.download(pdf)

That’s all we need to do to render a Form as a PDF. And we can download it with just one line of code!

Let’s test it out. Run the app again, fill in the form and click REGISTER. This time, the ticket will download.

Downloading the PDF ticket

Downloading the PDF ticket

Step 4 - Send your PDF as an email attachment

Once we’ve rendered our Form as a PDF, it’s easy to send an email with the document attached.

We first need to add the Email Service to our app. In the Sidebar Menu, click the blue plus button + and click on Email.

Adding the Email Service

Add the Email Service

We can then write a function in the Server Module which calls create_pdf and then sends the rendered PDF as an email attachment:

@anvil.server.callable
def send_pdf_email(email, name, date):
  pdf = create_pdf(name, date)
  anvil.email.send(
    from_address='no-reply',
    from_name='Events', 
    to=email, 
    subject='Your Ticket',
    text='Thanks for registering! Your ticket is attached to this email.',
    attachments=pdf
  )
  return pdf
anvil.email.send() is available to all Anvil users, but on the Free Plan, emails will be redirected to the app owner. Email service is available on the Hobby Plan and above, or by setting a custom SMTP server in the Email service.

Now let’s modify the register_button_click method in Form1 so that it calls the send_pdf_email function we just wrote. Since send_pdf_email returns the rendered PDF, we just need to swap it with create_pdf and pass in email:

  def register_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    name = self.name_box.text
    date = self.date_dropdown.selected_value
    email = self.email_box.text

    pdf = anvil.server.call('send_pdf_email', email, name, date)
    anvil.media.download(pdf)

We can now run the app again and our PDF ticket will be emailed to us. As long as we are in Test Mode, the ticket will always be emailed us no matter what email address we type in.


Step 5 - Check the input

We want to make sure that the user has actually filled in the registration form, so let’s add in a check before we generate the PDF ticket. We’ll alert the user if they haven’t filled the form, but if they have, we’ll tell them that their ticket will be downloaded and emailed to them:

 def register_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    name = self.name_box.text
    email = self.email_box.text
    date = self.date_dropdown.selected_value
    
    if name and email and date:
      alert(f'Thanks for registering! Your ticket is downloading and will be sent to {email}.')

      pdf = anvil.server.call('send_pdf_email', email, name, date)
      anvil.media.download(pdf)

    else:
        alert('You have not completed all required fields')

Try running the app again, but this time, leave at least one of the TextBoxes blank.

Validating user input

Validating the user input


Step 6 - Optional: Set the PDF filename

Our app now generates, downloads and emails a PDF attachment when a user registers for our event. However, the PDF is called ‘print.pdf’ when we download it, which isn’t very descriptive. If we want to specify settings for our PDF document, we can use PDFRender(). Let’s modify our create_pdf function and specify a filename using PDFRenderer().

First add from anvil.pdf import PDFRenderer to the top of the Server code, then modify the create_pdf function:

import anvil.pdf
from anvil.pdf import PDFRenderer

@anvil.server.callable
def create_pdf(name, date):
    pdf = PDFRenderer(filename=f'{name} Ticket.pdf').render_form('Ticket', name, date)
    return pdf

Now when our ticket is downloaded, it will have a more useful name.


Step 7 - Run the finished app

We’ve now created a simple app which converts an Anvil form to a downloadable PDF and sends it as an email attachment. Let’s test out the finished app.

Finished app demo

Run the finished app


That’s it!

Your app is ready to go live on the internet. Click the ‘Publish’ button at the top right of the editor, then select ‘Publish this app’ and use the public URL provided or enter your own.

Publishing your app

Publish your app

You can check out the finished app here:


What’s Next?

Head to the Anvil Learning Centre for more tutorials, or head to our examples page to see how to build some complex apps in Anvil.