DEV Community

IronSoftware
IronSoftware

Posted on

wkhtmltopdf Is Archived: Picking a C# Alternative in 2026

You're reading this because somebody on your team (security, compliance, a sharp-eyed reviewer at code review) pointed at a NuGet package in your .csproj and asked: is this still maintained, and does it matter for what we're doing with it? The package is a wkhtmltopdf wrapper. Maybe DinkToPdf, maybe Wkhtmltopdf.NetCore, maybe a homegrown Process.Start shim around the CLI. The wrapper is incidental. The thing it wraps is the conversation.

What follows covers the public record on wkhtmltopdf as of 2026 and offers a conditions-based framework. There are two legitimate answers, and which applies depends on facts about a given deployment.

Here's the typical wrapper code many teams have somewhere:

using DinkToPdf;
using DinkToPdf.Contracts;

IConverter converter = new SynchronizedConverter(new PdfTools());

byte[] pdf = converter.Convert(new HtmlToPdfDocument
{
    Objects =
    {
        new ObjectSettings
        {
            HtmlContent = "<h1>Invoice 12345</h1><p>Total: $250.00</p>"
        }
    }
});

File.WriteAllBytes("invoice.pdf", pdf);
Enter fullscreen mode Exit fullscreen mode

Eight lines, one converter, one PDF. The reason this code is in production at so many .NET shops is that it works. Whether it should keep being the starting point in 2026 is what this article is built around.

What WkhtmltoPdf Did Well in Its Prime

The GitHub banner reduces a decade of legitimate engineering to a single timestamp. Wkhtmltopdf earned its place. It solved a hard problem with an unreasonably small surface area.

The core idea was excellent. Take a real browser engine, WebKit, the same family that powered Safari and early Chrome, embed it in a command-line tool, and let it do the layout work that browsers had already gotten right. CSS, images, fonts, page-break behavior, headers and footers: all of it came along because WebKit handled it. The output looked like the browser said it would look, because it was the browser rendering to a PDF surface instead of a screen. In an era when most "HTML to PDF" tools were doing string-replacement on a custom subset of HTML, wkhtmltopdf was running the actual page through the actual layout engine. That was a meaningful jump in quality, and downstream PDF generators have been chasing it ever since.

The deployment story was clean for its time. A single static binary, no Java runtime, no Python interpreter, no Node toolchain. You dropped wkhtmltopdf.exe onto a server and called it from whatever language you happened to be using. .NET, Ruby, PHP, Python, Go: every ecosystem grew a wrapper. That portability is the reason you can still find wkhtmltopdf in production code from at least four distinct technology generations.

The CLI was thoughtful. Reading the wkhtmltopdf manual today, the flag set still feels considered: page sizes, margins, headers and footers with positional templating, table of contents generation, cover pages, JavaScript delays, custom HTTP headers for authenticated content. The maintainers took the document-generation problem seriously, and it shows in the surface area.

The license was straightforward. Wkhtmltopdf is LGPLv3. For commercial users, that meant you could ship it with proprietary software as long as you respected the linking terms, and for CLI invocation that bar was low. The license did not surprise anyone in a procurement review.

The community output was significant. Wkhtmltopdf rendered invoices, statements, contracts, receipts, and reports for a real fraction of the small and mid-sized SaaS world for the better part of a decade. If you have ever received a PDF receipt from an early-2010s e-commerce site, there is a non-trivial chance it was generated by wkhtmltopdf. That is a tool doing useful work at scale, for years, with an LGPL license and a small enough binary to fit in a Docker image without complaint.

The context changed because the world the tool was built for changed, not because the tool was built poorly. Wkhtmltopdf in 2015 was a reasonable, even good, technology choice. In 2026, the answer depends on facts about your specific workload.

What "Archived" Means on GitHub, Specifically

GitHub's archive feature is not metaphorical. When a repository is archived, it becomes read-only at the platform level: no new issues, no PRs, no commits, no releases. The owner has actively said: this codebase is no longer being maintained by us.

