- HTML to PDF APIConvert HTML and URLs into PDFsExtract PDF Form Data APIEasily extract data from PDFsWatermark PDF APIAdd custom watermarks to PDFsProtect PDF APISecure your PDFs with passwordCompress PDF APIReduce file size without losing qualityFlatten PDF APIFlatten PDFs to make form permanentDigital Signature APISend documents for signing
- API DocumentationAPI DocumentationFull REST API referenceNodeJS SDKClient library for Node.jsJava SDKClient library for JavaC# SDKClient library for C#PHP SDKClient library for PHPPython SDKClient library for Python
- Pricing
- Contact Us
How to Use Custom Fonts and Images in HTML to PDF
Learn how to load fonts and images reliably during HTML to PDF conversion.
Overview
A PDF that looks right in your browser can come out with a different font or missing images. This usually happens for one of three reasons: the font only exists on your machine, an asset is referenced with a path the renderer can't resolve, or the PDF is captured before the asset has finished loading.
This guide shows how to load fonts and images reliably with the HTML to PDF API, for both the url and raw html inputs.
Don't rely on system fonts
The rendering environment ships with only a minimal set of basic fonts. Fonts installed on your own computer (Helvetica, Segoe UI, Roboto, your brand font, and so on) are not available, so a font-family that names them without loading them will silently fall back to a generic font.
Always load the fonts you need explicitly, using one of the methods below, and keep a generic family (sans-serif, serif, monospace) at the end of your font stack.
Web fonts (Google Fonts)
The simplest option is a hosted web font. Add the Google Fonts <link> tag to the <head> of your HTML and reference the family in your CSS.
html: The HTML content to convert, including the font<link>.
1curl https://api.pdfgate.com/generate/pdf \2 -X POST \3 -H "Content-Type: application/json" \4 -H "Authorization: Bearer YOUR_API_KEY" \5 --data '{6 "html": "<!DOCTYPE html><html><head><link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin><link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap\" rel=\"stylesheet\"><style>body { font-family: \"Inter\", sans-serif; }</style></head><body><h1>Invoice #1042</h1><p>Rendered with the Inter web font.</p></body></html>"7 }' \8 -o output.pdfIf you can't edit the <head>, @import works too. It must be the first rule in the stylesheet:
1<style>2 @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap");34 body {5 font-family: "Inter", sans-serif;6 }7</style>Self-hosted fonts
For brand or licensed fonts, host the font files yourself and declare them with @font-face. Use absolute URLs, and add one @font-face block per weight and style you use.
1<style>2 @font-face {3 font-family: "Brand Sans";4 src: url("https://cdn.example.com/fonts/brand-sans-regular.woff2") format("woff2");5 font-weight: 400;6 font-style: normal;7 }89 @font-face {10 font-family: "Brand Sans";11 src: url("https://cdn.example.com/fonts/brand-sans-bold.woff2") format("woff2");12 font-weight: 700;13 font-style: normal;14 }1516 body {17 font-family: "Brand Sans", sans-serif;18 }19</style>Font files are fetched with CORS. If they are served from a different domain than the page, the server must return an Access-Control-Allow-Origin header, otherwise the font is blocked and the fallback is used.
Base64-embedded fonts
When you send raw html and want no external requests at all, embed the font directly in the @font-face rule as a data URI. The font is available immediately, with no network dependency or CORS setup.
1<style>2 @font-face {3 font-family: "Brand Sans";4 src: url("data:font/woff2;base64,d09GMgABAAAAA...") format("woff2");5 font-weight: 400;6 font-style: normal;7 }89 body {10 font-family: "Brand Sans", sans-serif;11 }12</style>You can generate the Base64 string from a font file in your own code:
1const fs = require("fs");23// Convert a local .woff2 file to a Base64 string4const fontBase64 = fs.readFileSync("./fonts/brand-sans-regular.woff2").toString("base64");56const fontFace = `7@font-face {8 font-family: "Brand Sans";9 src: url("data:font/woff2;base64,${fontBase64}") format("woff2");10 font-weight: 400;11}12`;Base64 adds roughly 33% to the file size, and every request carries the full font. Use woff2 (the most compact format), embed only the weights you need, and consider subsetting the font to the characters you actually use.
Inject fonts with the css parameter
When converting a urlyou don't control, or when you want to keep font rules out of your templates, pass them in the cssparameter. The CSS is added to the page after it loads, so you can declare a font and override the page's font stack in one place.
url: The URL of the page to convert.css: Additional CSS injected into the page before rendering.
1curl https://api.pdfgate.com/generate/pdf \2 -X POST \3 -H "Content-Type: application/json" \4 -H "Authorization: Bearer YOUR_API_KEY" \5 --data '{6 "url": "https://example.com/report",7 "css": "@font-face { font-family: \"Brand Sans\"; src: url(\"https://cdn.example.com/fonts/brand-sans-regular.woff2\") format(\"woff2\"); font-weight: 400; font-style: normal; } body, h1, h2, h3, p { font-family: \"Brand Sans\", sans-serif !important; }"8 }' \9 -o output.pdfThe css parameter accepts the same rules as a stylesheet, including @font-face with Base64 data URIs.
Make sure fonts finish loading
Right before creating the PDF, PDFGate waits for document.fonts.ready, so fonts used by content that is already on the page normally render correctly. Browsers only download a font once text that uses it is rendered, so if your page inserts content later, document.fonts.readycan resolve before that content's fonts are requested. Add the readiness marker only after the final content is in place and its fonts have loaded:
1<script>2 async function renderReport() {3 // 1. Insert the final content first (fetch data, render templates, etc.)4 await renderInvoice();56 // 2. Load the exact font faces the new content uses7 await Promise.all([8 document.fonts.load('400 16px "Brand Sans"'),9 document.fonts.load('700 16px "Brand Sans"'),10 ]);1112 // 3. Wait for any other font loads the new content triggered13 await document.fonts.ready;1415 // 4. Only now add the marker PDFGate waits for16 document.body.setAttribute("data-pdf-ready", "true");17 }1819 renderReport();20</script>Then send "waitForSelector": "body[data-pdf-ready]". waitForSelector gives up after 30 seconds, so the page must add the marker within that time. Put this script in your page itself: the javascript parameter is injected after waitForSelector runs, so it can't be used to create the marker. See How to wait for dynamic content for the full pattern.
Images
There are three reliable ways to include images:
Absolute URLs
Reference images with a full URL that is publicly reachable. The image must be accessible without your browser's cookies or session.
1<!-- Works -->2<img src="https://cdn.example.com/images/logo.png" alt="Logo" width="160" />34<!-- Does not resolve when sending raw html -->5<img src="/images/logo.png" alt="Logo" width="160" />Base64 data URIs
Embed small images such as logos, signatures, or icons directly in the HTML. They need no network request and always render. As with fonts, Base64 increases payload size, so keep this for small assets.
1<img2 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."3 alt="Logo"4 width="160"5/>Inline SVG
Icons, badges, and charts can be written as inline <svg> elements. They stay sharp at any zoom level and remain vector graphics in the PDF.
1<svg width="120" height="40" viewBox="0 0 120 40" xmlns="http://www.w3.org/2000/svg">2 <rect width="120" height="40" rx="6" fill="#1f6feb" />3 <text x="60" y="26" font-family="sans-serif" font-size="16" fill="#ffffff" text-anchor="middle">4 PAID5 </text>6</svg>Text inside an SVG follows the same font rules as the rest of the page, so load any custom font it uses.
loadImages and lazy-loaded images
Images marked with loading="lazy", or loaded by a script when they scroll into view, are often never requested because nothing scrolls the page. Set loadImages to true to handle this: PDFGate scrolls through the whole page to trigger lazy loading, then waits for every <img> to finish loading (or fail) before generating the PDF.
loadImages: Waits for all images to finish loading before generating the PDF.
1curl https://api.pdfgate.com/generate/pdf \2 -X POST \3 -H "Content-Type: application/json" \4 -H "Authorization: Bearer YOUR_API_KEY" \5 --data '{6 "url": "https://example.com/product-catalog",7 "loadImages": true8 }' \9 -o output.pdfThe image wait is capped at about 10 seconds. loadImages only tracks <img> elements; for CSS background-image assets, use waitForNetworkIdle instead. If you control the HTML, the simplest fix is to remove loading="lazy" from images in your PDF templates.
Relative paths with raw HTML
When you send raw html, the document has no base URL. Relative paths such as /images/logo.png or assets/styles.csshave nothing to resolve against, so those images, stylesheets, and fonts won't load. With the urlinput this isn't an issue, because paths resolve against the page URL.
Use absolute URLs or data URIs in raw HTML. If you have a template full of relative paths, you can add a <base> tag as the first element in <head> so the browser resolves them against your domain:
1<!DOCTYPE html>2<html>3 <head>4 <base href="https://www.example.com/" />5 <link rel="stylesheet" href="assets/styles.css" />6 </head>7 <body>8 <img src="images/logo.png" alt="Logo" width="160" />9 </body>10</html>Non-Latin scripts and emoji
Chinese, Japanese, Korean, Arabic, Hebrew, Thai, and other scripts need a font that contains their glyphs. Without one, characters render as empty boxes. The same applies to emoji. Load a web font that covers the script, such as the Noto Sans family, and add Noto Color Emoji if you use emoji:
1<head>2 <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&family=Noto+Color+Emoji&display=swap" rel="stylesheet">3 <style>4 body {5 font-family: "Noto Sans JP", "Noto Color Emoji", sans-serif;6 }7 </style>8</head>9<body>10 <p>請求書 ✅</p>11</body>For right-to-left text and more language-specific tips, see HTML to PDF in different languages.
Additional notes
- Assets must be publicly reachable. Pages on
localhostor a private network can't be loaded by the renderer. - Header and footer templates can't load external resources. Use Base64 images there, as shown in the headers and footers guide.
- Background images are printed by default. Set
printBackgroundtofalseto omit them. - To control page dimensions around your images, see page size and margins.
For a full list of supported options, see the HTML to PDF API Reference.