Getting Started
Set your licence key
Activate CobaltPDF by calling SetLicense once at application startup.
Without a valid licence the library runs in trial mode and watermarks output.
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 licence 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 Licence</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);
// Customise 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);
Security
Password protection & encryption
Protect PDFs with a password and control what recipients can do with the document.
Set separate owner and user passwords, and restrict printing, copying, or editing.
// 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 organisation.
// 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.
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
Deploy CobaltPDF as a standalone PDF rendering service and call it from any application.
Install the lightweight CobaltPDF.Requests package (~50 KB) in your client —
no Chromium or Playwright dependency required.
# 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;
var request = new PdfRequest
{
Url = "https://myapp.com/invoice/42",
PaperFormat = "A4",
Header = "<div style='font-size:10px;text-align:center;'>My Company</div>",
Footer = "<div style='font-size:10px;text-align:center;'>" +
"Page <span class='pageNumber'></span> of <span class='totalPages'></span></div>",
Metadata = new() { Title = "Invoice #42", Author = "Billing System" },
Cookies = [new() { Name = "session", Value = "abc123", Domain = "myapp.com" }]
};
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);
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");
});
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.