<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: GoPDFGenie</title>
    <description>The latest articles on Forem by GoPDFGenie (@gopdfgenie).</description>
    <link>https://forem.com/gopdfgenie</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3613795%2F888e36a4-5c09-4b1e-b306-d6e0d1d7266d.png</url>
      <title>Forem: GoPDFGenie</title>
      <link>https://forem.com/gopdfgenie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gopdfgenie"/>
    <language>en</language>
    <item>
      <title>I built an HTML to PDF API for real-world web apps (SPAs, iframes, dashboards)</title>
      <dc:creator>GoPDFGenie</dc:creator>
      <pubDate>Sun, 16 Nov 2025 14:17:58 +0000</pubDate>
      <link>https://forem.com/gopdfgenie/i-built-an-html-pdf-api-for-real-world-web-apps-spas-iframes-dashboards-5g05</link>
      <guid>https://forem.com/gopdfgenie/i-built-an-html-pdf-api-for-real-world-web-apps-spas-iframes-dashboards-5g05</guid>
      <description>&lt;p&gt;&lt;strong&gt;Most of us hit this at some point:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I need to generate a PDF from HTML… and it has to look exactly like the page.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you pick a CLI tool or library, wire it up, and it &lt;em&gt;kind of&lt;/em&gt; works…&lt;br&gt;&lt;br&gt;
until you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;heavy &lt;strong&gt;JavaScript&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;charts and dashboards in &lt;strong&gt;iframes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React / SPA&lt;/strong&gt; pages that render mostly on the client&lt;/li&gt;
&lt;li&gt;and a production server that now runs some fragile, homegrown setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve been through this a few times, so I ended up building a service I wish I had earlier:&lt;br&gt;&lt;br&gt;
a hosted &lt;strong&gt;HTML to PDF/PNG API&lt;/strong&gt; called &lt;strong&gt;&lt;a href="https://gopdfgenie.com" rel="noopener noreferrer"&gt;GoPDFGenie&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why I built it,&lt;/li&gt;
&lt;li&gt;how the async API works (jobs),&lt;/li&gt;
&lt;li&gt;a simple curl/pseudocode example,&lt;/li&gt;
&lt;li&gt;and how you can run it hosted or self-hosted.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  The pain with “just use a PDF library”
&lt;/h2&gt;

&lt;p&gt;Tools like &lt;code&gt;wkhtmltopdf&lt;/code&gt; and many HTML to PDF wrappers are fine when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the HTML is simple,&lt;/li&gt;
&lt;li&gt;there’s almost no JS,&lt;/li&gt;
&lt;li&gt;and you don’t care too much about pixel-perfect layout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In real products I kept seeing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDFs that didn’t match the live UI&lt;/li&gt;
&lt;li&gt;things breaking when the frontend team changed layout or added JS&lt;/li&gt;
&lt;li&gt;weird layout quirks in some HTML engines&lt;/li&gt;
&lt;li&gt;and the ongoing cost of maintaining your own conversion infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once SPAs, dashboards, and iframes show up, the “just install X and call it a day” story falls apart.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I wanted instead
&lt;/h2&gt;

&lt;p&gt;For my own projects, I wanted a service that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Has a &lt;strong&gt;conversion engine tuned for modern web apps&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Handles &lt;strong&gt;SPAs, iframes, dashboards, charts&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Is &lt;strong&gt;hosted&lt;/strong&gt;, so I don’t maintain conversion infra again&lt;/li&gt;
&lt;li&gt;Has &lt;strong&gt;simple, usage-based pricing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Still allows &lt;strong&gt;self-hosting/on-prem&lt;/strong&gt; if a team needs that&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I built &lt;strong&gt;GoPDFGenie&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 Live site: &lt;strong&gt;&lt;a href="https://gopdfgenie.com" rel="noopener noreferrer"&gt;https://gopdfgenie.com&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
👉 API docs (Swagger UI): &lt;strong&gt;&lt;a href="https://gopdfgenie.com/swagger-ui/index.html" rel="noopener noreferrer"&gt;https://gopdfgenie.com/swagger-ui/index.html&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
👉 GitHub examples: &lt;strong&gt;&lt;a href="https://github.com/gopdfgenie/html-to-pdf-api" rel="noopener noreferrer"&gt;https://github.com/gopdfgenie/html-to-pdf-api&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;


&lt;h2&gt;
  
  
  Real-world pages I tested it on
&lt;/h2&gt;

&lt;p&gt;When I started building this, I didn’t just test on tidy demo pages.&lt;/p&gt;