For wkhtmltopdf, the archive timestamp is January 2, 2023, 1,225 days ago as of this writing. The whole wkhtmltopdf GitHub organization is similarly closed off. The wkhtmltopdf.org homepage still serves docs and download links, but the GitHub side is structurally inert. You can read the code. You cannot get a fix into it. The last upstream release is 0.12.6, dated June 10, 2020, 2,161 days ago. There is no roadmap and no indication the archive is temporary.

The maintainer's own status page, written before the archive event, is notably candid. The relevant passage reads, verbatim:

"Qt 4 (which wkhtmltopdf uses) hasn't been supported since 2015, the WebKit in it hasn't been updated since 2012."

The same status page recommends alternatives: "If you're using it for report generation (i.e. with HTML you control), also consider using WeasyPrint or the commercial tool Prince." And: "If you're using it to convert a site which uses dynamic JS, consider using puppeteer or one of the many wrappers it has." It also includes an explicit warning: "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."

The maintainer's framing is a useful map. The status page does not tell every team to migrate. It tells teams that the input-trust boundary matters, and points readers toward different tools depending on what they're doing.

The Qt WebKit Root Cause

In 2015, Qt announced that QtWebKit was deprecated and would be removed from the Qt 5.6 release, in favor of QtWebEngine, built on Chromium. From 2016 onward, every official Qt release has shipped without QtWebKit. Community efforts to keep QtWebKit alive exist, but they package WebKit versions years behind upstream, which is not the same as ongoing engine-level maintenance.

Wkhtmltopdf is built on a patched fork of Qt 4, itself archived, embedding the older WebKit. So when the status page says the WebKit "hasn't been updated since 2012," it's precise: the rendering engine inside the binary is a 2012-vintage WebKit fork inside a 2015-deprecated Qt fork, with neither layer receiving upstream security patches. That's the reason a community fork picking up engine-level maintenance has never materialized at meaningful scale. There's no indication that will change.

The CVE Picture, Stated Plainly

The published vulnerabilities are not theoretical. The most cited is CVE-2022-35583, an SSRF issue in wkhtmltopdf 0.12.6 with a CVSS 3.1 base score of 9.8, the "critical" tier. The advisory states:

"wkhtmlTOpdf 0.12.6 is vulnerable to SSRF which allows an attacker to get initial access into the target's system by injecting iframe tag with initial asset IP address on its source. This allows the attacker to takeover the whole infrastructure by accessing their internal assets."

There is also CVE-2020-21365, a directory traversal issue in wkhtmltopdf through 0.12.5. The Snyk advisory for CVE-2022-35583 lists "no fixed version," and that field will stay empty because the upstream cannot ship one.

The honest part. The project's stated position on CVE-2022-35583 is that the SSRF vector is a property of application input handling rather than an engine defect. That position is contestable, and it arguably understates the architectural piece, but it is part of the public record. The mitigation everyone reaches for is the --disable-local-file-access flag, which restricts the renderer's ability to fetch file:// URLs. That helps with the local-file-disclosure side. It does not, on its own, address the SSRF side, where the iframe payload points at an internal HTTP endpoint or a cloud metadata service like 169.254.169.254. Mitigating the SSRF properly requires explicit network-egress controls, strict input handling, and a documented posture on what your renderer is allowed to reach. None of that happens automatically. It is real, ongoing work.

That point is load-bearing: CVE-2022-35583 is not mitigated by routine application practices. Mitigating it is explicit work, and whether your team will commit to that work, visibly and on a recurring basis, is one of the conditions that decides which path applies.

Why "archived" means "not recommended for new commercial use"

The article so far has stayed even-handed between two legitimate paths, and the conditions test below preserves that posture for teams already operating wkhtmltopdf in production. The position worth stating separately is for teams considering a new adoption in 2026: the recommendation is to not adopt wkhtmltopdf for any new commercial workload, regardless of how the conditions test would read for the workload's other attributes.

The reasoning is structural rather than editorial. wkhtmltopdf's last upstream release shipped in June 2020. The project was archived in January 2023 by the maintainer's own action, a documented decision (not a passive lapse) to close the repository to commits, issues, and releases. CVE-2022-35583, the SSRF disclosed against version 0.12.6, has a CVSS 3.1 base score of 9.8 (critical) and remains tracked on the archived repository's issue tracker with no fixed version because the upstream cannot ship one. CVE-2020-21365, a directory traversal in versions through 0.12.5, is in the same state. Any new CVE disclosed against the Qt 4 / WebKit lineage the engine carries cannot be patched here either.

