Elixir wrapper for the qpdf command-line tool.
Provides functions for PDF inspection, page selection, page extraction, and splitting. By default, operations automatically decrypt input PDFs if they are encrypted.
Input Types
All PDF processing functions accept input/0, which can be:
binary- in-memory PDF data{:file, path}- path to an existing PDF file on disk
When passing {:file, path}, qpdf directly reads the file on disk, avoiding
loading the entire document into BEAM memory or writing redundant temporary copies.
Summary
Functions
Embeds an attachment (file or binary data) into the PDF document.
Returns a list of all embedded attachments in the PDF.
Returns the version of the installed qpdf executable.
Checks whether the PDF file is syntactically valid.
Decrypts an encrypted PDF document, optionally using a password.
Inspects page geometry, bounding boxes, dimensions, orientation, and paper size.
Encrypts a PDF document with password protection and access permissions.
Checks whether the given PDF is encrypted.
Returns detailed encryption parameters, cipher methods, and access permissions for a PDF.
Returns the path to the qpdf executable, downloading and installing it if necessary.
Extracts the raw contents of an embedded attachment by key.
Extracts structural metadata, outlines, page details, and object trees as a decoded JSON map.
Optimizes a PDF for Fast Web View (linearization).
Checks whether the given PDF is linearized (optimized for Fast Web View).
Merges multiple PDFs into a single document.
Optimizes and compresses a PDF document to reduce file size.
Optimizes raster images within a PDF document using DCT (JPEG) compression.
Overlays pages from another PDF on top of the input document.
Returns the page count of a PDF without splitting it.
Returns a list with the byte size of each page in the PDF.
Extracts specified pages or page ranges from a PDF.
Checks whether the given password is valid to open the encrypted PDF.
Removes an embedded attachment from the PDF by key.
Checks whether the given PDF requires a password to open.
Rotates pages in a PDF by a specified angle.
Splits a PDF into pages or consecutive groups of pages.
Returns the base temporary directory used for PDF operations.
Underlays pages from another PDF behind the input document.
Types
Functions
Embeds an attachment (file or binary data) into the PDF document.
Outputs the resulting PDF directly to standard output or a destination file.
Options
:key- unique key for the attachment (defaults to filename or"attachment"):filename- displayed filename in PDF viewers (defaults to key or basename):mimetype- MIME type (e.g.,"application/xml","text/plain"):description- optional description string:creation_date- creation date string:mod_date- modification date string:replace- boolean, replace existing attachment if key already exists (defaultfalse):into- destination::memory(default) orpath/{:file, path}
Examples
# Embed XML invoice for Factur-X / ZUGFeRD compliance
{:ok, embedded} = Qpdf.add_attachment(pdf, xml_binary,
key: "factur-x.xml",
filename: "factur-x.xml",
mimetype: "text/xml"
)
# Embed a file from disk
{:ok, embedded} = Qpdf.add_attachment(pdf, {:file, "attachment.csv"}, into: "with_csv.pdf")
Returns a list of all embedded attachments in the PDF.
Extracts attachment metadata including key, filename, mime-type, description, creation/modification dates, and checksums.
Examples
{:ok, attachments} = Qpdf.attachments(pdf)
# => [%{key: "invoice.xml", filename: "invoice.xml", mimetype: "application/xml", ...}]
@spec bin_version() :: {:ok, String.t()} | :error
Returns the version of the installed qpdf executable.
Returns {:ok, version_string} on success or :error when the executable
is not available or cannot be run.
Examples
case Qpdf.bin_version() do
{:ok, version} -> IO.puts("Running qpdf #{version}")
:error -> IO.puts("qpdf is not installed")
end
Checks whether the PDF file is syntactically valid.
Uses qpdf --check.
Returns :ok if valid, or {:error, reason} if the PDF is corrupt or invalid.
Decrypts an encrypted PDF document, optionally using a password.
Outputs the unencrypted PDF directly to standard output without intermediate disk files.
Options
:password- password required to decrypt the document:into- destination::memory(default) orpath/{:file, path}
Examples
{:ok, plain_pdf} = Qpdf.decrypt(encrypted_input, password: "secret")
@spec dimensions(input(), :all | integer() | Range.t() | list()) :: {:ok, map() | [map()]} | {:error, any()}
Inspects page geometry, bounding boxes, dimensions, orientation, and paper size.
Returns width and height in points (1/72 inch), visual orientation (:portrait,
:landscape, :square), rotation angle (0, 90, 180, 270), and media/crop bounding boxes.
Parameters
input: The PDF as binary or{:file, path}page_spec: Target page or pages::all(default) - returns a list of dimensions for all pages- integer (e.g.
1) - returns a single dimension map for that page Rangeorlist(e.g.1..3) - returns a list of dimensions for specified pages
Examples
# Inspect all pages
{:ok, pages} = Qpdf.dimensions(pdf)
# => [%{page: 1, width: 595.28, height: 841.89, orientation: :portrait, paper_size: "A4", ...}]
# Inspect a single page
{:ok, page1} = Qpdf.dimensions(pdf, 1)
page1.orientation #=> :portrait
page1.paper_size #=> "A4"
Encrypts a PDF document with password protection and access permissions.
Outputs the encrypted PDF directly to standard output without intermediate disk files.
Options
:user_password- password required to open the PDF (default:""):owner_password- password required to modify permissions (required for 256-bit keys when:user_passwordis provided, unlessallow_insecure: true; default:""):key_length- encryption key length:40(requiresallow_weak_crypto: true),128, or256(default:256):use_aes- boolean, use AES encryption for 128-bit keys (default:true):allow_weak_crypto- boolean, allow writing insecure/legacy encryption (required forkey_length: 40or 128-bit RC4, default:false):allow_insecure- boolean, allow setting a user password with an empty owner password for 256-bit keys (insecure, allows passwordless opening; default:false):print- print permission::none,:low, or:full:modify- modification permission::none,:assembly,:form,:annotate, or:all:extract- boolean, allow text/graphic extraction:annotate- boolean, allow annotations and commenting:cleartext_metadata- boolean, keep metadata unencrypted:into- destination::memory(default) orpath/{:file, path}
Examples
# Encrypt with user and owner passwords
{:ok, enc} = Qpdf.encrypt(input, user_password: "open", owner_password: "admin")
# Encrypt with restricted permissions
{:ok, enc} = Qpdf.encrypt(input, owner_password: "admin", print: :none, extract: false)
Checks whether the given PDF is encrypted.
Uses qpdf --is-encrypted.
Returns true if encrypted, false if unencrypted, or {:error, reason} on failure.
Returns detailed encryption parameters, cipher methods, and access permissions for a PDF.
Uses qpdf --show-encryption.
Returns {:ok, %{encrypted: false}} if the document is not encrypted.
If encrypted, returns {:ok, map()} with encryption revision (:r), permission integer (:p),
cipher methods (:stream_method, :string_method, :file_method), permissions map,
and password details if supplied or recoverable.
Options
:password- optional password to test against the document
Examples
# Inspect an encrypted PDF
{:ok, info} = Qpdf.encryption_info(pdf)
info.encrypted #=> true
info.r #=> 6 (Revision)
info.stream_method #=> "AESv3"
info.permissions.print_high #=> false
# Test password type
{:ok, info} = Qpdf.encryption_info(pdf, password: "admin")
info.password_matched #=> :owner
@spec executable_path() :: String.t()
Returns the path to the qpdf executable, downloading and installing it if necessary.
@spec extract_attachment(input(), String.t(), keyword()) :: {:ok, binary() | Path.t()} | {:error, any()}
Extracts the raw contents of an embedded attachment by key.
Outputs the attachment directly into memory as a binary, or into a file if :into is specified.
Options
:into- destination::memory(default) orpath/{:file, path}
Examples
{:ok, xml_bytes} = Qpdf.extract_attachment(pdf, "invoice.xml")
{:ok, path} = Qpdf.extract_attachment(pdf, "invoice.xml", into: "extracted.xml")
Extracts structural metadata, outlines, page details, and object trees as a decoded JSON map.
Uses qpdf --json.
Decodes the JSON output into native Elixir maps and lists using the standard library JSON module.
Options
:version- JSON schema version (e.g.1or2, default:2)
Returns
{:ok, map}on success{:error, any}on failure
Optimizes a PDF for Fast Web View (linearization).
A linearized PDF enables viewers to display page 1 immediately over HTTP while the remainder of the document continues downloading.
Outputs the linearized PDF directly to standard output without intermediate disk files.
Parameters
- input: The PDF as a binary or
{:file, path}
@doc """ Optimizes a PDF for Fast Web View (linearization).
A linearized PDF enables viewers to display page 1 immediately over HTTP while the remainder of the document continues downloading.
Outputs the linearized PDF directly to standard output without intermediate disk files.
Options
:into- destination::memory(default) orpath/{:file, path}
Returns
{:ok, binary | Path.t()}on success{:error, any}on failure
Checks whether the given PDF is linearized (optimized for Fast Web View).
Uses qpdf --check-linearization.
Returns true if linearized, false if not linearized, or {:error, reason} on failure.
@spec merge( [input() | {input(), integer() | Range.t() | list() | String.t()}], keyword() ) :: {:ok, binary() | Path.t()} | {:error, any()}
Merges multiple PDFs into a single document.
Accepts a list of inputs. Each item in the list can be:
- an
input(binary or{:file, path}) to include all its pages - a
{input, page_spec}tuple to include only specific pages or ranges
Outputs the merged PDF directly to memory or a file specified with into:.
Examples
# Merge multiple binaries into memory
{:ok, merged} = Qpdf.merge([pdf1, pdf2])
# Merge files directly to a destination path without loading bytes into BEAM memory
{:ok, path} = Qpdf.merge([{:file, "cover.pdf"}, {:file, "body.pdf"}], into: "merged.pdf")
# Merge specific page selections from different documents
{:ok, merged} = Qpdf.merge([
{{:file, "report.pdf"}, 1..5},
{appendix_binary, "1-z:even"}
])
Optimizes and compresses a PDF document to reduce file size.
Compresses uncompressed streams, generates object streams (packing objects into compressed stream containers), and recompresses Flate streams.
Outputs the optimized PDF directly to standard output without intermediate disk files.
Options
:stream_data-:compress(default),:uncompress, or:preserve:object_streams-:generate(default),:preserve, or:disable:recompress_flate- boolean, whether to recompress flate streams (default:true):into- destination::memory(default) orpath/{:file, path}
Examples
{:ok, compressed} = Qpdf.optimize(input)
Optimizes raster images within a PDF document using DCT (JPEG) compression.
Combines qpdf --optimize-images with optional filtering by image dimensions,
JPEG quality levels, inline image handling, and unreferenced resource cleanup.
Outputs the optimized PDF directly to standard output or a destination file.
Options
:jpeg_quality- integer from0(lowest) to100(highest):min_width- minimum image width in pixels to optimize:min_height- minimum image height in pixels to optimize:min_area- minimum image area (width * height in pixels) to optimize:keep_inline_images- boolean, iftrue, exclude inline images from optimization (defaultfalse):externalize_inline_images- boolean, convert inline images to regular image objects (defaultfalse):remove_unreferenced- boolean, remove unreferenced fonts/images from page resource dictionaries (defaultfalse):into- destination::memory(default) orpath/{:file, path}
Examples
# Basic image optimization
{:ok, optimized_pdf} = Qpdf.optimize_images(pdf)
# Target images with quality and minimum area thresholds
{:ok, compact_pdf} = Qpdf.optimize_images(pdf,
jpeg_quality: 80,
min_area: 10_000,
remove_unreferenced: true,
into: "compact.pdf"
)
Overlays pages from another PDF on top of the input document.
Useful for applying stamps, watermarks, signatures, or foreground content. Outputs the result directly to standard output without intermediate disk files.
Options
:to- page specification on the destination document (e.g.1..5,"even"):from- page specification on the overlay document:repeat- repeat overlay pages (e.g."1-z"ortrueto repeat all pages):password- password for the overlay document if encrypted
Examples
# Apply a 1-page watermark across all pages
{:ok, watermarked} = Qpdf.overlay(doc, watermark_pdf, repeat: true)
# Apply a stamp to page 1 only
{:ok, stamped} = Qpdf.overlay(doc, stamp_pdf, to: 1)
@spec page_count(input()) :: {:ok, pos_integer()} | {:error, any()}
Returns the page count of a PDF without splitting it.
Uses --show-npages, which only reads the page tree structure without writing
per-page files, making it very fast even on large documents.
Returns
{:ok, pos_integer}on success{:error, any}on failure
@spec page_size_vector(input()) :: {:ok, [non_neg_integer()]} | {:error, any()}
Returns a list with the byte size of each page in the PDF.
Uses the same default flags as split/1 but avoids loading full page binaries
into memory by reading file metadata directly.
@spec pages(input(), integer() | Range.t() | list() | String.t(), keyword()) :: {:ok, binary() | Path.t()} | {:error, any()}
Extracts specified pages or page ranges from a PDF.
Accepts:
- an integer page number (e.g.
1) - an Elixir range (e.g.
1..5) - a list of page numbers (e.g.
[1, 3, 5]) - a qpdf page selection string (e.g.
"1-5","1-z:even","z-1")
Parameters
- input: The PDF as a binary or
{:file, path} - page_spec: An integer, range, list of pages, or selection string
Returns
{:ok, binary | Path.t()}on success{:error, any}on failure
Checks whether the given password is valid to open the encrypted PDF.
Uses qpdf --requires-password.
Returns true if the password is valid (user or owner password),
or false if the password is wrong or the document is not encrypted.
Parameters
input: The PDF as a binary or{:file, path}password: The password string to test
Examples
Qpdf.password_valid?(pdf, "secret") #=> true
Qpdf.password_valid?(pdf, "wrong") #=> false
@spec remove_attachment(input(), String.t(), keyword()) :: {:ok, binary() | Path.t()} | {:error, any()}
Removes an embedded attachment from the PDF by key.
Outputs the resulting PDF directly to standard output or a destination file.
Options
:into- destination::memory(default) orpath/{:file, path}
Examples
{:ok, cleaned_pdf} = Qpdf.remove_attachment(pdf, "factur-x.xml")
Checks whether the given PDF requires a password to open.
Returns true if a user password is required to open the document,
or false if the document is unencrypted or opens with an empty user password.
Examples
Qpdf.requires_password?(pdf) #=> true | false
@spec rotate(input(), integer() | String.t(), any(), keyword()) :: {:ok, binary() | Path.t()} | {:error, any()}
Rotates pages in a PDF by a specified angle.
The angle must be a multiple of 90 (e.g. 90, 180, 270, -90).
By default, all pages are rotated. A specific page, range, or page specification
can optionally be provided.
Outputs the rotated PDF directly to standard output without intermediate disk files.
Examples
# Rotate all pages 90 degrees clockwise
{:ok, rotated} = Qpdf.rotate(input, 90)
# Rotate only page 2 by 180 degrees
{:ok, rotated} = Qpdf.rotate(input, 180, 2)
# Rotate a range of pages
{:ok, rotated} = Qpdf.rotate(input, 90, 1..5)
Splits a PDF into pages or consecutive groups of pages.
Defaults to splitting into individual single-page documents (pages_per_group: 1).
When pages_per_group > 1, splits into multi-page documents of at most pages_per_group pages.
Grouping happens inside a single qpdf execution, so splitting a large PDF into
multiple chunks costs only one process invocation.
Options
:into- destination directory::memory(default, returns[binary]) orpath/{:dir, path}(returns[Path.t()]without loading files into memory)
Returns
{:ok, [binary] | [Path.t()]}on success, in page order{:error, any}on failure
@spec tmp_dir() :: String.t()
Returns the base temporary directory used for PDF operations.
Defaults to System.tmp_dir!(), but can be configured in your application:
config :qpdf, tmp_dir: "/path/to/custom/tmp"
Underlays pages from another PDF behind the input document.
Useful for applying digital letterheads, backgrounds, or stationery. Outputs the result directly to standard output without intermediate disk files.
Options
:to- page specification on the destination document:from- page specification on the underlay document:repeat- repeat underlay pages (e.g."1-z"ortrueto repeat all pages):password- password for the underlay document if encrypted:into- destination::memory(default) orpath/{:file, path}
Examples
# Apply a background letterhead across all pages
{:ok, with_letterhead} = Qpdf.underlay(doc, letterhead_pdf, repeat: true)