⭐ 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.AddGotenbergSharpClient(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 RetryPolicyOptions
{
Enabled = true,
RetryCount = 4,
BackoffPower = 1.5,
LoggingEnabled = true
};
});
.....
}
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddOptions<GotenbergSharpClientOptions>()
.Bind(Configuration.GetSection(nameof(GotenbergSharpClient)));
// Override or add settings programmatically
services.AddGotenbergSharpClient(options =>
{
options.TimeOut = TimeSpan.FromMinutes(10); // Override timeout
options.BasicAuthUsername = Environment.GetEnvironmentVariable("GOTENBERG_USER");
options.BasicAuthPassword = Environment.GetEnvironmentVariable("GOTENBERG_PASS");
});
.....
}
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);
}