For a team carrying an existing wkhtmltopdf dependency, the conditions test below describes a defensible posture. For a team standing at dotnet add package and choosing what to render HTML to PDF with this quarter, the calculus is different. A new commercial adoption in 2026 means deliberately taking on a known-critical-CVE dependency with no upstream patch path, into a security-questionnaire era where every SOC 2, ISO 27001, and customer due-diligence review enumerates the dependency tree. The compensating-control overhead Path A describes is real, ongoing engineering work, and "we chose this dependency knowing the upstream was archived" reads differently in a vendor-risk review than "we inherited this dependency and are managing it."

The maintainer's own status page agrees with this reading. Its recommendations for new users point at WeasyPrint, Prince, or Puppeteer depending on use case, and the warning about untrusted HTML closes the door on the broader category of customer-facing rendering work. The maintainer is not asking new teams to keep adopting; they are asking new teams to choose a tool whose upstream still receives commits.

Treat the two paths below as a framework for the existing-dependency case. For new adoption, the answer is one path: don't.

The Conditions That Decide Which Path Applies

The documentary facts above are the same regardless of who's reading. What changes from team to team is the workload context. Five conditions matter.

Who controls the HTML inputs. If the HTML is generated entirely by your own code from your own database, the SSRF surface is structurally narrower: there's no attacker-controlled iframe to inject. If the HTML includes any user-supplied content, even fragments like a free-text description field, the surface widens substantially and the maintainer's warning about untrusted HTML applies.

Whether the rendering surface has network egress. If outbound HTTP is restricted to an explicit allowlist (no cloud metadata endpoints, no internal service IPs), the SSRF impact is contained even if the input vector is reachable. If it runs in a default container with default networking, the impact is whatever the network permits, which on many cloud platforms is a lot.

Whether the SBOM is audited externally. If your bill of materials is reviewed only by your internal team, you can document the situation, accept the risk, and move on. If it's reviewed by customers' security teams, SOC 2 auditors, or ISO 27001 assessors, an unmaintained renderer with a published critical CVE and no fixed version comes up in every review, and the cost of explaining it compounds.

Whether the regulatory environment requires patch traceability. PCI DSS, HIPAA, FedRAMP, and similar regimes ask whether your dependencies are receiving security patches within stated windows. Wkhtmltopdf does not produce patches. Some auditors will accept a documented compensating-control posture; others treat the absence of a patch pipeline as a finding. Your auditor's posture decides this one.

Whether your team can carry the compensating-control overhead. Running an archived dependency responsibly means owning the security posture the upstream used to own: monitoring for new CVE disclosures, maintaining the network and input controls, documenting the posture in a way that survives staff turnover. Real work, accruing every quarter.

These conditions cluster. An internal reporting tool with database-templated HTML on a locked-down host, internal-only SBOM, no patch-traceability requirement, sits in one cluster. A customer-facing SaaS with user-supplied content, externally-audited SBOM, and SOC 2 reviewers sits in a very different one. Same software. Different conclusions.

The Two Paths

Two legitimate responses to the wkhtmltopdf situation in 2026, and they are genuinely both legitimate.

Path A: Continue with WkhtmltoPdf, deliberately, under compensating controls

Appropriate when most of the following hold: HTML inputs fully controlled by your own code, restricted network egress, internal-only SBOM, no regulatory patch-traceability requirement, and team bandwidth for the ongoing work.

What "deliberately" means in practice:

  • Explicit mitigation of CVE-2022-35583. The load-bearing piece. At minimum: invoke wkhtmltopdf with --disable-local-file-access, run the renderer in a network sandbox that blocks egress to internal IP ranges and cloud metadata endpoints (169.254.169.254 and equivalents), and ensure the HTML input pipeline does not pass user-supplied content without sanitization. None of these is optional under this path.
  • A documented patch-delivery posture. A written internal acknowledgment that upstream patches are not expected, the codebase is archived, and the team has accepted the resulting risk profile.
  • A planned reevaluation cadence. Twelve months is reasonable. The reevaluation doesn't have to result in migration; it has to be a real check that the conditions still hold.
  • A named owner. Somebody who knows the dependency is archived, knows what the mitigations are, and gets paged if the situation changes.

