---
source: https://carbone.io/alternatives/gotenberg.html
title: "Gotenberg Alternative: Carbone vs Gotenberg (Benchmark, Features, Migration) | Carbone"
description: "Carbone vs Gotenberg: up to 47x more conversions per second in a public benchmark, JSON templating, 100+ output formats, API equivalents and a migration guide."
generated_at: "2026-10-21"
---

Gotenberg alternative

# Carbone vs Gotenberg

A factual comparison of two self-hosted document conversion APIs: public benchmark, features, API equivalents in cURL, Node.js and Python, and a step-by-step migration guide.

Last updated October 21, 2026

> **Carbone vs Gotenberg in 30 seconds**
> 
> -   **Same starting point**: both are Docker-ready HTTP APIs that drive Chromium and LibreOffice to turn HTML and Office files into PDF.
> -   **Speed**: in a [public, reproducible benchmark](https://github.com/carboneio/document-converter-benchmark) run on October 21, 2026, Carbone 5.16.0 served **47.0x more HTML to PDF conversions per second** than Gotenberg 8.38.1. For DOCX to PDF on one thread, Carbone served **7.8x more** with its built-in Carbone ICE converter, and 2.4x more when both use LibreOffice.
> -   **Scope**: Gotenberg is a PDF toolbox (URL to PDF, screenshots, merge, split, watermarks and more). Carbone is a document generation engine: it fills DOCX, XLSX, PPTX, ODT, HTML or Markdown templates with JSON data and outputs PDF or 100+ other formats.
> -   **Cost**: Gotenberg is free and MIT-licensed. The Carbone Community Edition is free to self-host; Enterprise features and the EU-hosted Cloud API are paid, with a free Cloud plan.
> -   **Migration**: the same Docker workflow, one JSON endpoint instead of one route per converter. Most calls migrate in minutes.

**47.0x** HTML to PDF throughput, 4 threads

**7.8x** DOCX to PDF throughput, 1 thread, Carbone ICE

**100+** output formats, not only PDF

**1** endpoint for every conversion

[Start for free](https://account.carbone.io/login?register=true) **[Run Carbone with Docker](/documentation/developer/self-hosted-deployment/deploy-with-docker.md)**

## Carbone or Gotenberg: which one should you choose?

Gotenberg and Carbone overlap on HTML and Office to PDF conversion, but they are built for different jobs. Pick the one that matches what you actually need to produce.

### Choose Gotenberg if...

-   You only need **HTML or Office to PDF** and you already render the HTML yourself.
-   You need **URL to PDF** or **page screenshots** out of the box.
-   Your HTML needs **JavaScript to render** (client-side charts, single-page apps): Carbone disables JavaScript in Chrome for security, so it only converts HTML that is already rendered.
-   You need a **toolbox for existing PDFs**: merge, split, rotate, flatten, stamp, bookmarks, Factur-X.
-   You require an **MIT license** and your volumes are modest.

### Choose Carbone if...

-   You need **throughput**: parallel Chromium, LibreOffice and Carbone ICE workers, 47.0x more HTML to PDF conversions per second in the benchmark.
-   You generate documents **from data**: a JSON payload and a DOCX, XLSX, PPTX, ODT, HTML or Markdown template, merged and converted in the same call.
-   You need formats **beyond PDF**: DOCX, XLSX, ODT, CSV, Markdown, PNG, WebP and 100+ more.
-   You want production features built in: converter hardening, OnlyOffice engine, template storage, a visual Studio, HA cluster, or a **managed EU Cloud API**.

## Carbone vs Gotenberg benchmark

Carbone published an open-source benchmark of free document converters, with the scripts, sample files and Docker commands needed to reproduce it: [carboneio/document-converter-benchmark](https://github.com/carboneio/document-converter-benchmark). Here are the Carbone and Gotenberg results, in requests per second (higher is better).

| Tool | Conversion | Threads | Avg latency | Requests per second |
| --- | --- | --- | --- | --- |
| Carbone | HTML to PDF (Chromium) | 4 | 7.74 ms | 626.12 |
| Carbone | DOCX to PDF (Carbone ICE) | 4 | 16.96 ms | 280.52 |
| Carbone | HTML to PDF (Chromium) | 1 | 18.03 ms | 272.48 |
| Carbone | DOCX to PDF (LibreOffice) | 4 | 49.07 ms | 99.87 |
| Carbone | DOCX to PDF (Carbone ICE) | 1 | 50.76 ms | 96.48 |
| Carbone | DOCX to PDF (LibreOffice) | 1 | 169.21 ms | 29.31 |
| Gotenberg | HTML to PDF (Chromium) | 4 | 374.74 ms | 13.32 |
| Gotenberg | DOCX to PDF (LibreOffice) | 1 | 400.58 ms | 12.44 |
| Gotenberg | HTML to PDF (Chromium) | 1 | 478.42 ms | 10.41 |

-   **HTML to PDF**: with 4 threads, Carbone answers in 7.74 ms on average against 374.74 ms for Gotenberg, which is **47.0x more requests per second**. With a single thread the gap is 26.2x.
-   **DOCX to PDF with LibreOffice on both sides**: on one thread, Carbone serves **2.4x more requests per second** (29.31 against 12.44). Gotenberg runs a single LibreOffice process per container (its documentation recommends adding containers to scale), while one Carbone container with 4 threads reaches 99.87 requests per second, 8.0x a Gotenberg container.
-   **DOCX to PDF with Carbone ICE**: the converter built into Carbone, with no office suite involved, serves 96.48 requests per second on one thread (**7.8x Gotenberg**) and 280.52 with 4 threads.
-   Carbone does slightly more work than a pure converter: it also scans every document for Carbone tags before converting it.

> **Methodology**
> 
> Benchmark run on October 21, 2026: MacBook Pro M4 Max, Docker Desktop with 14 CPUs and 8 GB, k6 load tests with 5 virtual users for 60 seconds per configuration, after 10 verified warmup conversions, with the same `sample.html` and `sample.docx` for every tool. Images: `carbone/carbone-ee:full-5.16.0` (free Community Edition, `webserver -s -f 1` or `-f 4`, DOCX converted with `"converter": "L"` for LibreOffice and `"I"` for Carbone ICE) and `gotenberg/gotenberg:8.38.1` (`--chromium-max-concurrency=1` or `4`, `--chromium-auto-start=true`, and engine restarts disabled with `--chromium-restart-after=0 --libreoffice-restart-after=0` to measure raw conversion speed). Results depend on your hardware and documents: clone the repository and run `node bench.mjs run --only carbone,gotenberg` on your own files.

## Feature comparison

What each tool supports out of the box, based on the Carbone 5 and Gotenberg 8 documentation.

| Feature | Carbone | Gotenberg |
| --- | --- | --- |
| **Conversion** |
| HTML to PDF | Yes: Chromium | Yes: Chromium |
| URL to PDF | No: send the HTML content instead | Yes |
| Word, Excel, PowerPoint, ODT to PDF | Yes: LibreOffice, OnlyOffice or Carbone ICE | Yes: LibreOffice |
| Markdown to PDF | Yes: send the `.md` file as is | Partial: Markdown wrapped in an HTML page |
| Output formats | Yes: 100+ (PDF, DOCX, XLSX, PPTX, ODT, CSV, HTML, MD, PNG, JPG, WebP...) | Partial: PDF, plus PNG, JPEG and WebP screenshots |
| PDF to DOCX, PDF to image, Office to Office | Yes: PDF to DOCX with OnlyOffice, PDF to ODT, PNG or JPG with LibreOffice | No |
| Screenshots | Partial: first page of an HTML, Office or PDF file as PNG, JPG or WebP, rendered by LibreOffice, not Chromium | Yes: HTML, URL and Markdown, rendered by Chromium |
| **PDF features** |
| PDF/A and PDF/UA | Yes: PDF/A-1 to PDF/A-4, PDF/UA | Yes: PDF/A-1b, 2b, 3b and PDF/UA |
| Password protection and permissions | Yes | Yes |
| Watermarks | Yes: up to 4 text watermarks | Yes: text, image or PDF watermarks and stamps |
| Headers and footers with page numbers | Yes | Yes |
| Merge PDFs | Partial: append PDFs to a generated document (`:appendFile`, Enterprise) or merge a batch | Yes: dedicated route |
| Embedded attachments, Factur-X and ZUGFeRD | Yes: `:attachFile` (Enterprise) | Yes |
| Split, rotate, flatten, optimize, metadata, bookmarks | No | Yes |
| **Templating** |
| Inject JSON data into documents | Yes: DOCX, XLSX, PPTX, ODT, HTML, Markdown, XML... | No: render the HTML yourself |
| Loops, conditions, formatters, translations | Yes | No |
| Charts, barcodes, dynamic images | Yes: Enterprise | No |
| Template storage, versioning and visual Studio | Yes | No |
| **AI assistants** |
| MCP server | Yes: [official](/documentation/developer/ai/mcp.md), hosted at `mcp.carbone.io` for Claude, ChatGPT, Cursor, VS Code and more, or self-hosted next to Carbone | No: community projects only |
| Skill to write templates with AI | Yes: [official Carbone Skill](/documentation/developer/ai/skills.md) (Agent Skills standard) for Claude, ChatGPT, Cursor, Copilot, Gemini and more | No |
| Plugin in AI marketplaces | Yes: MCP server and Skill in one plugin, on the Claude, ChatGPT, Azure and Mistral marketplaces | No |
| **Operations** |
| Parallel conversions | Yes: N LibreOffice, Chromium or ICE workers per server | Partial: parallel Chromium conversions (6 by default), one LibreOffice process per container |
| Timeouts, crash recovery, automatic restarts | Yes | Yes |
| Asynchronous webhooks | Yes | Yes |
| Fetch inputs from URLs or cloud storage | Partial: images and appended PDFs from URLs | Yes: URLs, S3, MinIO, GCS |
| Metrics and health check | Yes: OpenMetrics (Prometheus) | Yes: OpenTelemetry |
| Stateless horizontal scaling | Yes: plus a synchronized HA cluster (Enterprise) | Yes |
| **Security** |
| Converter hardening (macros, scripts, external links) | Yes: ANSSI and BSI guidelines | Partial: outbound URL filtering |
| API authentication | Yes: JWT tokens | Yes: basic auth or OIDC |
| **Deployment and licensing** |
| Self-hosted Docker image | Yes: `carbone/carbone-ee` | Yes: `gotenberg/gotenberg` |
| Managed Cloud API | Yes: EU-hosted, free plan | No |
| License | Community Edition free, Enterprise paid | MIT |
| Official SDKs | Yes: Node.js, Python, PHP, Go, Java, Rust, JavaScript | Partial: official PHP client, community clients |
| Commercial support and SLA | Yes | No |

## API equivalents: Gotenberg to Carbone

Both tools run as a local Docker container. The examples below target self-hosted instances: Gotenberg on port 3000 with multipart forms, Carbone on port 4000 with a JSON body where the file is encoded in base64.

> **Using the Carbone Cloud API instead**
> 
> Replace `http://localhost:4000` with `https://api.carbone.io` and add two headers: `Authorization: Bearer YOUR_API_KEY` and `carbone-version: 5`. Everything else stays the same.

### HTML to PDF

Gotenberg expects a file named `index.html`. Carbone accepts any file name and uses its Chromium engine when `converter` is `"C"`. Send a complete page with its `<html>` element: Carbone detects the file type from its content, and reads a fragment like `<h1>Hello</h1>` as XML.

> **Uploading CSS, images or fonts next to index.html?**
> 
> Gotenberg resolves relative paths against the files uploaded in the same form. Carbone receives a single file: inline your CSS in a `<style>` tag, embed images as data URIs, or use absolute URLs reachable from the Carbone server. Carbone downloads these resources itself, and by default refuses addresses of a local network (`localhost`, `10.x`, `192.168.x`...): remove `1024` from `CARBONE_SECURITY_LEVEL` to allow them.

**Gotenberg (cURL)**

```bash
curl --request POST http://localhost:3000/forms/chromium/convert/html \
  --form files=@index.html \
  --output result.pdf
```

**Carbone (cURL)**

```bash
TEMPLATE=$(base64 < index.html | tr -d '\n')
curl --request POST 'http://localhost:4000/render/template?download=true' \
  --header 'Content-Type: application/json' \
  --data '{ "template": "'"$TEMPLATE"'", "convertTo": "pdf", "converter": "C" }' \
  --output result.pdf
```

**Gotenberg (Node.js)**

```js
import { readFile, writeFile } from 'node:fs/promises';

const form = new FormData();
form.append('files', new Blob([await readFile('index.html')]), 'index.html');

const res = await fetch('http://localhost:3000/forms/chromium/convert/html', {
  method: 'POST',
  body: form
});
await writeFile('result.pdf', Buffer.from(await res.arrayBuffer()));
```

**Carbone (Node.js)**

```js
import { readFile, writeFile } from 'node:fs/promises';

const res = await fetch('http://localhost:4000/render/template?download=true', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    template: (await readFile('index.html')).toString('base64'),
    convertTo: 'pdf',
    converter: 'C'
  })
});
await writeFile('result.pdf', Buffer.from(await res.arrayBuffer()));
```

**Gotenberg (Python)**

```python
import requests

with open("index.html", "rb") as f:
    res = requests.post(
        "http://localhost:3000/forms/chromium/convert/html",
        files={"files": ("index.html", f, "text/html")},
    )
res.raise_for_status()
open("result.pdf", "wb").write(res.content)
```

**Carbone (Python)**

```python
import base64, requests

with open("index.html", "rb") as f:
    template = base64.b64encode(f.read()).decode()

res = requests.post(
    "http://localhost:4000/render/template?download=true",
    json={"template": template, "convertTo": "pdf", "converter": "C"},
)
res.raise_for_status()
open("result.pdf", "wb").write(res.content)
```

### Word, Excel or PowerPoint to PDF

The same request works for DOCX, XLSX, PPTX, ODT, ODS, ODP and RTF. With Carbone, `"converter": "L"` uses LibreOffice; `"O"` switches to OnlyOffice and `"I"` to Carbone ICE, the fastest engine for DOCX and ODT.

**Gotenberg (cURL)**

```bash
curl --request POST http://localhost:3000/forms/libreoffice/convert \
  --form files=@document.docx \
  --output result.pdf
```

**Carbone (cURL)**

```bash
TEMPLATE=$(base64 < document.docx | tr -d '\n')
curl --request POST 'http://localhost:4000/render/template?download=true' \
  --header 'Content-Type: application/json' \
  --data '{ "template": "'"$TEMPLATE"'", "convertTo": "pdf", "converter": "L" }' \
  --output result.pdf
```

**Gotenberg (Node.js)**

```js
import { readFile, writeFile } from 'node:fs/promises';

const form = new FormData();
form.append('files', new Blob([await readFile('document.docx')]), 'document.docx');

const res = await fetch('http://localhost:3000/forms/libreoffice/convert', {
  method: 'POST',
  body: form
});
await writeFile('result.pdf', Buffer.from(await res.arrayBuffer()));
```

**Carbone (Node.js)**

```js
import { readFile, writeFile } from 'node:fs/promises';

const res = await fetch('http://localhost:4000/render/template?download=true', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    template: (await readFile('document.docx')).toString('base64'),
    convertTo: 'pdf',
    converter: 'L'
  })
});
await writeFile('result.pdf', Buffer.from(await res.arrayBuffer()));
```

**Gotenberg (Python)**

```python
import requests

with open("document.docx", "rb") as f:
    res = requests.post(
        "http://localhost:3000/forms/libreoffice/convert",
        files={"files": ("document.docx", f)},
    )
res.raise_for_status()
open("result.pdf", "wb").write(res.content)
```

**Carbone (Python)**

```python
import base64, requests

with open("document.docx", "rb") as f:
    template = base64.b64encode(f.read()).decode()

res = requests.post(
    "http://localhost:4000/render/template?download=true",
    json={"template": template, "convertTo": "pdf", "converter": "L"},
)
res.raise_for_status()
open("result.pdf", "wb").write(res.content)
```

### Markdown to PDF

Gotenberg renders Markdown through an `index.html` wrapper that calls `{{ toHTML "document.md" }}`. Carbone converts the Markdown file directly, and the same call can output DOCX or HTML instead of PDF.

**Gotenberg (cURL)**

```bash
# index.html must contain {{ toHTML "document.md" }}
curl --request POST http://localhost:3000/forms/chromium/convert/markdown \
  --form files=@index.html \
  --form files=@document.md \
  --output result.pdf
```

**Carbone (cURL)**

```bash
TEMPLATE=$(base64 < document.md | tr -d '\n')
curl --request POST 'http://localhost:4000/render/template?download=true' \
  --header 'Content-Type: application/json' \
  --data '{ "template": "'"$TEMPLATE"'", "convertTo": "pdf", "converter": "L" }' \
  --output result.pdf
```

**Gotenberg (Node.js)**

```js
import { readFile, writeFile } from 'node:fs/promises';

// index.html must contain {{ toHTML "document.md" }}
const form = new FormData();
form.append('files', new Blob([await readFile('index.html')]), 'index.html');
form.append('files', new Blob([await readFile('document.md')]), 'document.md');

const res = await fetch('http://localhost:3000/forms/chromium/convert/markdown', {
  method: 'POST',
  body: form
});
await writeFile('result.pdf', Buffer.from(await res.arrayBuffer()));
```

**Carbone (Node.js)**

```js
import { readFile, writeFile } from 'node:fs/promises';

const res = await fetch('http://localhost:4000/render/template?download=true', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    template: (await readFile('document.md')).toString('base64'),
    convertTo: 'pdf',
    converter: 'L'
  })
});
await writeFile('result.pdf', Buffer.from(await res.arrayBuffer()));
```

**Gotenberg (Python)**

```python
import requests

# index.html must contain {{ toHTML "document.md" }}
with open("index.html", "rb") as html, open("document.md", "rb") as md:
    res = requests.post(
        "http://localhost:3000/forms/chromium/convert/markdown",
        files=[("files", ("index.html", html)), ("files", ("document.md", md))],
    )
res.raise_for_status()
open("result.pdf", "wb").write(res.content)
```

**Carbone (Python)**

```python
import base64, requests

with open("document.md", "rb") as f:
    template = base64.b64encode(f.read()).decode()

res = requests.post(
    "http://localhost:4000/render/template?download=true",
    json={"template": template, "convertTo": "pdf", "converter": "L"},
)
res.raise_for_status()
open("result.pdf", "wb").write(res.content)
```

### Paper size, margins, headers and footers

Gotenberg reads page settings from form fields and separate `header.html` and `footer.html` files. Carbone reads them from custom elements placed inside the HTML itself, so the request body doesn't change.

**Gotenberg**

```bash
curl --request POST http://localhost:3000/forms/chromium/convert/html \
  --form files=@index.html \
  --form files=@header.html \
  --form files=@footer.html \
  --form paperWidth=8.27 \
  --form paperHeight=11.7 \
  --form marginTop=1 \
  --form marginBottom=1 \
  --form landscape=true \
  --form printBackground=true \
  --output result.pdf
```

**Carbone**

```html
<!-- index.html, sent with the same request as above ("converter": "C") -->
<!DOCTYPE html>
<html>
<body>
  <carbone-pdf-options paper-size="A4" landscape="true"
                       margin-top="1" margin-bottom="1" />

  <carbone-pdf-header>
    <div style="font-size: 10px; width: 100%; text-align: center;">ACME Corp.</div>
  </carbone-pdf-header>

  <carbone-pdf-footer>
    <div style="font-size: 10px; width: 100%; text-align: center;">
      Page <span class="pageNumber"></span> of <span class="totalPages"></span>
    </div>
  </carbone-pdf-footer>

  <h1>Quarterly report</h1>
</body>
</html>
```

All the options are listed in the [HTML template documentation](/documentation/design/template-formats/html.md#pdf-conversion-options).

### Password, watermark and PDF/UA

Both tools apply PDF options in the conversion request. Gotenberg reads them from form fields, Carbone from `convertTo.formatOptions`.

**Gotenberg**

```bash
curl --request POST http://localhost:3000/forms/libreoffice/convert \
  --form files=@document.docx \
  --form pdfua=true \
  --form userPassword=s3cr3t \
  --form watermarkSource=text \
  --form watermarkExpression=CONFIDENTIAL \
  --output protected.pdf
```

**Carbone**

```bash
TEMPLATE=$(base64 < document.docx | tr -d '\n')
curl --request POST 'http://localhost:4000/render/template?download=true' \
  --header 'Content-Type: application/json' \
  --data '{
    "template": "'"$TEMPLATE"'",
    "converter": "L",
    "convertTo": {
      "formatName": "pdf",
      "formatOptions": {
        "PDFUACompliance": true,
        "EncryptFile": true,
        "DocumentOpenPassword": "s3cr3t",
        "Watermark": "CONFIDENTIAL"
      }
    }
  }' \
  --output protected.pdf
```

> **Archiving with PDF/A**
> 
> Replace the password with `"SelectPdfVersion": 2` (Gotenberg: `pdfa=PDF/A-2b`). The PDF/A standard forbids encryption, so keep archival and protected copies as separate files.

See all [PDF options](/documentation/developer/http-api/generate-reports.md#pdf-options) and [watermark options](/documentation/developer/http-api/generate-reports.md#watermark-options).

### Merge PDFs

Gotenberg has a dedicated merge route. Carbone merges PDFs while generating a document: the `:appendFile` formatter appends PDF files, given as URLs or base64 data URIs, at the end of the rendered template. This is an Enterprise feature, included in every Carbone Cloud plan.

**Gotenberg**

```bash
curl --request POST http://localhost:3000/forms/pdfengines/merge \
  --form files=@cover.pdf \
  --form files=@annex-1.pdf \
  --form files=@annex-2.pdf \
  --output merged.pdf
```

**Carbone**

```bash
# cover.html is a complete HTML page whose <body> contains:
#   <h1>{d.title}</h1>
#   {d.annexes[i].url:appendFile} {d.annexes[i+1].url}
TEMPLATE=$(base64 < cover.html | tr -d '\n')
curl --request POST 'http://localhost:4000/render/template?download=true' \
  --header 'Content-Type: application/json' \
  --data '{
    "template": "'"$TEMPLATE"'",
    "converter": "C",
    "convertTo": "pdf",
    "data": {
      "title": "Contract 2026-042",
      "annexes": [
        { "url": "https://example.com/annex-1.pdf" },
        { "url": "https://example.com/annex-2.pdf" }
      ]
    }
  }' \
  --output merged.pdf
```

To merge many documents generated from the same template, use [batch generation](/documentation/developer/http-api/generate-reports.md#batch-processing) with `"batchOutput": "pdf"`. It is included in Carbone Cloud. When self-hosted, it requires the Enterprise Edition and `CARBONE_NB_REPORT_MAX_PER_BATCH` set to the maximum number of documents per batch.

### Asynchronous conversion with a webhook

Both tools can answer immediately and call your server when the file is ready. Gotenberg replies `204 No Content`, then posts the PDF to your webhook. Carbone posts a JSON object with a `renderId`, and you download the file with `GET /render/{renderId}`. Webhooks are available in every edition, including the free Community Edition. When self-hosted, Carbone refuses webhook URLs on a local network by default (see `CARBONE_SECURITY_LEVEL`).

**Gotenberg**

```bash
curl --request POST http://localhost:3000/forms/libreoffice/convert \
  --header 'Gotenberg-Webhook-Url: https://my-app.com/webhook' \
  --header 'Gotenberg-Webhook-Events-Url: https://my-app.com/webhook/events' \
  --form files=@document.docx
```

**Carbone**

```bash
TEMPLATE=$(base64 < document.docx | tr -d '\n')
curl --request POST 'http://localhost:4000/render/template' \
  --header 'Content-Type: application/json' \
  --header 'carbone-webhook-url: https://my-app.com/webhook' \
  --header 'carbone-webhook-header-authorization: my-secret' \
  --data '{ "template": "'"$TEMPLATE"'", "convertTo": "pdf" }'
```

### URL to PDF

Gotenberg can open a URL in Chromium and print it. Carbone doesn't fetch web pages: download the HTML first, then convert it like any HTML file.

**Gotenberg**

```bash
curl --request POST http://localhost:3000/forms/chromium/convert/url \
  --form url=https://example.com/invoice/42 \
  --output result.pdf
```

**Carbone**

```bash
curl --silent https://example.com/invoice/42 --output page.html
TEMPLATE=$(base64 < page.html | tr -d '\n')
curl --request POST 'http://localhost:4000/render/template?download=true' \
  --header 'Content-Type: application/json' \
  --data '{ "template": "'"$TEMPLATE"'", "convertTo": "pdf", "converter": "C" }' \
  --output result.pdf
```

> **Make assets self-contained**
> 
> Relative links to stylesheets, images and fonts can't be resolved once the HTML leaves its website. Use absolute URLs, or inline CSS and images as data URIs, before sending the page to Carbone.

### Beyond conversion: fill a template with JSON data

This is where Carbone and Gotenberg differ most. With Gotenberg, you render the HTML with your own template engine before converting it. Carbone does both in one call: add `{d.field}` tags to an HTML, DOCX, XLSX, PPTX or Markdown file, and send your JSON in `data`.

```bash
# invoice.html: <html><body><h1>Invoice {d.id}</h1> <p>Customer: {d.customer.name}</p></body></html>
TEMPLATE=$(base64 < invoice.html | tr -d '\n')
curl --request POST 'http://localhost:4000/render/template?download=true' \
  --header 'Content-Type: application/json' \
  --data '{
    "template": "'"$TEMPLATE"'",
    "converter": "C",
    "convertTo": "pdf",
    "data": { "id": "2026-042", "customer": { "name": "ACME Corp." } }
  }' \
  --output invoice.pdf
```

Loops, conditions, number and date formatters, translations, charts and barcodes are described in the [template design documentation](/documentation/design/overview/getting-started.md).

## How to migrate from Gotenberg to Carbone

Moving from Gotenberg to Carbone is mostly a matter of changing the URL and the request body. Run both side by side, compare the PDFs, then switch traffic.

### 1\. Start Carbone next to Gotenberg

The `full` image ships LibreOffice, OnlyOffice and Chromium. Without a license key, it runs the free Community Edition.

```bash
docker run --rm -p 4000:4000 -e CARBONE_FACTORIES=4 carbone/carbone-ee:full
```

> **Get the fonts right from day one**
> 
> Use the `full-fonts` image to include all Google Fonts, or [add your own fonts](/documentation/developer/on-premise-installation/container-customization.md) to the image, so PDFs render exactly like with your current setup.

### 2\. Replace each Gotenberg route with a single endpoint

Every conversion goes through `POST /render/template`. The input file and the `converter` field replace the route.

| Gotenberg route | Carbone equivalent |
| --- | --- |
| `POST /forms/chromium/convert/html` | HTML file, `"converter": "C"` |
| `POST /forms/chromium/convert/url` | Download the HTML first, then `"converter": "C"` |
| `POST /forms/chromium/convert/markdown` | Markdown file, `"converter": "L"`, no HTML wrapper |
| `POST /forms/chromium/screenshot/html` | HTML file, `"convertTo": "png"`, `"jpg"` or `"webp"`, without `converter`: LibreOffice renders the first page, not Chromium |
| `POST /forms/libreoffice/convert` | Office file, `"converter": "L"`, `"O"` (OnlyOffice) or `"I"` (Carbone ICE) |
| `POST /forms/pdfengines/convert` | PDF file, `"hardRefresh": true`, `SelectPdfVersion` in `formatOptions` |
| `POST /forms/pdfengines/encrypt` | `EncryptFile` and `DocumentOpenPassword` in `formatOptions` |
| `POST /forms/pdfengines/watermark` | `Watermark` or `Watermarks` in `formatOptions` (text only) |
| `POST /forms/pdfengines/merge` | `:appendFile` in a template, or `"batchOutput": "pdf"` |
| `POST /forms/pdfengines/embed`, `/forms/pdfengines/factur-x` | `:attachFile` in a template |
| `POST /forms/pdfengines/split`, `rotate`, `flatten`, `optimize`, `bookmarks` | No: equivalent: keep Gotenberg or a PDF library for these steps |
| `GET /health` | `GET /status` |

### 3\. Send the file as base64 JSON instead of multipart

Read the file, encode it in base64 and put it in the `template` field, as in the [API equivalents](#api-equivalents-gotenberg-to-carbone) above. For pure conversion, don't send a `data` field: Carbone then converts the file as is.

> **Large files**
> 
> Base64 makes the payload about 33% bigger. With cURL, write the JSON body to a file and send it with `--data @body.json` to avoid shell limits. If you convert the same file again and again, upload it once with `POST /template` and render it by ID.

### 4\. Map your options

| Gotenberg | Carbone | Where |
| --- | --- | --- |
| `paperWidth`, `paperHeight` (US Letter by default) | `paper-width`, `paper-height` (inches) or `paper-size="A4"` | `<carbone-pdf-options>` in the HTML |
| `marginTop`, `marginBottom`, `marginLeft`, `marginRight` | `margin-top`, `margin-bottom`, `margin-left`, `margin-right` | `<carbone-pdf-options>` |
| `landscape`, `scale`, `preferCssPageSize` | `landscape`, `scale`, `prefer-css-page-size` | `<carbone-pdf-options>` |
| `printBackground` | `print-background` | `<carbone-pdf-options>` |
| `nativePageRanges` | `page-ranges` | `<carbone-pdf-options>` |
| `header.html`, `footer.html` | `<carbone-pdf-header>`, `<carbone-pdf-footer>` with `pageNumber` and `totalPages` | Inside the HTML |
| `pdfa=PDF/A-2b`, `pdfua=true` | `"SelectPdfVersion": 2`, `"PDFUACompliance": true` | `convertTo.formatOptions` |
| `userPassword` | `"EncryptFile": true`, `"DocumentOpenPassword"` | `convertTo.formatOptions` |
| `watermarkSource=text`, `watermarkExpression` | `"Watermark"`, or `"Watermarks"` for size, color, opacity and angle | `convertTo.formatOptions` |
| `Gotenberg-Webhook-Url` | `carbone-webhook-url` | Request header |
| `Gotenberg-Webhook-Extra-Http-Headers` | `carbone-webhook-header-X`, one header per value | Request header |
| `Gotenberg-Output-Filename` | `"reportName"`, sent with a `data` field (`{}` for a pure conversion) | Request body |
| `--chromium-max-concurrency` | `CARBONE_FACTORIES` or `--factories` | Server |
| `--api-timeout` (30 seconds by default) | `CARBONE_MAX_GENERATION_TIME` (60 seconds by default) | Server |

> **Backgrounds are printed by default**
> 
> Gotenberg only prints CSS backgrounds when `printBackground=true`. Carbone prints them by default: add `print-background="false"` to `<carbone-pdf-options>` to keep the old behavior.

### 5\. Check which edition you need

> **Community Edition or Enterprise?**
> 
> HTML, Office and Markdown conversion, PDF options, asynchronous webhooks and templating with text data are free in the self-hosted Community Edition. `:appendFile`, batch generation, dynamic images, charts, barcodes and the HA cluster require an [Enterprise license](/pricing.md#compare-plans-on-premise) when self-hosted. Every Carbone Cloud plan, including the free one, includes them.

## Frequently asked questions

Is Carbone a drop-in replacement for Gotenberg?

For HTML, Office and Markdown to PDF, yes: Carbone runs as a Docker container like Gotenberg and uses Chromium and LibreOffice under the hood. The request format changes: one JSON endpoint (`POST /render/template`) with the file encoded in base64, instead of one multipart route per converter. Page options move into the HTML with `<carbone-pdf-options>`. URL to PDF, screenshots of URLs and the routes that edit existing PDFs (split, rotate, flatten, bookmarks) have no direct equivalent.

* * *

Is Carbone free like Gotenberg?

Gotenberg is free and MIT-licensed. The Carbone Community Edition is free to self-host and covers HTML, Office and Markdown conversion, PDF options, asynchronous webhooks and templating with text data. Enterprise features (appending PDFs, batch generation, charts, dynamic images, HA cluster) require a license when self-hosted, and every Carbone Cloud plan includes them, starting with a free plan.

* * *

Is Carbone faster than Gotenberg?

In the public benchmark published by Carbone (October 2026, Carbone 5.16.0 against Gotenberg 8.38.1, same files and hardware), Carbone served about 47 times more HTML to PDF conversions per second with 4 threads. For DOCX to PDF on one thread, Carbone served about 2.4 times more conversions per second when both tools use LibreOffice, and 7.8 times more with Carbone ICE, the converter built into Carbone. Gotenberg runs a single LibreOffice process per container, while Carbone runs several LibreOffice, Chromium or Carbone ICE workers in parallel. The scripts are open source, so you can reproduce the test on your own documents.

* * *

Can Carbone convert a URL to PDF?

No. Carbone converts the content you send, not web pages it fetches. Download the HTML of the page first, make its stylesheets and images absolute or inline, then convert it with the Chromium engine (`"converter": "C"`). If URL to PDF is your main need, Gotenberg handles it natively.

* * *

How do I merge PDFs with Carbone?

Use the `:appendFile` formatter in a template: each PDF given as a URL or a base64 data URI is appended to the generated document, for example a cover page followed by annexes. To combine many documents generated from the same template, use batch generation with `"batchOutput": "pdf"`. Both are available in Carbone Cloud and in the self-hosted Enterprise Edition.

* * *

Can I generate PDF/A, PDF/UA, password-protected or watermarked PDFs?

Yes. Set `SelectPdfVersion` (PDF/A-1 to PDF/A-4), `PDFUACompliance`, `EncryptFile` with `DocumentOpenPassword`, and `Watermark` (or up to 4 styled `Watermarks`) in `convertTo.formatOptions`, in the same request as the conversion. PDF/A forbids encryption, so generate archival and password-protected copies separately. Image and PDF watermarks, which Gotenberg supports, are not available in Carbone.

* * *

What does Carbone do that Gotenberg doesn't?

Carbone is also a document generation engine: it fills DOCX, XLSX, PPTX, ODT, HTML and Markdown templates with JSON data (loops, conditions, formatters, translations, charts) and outputs PDF or 100+ other formats such as DOCX, XLSX, CSV or PNG. It also offers OnlyOffice and Carbone ICE engines, template storage, a visual Studio, official SDKs and a managed EU-hosted Cloud API.

* * *

Can I run Carbone and Gotenberg side by side during a migration?

Yes. Carbone listens on port 4000 by default and Gotenberg on port 3000, so both containers can run on the same host. Send the same documents to both, compare the PDFs, then switch your traffic route by route.

* * *

Still have a question? Chat with us.

## Try Carbone on your own documents

Run the benchmark on your files, or convert your first document in a few minutes with the free Cloud plan or the Community Edition Docker image.

[Start for free](https://account.carbone.io/login?register=true) **[Deploy with Docker](/documentation/developer/self-hosted-deployment/deploy-with-docker.md)** **[Read the conversion API](/documentation/developer/http-api/convert-reports.md)**

Related pages: [Document Conversion API](/features/document-converter.md), [HTML templates](/documentation/design/template-formats/html.md), [benchmark source code](https://github.com/carboneio/document-converter-benchmark).

## Trusted by 800+ paid customers in 40+ countries
