Getting Started
Choose your engine
CobaltPDF ships as two separate libraries with an identical fluent API:
CobaltPdf (Chromium — pixel-perfect fidelity, Windows & Linux production) and
CobaltPdf.WebKit (WebKit — up to 96% lower memory, Linux production, Windows dev via WSL2/Docker).
Every snippet on this page works in both — only the using statement differs.
# Chromium engine — pixel-perfect fidelity
dotnet add package CobaltPdf
# WebKit engine — a fraction of the memory
dotnet add package CobaltPdf.WebKit
One license, both libraries: a single CobaltPDF license key activates
CobaltPdf and
CobaltPdf.WebKit — install one or both.
See the
engine comparison or the
WebKit edition docs for details.
Getting Started
Set your license key
Activate CobaltPDF by calling SetLicense once at application startup.
Without a valid license the library runs in free evaluation mode and watermarks output.
The same key activates both the Chromium and WebKit libraries.
using CobaltPdf;
// Call once at startup — before any rendering
CobaltEngine.SetLicense("COBALTPDF-AcmeCorp-XXXXX-Annual-2026-06-15");
var renderer = new CobaltEngine();
var pdf = await renderer.RenderUrlAsPdfAsync("https://example.com");
// Load the key from configuration (recommended)
CobaltEngine.SetLicense(builder.Configuration["CobaltPdf:LicenseKey"]!);
builder.Services.AddCobaltPdf();
Tip: Store your license key in
appsettings.json, environment variables, or user secrets —
never hard-code it in source control. The key is validated on first use and
throws InvalidOperationException if invalid or expired.
Getting Started
Render a URL to PDF
The simplest use case — point CobaltPDF at any URL and get a pixel-perfect PDF back.
The engine handles navigation, rendering, and cleanup automatically.
using CobaltPdf;
var renderer = new CobaltEngine();
// Render any URL to a PDF
var pdf = await renderer
.RenderUrlAsPdfAsync("https://example.com");
// Save to disk
pdf.SaveAs("output.pdf");
// Or get the raw bytes
byte[] bytes = pdf.BinaryData;
Tip: The CobaltEngine is thread-safe and designed to be used as a
singleton. Create one instance and reuse it across your application.
Rendering
Render an HTML string to PDF
Pass raw HTML directly — ideal for generating invoices, reports, or documents
from templates rendered server-side with Razor, Handlebars, or any templating engine.
var html = $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: 'Segoe UI', sans-serif; padding: 40px; }}
h1 {{ color: #1e293b; }}
table {{ width: 100%; border-collapse: collapse; }}
th, td {{ padding: 12px; border-bottom: 1px solid #e2e8f0; }}
</style>
</head>
<body>
<h1>Invoice #{invoiceNumber}</h1>
<p>Date: {DateTime.Now:dd MMM yyyy}</p>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
<tr><td>CobaltPDF License</td><td>1</td><td>£149</td></tr>
</table>
</body>
</html>";
var pdf = await renderer
.RenderHtmlAsPdfAsync(html);
pdf.SaveAs("invoice.pdf");
Layout
Custom paper size & margins
Configure paper format, orientation, and per-side margins.
Use standard sizes like A4 and Letter, or specify custom dimensions in any CSS unit.
// A3 landscape with custom margins
var pdf = await renderer
.WithPaperFormat("A3")
.WithLandscape()
.WithMargins(top: "20mm", right: "15mm", bottom: "20mm", left: "15mm")
.WithPrintBackground()
.RenderUrlAsPdfAsync("https://app.example.com/dashboard");
// Custom paper size (width × height)
var receipt = await renderer
.WithPageSize(width: "80mm", height: "200mm")
.WithMargins("5mm")
.RenderHtmlAsPdfAsync(receiptHtml);
Supported formats: A3, A4, A5, Legal, Letter, Tabloid — or any custom
width/height using px, mm, cm, or in.
Authentication
Cookies & authenticated pages
Inject cookies, localStorage, and sessionStorage before the page loads.
Perfect for rendering pages that sit behind authentication.
// Inject auth cookies so the page loads as the logged-in user
var pdf = await renderer
.AddCookie("session", userSessionId)
.AddCookie("csrf_token", csrfToken)
.RenderUrlAsPdfAsync("https://app.example.com/dashboard");
// You can also inject localStorage and sessionStorage
var pdf2 = await renderer
.AddCookie(".AspNetCore.Identity", identityCookie)
.AddLocalStorage("theme", "dark")
.AddLocalStorage("locale", "en-GB")
.AddSessionStorage("activeTab", "analytics")
.RenderUrlAsPdfAsync(dashboardUrl);
SPA frameworks: If your page uses Angular, React, or another SPA framework that reads
storage values only during initialisation, chain .WithReloadAfterStorage() to force
a page reload after localStorage and sessionStorage values have been injected. This ensures the
framework picks up the injected values during its startup lifecycle.
Advanced
Wait strategies
Control exactly when CobaltPDF captures the page. Choose from network idle,
DOM selector, JavaScript expression, custom signal, or a fixed delay.
// Wait for network to be idle (no requests for 500ms)
var pdf1 = await renderer
.WithWaitStrategy(WaitOptions.DefaultNetworkIdle)
.RenderUrlAsPdfAsync(url);
// Wait for a specific element to appear in the DOM
var pdf2 = await renderer
.WithWaitStrategy(WaitOptions.ForSelector("#chart-container svg"))
.RenderUrlAsPdfAsync(url);
// Wait for a JavaScript expression to return true
var pdf3 = await renderer
.WithWaitStrategy(WaitOptions.ForJavaScript("window.dataLoaded === true"))
.RenderUrlAsPdfAsync(url);
// Fixed delay (useful as a last resort)
var pdf4 = await renderer
.WithWaitStrategy(WaitOptions.ForDelay(TimeSpan.FromSeconds(2)))
.RenderUrlAsPdfAsync(url);
Advanced
Custom JavaScript execution
Execute JavaScript before capture to manipulate the page — hide elements, inject data,
or trigger SPA rendering. Use cobaltNotifyRender() to signal
when your app is ready.
// Run JS before capture — e.g. hide nav, expand sections
var pdf = await renderer
.WithCustomJS(@"
document.querySelector('nav').style.display = 'none';
document.querySelector('.sidebar').style.display = 'none';
document.querySelectorAll('details').forEach(d => d.open = true);
")
.RenderUrlAsPdfAsync(url);
// For SPAs: wait for the app to signal readiness
var spaPdf = await renderer
.WithWaitStrategy(WaitOptions.ForSignal())
.RenderUrlAsPdfAsync(spaUrl);
// In your SPA (React, Vue, Angular, etc.):
// window.cobaltNotifyRender() ← call this when ready
SPA tip: Call window.cobaltNotifyRender() from your front-end
code after all data has loaded and the DOM is stable. CobaltPDF will wait indefinitely
(up to the timeout) for this signal before capturing.
Advanced
Lazy-loaded content
Modern sites defer images and content until they scroll into view.
CobaltPDF can auto-scroll the page to trigger lazy loading before capture,
ensuring every image and component is fully rendered in the final PDF.
// Scroll 10 viewport-heights to trigger lazy-loaded images
var pdf = await renderer
.WithLazyLoadPages(10)
.RenderUrlAsPdfAsync("https://example.com/gallery");
// Fine-tune scroll speed and timeout
var pdf2 = await renderer
.WithLazyLoadPages(
pageCount: 20,
delay: TimeSpan.FromMilliseconds(300),
timeout: TimeSpan.FromSeconds(60))
.RenderUrlAsPdfAsync("https://example.com/infinite-feed");
// Combine with custom JS for stubborn lazy attributes
var pdf3 = await renderer
.WithLazyLoadPages(10)
.WithCustomJS(@"
document.querySelectorAll('img[loading=lazy]')
.forEach(img => img.removeAttribute('loading'));
")
.RenderUrlAsPdfAsync("https://example.com/blog");
How it works: WithLazyLoadPages(n) scrolls the viewport
n viewport-heights down the page, pausing between each step to let
IntersectionObserver-based content load. The default delay is 200ms per step
with a 30s timeout.
Output
Watermarks
Overlay text or HTML watermarks on every page of the PDF.
Configure opacity, rotation, position, and font size.
// Pre-styled text watermark
var pdf = await renderer
.WithWatermark(
WatermarkOptions.WithText("DRAFT", WatermarkStyle.RedDraft))
.RenderUrlAsPdfAsync(url);
// Customize position, rotation and opacity via fluent methods
var pdf2 = await renderer
.WithWatermark(
WatermarkOptions.WithText("CONFIDENTIAL", WatermarkStyle.SoftGray)
.WithRotation(-45)
.WithOpacity(0.15)
.WithPosition(WatermarkPosition.Center))
.RenderUrlAsPdfAsync(url);
// Full control with custom HTML
var pdf3 = await renderer
.WithWatermark(new WatermarkOptions
{
Html = @"<div style='font-size:60px; font-weight:bold;
color:red;'>DO NOT DISTRIBUTE</div>",
Rotation = -30,
Opacity = 0.06
})
.RenderUrlAsPdfAsync(url);
// Rasterize: flatten the watermark into the page pixels so it
// can't be selected or deleted in a PDF editor (default: off)
var pdf4 = await renderer
.WithWatermark(
WatermarkOptions.WithText("CONFIDENTIAL", WatermarkStyle.SoftGray)
.WithRotation(-45)
.WithOpacity(0.3)
.WithRasterize())
.RenderUrlAsPdfAsync(url);
Rasterized watermarks are baked into every page as an image — no selectable
text, no separate object to delete in a PDF editor. This is tamper
resistance, not DRM:
a determined user can still crop or paint over page pixels.
See
the docs for details.
Security
Password protection & encryption
Protect PDFs with AES-256 encryption and control what recipients can do with the
document. Set separate owner and user passwords, and restrict printing, copying, or editing.
Both libraries produce identical AES-256 protection from identical options.
// Require a password to open the PDF
var pdf = await renderer
.WithEncryption(new PdfEncryptionOptions
{
UserPassword = "read-only-pass"
})
.RenderUrlAsPdfAsync(url);
// Owner + user password with restricted permissions
var secured = await renderer
.WithEncryption(new PdfEncryptionOptions
{
UserPassword = "viewer-pass",
OwnerPassword = "admin-secret",
AllowPrinting = true,
AllowCopying = false,
AllowModification = false
})
.RenderUrlAsPdfAsync(url);
Note: The user password is required to open the document.
The owner password grants full access and allows changing permissions.
By default, AllowPrinting is true, while
AllowCopying and AllowModification are false.
Output
PDF metadata
Embed document properties like title, author, subject, and keywords into the PDF.
Metadata is visible in PDF readers and improves searchability and organization.
// Set standard PDF document properties
var pdf = await renderer
.WithMetadata(meta =>
{
meta.Title = "Q4 Financial Report";
meta.Author = "Acme Corp";
meta.Subject = "Quarterly earnings summary";
meta.Keywords = "finance, Q4, earnings, 2026";
meta.Creator = "Report Generator v2";
})
.RenderUrlAsPdfAsync(reportUrl);
// Combine with headers for a polished document
var polished = await renderer
.WithMetadata(m => {
m.Title = "Invoice #1042";
m.Author = "Billing Dept";
m.CreationDate = DateTime.UtcNow;
})
.WithFooter(footerHtml)
.WithPrintBackground()
.RenderHtmlAsPdfAsync(invoiceHtml);
Tip: The Title property appears in the PDF reader's title bar
and browser tab. Keywords help with document search and indexing in
enterprise document management systems.
Output
Splitting & merging PDFs
Extract single pages, page ranges, or arbitrary subsets from any PDF, split a document
into one file per page, or merge documents together. Works on freshly rendered output
and PDFs loaded from disk — pure in-process post-processing, no browser involved.
// Render once, then split into one PDF per page
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
for (int i = 0; i < pdf.PageCount; i++)
pdf.ExtractPage(i).SaveAs($"page_{i + 1}.pdf");
// Extract a page range (inclusive, 0-based)
var summary = pdf.ExtractPages(0, 2);
// Load any existing PDF — even one CobaltPDF didn't create
var existing = PdfDocument.FromFile("multipage.pdf");
var pages = existing.SplitIntoPages();
// Merge documents back together
var combined = PdfDocument.Merge(coverPdf, reportPdf, appendixPdf);
combined.SaveAs("complete-report.pdf");
Note: Page indices are
0-based; out-of-range indices throw
ArgumentOutOfRangeException. Extracted pages keep their fonts, images, and vector
content, so they render identically to the original. This is distinct from the render-time
pageRanges option, which selects which pages of the HTML get rendered.
See
the docs for the full API.
Optional
Pool & browser options
CobaltPDF works out of the box with sensible defaults — no configuration required.
Optionally tune the browser pool size, recycling policy, and Chromium flags for
production workloads or constrained environments.
// Register with custom pool options
builder.Services.AddCobaltPdf(options =>
{
options.MinSize = 2;
options.MaxSize = Environment.ProcessorCount;
options.MaxUsesPerBrowser = 200;
options.BrowserLeaseTimeout = TimeSpan.FromSeconds(60);
});
// Or use a cloud preset for Docker, Azure, AWS, etc.
builder.Services.AddCobaltPdf(CloudEnvironment.ConfigureForDocker);
// Static configuration for non-DI scenarios
CobaltEngine.Configure(options =>
{
options.MaxSize = 4;
options.ExtraArgs.Add("--no-sandbox");
});
// Shorthand: just set max pool size
CobaltEngine.Configure(maxSize: 4);
Cloud presets:
ConfigureForDocker,
ConfigureForAzure,
ConfigureForAwsEcs,
ConfigureForLinux, and
ConfigureForLowMemory
each set sensible pool sizes and Chromium flags for the target environment.
Integration
ASP.NET Core dependency injection
Register CobaltPDF as a singleton service in your ASP.NET Core app.
The engine is injected into controllers, Razor Pages, or minimal API handlers
and shared across all requests.
// Program.cs — register CobaltPDF
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCobaltPdf(options =>
{
options.MaxSize = 4;
CloudEnvironment.ConfigureForDocker(options);
});
var app = builder.Build();
// Pre-warm the browser pool at startup
await app.Services
.GetRequiredService<CobaltEngine>()
.PreWarmAsync();
// Inject into a controller or Razor Page
public class ReportController : ControllerBase
{
private readonly CobaltEngine _renderer;
public ReportController(CobaltEngine renderer)
=> _renderer = renderer;
[HttpGet("export")]
public async Task<IActionResult> Export(CancellationToken ct)
{
var pdf = await _renderer
.WithPaperFormat("A4")
.WithPrintBackground()
.RenderUrlAsPdfAsync("https://app.example.com/report", ct);
return File(pdf.BinaryData, "application/pdf", "report.pdf");
}
}
Why pre-warm? Calling PreWarmAsync() at startup launches
the browser pool eagerly. Without it, the first render request pays a 1–2 second cold-start
cost while Chromium spins up.
Integration
CobaltPDF.Requests — microservice mode
What it does: CobaltPDF.Requests is a tiny (~50 KB) NuGet package with
no browser or Chromium dependency. Add it to any app, describe the PDF you want, and POST it
to your CobaltPDF rendering service — all the heavy rendering stays on the server. A request is just JSON,
so clients written in any language can call the service too.
Install from NuGet → dotnet add package CobaltPDF.Requests
# Client app (web API, mobile backend, other service)
dotnet add package CobaltPDF.Requests
# Rendering service (CobaltPDF.Requests is included automatically)
dotnet add package CobaltPDF
using CobaltPdf.Requests;
using System.Net.Http.Json;
// Fluent builder — the recommended way to describe a PDF
var request = PdfRequest.ForUrl("https://myapp.com/invoice/42")
.WithPaperFormat("A4")
.WithHeader("<div style='font-size:10px;text-align:center;'>My Company</div>")
.WithFooter("Page <span class='pageNumber'></span> of <span class='totalPages'></span>")
.WithMetadata(m => { m.Title = "Invoice #42"; m.Author = "Billing System"; })
.AddCookie("session", "abc123", "myapp.com")
.Build();
var httpResponse = await httpClient
.PostAsJsonAsync("https://pdf-service/api/pdf", request);
httpResponse.EnsureSuccessStatusCode();
byte[] pdfBytes = await httpResponse.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("invoice.pdf", pdfBytes);
// Prefer the fluent builder above — this maps to exactly the same request
var request = new PdfRequest
{
Url = "https://myapp.com/invoice/42",
PaperFormat = "A4",
Metadata = new() { Title = "Invoice #42", Author = "Billing System" },
Cookies = [new() { Name = "session", Value = "abc123", Domain = "myapp.com" }]
};
using CobaltPdf;
using CobaltPdf.Requests;
builder.Services.AddCobaltPdf(o =>
CloudEnvironment.ConfigureForDocker(o));
var app = builder.Build();
await CobaltEngine.PreWarmAsync();
// ExecuteAsync maps every PdfRequest property to the fluent API
app.MapPost("/api/pdf", async (
PdfRequest request,
CobaltEngine renderer,
CancellationToken ct) =>
{
var pdf = await request.ExecuteAsync(renderer, ct);
return Results.File(pdf.BinaryData, "application/pdf", "output.pdf");
});
// JavaScript — build the request as JSON and POST it
const request = { url: "https://myapp.com/invoice/42", paperFormat: "A4" };
const res = await fetch("https://pdf-service/api/pdf", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request)
});
const pdf = await res.blob(); // the finished PDF
# Python — same JSON, any HTTP client works
import requests
payload = { "url": "https://myapp.com/invoice/42", "paperFormat": "A4" }
res = requests.post("https://pdf-service/api/pdf", json=payload)
open("invoice.pdf", "wb").write(res.content)
Full guide: See the
CobaltPDF.Requests documentation
for Azure Functions, AWS Lambda, ECS/Fargate deployment examples,
the full JSON schema, and security considerations.
Optional
Logging
CobaltPDF can capture browser console messages and internal warnings. Route them to the
console, a log file with daily rotation, or a custom callback for integration with your
existing logging pipeline.
// Write browser console output to the application console
CobaltEngine.LoggingMode = BrowserLoggingMode.Console;
// Write to a log file with daily rotation
CobaltEngine.LoggingMode = BrowserLoggingMode.File;
CobaltEngine.BrowserLogPath = "logs/browser.log";
CobaltEngine.RetainedLogCount = 7; // keep 7 days of rotated files
// Or both console and file at once
CobaltEngine.LoggingMode = BrowserLoggingMode.Both;
// Custom callback — integrate with ILogger or any logging framework
CobaltEngine.OnBrowserLog = message =>
logger.LogDebug("[Browser] {Message}", message);
Note: BrowserLoggingMode is a [Flags] enum.
File logging uses daily rotation — files are named browser_2025-03-01.log — and
old files beyond RetainedLogCount are automatically deleted.
The OnBrowserLog callback fires before console or file output, so you can use it
alongside the built-in modes.
Resources
GitHub Examples
Browse complete, ready-to-run projects for every feature on this page — from basic
URL-to-PDF conversion to ASP.NET Core DI, microservice mode, and everything in between.
CobaltPDF/CobaltPDF-Examples