Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Flask and Jinja to render the HTML, then use WeasyPrint to turn it into PDF bytes and return those bytes from a Flask route. Flask supplies the page and request context; WeasyPrint is the separate rendering engine. For application URLs and assets, Flask-WeasyPrint adapts WeasyPrint’s resource fetching to Flask.

Choose the right conversion path

For a Flask page that uses your templates, the practical path is to render a dedicated, print-oriented template and pass it to Flask-WeasyPrint’s HTML wrapper from a view. The integration is intended to run inside an active Flask request context and can fetch local application resources through the WSGI layer.

WeasyPrint can also convert a URL, filename, readable file object, or in-memory HTML string. Its write_pdf() method returns PDF bytes if you do not give it an output destination. Flask and Jinja do not perform the PDF conversion themselves: they produce the HTML, and the renderer lays it out as a document.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Use Flask-WeasyPrint when you want to render your Flask templates and resolve app-local URLs within the request.
  • Use WeasyPrint’s plain HTML API when your input is already an HTML string, file, or URL and Flask-specific resource handling is unnecessary.
  • If your template depends on JavaScript executing to create its content, check whether your chosen renderer supports that requirement. The sources consulted describe wkhtmltopdf as an option for JavaScript-dependent templates, but do not establish that it is universally better or more current.

Install and check deployment requirements

The Flask-WeasyPrint first-steps documentation gives pip install flask_weasyprint as the integration install command and says it installs the integration and its Flask and WeasyPrint dependencies. Confirm the current installation guidance for your operating system and deployment image before relying on that command: the available documentation reviewed here does not establish a current native-library and compatible-version matrix for Linux, macOS, or Windows.

python -m pip install flask_weasyprint

Run the command in the same Python environment used by the Flask app. A successful Python package installation is not, by itself, proof that every required system dependency is present in the production image. Verify by generating a representative PDF in that image, not only on a developer workstation.

Build a Flask route that returns a PDF

This example renders a Jinja template through Flask-WeasyPrint and returns the resulting PDF bytes as a downloadable attachment. It assumes an application package with app.py and a templates/ directory.

Flask view

from flask import Flask, make_response, render_template
from flask_weasyprint import HTML

app = Flask(__name__)

@app.get("/reports/<int:report_id>.pdf")
def report_pdf(report_id):
    # Replace this sample data lookup with your application logic.
    report = {
        "id": report_id,
        "title": f"Report {report_id}",
        "summary": "A print-ready report generated by Flask.",
    }

    html = render_template("report_pdf.html", report=report)
    pdf_bytes = HTML(string=html).write_pdf()

    response = make_response(pdf_bytes)
    response.headers["Content-Type"] = "application/pdf"
    response.headers["Content-Disposition"] = (
        f'attachment; filename="report-{report_id}.pdf"'
    )
    return response

if __name__ == "__main__":
    app.run(debug=True)

The view uses Flask’s normal template rendering, then gives the resulting HTML string to the integration’s HTML wrapper. Since the view runs in a request context, Flask-WeasyPrint can handle application-root resources through Flask’s WSGI layer. The response headers mark the content as a PDF and ask the browser to download it. If you prefer the browser to attempt inline display, change the disposition to inline; actual display still depends on the browser.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Print-oriented template

Create templates/report_pdf.html. Keep PDF styles focused on print layout rather than assuming a screen page will paginate cleanly.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ report.title }}</title>
  <style>
    @page {
      size: A4;
      margin: 18mm 16mm;
    }
    body {
      font-family: sans-serif;
      font-size: 11pt;
      line-height: 1.45;
      color: #222;
    }
    h1 {
      font-size: 22pt;
      margin: 0 0 12pt;
    }
    .keep-together {
      page-break-inside: avoid;
    }
  </style>
</head>
<body>
  <h1>{{ report.title }}</h1>
  <section class="keep-together">
    <p>{{ report.summary }}</p>
    <p>Report ID: {{ report.id }}</p>
  </section>
</body>
</html>

Flask configures Jinja for template rendering. For static CSS or image assets, Flask’s url_for('static', filename='...') can generate URLs for the app’s static endpoint. Flask-WeasyPrint’s URL handling can resolve application resources in-process, but that does not guarantee that arbitrary external assets will be reachable in production.

Alternative: render the template directly as a string

The example explicitly assigns the result of render_template() to a string before conversion. This makes the boundary clear: Jinja renders the page, and WeasyPrint converts the rendered markup. Keep the template’s inputs controlled and escaped as appropriate; do not treat rendering arbitrary user-supplied markup as safe.

Return bytes, save a file, or render outside a request

Return a PDF response