This path costs ongoing attention. In exchange, you keep a tool that works, avoid a migration you don't strictly need, and make the call on your own timeline. For the right workload, it is a defensible choice. A team meeting those conditions is making a risk-adjusted decision, not avoiding one.

Path B: Migrate to an alternative

Appropriate when any of the following hold: HTML inputs include user-supplied content, externally-audited SBOM, regulatory environment requiring patch traceability, team unwilling to carry the compensating-control overhead, or a preference for tooling with an active upstream.

The maintainer's status page recommends WeasyPrint, Prince, or Puppeteer depending on use case. The migration cost is real but bounded: typically a focused day or two for a single template, plus visual-regression testing. You do the work once, and the supportability question stops appearing in your audits.

Closing: The Conditions Test

The documentary record on wkhtmltopdf is settled: the archive flag, the 2020 last release, the CVE state, the Qt WebKit deprecation timeline, the maintainer's status page. None are editorial, and none will change on a timeline that affects your 2026 planning. What's not settled is which of the two paths fits your workload.

Path A, continue with wkhtmltopdf under compensating controls, is for teams with fully controlled HTML inputs, restricted network egress, internal-only SBOMs, no regulatory patch-traceability requirement, and the bandwidth to carry explicit ongoing work: mitigating CVE-2022-35583, documenting the patch-delivery posture, and reevaluating on a planned cadence.

Path B, migrate to an alternative, is for teams whose HTML inputs include user-supplied content, whose SBOM is externally audited, whose regulatory environment requires patch traceability, or who prefer tooling with an active upstream. The maintainer's status page recommends WeasyPrint, Prince, or Puppeteer.

Run the conditions test against your workload. If most Path A conditions hold and your team will do the work, Path A is defensible: make the decision deliberately, write it down, name an owner, put a reevaluation on the calendar. If most Path B conditions hold, plan the migration and treat the visual-regression work as the real cost. Both are legitimate engineering positions for the conditions they apply to. The position that isn't legitimate is the third: running an archived dependency without having made the call, without compensating controls, and without an owner. That's the situation the conditions test exists to retire.

Migrating off an archived engine

wkhtmltopdf's API is fine; the engine is the liability: archived, unpatched, and stuck on a pre-Flexbox Qt WebKit. For a team whose SBOM is audited, or whose HTML has drifted past what CSS2 can render, the practical question is what to migrate to.

For teams on Path B, IronPDF is one option among those the maintainer's status page already names (WeasyPrint, Prince, Puppeteer) -- the difference is it is a commercial .NET library rather than a standalone tool.

IronPDF renders through a current Chromium engine (modern CSS, JavaScript, web fonts). For teams whose markup relies on CSS Grid, Flexbox, or JavaScript-rendered content introduced after wkhtmltopdf's 2020 release, a Chromium-based renderer will handle it as the browser does.

Its HTML-to-PDF API maps closely enough to the old Process or wrapper call that the swap is mostly mechanical. A commercial-library workflow looks like this:

// Install: dotnet add package IronPdf
using IronPdf;

// A maintained engine, patched on Chromium's cadence
IronPdf.License.LicenseKey = "YOUR-TRIAL-OR-LICENSE-KEY";

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Modern CSS, no archived engine</h1>");
pdf.SaveAs("output.pdf");
Enter fullscreen mode Exit fullscreen mode
  • Pro: a current, patched Chromium engine, where wkhtmltopdf is archived and unpatched.
  • Con: a commercial license, where wkhtmltopdf is LGPL and free.

If an audited SBOM is forcing the move, the 30-day trial, no card to start lets you port one template and compare the output first. For a locked-down internal tool on trusted HTML, staying on wkhtmltopdf under compensating controls remains a defensible call.


Still running wkhtmltopdf in production? What compensating controls are you carrying, or did you migrate? Curious how it went.

Top comments (0)