A modern Swift SDK for Gotenberg that provides a type-safe, async/await interface for PDF generation and document conversion. Transform HTML, Markdown, URLs, and office documents into PDFs with intelligent retry logic and comprehensive error handling.
- 🚀 Async/await support with modern Swift concurrency
- 🛡️ Type-safe APIs with comprehensive error handling
- 🔄 Intelligent retry logic for transient failures
- 📄 Multiple input formats (HTML, Markdown, URLs, Office docs)
- 🖼️ Screenshot capture from web pages
- 📋 PDF manipulation (merge, split, metadata)
- 🔐 PDF encryption (during conversion + dedicated endpoint)
- 🔑 Authentication support (Basic Auth + custom headers)
- 📱 Cross-platform (iOS, macOS, Linux, Windows)
Add GotenbergKit to your Swift package:
dependencies: [ .package(url: "https://github.com/thoven87/gotenberg-kit.git", from: "0.1.0") ]Run Gotenberg using Docker:
docker run --rm -p 3000:3000 gotenberg/gotenberg:8import GotenbergKit let client = GotenbergClient(baseURL: URL(string: "http://localhost:3000")!) // Convert HTML to PDF let pdfData = try await client.convertHTMLToPDF( htmlContent: "<h1>Hello World</h1>", options: ConversionOptions() ) // Convert URL to PDF let urlResponse = try await client.convertURLToPDF( url: URL(string: "https://apple.com")!, options: ConversionOptions() ) // Save the PDF try await client.writeToFile(urlResponse, at: "/path/to/output.pdf")Convert HTML content with optional CSS and JavaScript:
let options = ConversionOptions( paperWidth: 8.27, paperHeight: 11.7, marginTop: 1.0, marginBottom: 1.0, marginLeft: 1.0, marginRight: 1.0, printBackground: true, landscape: false, scale: 1.0 ) let response = try await client.convertHTMLToPDF( htmlContent: """ <!DOCTYPE html> <html> <head> <style>body { font-family: Arial, sans-serif; }</style> </head> <body> <h1>Professional Document</h1> <p>Generated with GotenbergKit</p> </body> </html> """, options: options )Convert web pages to PDF with customizable options:
let response = try await client.convertURLToPDF( url: URL(string: "https://swift.org")!, options: ConversionOptions( waitDelay: 2000, // Wait 2 seconds for page load printBackground: true, scale: 0.8 ) )Capture screenshots of web pages:
let screenshotOptions = ScreenshotOptions( width: 1920, height: 1080, clip: true, format: .png, quality: 100 ) let screenshot = try await client.captureScreenshot( url: URL(string: "https://github.com")!, options: screenshotOptions )Convert office documents (Word, Excel, PowerPoint) to PDF:
let documents = [ "document.docx": try Data(contentsOf: docxURL), "spreadsheet.xlsx": try Data(contentsOf: xlsxURL) ] let response = try await client.convertWithLibreOffice( documents: documents, options: LibreOfficeConversionOptions(merge: true) )let response = try await client.mergeWithPDFEngines( documents: [ "file1.pdf": pdfData1, "file2.pdf": pdfData2 ], options: PDFEngineOptions() )let response = try await client.splitPDF( documents: ["document.pdf": pdfData], options: SplitPDFOptions( splitMode: .pages, splitSpan: "1-5", splitUnify: false ) )Read and write PDF metadata:
// Read metadata let metadata = try await client.readPDFMetadata( documents: ["document.pdf": pdfData] ) // Write metadata let response = try await client.writePDFMetadata( documents: ["document.pdf": pdfData], metadata: [ "Title": "My Document", "Author": "GotenbergKit", "Subject": "PDF Generation", "Keywords": ["swift", "pdf", "gotenberg"] ] )GotenbergKit provides two ways to encrypt PDFs with password protection:
Add password protection while converting documents to PDF:
// Encrypt HTML to PDF with both user and owner passwords let options = ConversionOptions( userPassword: "user123", // Password for opening/viewing the PDF ownerPassword: "owner456" // Password for full access/editing ) let response = try await client.convertHTMLToPDF( htmlContent: "<h1>Confidential Document</h1>", options: options ) // Encrypt office documents let libreOptions = LibreOfficeConversionOptions( password: "source_password", // Password for opening source file (if encrypted) userPassword: "view_password", // Password for viewing output PDF ownerPassword: "edit_password" // Password for editing output PDF ) let response = try await client.convertWithLibreOffice( documents: ["confidential.docx": docData], options: libreOptions ) // Encrypt when processing existing PDFs let pdfOptions = PDFEngineOptions( userPassword: "reader_access", ownerPassword: "full_access" ) let encryptedPDF = try await client.mergeWithPDFEngines( documents: ["file1.pdf": data1, "file2.pdf": data2], options: pdfOptions )Use the dedicated encryption endpoint to add password protection to existing PDF files with full metadata override support:
// Basic encryption with passwords only let encryptedResponse = try await client.encryptPDFs( documents: [ "document1.pdf": try Data(contentsOf: pdf1URL), "document2.pdf": try Data(contentsOf: pdf2URL) ], options: PDFEngineOptions( userPassword: "viewer_password", ownerPassword: "admin_password" // Optional ) ) // Encrypt with custom metadata override let customMetadata = Metadata( author: "Secure Author", copyright: "Company Confidential", creator: "GotenbergKit", marked: true, producer: "Swift PDF Processor", subject: "Encrypted Document", title: "Confidential Report" ) let encryptedWithMetadata = try await client.encryptPDFs( documents: ["report.pdf": reportData], options: PDFEngineOptions( metadata: customMetadata, // Override metadata during encryption userPassword: "user123", ownerPassword: "owner456", flatten: true, // Additional PDF processing options pdfua: true ) ) // Encrypt PDFs from URLs let pdfURLs = [ DownloadFrom(url: "https://example.com/file1.pdf"), DownloadFrom(url: "https://example.com/file2.pdf") ] let encryptedFromURLs = try await client.encryptPDFs( urls: pdfURLs, options: PDFEngineOptions( userPassword: "required_password" ) )Key Features:
- Password Protection: User password (required) and owner password (optional)
- Metadata Override: Set custom metadata during encryption process
- PDF Processing: Support for flattening, PDF/UA compliance, and format options
- Flexible Input: Encrypt from file data or URLs
- Consistent API: Uses PDFEngineOptions like other PDF operations
Password Types:
- User Password: Required to open and view the PDF (required for encryption)
- Owner Password: Required for full access (editing, copying, printing) (optional)
GotenbergKit supports embedding files within PDFs in two ways:
- During Conversion: Embed files while generating PDFs from HTML, Markdown, or office documents
- Post-Processing: Use the dedicated embed route to add files to existing PDFs
This enables compliance with standards like ZUGFeRD/Factur-X that require XML invoices and other attachments to be embedded in the PDF.
// Create invoice XML for ZUGFeRD/Factur-X compliance let invoiceXML = """ <?xml version="1.0" encoding="UTF-8"?> <Invoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"> <ExchangedDocument> <ID>INV-12345</ID> <TypeCode>380</TypeCode> </ExchangedDocument> </Invoice> """.data(using: .utf8)! let metadataJSON = """ { "invoice_number": "INV-12345", "amount": 1000.00, "currency": "USD" } """.data(using: .utf8)! // Embed files during HTML to PDF conversion let options = ChromiumOptions( embeds: [ "invoice.xml": invoiceXML, "metadata.json": metadataJSON, "logo.png": try Data(contentsOf: logoURL) ] ) let response = try await client.convert( html: htmlContent.data(using: .utf8)!, options: options ) // Embed files during LibreOffice conversion let libreOptions = LibreOfficeConversionOptions( embeds: [ "factur-x.xml": facturXData, "additional-info.pdf": additionalPdfData ] ) let response = try await client.convertWithLibreOffice( documents: ["invoice.docx": docData], options: libreOptions )// Embed files when encrypting existing PDFs let pdfOptions = PDFEngineOptions( userPassword: "password123", embeds: [ "invoice.xml": invoiceXMLData, "receipt.pdf": receiptPdfData ] ) let encryptedResponse = try await client.encryptPDFs( documents: ["document.pdf": pdfData], options: pdfOptions ) // Embed files when merging PDFs let mergeOptions = PDFEngineOptions( embeds: [ "source-data.xml": xmlData, "attachments.zip": zipData ] ) let mergedResponse = try await client.mergeWithPDFEngines( documents: ["doc1.pdf": pdf1Data, "doc2.pdf": pdf2Data], options: mergeOptions )For adding files to existing PDFs, use the dedicated embedFiles method that utilizes Gotenberg's /forms/pdfengines/embed route:
// Create files to embed let invoiceXML = """ <?xml version="1.0" encoding="UTF-8"?> <Invoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"> <ExchangedDocument> <ID>INV-2025-001</ID> <TypeCode>380</TypeCode> </ExchangedDocument> </Invoice> """.data(using: .utf8)! let metadataJSON = """ { "invoice_id": "INV-2025-001", "amount": 1500.00, "currency": "USD" } """.data(using: .utf8)! // Embed files into existing PDFs let embedOptions = PDFEngineOptions( metadata: Metadata( author: "Invoice System", title: "Invoice with Embedded Data", subject: "ZUGFeRD Compliant Invoice" ), embeds: [ "factur-x.xml": invoiceXML, "invoice-metadata.json": metadataJSON ] ) let embeddedResponse = try await client.embedFiles( documents: ["invoice.pdf": existingPdfData], options: embedOptions ) // Embed files from URLs let embeddedFromUrls = try await client.embedFiles( urls: [DownloadFrom(url: "https://example.com/document.pdf")], options: embedOptions )Key Features:
- Multiple Files: Embed multiple files of different types (XML, JSON, PDF, images, etc.)
- Standards Compliance: Perfect for ZUGFeRD/Factur-X invoice standards
- Two Approaches: Embed during conversion or post-process existing PDFs
- Flexible Input: Support for file data and URLs
- Metadata Control: Override PDF metadata during embedding
- Preserved Attachments: Embedded files can be extracted by PDF readers that support attachments
Use Cases:
- Electronic Invoicing: ZUGFeRD/Factur-X compliant invoices with embedded XML
- Legal Documents: Contracts with embedded supporting documents
- Document Archival: Add source files to final PDFs
- Compliance: Meet regulatory requirements for embedded attachments
- Post-Processing: Add files to existing PDF workflows
let client = GotenbergClient( baseURL: URL(string: "http://localhost:3000")!, username: "gotenberg", password: "secret" )let response = try await client.convertHTMLToPDF( htmlContent: "<h1>Authenticated Request</h1>", options: ConversionOptions(), customHeaders: [ "Authorization": "Bearer \(token)", "X-Request-ID": "unique-id" ] )GotenbergKit provides comprehensive error handling with intelligent retry logic:
do { let response = try await client.convertHTMLToPDF( htmlContent: html, options: options ) } catch GotenbergError.apiError(let statusCode, let message) { switch statusCode { case 400: print("Bad request: \(message)") case 409: print("Resource conflict (won't retry): \(message)") case 500: print("Server error (may have been retried): \(message)") default: print("API error \(statusCode): \(message)") } } catch GotenbergError.networkError(let description) { print("Network error: \(description)") } catch { print("Unexpected error: \(error)") }let client = GotenbergClient( baseURL: URL(string: "http://localhost:3000")!, maxRetries: 3, // Default: 3 retries logger: Logger(label: "gotenberg-client") )// Per-request timeout let response = try await client.convertHTMLToPDF( htmlContent: html, options: options, timeoutSeconds: 30 // 30 seconds timeout )// Use strong, unique passwords for conversion let options = ConversionOptions( userPassword: generateSecurePassword(length: 12), ownerPassword: generateSecurePassword(length: 16) ) // For dedicated encryption endpoint with metadata control let encryptionOptions = PDFEngineOptions( metadata: Metadata( author: "Document Owner", copyright: "Confidential", creator: "Secure App", marked: true, subject: "Encrypted Content", title: "Protected Document" ), userPassword: generateSecurePassword(length: 12), ownerPassword: generateSecurePassword(length: 16), flatten: true // Prevent form modifications ) // User password only (allows viewing) let viewOnlyOptions = PDFEngineOptions( userPassword: "view_password" ) // Both passwords for maximum control let secureOptions = PDFEngineOptions( userPassword: "user_access", // Required to open ownerPassword: "admin_access" // Full permissions ) // Apply encryption to existing PDFs let encrypted = try await client.encryptPDFs( documents: ["sensitive.pdf": pdfData], options: encryptionOptions )Process multiple documents concurrently:
let urls = [ URL(string: "https://apple.com")!, URL(string: "https://swift.org")!, URL(string: "https://github.com")! ] await withTaskGroup(of: Void.self) { group in for url in urls { group.addTask { do { let response = try await client.convertURLToPDF( url: url, options: ConversionOptions() ) let filename = "\(url.host ?? "unknown").pdf" try await client.writeToFile(response, at: filename) print("✅ Generated: \(filename)") } catch { print("❌ Failed to convert \(url): \(error)") } } } }let options = ConversionOptions( paperWidth: 21.0, // A4 width in cm paperHeight: 29.7, // A4 height in cm marginTop: 2.54, // 1 inch margins marginBottom: 2.54, marginLeft: 2.54, marginRight: 2.54 )- Swift 6.1+
- Gotenberg server instance
We welcome contributions! Please see our Contributing Guidelines for details.
GotenbergKit is released under the MIT License. See LICENSE for details.
- Gotenberg - The Docker-powered stateless API for PDF files
- AsyncHTTPClient - HTTP client library used internally