Merge PDFs
Merges multiple PDF files into a single PDF. Files are combined in alphanumeric order (numbers first, then alphabetical).
See the PDF Engines module configuration for engine selection flags.
Basics
curl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
-o my.pdf
- 200
- 400
- 503
Content-Disposition: attachment; filename={output-filename.pdf}
Content-Type: {content-type}
Content-Length: {content-length}
Gotenberg-Trace: {trace}
Body: {output-file}
Content-Type: text/plain; charset=UTF-8
Gotenberg-Trace: {trace}
Body: {error}
Content-Type: text/plain; charset=UTF-8
Gotenberg-Trace: {trace}
Body: {error}
Structure & Metadata
Metadata (PDF Engines)
Inject XMP metadata (Author, Title, Copyright, Keywords, etc.) into the PDF as a JSON object.
Not all tags are writable. Gotenberg uses ExifTool under the hood. See the XMP Tag Name documentation for valid keys. Writing metadata usually breaks PDF/A compliance.
Nonecurl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form 'metadata={"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreationDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"}' \
-o my.pdf
Bookmarks (PDF Engines)
NonefalsefalseUse autoIndexBookmarks to automatically extract existing bookmarks and offset their page numbers based on position in the merged document.
curl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form autoIndexBookmarks=true \
-o my.pdf
Use titleBookmarks to build a table of contents from the merged documents. Gotenberg adds one bookmark per input, labeled by its Title metadata (or its filename when the title is absent), pointing to that document's first page, with the document's own bookmarks nested beneath it.
curl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form titleBookmarks=true \
-o my.pdf
You can also provide custom bookmarks with the bookmarks form field. When provided as a list, it is applied directly to the final merged PDF. When provided as a map of filename to bookmarks, page indexes are shifted per file before merging.
Both options work together. When autoIndexBookmarks is enabled, Gotenberg respects your custom mappings for specific files while automatically recalculating indexes for the others.
curl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form autoIndexBookmarks=true \
--form 'bookmarks={"1_pdf.pdf":[{"title":"Introduction","page":1,"children":[]}],"2_pdf.pdf":[{"title":"Appendix","page":1,"children":[]}]}' \
-o my.pdf
Attachments (PDF Engines)
Attach external files directly inside the PDF container. Commonly used for e-invoicing standards like ZUGFeRD / Factur-X, which require a machine-readable XML invoice as an attachment.
Provide per-attachment metadata with embedsMetadata to satisfy PDF/A-3 and Factur-X requirements: each entry sets the embedded file stream's /Subtype, writes /AFRelationship on the file specification, and references the attachment from the Document Catalog's /AF array.
NoneNonecurl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form embeds=@factur-x.xml \
--form embeds=@logo.png \
--form embedsMetadata='{"factur-x.xml":{"mimeType":"text/xml","relationship":"Alternative"},"logo.png":{"mimeType":"image/png","relationship":"Supplement"}}' \
-o my.pdf
Factur-X (PDF Engines)
Produces a Factur-X / ZUGFeRD e-invoice in one request: upload the CII invoice XML with facturxXml and set facturxConformanceLevel. Both fields are required together. Gotenberg embeds the XML into the resulting PDF and never converts or merges it like other uploaded files. Requires a PDF engine that supports the feature (QPDF by default). See PDF Engines module configuration.
See the dedicated Factur-X route for the full behavior: canonical embedding, XMP metadata, and the PDF/A-3 requirement.
NoneINVOICE1.0Nonecurl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form facturxXml=@/path/to/factur-x.xml \
--form 'facturxConformanceLevel=EN 16931' \
-o my.pdf
Flatten (PDF Engines)
Merges all interactive form fields (text inputs, checkboxes, etc.) into the page content, making the PDF non-editable.
falsecurl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form flatten=true \
-o my.pdf
Watermark (PDF Engines)
Adds a watermark behind the content of each page during post-processing. Sources: text, image, or pdf.
image and pdf sources require an uploaded watermark file. watermarkExpression cannot reference an arbitrary filesystem path; the route returns 400 Bad Request.
Repeat these fields to apply several watermarks in one request. They are applied in order, and each image or pdf watermark consumes the uploaded watermark files in order.
The watermarkOptions form field accepts a JSON object whose keys depend on the configured PDF engine:
| Engine | Syntax Reference |
|---|---|
| pdfcpu (Default) | See pdfcpu watermark documentation |
| pdftk | See pdftk documentation |
Available keys include font, points (font size), color, rotation, opacity, scale, offset, and more. Example:
{
"font": "Helvetica",
"points": 48,
"color": "#808080",
"rotation": 45,
"opacity": 0.15
}
Check the PDF Engines module configuration to see which engine is active.
curl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form watermarkSource=text \
--form watermarkExpression=CONFIDENTIAL \
--form 'watermarkOptions={"opacity":0.25,"rotation":45}' \
-o my.pdf
Stamp (PDF Engines)
Adds a stamp on top of the content of each page during post-processing. Sources: text, image, or pdf.
image and pdf sources require an uploaded stamp file. stampExpression cannot reference an arbitrary filesystem path; the route returns 400 Bad Request.
Repeat these fields to apply several stamps in one request. They are applied in order, and each image or pdf stamp consumes the uploaded stamp files in order.
The stampOptions form field accepts a JSON object whose keys depend on the configured PDF engine:
| Engine | Syntax Reference |
|---|---|
| pdfcpu (Default) | See pdfcpu stamp documentation |
| pdftk | See pdftk documentation |
Available keys include font, points (font size), color, rotation, opacity, scale, offset, and more. Example:
{
"font": "Helvetica",
"points": 24,
"color": "#008000",
"rotation": 0,
"opacity": 0.6
}
Check the PDF Engines module configuration to see which engine is active.
curl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form stampSource=text \
--form stampExpression=APPROVED \
--form 'stampOptions={"opacity":0.5,"rotation":0}' \
-o my.pdf
Rotate (PDF Engines)
Rotates pages by 90, 180, or 270 degrees during post-processing.
curl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form rotateAngle=90 \
-o my.pdf
Optimize Images (PDF Engines)
Reduces the resulting file size by re-encoding its images to JPEG during post-processing. Text, vectors, fonts, and structure are left untouched.
false80curl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form optimizeImages=true \
--form imageQuality=80 \
-o my.pdf
PDF/A & PDF/UA (PDF Engines)
Converts to PDF/A (archival) or PDF/UA (accessibility) during post-processing using LibreOffice. Slower than native conversion (requires a second pass).
PDF/A and encryption are mutually exclusive: requesting both returns 400 Bad Request. PDF/A-1b and PDF/A-2b don't support file attachments; use PDF/A-3b if you need both.
When PDF/A runs alongside other post-processing, LibreOffice overwrites CreateDate, ModDate, and Keywords. Other metadata fields are preserved.
LibreOffice rasterizes table cells that carry a background color, which costs accessibility (Tables Rasterized to Images).
falsecurl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form pdfa=PDF/A-1b \
--form pdfua=true \
-o my.pdf
Encryption (PDF Engines)
Set passwords and permissions to control PDF access. The user password is required to open the PDF. The owner password grants full access and lifts the permission restrictions; when empty, it defaults to the user password.
Since 8.34.0, an owner password alone produces an owner-only PDF: it opens without a password, but the document permissions apply. Permission restrictions require a userPassword or an ownerPassword, otherwise Gotenberg returns 400 Bad Request. Restrictions are advisory: viewers honor them, but they are not cryptographically enforced once the document opens.
Encryption strength (e.g., AES-256) and permission handling depend on the active PDF engine: QPDF honors each permission individually, pdfcpu restricts all permissions if any one is denied, and PDFtk supports neither owner-only encryption nor permission restrictions. See PDF Engines module configuration.
NoneNonetruetruetruetruetruetruecurl \
--request POST http://localhost:3000/forms/pdfengines/merge \
--form files=@/path/to/1_pdf.pdf \
--form files=@/path/to/2_pdf.pdf \
--form files=@/path/to/3_pdf.pdf \
--form userPassword=my_user_password \
--form ownerPassword=my_owner_password \
--form allowCopying=false \
-o my.pdf
What's Next?
Break files apart with Split, or produce archival files with PDF/A & PDF/UA.