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.

To use wkhtmltopdf in a Flask app on Heroku, you need to deploy two separate things: the Python package or wrapper your app calls, and the wkhtmltopdf executable itself. A wrapper alone does not install that executable. Choose an installation route only after checking your app’s Heroku stack and build system: the community buildpack instructions documented for wkhtmltopdf cover Heroku-18, Heroku-20 and Heroku-22, not every newer stack. Verify the binary runs in the deployed dyno before relying on PDF generation.

Before you install it, identify your Heroku build environment

Heroku’s Python buildpack installs Python dependencies; wkhtmltopdf is a separate native command-line program. That distinction is the main deployment trap: installing a Flask integration package does not make the executable available in a dyno.

First determine whether the app uses classic buildpacks or Cloud Native Buildpacks (CNBs), and identify the stack or builder environment. Do not apply instructions written for one build model to the other.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

For a classic buildpack app

Check the app’s configured stack and buildpacks with the Heroku CLI, for example:

heroku stack --app YOUR_APP_NAME
heroku buildpacks --app YOUR_APP_NAME

Replace YOUR_APP_NAME with the Heroku app name. Confirm that the app is using the official Python buildpack and note its stack before choosing a wkhtmltopdf source. The community buildpack listing documented for this setup names Heroku-18, Heroku-20 and Heroku-22. Its instructions do not establish compatibility with newer stacks, other operating-system images or every architecture.

For a Cloud Native Buildpack app

CNB apps use a different package-installation path. Heroku’s heroku/deb-packages CNB demonstrates installing Debian packages through a project.toml configuration for specified Ubuntu builder environments. That does not prove a wkhtmltopdf package exists for the builder your app uses, nor does it make the configuration applicable to a classic git-push deployment. Check the exact builder and package availability before adopting this route.

Prepare the Flask app’s Python dependencies

Keep Flask and any Python integration wrapper in the root dependency manifest used by the Python buildpack. A root-level requirements.txt is a common choice; use another supported dependency manifest or lock file if that is how the app is already configured. Select the Python runtime with a root-level .python-version file, following Heroku’s current Python buildpack guidance.

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

A wrapper and the renderer have distinct roles: the wrapper helps Python call wkhtmltopdf, while the executable performs the rendering. Review the wrapper’s documentation for its expected configuration and invocation rather than assuming that installing it also installs the renderer.

# Example requirements.txt entries; select versions appropriate for your app
Flask
# Add the Python wkhtmltopdf wrapper your application uses

The second line is intentionally not a package name: choose and pin the wrapper your code already uses. The integration documentation for Flask-WkHTMLtoPDF specifically says to download the appropriate wkhtmltopdf tool separately.

Choose a binary or buildpack that matches the app

For a classic buildpack deployment, use a wkhtmltopdf binary source or community buildpack only when its release explicitly supports the app’s stack and architecture. Before adopting it, verify all of the following:

  • The release supports the exact Heroku stack and CPU architecture used by the app.
  • The executable is placed somewhere available in the dyno, and you know its full path. A community listing describes an executable under /app/bin for its documented setup; do not assume that path for another release.
  • Required shared libraries and fonts are present in the deployed environment.
  • The binary can start and render representative pages in the actual slug, not just on a developer workstation.

If the community buildpack offers an Aptfile URL override, its listing warns that a custom URL bypasses stack detection. Use a custom URL only after independently confirming that the binary matches your stack. Do not copy an old buildpack recipe into a newer stack merely because the deployment completes.

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

For a CNB app, follow the CNB package mechanism only if the package and its dependencies are available for the selected Ubuntu builder. The fact that Heroku’s deb-packages CNB exists is not proof that wkhtmltopdf is installable in a particular builder image.

Deploy, then verify the executable inside a dyno

A successful build does not prove the renderer is installed correctly. After deployment, open a one-off dyno or otherwise run checks in the same runtime environment as the web process. With the Heroku CLI, a typical check is:

heroku run bash --app YOUR_APP_NAME
which wkhtmltopdf
wkhtmltopdf --version

If which prints no path, the executable is not on the dyno’s PATH; locate the installed binary and configure the wrapper to use its full path, or correct the installation. If the version command cannot start because of a missing shared library, the selected binary is not self-sufficient in that runtime. Resolve the dependency for the same stack rather than attempting to fix it by changing the Python wrapper.

The upstream project’s stable series is wkhtmltopdf 0.12.6, released June 11, 2020. Treat that release as legacy software: the main repository was archived on January 2, 2023. Check what version your chosen distribution actually supplies; do not infer it from the project’s stable-series number.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Call wkhtmltopdf from Flask only after runtime checks pass

