Generate PDF from Text: A Developer's Guide to Production APIs
Learn to generate PDF from text, HTML, or URLs using APIs like PDFGeny. We cover code examples, common pitfalls, and the real costs of self-hosting renderers.
Generating PDFs from text, HTML, or dynamic content is a recurring requirement for backend and full-stack developers. Applications frequently need to output invoices, receipts, contracts, certificates, reports, and shipping labels. While seemingly straightforward, rendering complex HTML into a pixel-perfect PDF often introduces significant operational overhead, especially in production environments.
The Core Challenge: Consistent HTML-to-PDF Rendering
The primary hurdle in generating PDFs from text or HTML is achieving consistent, high-fidelity rendering across diverse content and environments. Browsers excel at displaying HTML, but printing that same HTML to a PDF involves a different rendering engine and print-specific CSS rules. This discrepancy can lead to unexpected page breaks, clipped content, or missing styles.
For instance, an HTML invoice designed for a web browser might render differently when converted to PDF, requiring specific @media print CSS rules to ensure proper pagination and layout. The actual renderer used plays a critical role here. Headless Chromium, for example, offers strong fidelity to modern web standards, while older tools like wkhtmltopdf struggle with contemporary CSS features.
Renderer Choices: Local Libraries vs. Hosted APIs
Developers face a fundamental choice: self-host a rendering engine or use a hosted API. Local libraries such as Puppeteer (for Chromium) or Playwright offer direct control. However, they introduce dependencies, resource management challenges, and maintenance burdens.
Hosted APIs like PDFGeny abstract away the infrastructure. PDFGeny uses headless Chromium as its default engine, alongside WeasyPrint for specific use cases and Ghostscript for PDF/A-2b compliance. This multi-engine approach provides flexibility, ensuring a median render time of 0.6 seconds for typical documents. For comparison, a cold headless Chromium instance can take approximately 7.8 seconds per request, highlighting the performance advantage of a pre-warmed, optimized service.
Generating PDF from Text via API: A Practical Approach
Using a hosted API simplifies PDF generation significantly. Instead of managing browser instances, developers send HTML, a URL, or even plain text to an endpoint and receive a PDF in return. PDFGeny provides an endpoint at POST https://pdfgeny.com/api/v1/render, accepting various inputs. This method is particularly useful for applications that must ship many documents, from occasional receipts to high-volume reports.
Example: Generating PDF from HTML in Python
Python developers can integrate PDFGeny with minimal code. This example demonstrates converting a simple HTML string into a PDF document using the requests library. Replace YOUR_API_KEY with your actual PDFGeny API key.
import requests
import base64
api_key = "YOUR_API_KEY"
html_content = "<h1>Hello, PDFGeny!</h1><p>This is a test document from Python.</p>"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"html": html_content,
"options": {
"output": "base64"
}
}
try:
response = requests.post("https://pdfgeny.com/api/v1/render", json=payload, headers=headers)
response.raise_for_status() # Raise an exception for HTTP errors
if response.status_code == 200:
pdf_data_base64 = response.json().get("data")
if pdf_data_base64:
pdf_bytes = base64.b64decode(pdf_data_base64)
with open("output.pdf", "wb") as f:
f.write(pdf_bytes)
print("PDF generated successfully: output.pdf")
else:
print("Error: No PDF data received.")
else:
print(f"Error generating PDF: {response.status_code} - {response.text}")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
This code sends the HTML content to the API, requesting the PDF as a base64-encoded string, then decodes and saves it locally. PDFGeny also supports direct binary output, webhooks for asynchronous jobs, and batch processing up to 100 documents in one call.
Common Failure Modes and Their Solutions
Even with an API, certain challenges are inherent to PDF generation. Understanding these failure modes is crucial for reliable implementation.
Page Breaks and Pagination Control
Failure Mode: Uncontrolled page breaks can split tables, images, or critical text blocks across pages, making documents unreadable or unprofessional. This often occurs when the renderer lacks specific instructions on how to handle content flow.
Fix: Implement CSS properties like page-break-before, page-break-after, and page-break-inside. For example, page-break-inside: avoid; on a div containing an invoice item ensures it stays on one page. Modern renderers like headless Chromium respect these print-specific CSS rules. PDFGeny leverages this by running Chromium, allowing precise control via standard CSS.
Web Fonts Not Loading or Falling Back
Failure Mode: Custom web fonts (e.g., Google Fonts, self-hosted TTF/WOFF) sometimes fail to load in the PDF, leading to silent fallback to system fonts. This makes the PDF look different from the intended design.
Fix: The renderer might finish before the font assets fully download, especially for remote fonts or during cold starts. For PDFGeny, setting a sufficiently long delay option in the API request can give the renderer more time for assets to load. For self-hosted solutions, pre-loading fonts or ensuring they are locally available and cached can mitigate this. For instance, a delay of 1-2 seconds often resolves this issue for external web fonts.
SSRF Vulnerabilities with URL-to-PDF
Failure Mode: An endpoint that accepts a URL to render into a PDF can become a Server-Side Request Forgery (SSRF) vulnerability. Malicious actors could supply internal network URLs (e.g., http://localhost/admin, file:///etc/passwd) to access sensitive resources or files on your server or internal network.
Fix: Before making any request, validate and sanitize the URL. Crucially, resolve the host and reject private, loopback, and metadata IP addresses (e.g., 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1, 169.254.169.254). This prevents the rendering service from accessing internal resources. PDFGeny's URL rendering inherently includes these protections.
For more detailed information on preventing SSRF, refer to OWASP's SSRF Prevention Cheat Sheet.
Memory Leaks and Resource Exhaustion (Self-Hosted)
Failure Mode: Running headless browsers like Chromium locally can be memory-intensive. Each instance consumes significant RAM, and if not managed properly (e.g., closing instances after use, limiting concurrency), it can lead to memory leaks, system slowdowns, or crashes.
Fix: If self-hosting, implement robust resource management. This includes strictly limiting the number of concurrent browser instances, implementing timeouts, and ensuring all browser processes are properly terminated. For example, a single headless Chromium instance might consume hundreds of MBs of RAM, scaling quickly with concurrency. Using a hosted API like PDFGeny completely offloads this operational burden, as the service manages its own fleet of rendering instances to ensure consistent performance and resource availability.
When Local Libraries Still Win: A Contrarian View
While hosted APIs offer convenience and scalability, it's important to acknowledge situations where a local library is a better choice. For a handful of documents a day, the overhead of integrating and paying for an API might outweigh the benefits. If an application generates only a few PDFs daily and has ample local resources, a direct library integration could be simpler and cheaper.
Consider a small internal tool that generates 10 reports a month. Installing Playwright locally and running it on demand might be perfectly adequate. The developer maintains full control and avoids external dependencies. However, once that volume scales, or the operational burden of keeping a browser warm (a cold headless Chromium costs about 7.8 seconds per request; keeping it warm brings it to ~0.65 seconds) becomes significant, an API becomes compelling. PDFGeny's free plan offers 50 documents a month, which covers many low-volume use cases without requiring a credit card.
The Real Cost of wkhtmltopdf: Unmaintained CSS
Many developers still reference wkhtmltopdf. It is not 'dead' in the sense of being unusable, but it is unmaintained. The real cost of wkhtmltopdf is not its initial setup, but the CSS it never learned. Modern CSS features like Flexbox, Grid, and many advanced print-specific properties are either poorly supported or entirely absent. This forces developers into frustrating workarounds, often leading to brittle, outdated HTML/CSS structures just to satisfy the renderer. Maintaining these workarounds costs development time and introduces technical debt.
What Surprised Us: Django's SynchronousOnlyOperation
One unexpected challenge we encountered when integrating local rendering engines with web frameworks like Django was the SynchronousOnlyOperation error. Django's ORM and other core components are designed to be synchronous by default. Attempting to run an asynchronous operation, such as launching a headless browser with sync_playwright, directly within a synchronous Django view function will raise this error.
The fix is to ensure that any asynchronous rendering logic happens on its own thread, separate from Django's main request-response cycle. This can be achieved using Python's threading module or by offloading the task to an asynchronous task queue like Celery. This highlights a subtle architectural impedance mismatch when mixing modern async Python libraries with traditional synchronous frameworks without careful threading management.
Practical Takeaways
- Evaluate Volume and Operational Overhead: For applications generating more than 50-100 documents per month, or those requiring high availability and low latency, a hosted API like PDFGeny (median render time: 0.6 seconds) significantly reduces operational burden.
Expected Outcome: Reduced infrastructure maintenance, faster development cycles. Time Estimate: 1-2 hours for API integration; weeks to months for robust self-hosted setup. Difficulty: Low (API) to High (self-hosted).
- Prioritize CSS for Print Media: Always design your HTML with print-specific CSS rules (
@media print,page-break-inside: avoid;) to ensure consistent pagination and layout.
Expected Outcome: Professional-looking PDFs without unexpected page breaks. Time Estimate: 2-4 hours per complex document template. Difficulty: Medium.
- Sanitize URLs for Security: If your application accepts URLs for PDF generation, implement strict host resolution and reject private/loopback IP addresses to prevent SSRF vulnerabilities.
Expected Outcome: Enhanced application security, protected internal networks. Time Estimate: 1-3 hours for implementing URL validation logic. Difficulty: Medium.
- Consider Async for Performance: For long-running PDF generation tasks (e.g., complex reports, batch processing), use asynchronous jobs and webhooks. PDFGeny supports both sync and async jobs, with webhooks signed with HMAC for security.
Expected Outcome: Non-blocking UI, improved user experience, efficient resource utilization. Time Estimate: 3-5 hours for webhook integration. Difficulty: Medium.
Stop wrestling with headless browsers and focus on your application logic. PDFGeny provides a reliable, scalable API to generate PDFs from any content. Send HTML, a URL, or one of 40 ready document templates. Get a free API key and generate 50 documents per month without a credit card.
FAQ Section
Q: How can I generate PDF from text with specific fonts?
A: To generate a PDF from text with specific fonts, embed the text within HTML and reference your desired web fonts using @font-face rules in your CSS. Ensure the font files (e.g., WOFF, TTF) are accessible to the rendering engine. If using an API like PDFGeny, include the HTML with embedded CSS and font links in your request. For external web fonts, consider adding a delay option in the API call (e.g., delay: 1000 for 1 second) to allow sufficient time for fonts to load before rendering, mitigating silent font fallbacks.
Q: What is the fastest way to generate a PDF from a URL?
A: The fastest way to generate a PDF from a URL is by using a hosted API like PDFGeny. Such services maintain warm rendering instances, reducing the overhead of starting a new browser process. PDFGeny reports a median render time of 0.6 seconds. In contrast, a cold headless Chromium instance can take approximately 7.8 seconds to start and render a page locally. Using an API also offloads the need for server-side URL validation against SSRF vulnerabilities.
Q: Can I generate PDF/A-2b compliant documents?
A: Yes, PDFGeny supports generating PDF/A-2b compliant documents using Ghostscript as one of its rendering engines. PDF/A-2b is an ISO standard for long-term archiving of electronic documents. To generate a PDF/A-2b document, specify this requirement in your API request. This feature is critical for industries with strict regulatory compliance or archival needs.