NuGet version Downloads Build status
⭐ For Gotenberg v7 & v8 ⭐
.NET C# Client for interacting with the Gotenberg v7 & v8 micro-service's API. Gotenberg is a Docker-powered stateless API for converting & merging HTML, Markdown and Office documents to PDF. The client supports a configurable Polly retry policy with exponential backoff for handling transient exceptions.
Pull the image from dockerhub.com
> docker pull gotenberg/gotenberg:latest
Create & start a container
docker run --name gotenbee8x --rm -p 3000:3000 gotenberg/gotenberg:latest gotenberg --api-timeout=1800s --log-level=debug
For local development with basic authentication enabled, use the provided docker-compose file:
docker-compose -f docker/docker-compose-basic-auth.yml up -d
Pre-configured with test credentials:
- Username:
testuser - Password:
testpass
Install nuget package into your project
PM> Install-Package Gotenberg.Sharp.Api.Client
Note: Use v1.x nugets for Gotenberg v6.
All public APIs include comprehensive XML documentation with clear descriptions, parameter details, and links to official Gotenberg documentation. IntelliSense provides:
- Method descriptions with Gotenberg route documentation links
- Parameter explanations and valid value ranges
- Exception documentation for error handling
- Usage notes and best practices
"GotenbergSharpClient": { "ServiceUrl": "http://localhost:3000", "HealthCheckUrl": "http://localhost:3000/health", "RetryPolicy": { "Enabled": true, "RetryCount": 4, "BackoffPower": 1.5, "LoggingEnabled": true } }
Gotenberg v8+ - If your Gotenberg instance is configured with basic authentication (using --api-enable-basic-auth), you can provide credentials in the settings:
"GotenbergSharpClient": { "ServiceUrl": "http://localhost:3000", "HealthCheckUrl": "http://localhost:3000/health", "BasicAuthUsername": "your-username", "BasicAuthPassword": "your-password", "RetryPolicy": { "Enabled": true, "RetryCount": 4, "BackoffPower": 1.5, "LoggingEnabled": true } }
public void ConfigureServices(IServiceCollection services) { ..... services.AddOptions<GotenbergSharpClientOptions>() .Bind(Configuration.GetSection(nameof(GotenbergSharpClient))); services.AddGotenbergSharpClient(); ..... }
public void ConfigureServices(IServiceCollection services) { ..... // Configure with an action services.AddOptions<GotenbergSharpClientOptions>() .Configure(options => { options.ServiceUrl = new Uri("http://localhost:3000"); options.TimeOut = TimeSpan.FromMinutes(5); options.BasicAuthUsername = "username"; options.BasicAuthPassword = "password"; // Configure retry policy options.RetryPolicy = new RetryOptions { Enabled = true, RetryCount = 4, BackoffPower = 1.5, LoggingEnabled = true }; }); services.AddGotenbergSharpClient(); ..... }
public void ConfigureServices(IServiceCollection services) { ..... services.AddOptions<GotenbergSharpClientOptions>() .Bind(Configuration.GetSection(nameof(GotenbergSharpClient))) .PostConfigure(options => { // Override or add settings programmatically (runs after binding) options.TimeOut = TimeSpan.FromMinutes(10); // Override timeout options.BasicAuthUsername = Environment.GetEnvironmentVariable("GOTENBERG_USER"); options.BasicAuthPassword = Environment.GetEnvironmentVariable("GOTENBERG_PASS"); }); services.AddGotenbergSharpClient(); ..... }
See the examples folder for complete working examples as console applications.
using Gotenberg.Sharp.API.Client; using Gotenberg.Sharp.API.Client.Domain.Builders; using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted; using Gotenberg.Sharp.API.Client.Domain.Requests.Facets; // For Cookie, etc.
With embedded assets:
[HttpGet] public async Task<ActionResult> HtmlToPdf([FromServices] GotenbergSharpClient sharpClient) { var builder = new HtmlRequestBuilder() .AddDocument(doc => doc.SetBody(GetBody()).SetFooter(GetFooter()) ).WithPageProperties(pp => { pp.SetPaperSize(PaperSizes.A3) .SetMargins(Margins.None) .SetScale(.99); }).WithAsyncAssets(async assets => assets.AddItem("some-image.jpg", await GetImageBytes())); var req = await builder.BuildAsync(); var result = await sharpClient.HtmlToPdfAsync(req); return this.File(result, "application/pdf", "gotenbergFromHtml.pdf"); }
URL to PDF with custom page range, header & footer:
public async Task<Stream> CreateFromUrl(string headerPath, string footerPath) { var builder = new UrlRequestBuilder() .SetUrl("https://www.cnn.com") .ConfigureRequest(config => { config.SetPageRanges("1-2"); }) .AddAsyncHeaderFooter(async doc => doc.SetHeader(await File.ReadAllTextAsync(headerPath)) .SetFooter(await File.ReadAllBytesAsync(footerPath) )).WithPageProperties(pp => { pp.SetPaperSize(PaperSizes.A4) .SetMargins(Margins.None) .SetScale(.90) .SetLandscape(); }); var request = await builder.BuildAsync(); return await _sharpClient.UrlToPdfAsync(request); }
Merges office documents:
public async Task<Stream> DoOfficeMerge(string sourceDirectory) { var builder = new MergeOfficeBuilder() .WithAsyncAssets(async a => a.AddItems(await GetDocsAsync(sourceDirectory))) .SetPdfFormat(LibrePdfFormats.A2b); var request = await builder.BuildAsync(); return await _sharpClient.MergeOfficeDocsAsync(request); }
Markdown to PDF conversion with embedded assets:
public async Task<Stream> CreateFromMarkdown() { var builder = new HtmlRequestBuilder() .AddAsyncDocument(async doc => doc.SetHeader(await this.GetHeaderAsync()) .SetBody(await GetBodyAsync()) .SetContainsMarkdown() .SetFooter(await GetFooterAsync()) ).WithPageProperties(pp => { pp.UseChromeDefaults().SetLandscape().SetScale(.90); }).WithAsyncAssets(async a => a.AddItems(await GetMarkdownAssets()) ); var request = await builder.BuildAsync(); return await _sharpClient.HtmlToPdfAsync(request); }
Add cookies to the Chromium cookie jar for authenticated requests (v2.8.1+):
public async Task<Stream> CreatePdfWithCookies() { var builder = new UrlRequestBuilder() .SetUrl("https://example.com/protected") .SetConversionBehaviors(b => { b.AddCookie(new Cookie { Name = "session_token", Value = "abc123xyz", Domain = "example.com", Path = "/", Secure = true, HttpOnly = true, SameSite = "Lax" }); }) .WithPageProperties(pp => pp.UseChromeDefaults()); var request = await builder.BuildAsync(); return await _sharpClient.UrlToPdfAsync(request); }
Add custom metadata to your PDFs (v2.6+):
public async Task<Stream> CreatePdfWithMetadata() { var builder = new HtmlRequestBuilder() .AddDocument(doc => doc.SetBody("<html><body><h1>Document with Metadata</h1></body></html>")) .SetConversionBehaviors(b => { b.SetMetadata(new Dictionary<string, object> { { "Author", "John Doe" }, { "Title", "My Document" }, { "Subject", "Important Report" }, { "Keywords", "report, PDF, gotenberg" } }); }) .WithPageProperties(pp => pp.UseChromeDefaults()); var request = await builder.BuildAsync(); return await _sharpClient.HtmlToPdfAsync(request); }
Convert PDFs to PDF/A formats (v2.8+):
public async Task<Stream> ConvertToPdfA(string pdfPath) { var builder = new PdfConversionBuilder() .WithPdfs(b => b.AddItem("document.pdf", File.ReadAllBytes(pdfPath))) .SetPdfFormat(LibrePdfFormats.A2b); var request = await builder.BuildAsync(); return await _sharpClient.ConvertPdfDocumentsAsync(request); }
Generate a single-page PDF from multi-page content (v2.8.1+):
public async Task<Stream> CreateSinglePagePdf() { var builder = new UrlRequestBuilder() .SetUrl("https://www.example.com") .WithPageProperties(pp => { pp.UseChromeDefaults() .SetSinglePage(true); }); var request = await builder.BuildAsync(); return await _sharpClient.UrlToPdfAsync(request); }
All request types support webhooks
public async Task SendUrlToWebhookEndpoint(string headerPath, string footerPath) { var builder = new UrlRequestBuilder() .SetUrl("https://www.cnn.com") .ConfigureRequest(reqBuilder => { reqBuilder.AddWebhook(hook => { hook.SetUrl("http://host.docker.internal:5000/api/your/webhookReceiver") .SetErrorUrl("http://host.docker.internal:5000/api/your/webhookReceiver/error") .AddExtraHeader("custom-header", "value"); }) .SetPageRanges("1-2"); }) .AddAsyncHeaderFooter(async b => b.SetHeader(await System.IO.File.ReadAllTextAsync(headerPath)) .SetFooter(await System.IO.File.ReadAllBytesAsync(footerPath)) ).WithPageProperties(pp => { pp.SetPaperSize(PaperSizes.A4) .SetMargins(Margins.None) .SetScale(.90) .SetLandscape(); }); var request = await builder.BuildAsync(); await _sharpClient.FireWebhookAndForgetAsync(request); }
Builds a 30 page PDF by merging the front two pages of 15 news sites. Takes about a minute to complete
public async Task<Stream> CreateWorldNewsSummary() { var sites = new[] { "https://www.nytimes.com", "https://www.axios.com/", "https://www.csmonitor.com", "https://www.wsj.com", "https://www.usatoday.com", "https://www.irishtimes.com", "https://www.lemonde.fr", "https://calgaryherald.com", "https://www.bbc.com/news/uk", "https://www.thehindu.com", "https://www.theaustralian.com.au", "https://www.welt.de", "https://www.cankaoxiaoxi.com", "https://www.novinky.cz", "https://www.elobservador.com.uy" } .Select(u => new Uri(u)); var builders = CreateBuilders(sites); var requests = builders.Select(b => b.Build()); return await ExecuteRequestsAndMerge(requests); } IEnumerable<UrlRequestBuilder> CreateBuilders(IEnumerable<Uri> uris) { foreach (var uri in uris) { yield return new UrlRequestBuilder() .SetUrl(uri) .ConfigureRequest(req => { req.SetPageRanges("1-2"); }) .AddHeaderFooter(docBuilder => { docBuilder.SetHeader(GetHeadFoot(uri.Host.Replace("www.", string.Empty).ToUpper())) .SetFooter(GetHeadFoot(uri.ToString())); }) .WithPageProperties(pp => { pp.UseChromeDefaults() .SetScale(.90) .SetLandscape() .SetMarginLeft(.5) .SetMarginRight(.5); }); } static string GetHeadFoot(string heading) => "<html><head> <style> body { font-size: 8rem; } h1 { margin-left: auto; margin-right: auto; } </style></head><body><h1>" + heading + "</h1></body></html>"; } async Task<Stream> ExecuteRequestsAndMerge(IEnumerable<UrlRequest> requests) { var tasks = requests.Select(r => _sharpClient.UrlToPdfAsync(r)); var results = await Task.WhenAll(tasks); var mergeBuilder = new MergeBuilder() .WithAssets(b => { b.AddItems(results.Select((r, i) => KeyValuePair.Create($"{i}.pdf", r))); }); var request = mergeBuilder.Build(); return await _sharpClient.MergePdfsAsync(request); }
Enable Universal Access for accessible PDFs from HTML (v2.4+):
public async Task<Stream> CreateAccessiblePdf() { var builder = new HtmlRequestBuilder() .AddDocument(doc => doc.SetBody("<html><body><h1>Accessible Document</h1></body></html>")) .SetConversionBehaviors(b => b.SetPdfUa(true)) .WithPageProperties(pp => pp.UseChromeDefaults()); var request = await builder.BuildAsync(); return await _sharpClient.HtmlToPdfAsync(request); }
Enable Universal Access when converting PDFs to PDF/A (v2.4+):
public async Task<Stream> ConvertToAccessiblePdfA(string pdfPath) { var builder = new PdfConversionBuilder() .WithPdfs(b => b.AddItem("document.pdf", File.ReadAllBytes(pdfPath))) .SetPdfFormat(LibrePdfFormats.A2b) .EnablePdfUa(true); var request = await builder.BuildAsync(); return await _sharpClient.ConvertPdfDocumentsAsync(request); }
Flatten PDF forms and annotations (v2.8+):
public async Task<Stream> FlattenPdf(string pdfPath) { var builder = new PdfConversionBuilder() .WithPdfs(b => b.AddItem("form.pdf", File.ReadAllBytes(pdfPath))) .EnableFlatten(true); var request = await builder.BuildAsync(); return await _sharpClient.ConvertPdfDocumentsAsync(request); }
Speed up conversions by skipping network idle wait (Gotenberg v8+ only):
public async Task<Stream> FastConversion() { var builder = new UrlRequestBuilder() .SetUrl("https://example.com") .SetConversionBehaviors(b => b.SkipNetworkIdleEvent()) .WithPageProperties(pp => pp.UseChromeDefaults()); var request = await builder.BuildAsync(); return await _sharpClient.UrlToPdfAsync(request); }
Fine-tune page dimensions and properties:
public async Task<Stream> CustomPageProperties() { var builder = new HtmlRequestBuilder() .AddDocument(doc => doc.SetBody("<html><body><h1>Custom Page</h1></body></html>")) .WithPageProperties(pp => { pp.SetPaperSize(PaperSizes.Letter) .SetMargins(Margins.Normal) .SetScale(0.95) .SetLandscape() .SetPrintBackground(true) .SetGenerateDocumentOutline(true) .SetOmitBackground(false); }); var request = await builder.BuildAsync(); return await _sharpClient.HtmlToPdfAsync(request); }