How to Convert HTML to PDF with ephtmltopdf
Convert a URL or local HTML file to PDF with ephtmltopdf in C#, choose WebKit or IE rendering, and troubleshoot missing content.
Direct answer: create a PdfConverter, configure it if needed, then call SavePdfFromUrlToFile(urlOrLocalHtmlPath, outputPdfPath). The input can be a web URL or a full path to an HTML file. The method renders the document and writes the PDF to disk.
For example:
using ExpertPdf.HtmlToPdf;
var converter = new PdfConverter();
converter.SavePdfFromUrlToFile(
"https://example.com/invoice/123",
@"C:\\output\\invoice-123.pdf");
The API documentation describes SavePdfFromUrlToFile(string url, string outFile) as converting a specified URL into a PDF document and saving it into the specified disk file. A local HTML file can be supplied instead of an HTTP URL. Confirm the exact namespace and referenced assembly for the version installed in your application; the checked reference displays assembly version 17.0.0, which is documentation metadata rather than proof that it is the newest release.
1. Set up a minimal C# conversion
Add the ephtmltopdf assembly or package your application uses, then create a converter and call the URL-to-file method. Use an absolute output path and make sure the worker process has permission to create or overwrite that file.
using System;
using System.IO;
using ExpertPdf.HtmlToPdf;
public static class HtmlPdf
{
public static void Convert(string source, string destination)
{
if (string.IsNullOrWhiteSpace(source))
throw new ArgumentException("An HTML URL or local file path is required.", nameof(source));
var fullDestination = Path.GetFullPath(destination);
Directory.CreateDirectory(Path.GetDirectoryName(fullDestination)!);
var converter = new PdfConverter();
converter.SavePdfFromUrlToFile(source, fullDestination);
}
public static void Main()
{
Convert(
"https://example.com/report.html",
@"C:\\reports\\report.pdf");
}
}
For a local file, pass a complete path such as C:\\reports\\report.html. Test local paths on the same machine and under the same account that runs the application. A path that works in an interactive desktop session may not be readable by a service account.
2. Choose the rendering engine
The documented HtmlToPdfElement.RenderingEngine values are WebKit and IE. The reference describes WebKit as an internal renderer similar to Chrome and Safari. The IE option uses the Internet Explorer engine available on the machine.
Choose based on the HTML you actually render and the runtime installed on the production host:
| Engine | Use when | Check before deployment |
|---|---|---|
| WebKit | Your pages depend on the rendering behavior provided by the library’s WebKit engine. | CSS layout, fonts, images, JavaScript, and local resources on the target host. |
| IE | Your application requires the machine’s IE rendering engine. | That engine and its required Windows configuration are present for the service account. |
Do not assume either option is identical to a current Chromium browser. Validate representative pages, including tables, web fonts, charts, JavaScript-generated content, and print styles, on the machine that will generate the PDFs.
3. Convert a page that needs authentication or a stable URL
The converter fetches the supplied URL from the conversion host. For protected pages, expose a conversion endpoint that authenticates the request and returns deterministic HTML, or configure the converter’s available request options for your installed version. Avoid relying on a browser session that exists only on your development workstation.
Before conversion, verify:
- The URL resolves from the server running ephtmltopdf.
- Relative CSS, image, font, and script URLs resolve from that page.
- TLS certificates and outbound firewall rules permit the request.
- Server-side data is present in the HTML delivered to the converter.
- Client-side rendering has completed before the converter captures the page.
4. Save to a file or continue with a stream
SavePdfFromUrlToFile is the direct URL-or-file-to-disk workflow. The related Document.Save APIs have overloads for a file path and for a Stream. These are separate document-saving methods; do not substitute a Document.Save overload for the converter call unless your integration already creates a document object.
A stream-based pipeline is useful when the rest of your application uploads the PDF, stores it in object storage, or returns it from an HTTP response. The exact object construction and document lifecycle depend on the ephtmltopdf version and the conversion method you use, so check the API reference for the assembly in your project.
5. Make the HTML deterministic for PDF output
Most conversion problems are input or environment problems rather than the final file-write call. Make the page predictable:
- Use absolute or correctly rooted resource URLs.
- Include print-specific CSS with
@media printwhere appropriate. - Set explicit widths for tables, images, and important containers.
- Embed or reliably serve the fonts required for the document.
- Render data on the server when possible instead of depending on late JavaScript.
- Keep a minimal HTML fixture that reproduces the layout.
For long tables, define the page-break behavior in CSS and test rows that cross page boundaries. A table that is absent from the HTML response cannot be recovered by a PDF setting.
6. Diagnose a missing table or other missing content
A January 2015 Stack Overflow question about a missing table in an ASP.NET conversion shows a PdfConverter call. The accepted response suggested assigning pdfConverter.PdfHeaderOptions.HtmlToPdfArea to an HtmlToPdfArea for the URL. That is a historical, single-user suggestion, not evidence that placing body content in a header fixes missing tables in general.
Use this investigation sequence instead:
- Inspect the delivered HTML. Save the response or local source and confirm that the table markup exists.
- Check where the table lives. Confirm it is in the document body and has not been excluded by a configured header, footer, frame, or page area.
- Remove dynamic dependencies. Replace API calls and client-side templates with a small static table to determine whether JavaScript timing is involved.
- Check resource paths. Missing CSS can make a table appear blank, clipped, or outside the printable area.
- Check dimensions and margins. An oversized container or unexpected scaling can move content beyond the page.
- Compare engines. Render the same fixture with WebKit and IE on the production host.
7. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| File not found or access denied | Relative output path or insufficient service-account permissions. | Use an absolute path, create the directory, and grant write access to the worker identity. |
| Blank PDF | The URL cannot be reached, returns an error page, or requires authentication. | Fetch the URL from the conversion host and inspect the returned HTML and status behavior. |
| Images or fonts missing | Relative URLs, blocked resources, or unavailable files. | Use resolvable URLs or local paths and verify access from the converter process. |
| Table missing | Markup is generated after capture, hidden by CSS, outside the page area, or absent from the response. | Use a static reproduction, inspect source, check timing and layout, then compare rendering engines. |
| Layout differs between machines | Different engine, fonts, runtime, or installed dependencies. | Pin the deployment environment and test on the target host with the same engine. |
| JavaScript content is incomplete | Capture occurs before data or components finish rendering. | Prefer server-rendered HTML or use the converter’s documented timing/configuration features for your version. |
| PDF is clipped | Content exceeds the page width or margins. | Set print CSS, constrain widths, inspect page sizing and margins, and test long rows. |
8. Performance, reliability, and operating cost
Conversion time depends on network latency, page size, images, fonts, JavaScript, and the selected renderer. For predictable throughput, keep input pages self-contained, avoid unnecessary third-party resources, reuse a controlled host configuration, and measure representative documents rather than a trivial HTML fixture.
For reliability:
- Write to a temporary file and move it into place after a successful conversion.
- Log the source URL or document identifier, engine choice, elapsed time, and output path.
- Retry only transient fetch failures; do not blindly repeat malformed HTML or permission errors.
- Set an application-level timeout around conversion and isolate conversions if untrusted pages can consume excessive resources.
- Keep fonts, runtime dependencies, and renderer configuration consistent between staging and production.
The research dossier does not establish current ExpertPDF licensing, supported operating systems, compatibility guarantees, or pricing. Verify those details with the vendor before selecting it for a new deployment.
9. When a hosted screenshot or PDF API is simpler
If you do not want to maintain a browser or renderer runtime, ScreenshotNeo provides a website screenshot API and MCP server. It accepts one GET request and can return PNG, JPEG, WebP, or PDF. It supports full-page capture, print-oriented PDF options such as paper size, margins, landscape mode, and page ranges, plus custom CSS and JavaScript.
Or skip the browser setup
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing result in headers. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation for the available options.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
There is a free plan with 1,000 screenshots per month and no card required. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
FAQ
Can ephtmltopdf convert a local HTML file?
Yes. The documented URL parameter may also be a full local HTML file path. Test access under the identity that runs the converter.
Is WebKit the same as modern Chrome?
No guarantee of complete browser parity is established by the reference. It describes WebKit as similar to Chrome and Safari, so validate your own pages on the target host.
Can I return the PDF directly from memory?
The converter method saves to a disk file. Related Document.Save APIs include path and stream overloads, but they are separate document APIs.
Why does a page work in my browser but fail in conversion?
The converter may run on another host, use another engine, lack fonts or permissions, or capture before JavaScript finishes. Compare the delivered HTML and runtime environment.
Should I use the old header-area workaround for a missing table?
Treat it as a historical troubleshooting lead only. First confirm that the table exists in the delivered HTML and is inside the intended document area.