The route above calls write_pdf() with no destination, so it receives PDF bytes that can be used as a Flask response body. Set Content-Type to application/pdf. Use Content-Disposition: attachment for a download-oriented response or inline if you want the browser to try to display the document.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Write to a file

WeasyPrint’s API also accepts an output path to write a file instead of returning the bytes. This is useful for a controlled background workflow, but consider where generated files live, how long they remain there, and whether the path is writable in the deployment environment.

Work outside a view

Flask-WeasyPrint’s documentation demonstrates using an application test request context with a base URL when conversion happens outside a normal view. That provides context for resolving application URLs. Do not assume the request-bound pattern can be copied into a worker unchanged: set up the application context and base URL deliberately, and test resource resolution in that execution environment.

Make pagination and assets predictable

A PDF renderer is not a full browser, and its output is constrained by the CSS and layout features it implements. Test the document with representative content rather than promising browser-perfect equivalence.

  • Page size and margins: define print dimensions with CSS @page rules and verify the actual printed margins.
  • Page breaks: test long sections, tables, headings, and blocks that should stay together; a layout that fits on screen may split awkwardly across pages.
  • Fonts: verify that the intended fonts are available and load in the target environment. A missing font can change line wrapping and page count.
  • Images and stylesheets: check that every referenced resource resolves from the rendering context. Prefer application URLs generated through Flask where appropriate, and test external resources separately.
  • Content variation: use both short and long records, empty fields, and unusually long text. Pagination failures often appear only with content that differs from the happy-path sample.

Flask-WeasyPrint’s in-process handling of local application URLs avoids a network request for those app resources. It does not establish that external hosts are safe, reachable, or correctly configured in production.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Security, performance, and operational trade-offs

Do not render untrusted HTML or CSS casually

WeasyPrint explicitly warns that untrusted HTML or CSS can create security problems. Treat template data, user-provided styles, and resource URLs as part of the threat model. Review URL fetching and file access behavior for your deployment; unrestricted resource URLs can expose resources or create unwanted outbound requests. Constrain what content and URLs the renderer is allowed to process.

Measure workload behavior with realistic documents

The available sources do not provide benchmark figures for conversion speed, memory use, or safe concurrency. Measure CPU, memory, latency, and failure behavior using documents similar to your real workload. If PDF generation is resource-intensive, separate it from latency-sensitive request handling or use asynchronous processing where it fits your application; do not assume a particular throughput without measurement.

Choose based on rendering requirements

WeasyPrint’s Python API can fit an in-process Flask workflow and can return bytes directly. A wkhtmltopdf-based integration may be relevant when a template requires JavaScript-dependent content, but it also changes the renderer and deployment considerations. Compare the actual JavaScript and CSS needs, operating-system dependencies, process model, and expected resource use for your app rather than choosing on a blanket claim of superiority.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common failures

  • Import or installation errors: confirm the package was installed into the Python environment running Flask, then check the current WeasyPrint installation guidance for your operating system and image. Do not infer native dependency compatibility from a different machine.
  • Missing CSS, images, or fonts: inspect the URLs produced in the rendered HTML and verify they resolve under the request or the configured base URL. A relative path that works in a browser may not resolve the way you expect during conversion.
  • Blank or incomplete output: inspect the rendered HTML before conversion and verify that the content is present without relying on JavaScript to create it. WeasyPrint’s documented conversion path does not establish browser-style JavaScript execution.
  • Unexpected page breaks or clipping: reproduce with the exact long or wide content, then adjust print CSS, page margins, and break behavior. Validate multiple pages, not only the first.
  • Conversion errors for user-supplied content: treat the input and its referenced resources as untrusted. Restrict accepted markup and resource URLs, and review the renderer’s URL-fetching and file-access behavior rather than exposing a general-purpose HTML-to-PDF endpoint.
  • Failure only in production: compare installed Python packages, native dependencies, permissions, fonts, and outbound-resource access between the working environment and deployment image. The operating-system dependency matrix is not established here, so confirm it against current project installation guidance.

Or skip the browser setup

If your goal is to capture a publicly reachable web page as a PDF rather than generate a Flask/Jinja document, ScreenshotNeo can return a PDF from one GET request. It is not a replacement for a template-driven Flask report with application-specific data.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For example, adapt the supplied request pattern to a page you can access:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.pdf

See the ScreenshotNeo API documentation for request options. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month with no card.

Frequently Asked Questions

Can Flask itself convert an HTML template into a PDF?

No. Flask and Jinja render the HTML; a separate renderer such as WeasyPrint performs the PDF conversion.

Does WeasyPrint run JavaScript from the page?

The sources used here do not establish browser-style JavaScript execution. If rendered content depends on JavaScript, verify the renderer requirement or evaluate a JavaScript-dependent alternative.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.