Once the binary is present, test the command directly with a representative page or HTML document in the deployed environment. Check the resulting PDF for missing fonts, images, styles or page breaks. A successful process exit alone does not establish that the output meets the app’s requirements.

For code that invokes the executable directly, Python’s standard library can locate it and report a clear error when it is absent. This minimal example assumes the application has already created a trusted local HTML file and that the executable is available on PATH:

from pathlib import Path
import shutil
import subprocess

def html_to_pdf(html_path: str, pdf_path: str) -> None:
    executable = shutil.which("wkhtmltopdf")
    if executable is None:
        raise RuntimeError("wkhtmltopdf is not installed or is not on PATH")

    subprocess.run(
        [executable, html_path, pdf_path],
        check=True,
        timeout=60,
    )

html_to_pdf("/tmp/report.html", "/tmp/report.pdf")

This is an example of direct command invocation, not a substitute for configuring a particular Flask wrapper. Set the path according to the binary’s actual deployed location if it is not on PATH. Choose a timeout that fits your workload, and handle subprocess failures in the application so a renderer error does not silently become an empty or incomplete download.

Protect the renderer from untrusted HTML

The wkhtmltopdf project warns: “Do not use wkhtmltopdf with any untrusted HTML – be sure to sanitize any user-supplied HTML/JS, otherwise it can lead to complete takeover of the server it is running on!” Treat this as a serious server-security warning, not simply a formatting caveat.

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

Do not pass arbitrary user HTML or JavaScript directly to the renderer. Sanitize user-controlled content and apply appropriate isolation and access controls for the rendering process. If the product requirement depends on accepting untrusted markup, evaluate a maintained renderer and a security model designed for that workload before deploying wkhtmltopdf.

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

Troubleshoot common Heroku deployment failures

Symptom Likely cause What to check
FileNotFoundError or “wkhtmltopdf: command not found” The Python wrapper is installed but the executable is not, or it is not on PATH. Run which wkhtmltopdf inside a dyno. Confirm the build step installed a binary and configure the wrapper with its actual path.
The build succeeds, but the executable will not start The binary may target another stack or architecture, or may need shared libraries absent from the slug. Check the binary release’s supported stack and architecture, then inspect the runtime error in a dyno. Rebuild with a compatible source and dependencies.
The app uses a newer stack than the listed buildpack supports The available community instructions establish Heroku-18, -20 and -22 support only. Do not assume compatibility. Find a release that explicitly supports the app’s stack, or evaluate a different renderer or deployment method.
A custom Aptfile URL installs an unsuitable binary The community listing warns that a custom URL bypasses stack detection. Verify the URL’s binary, target stack and architecture yourself; do not rely on automatic stack selection when overriding the source.
The PDF is missing fonts or differs from local output The dyno may not have the same fonts, libraries or rendering environment as the developer machine. Test representative documents inside the deployed runtime and confirm required fonts and assets are available there.
A wrapper setting appears to have no effect The wrapper may be looking for a different executable path or using different options than expected. Check the wrapper’s own documentation and runtime configuration. Verify the executable and its version independently before debugging PDF options.
Untrusted user content reaches the renderer This creates the server-compromise risk highlighted by the wkhtmltopdf project. Stop passing arbitrary HTML or scripts to wkhtmltopdf; sanitize the input and reassess isolation and renderer choice.

When to choose a different renderer

wkhtmltopdf’s age and archived upstream repository matter for a new deployment, especially if ongoing maintenance or security updates are requirements. The project recommends considering WeasyPrint or Prince for controlled report generation, and Puppeteer for pages that depend on dynamic JavaScript. Those alternatives have different runtime needs; check support for the app’s stack, required CSS and JavaScript behavior, fonts, native libraries and operational complexity before switching.

If you keep wkhtmltopdf, treat it as a deliberately chosen legacy dependency: pin down the binary source and runtime assumptions, test the deployed output, and avoid accepting untrusted HTML. If you are starting fresh, compare a maintained renderer against the exact documents your app must produce rather than choosing on installation convenience alone.

Or skip the browser setup

If your actual need is a clean screenshot of a public web page rather than a locally rendered HTML document, ScreenshotNeo can return a screenshot through one GET request. It is a website screenshot API, not a drop-in wkhtmltopdf executable or a replacement for rendering arbitrary Flask templates into PDFs. Its screenshot request can be useful when that distinction fits the task.

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

Example using cURL; the API key and target URL are placeholders to replace with your own values. See the ScreenshotNeo API documentation for request details.

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

ScreenshotNeo removes cookie/consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages and failed loads are not billed. It also offers an MCP server for AI agents, and includes 1,000 screenshots per month free without a card; paid plans start at $5 for 3,000. See ScreenshotNeo for the service details.

Sign up for ScreenshotNeo’s free plan to try up to 1,000 screenshots a month with no card.

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.

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