&lt;p&gt;I tried it on real, messy, modern sites like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;public charting pages on &lt;strong&gt;TradingView&lt;/strong&gt; (&lt;code&gt;https://www.tradingview.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;long, responsive templates like &lt;strong&gt;Story&lt;/strong&gt; and &lt;strong&gt;Solid State&lt;/strong&gt; from HTML5 UP
(&lt;code&gt;https://html5up.net/story&lt;/code&gt;, &lt;code&gt;https://html5up.net/solid-state&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;interactive chart galleries like Datawrapper’s scatter plot examples
(&lt;code&gt;https://academy.datawrapper.de/article/414-examples-of-datawrapper-scatter-plots-time&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my own testing, a lot of the online HTML to PDF tools I tried — including several that show up right at the top when you search for &lt;strong&gt;“html to pdf”&lt;/strong&gt; — would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fail to finish the conversion,&lt;/li&gt;
&lt;li&gt;return an error or blank page, or&lt;/li&gt;
&lt;li&gt;produce PDFs where key parts of the page (charts, iframes, long sections) were missing or cut off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That experience is a big part of why I built GoPDFGenie and tuned it specifically for &lt;strong&gt;JS-heavy, layout-rich pages&lt;/strong&gt;, so you can feed it “real” URLs instead of only perfectly controlled internal templates.&lt;/p&gt;


&lt;h2&gt;
  
  
  What GoPDFGenie does (and how the async API works)
&lt;/h2&gt;

&lt;p&gt;At a high level, GoPDFGenie is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Give me a URL or HTML, I’ll run it through a modern HTML-to-document pipeline, and you fetch the PDF/PNG when it’s ready.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The API is &lt;strong&gt;asynchronous&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You create a &lt;strong&gt;conversion job&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The API returns a &lt;code&gt;jobId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You poll the job status.&lt;/li&gt;
&lt;li&gt;When it’s &lt;code&gt;COMPLETED&lt;/code&gt;, you fetch the result file as PDF or PNG.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Base URL:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://gopdfgenie.com/api/v1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Main endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /convert/url/async&lt;/code&gt; — create a job from a public URL
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /convert/async&lt;/code&gt; — create a job by uploading HTML/ZIP via &lt;code&gt;multipart/form-data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /jobs/{jobId}/status&lt;/code&gt; — check job status
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /jobs/{jobId}/result&lt;/code&gt; — download the finished PDF/PNG
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every request uses bearer auth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer YOUR_API_KEY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Hosted SaaS and self-hosted option
&lt;/h2&gt;

&lt;p&gt;By default GoPDFGenie is a &lt;strong&gt;hosted HTML to PDF API SaaS&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sign up on the site
&lt;/li&gt;
&lt;li&gt;Get an API key
&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;https://gopdfgenie.com/api/v1/...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pay via a &lt;strong&gt;credit-based&lt;/strong&gt; model (Free, Starter, Pro, Business; credits are based on output size)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams that &lt;strong&gt;must&lt;/strong&gt; keep everything in their own environment (compliance, data residency, locked-down networks), I also offer a &lt;strong&gt;self-hosted / on-prem license&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same core conversion engine
&lt;/li&gt;
&lt;li&gt;You run it inside your own infra (containers/VMs, etc.)
&lt;/li&gt;
&lt;li&gt;Direct contract for licensing &amp;amp; support
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that’s interesting, there’s an Enterprise section + contact form on the &lt;a href="https://gopdfgenie.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pseudocode: full async flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiKey   = "YOUR_API_KEY"
apiBase  = "https://gopdfgenie.com/api/v1"

# 1) Submit a URL conversion job
response = HTTP_POST(
    url    = apiBase + "/convert/url/async?orientation=portrait&amp;amp;outputFormat=pdf&amp;amp;pageSize=A4&amp;amp;quality=STANDARD",
    headers = {
        "Authorization": "Bearer " + apiKey,
        "Content-Type": "application/json"
    },
    body = { "url": "https://example.com/report" }
)

jobId = response.jobId

# 2) Poll status until job is done
loop:
    wait a few seconds
    statusResponse = HTTP_GET(
        url = apiBase + "/jobs/" + jobId + "/status",
        headers = { "Authorization": "Bearer " + apiKey }
    )

    status = statusResponse.status

    if status == "FAILED":
        stop with error

    if status == "COMPLETED":
        break loop

# 3) Download the resulting PDF/PNG
fileBytes = HTTP_GET(
    url = apiBase + "/jobs/" + jobId + "/result",
    headers = { "Authorization": "Bearer " + apiKey }
)

save fileBytes as "output.pdf"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can translate this to Node, Python, Java, etc. I’ve put more concrete examples in the GitHub repo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this is useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Invoices / billing PDFs that must match your UI
&lt;/li&gt;
&lt;li&gt;Analytics dashboards and JS charts you need as PDF reports
&lt;/li&gt;
&lt;li&gt;React / SPA routes like &lt;code&gt;/reports/123&lt;/code&gt; that rely on client-side rendering&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>html</category>
      <category>pdf</category>
      <category>api</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
