I've debugged more broken images in PDFs than I care to count. The HTML renders perfectly in a browser, but when converted to PDF, all the images are missing. CSS doesn't load. JavaScript files 404.
The culprit is almost always relative paths. Your HTML references images/logo.png, but the PDF renderer doesn't know where to find it. The solution: base URLs.
using IronPdf;
// Install via NuGet: Install-Package IronPdf
var html = "<img src='images/logo.png' />";
var renderer = new [ChromePdfRenderer](https://ironpdf.com/blog/videos/how-to-render-html-string-to-pdf-in-csharp-ironpdf/)();
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: "https://example.com/");
pdf.SaveAs("output.pdf");
The base URL resolves relative paths. images/logo.png becomes https://example.com/images/logo.png.
Why Do Images and CSS Break in PDFs?
When you convert HTML to PDF, the rendering engine processes your HTML in isolation. It doesn't have the context of a web server or file system.
Relative paths like these fail:
<img src="logo.png" /><link rel="stylesheet" href="styles.css" /><script src="app.js"></script>
The renderer asks: "Relative to what?" Without a base URL, it has no answer.
I built a reporting system that rendered invoices from HTML templates. Every invoice had a company logo. The logo worked in the browser but was missing in PDFs. Setting the base URL fixed it instantly.
How Do I Set a Base URL for Web Assets?
Pass it to RenderHtmlAsPdf():
var html = @"
<link rel='stylesheet' href='styles/invoice.css' />
<img src='images/logo.png' alt='Logo' />
<p>Invoice content here</p>
";
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: "https://cdn.example.com/");
pdf.SaveAs("invoice.pdf");
Now:
-
styles/invoice.cssresolves tohttps://cdn.example.com/styles/invoice.css -
images/logo.pngresolves tohttps://cdn.example.com/images/logo.png
I use CDNs for static assets in production PDFs. It's faster than reading from disk and works across distributed systems.
Can I Use Local File Paths as Base URLs?
Yes. For local HTML files with local assets:
var html = @"
<link rel='stylesheet' href='styles/report.css' />
<img src='images/chart.png' />
";
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: @"C:\Projects\Reports\");
pdf.SaveAs("report.pdf");
The base path must be absolute. Relative paths like ../assets/ won't work.
I use this pattern for desktop applications that generate PDFs from local templates and images.
What About ASP.NET MVC Applications?
In MVC, your assets are typically in wwwroot. The base URL needs to point there:
var html = "<img src='images/banner.jpg' />";
var baseUrl = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: baseUrl);
pdf.SaveAs("output.pdf");
For deployed applications, use IWebHostEnvironment:
public class PdfService
{
private readonly IWebHostEnvironment _env;
public PdfService(IWebHostEnvironment env)
{
_env = env;
}
public void GeneratePdf()
{
var html = "<img src='images/logo.png' />";
var baseUrl = _env.WebRootPath;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: baseUrl);
pdf.SaveAs("output.pdf");
}
}
This works in development and production because WebRootPath adapts to the deployment environment.
How Do I Handle Mixed Absolute and Relative Paths?
Absolute paths in your HTML override the base URL:
var html = @"
<img src='logo.png' /> <!-- Uses base URL -->
<img src='https://cdn.example.com/banner.jpg' /> <!-- Absolute, ignores base -->
";
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: "https://example.com/images/");
Result:
-
logo.png→https://example.com/images/logo.png -
banner.jpg→https://cdn.example.com/banner.jpg(unchanged)
I use this when most assets are local but some (like analytics pixels or third-party logos) are external.
What If I Can't Use a Base URL?
Embed assets as base64 data URIs:
var imageBytes = File.ReadAllBytes("logo.png");
var base64 = Convert.ToBase64String(imageBytes);
var html = $"<img src='data:image/png;base64,{base64}' />";
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("embedded.pdf");
The image data is part of the HTML. No external files needed.
I use this for:
- Self-contained PDF templates
- Environments where file access is restricted
- Small images (logos, icons) where file size isn't a concern
How Do Headers and Footers Handle Base URLs?
Headers and footers are separate HTML documents. They need their own base URL:
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter()
{
HtmlFragment = "<img src='header-logo.png' />",
BaseUrl = "https://cdn.example.com/images/"
};
var html = "<p>Main content</p>";
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: "https://example.com/");
pdf.SaveAs("output.pdf");
The main content uses https://example.com/, but the header uses https://cdn.example.com/images/. They're independent.
I learned this the hard way when headers had missing logos despite setting a base URL for the main content.
Can I Load CSS from a Different URL?
Yes, with CustomCssUrl:
var html = "<p>Content without CSS link</p>";
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CustomCssUrl = "https://cdn.example.com/styles/global.css";
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: "https://example.com/");
pdf.SaveAs("styled.pdf");
This injects the CSS without modifying your HTML. I use it for global styles applied to all PDFs.
How Do I Debug Base URL Issues?
If assets still don't load:
-
Check the full resolved URL: Print
baseUrl + relativePathto verify it's correct - Test in a browser: Visit the resolved URL to confirm it's accessible
- Check file permissions: Ensure the rendering process can read local files
- Use absolute URLs temporarily: Replace relative paths with absolute URLs to isolate the issue
- Check network access: If using web URLs, ensure the server can reach them
I debug by rendering the HTML in Chrome first. If assets load there, the URLs are correct. Then I check that the PDF renderer has access to those URLs.
What About Different Asset Types?
The base URL applies to all relative asset types:
var html = @"
<link rel='stylesheet' href='css/style.css' />
<script src='js/app.js'></script>
<img src='images/photo.jpg' />
<video src='videos/demo.mp4'></video>
";
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html, baseUrl: "https://cdn.example.com/");
All paths resolve relative to https://cdn.example.com/:
- CSS:
https://cdn.example.com/css/style.css - JS:
https://cdn.example.com/js/app.js - Images:
https://cdn.example.com/images/photo.jpg - Video:
https://cdn.example.com/videos/demo.mp4
Quick Reference
| Scenario | Base URL Example |
|---|---|
| Web assets | https://cdn.example.com/ |
| Local files (Windows) | C:\Projects\Assets\ |
| Local files (Linux) | /var/www/assets/ |
| MVC wwwroot | Path.Combine(env.WebRootPath) |
| Headers/footers | Set on HtmlHeaderFooter.BaseUrl
|
| No base URL | Use base64 data URIs |
Key Principles:
- Always use absolute base URLs (web or file system)
- Headers/footers need separate base URLs
- Test resolved URLs in a browser first
- Use base64 for small, self-contained assets
- Absolute URLs in HTML override the base URL
The complete guide to base URLs covers edge cases like mixed protocols and network shares.
Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.
Top comments (0)