How do I install pdfx?
Install the package globally to get the pdfx command. Or run it once without installing.
npm install -g @pdf123/cli
pdfx --versionnpx @pdf123/cli merge a.pdf b.pdf -o merged.pdf
bunx @pdf123/cli merge a.pdf b.pdf -o merged.pdfbun add -g @pdf123/cli and pnpm add -g @pdf123/cli also work. Nothing else needs to be installed.
How do I get started?
These six commands show the common patterns.
pdfx list # every tool; --category security for one category
pdfx describe watermark # a tool's fields and defaults
pdfx merge a.pdf b.pdf -o merged.pdf
pdfx compress *.pdf -o compressed/ # several files: one result per file
pdfx watermark in.pdf --watermarkText DRAFT
pdfx pipeline a.pdf b.pdf --step merge --step compress -o out.pdfHow do I run any tool?
The first argument is the tool id. Input files and the tool's options follow. Every option of a tool is a flag, written as the API field name (--pageNumbers) or in kebab case (--page-numbers). A negative number can follow its flag after a space, as in --rotation -90, or after an equals sign.
pdfx <tool> [files...] [--<field> <value>]...Use pdfx list to see the tools. It accepts --category or --group, and --query with words to search. Use pdfx describe <tool> to see the endpoint, the accepted inputs, whether the tool runs as a batch, and each field with its default and allowed values. An unknown tool id gets suggestions, and a mistyped one exits with code 2.
Which commands map to which tool pages?
| Command | Tool page | What it does |
|---|---|---|
pdfx merge a.pdf b.pdf -o merged.pdf | Merge | Combine several PDFs into one |
pdfx split report.pdf --pageNumbers 3,7 -o parts.zip | Split | Divide a PDF into separate files |
pdfx compress in.pdf -o out/ | Compress | Recompress PDF streams to reduce size |
pdfx watermark in.pdf --watermarkText DRAFT | Watermark | Add a text or image watermark |
pdfx protect in.pdf --password secret -o locked.pdf | Protect | Encrypt a PDF with a password |
pdfx unlock locked.pdf --password secret | Unlock | Remove password protection |
pdfx get-info in.pdf | Document info | Print metadata, permissions and structure as JSON |
pdfx ocr scan.pdf -o out/ | OCR | Read a scanned PDF and save Markdown |
pdfx pdf-to-markdown in.pdf -o out/ | PDF to Markdown | Convert a PDF into Markdown |
pdfx rotate in.pdf --angle 90 | Rotate | Change page orientation by 90, 180 or 270 degrees |
pdfx repair broken.pdf | Repair | Rebuild the structure of a damaged PDF |
How do I process many files at once?
Give several files to a single-file tool and pdfx runs a batch. Use -o with a directory that ends in a slash. The tool runs once per file, two files at a time by default. Change that with --concurrency.
pdfx compress *.pdf -o compressed/
pdfx protect *.pdf --password secret --concurrency 4 -o locked/pdfx prints one line, input -> saved, as each file finishes. A failed file is reported on standard error and the other files still finish. Press Ctrl-C once to abort the request in flight; files already saved are kept. A second Ctrl-C leaves immediately with code 130. With --idempotency-key k, each file sends k:<index>, and a repeat of the same request replays the first result for 24 hours.
A batch of a tool that returns a report, such as get-info, writes no files without -o. It prints the reports keyed by input file.
How do I chain tools in one request?
pdfx pipeline runs several tools in one request. Repeat --step for each tool. To set options, pass a JSON array with --steps.
pdfx pipeline a.pdf b.pdf --step merge --step compress -o out.pdf
pdfx pipeline in.pdf --steps '[{"tool":"watermark","params":{"watermarkText":"DRAFT"}},{"tool":"compress"}]' -o out.pdfSteps cannot use tools that need a second file.
How do I open password-protected PDFs?
--input-password opens every encrypted input first. --password-for <file>=<password> sets the password of one file. It can be repeated, and * as the file name covers the rest. This suits a batch that mixes locked and open files. For merge and images-to-pdf, each named file is unlocked separately before the tool runs.
pdfx compress locked.pdf --input-password secret -o out.pdf
pdfx compress report.pdf --password-for report.pdf=secret -o out.pdf
pdfx merge a.pdf b.pdf --password-for a.pdf=secret -o merged.pdfThe same options work in a pipeline. To remove protection permanently, use the Unlock tool with its own --password option.
How do input and output work?
- An input of
-reads standard input, once per run.-o -writes the result to standard output. -o file.pdfwrites that file.-o dir/writes into a directory. A directory that does not exist yet needs the trailing slash, because a name without one is written as a file.- Without
-o, results go to the current directory under the server's file name. - Into a directory,
pdfxnever replaces an existing file; it picks a new name. An explicit-o file.pdfreplaces that file. - An
-ofile name whose extension contradicts the result, such as a ZIP fromsplitsaved as.pdf, is refused with exit code 2 andcode: output_mismatch. Nothing is written. - An output that cannot be written is a usage error found before anything is uploaded.
cat in.pdf | pdfx compress - -o - > out.pdfWhat does --json print?
For a saved result, --json prints the path, the content type and the size. A tool that returns a report prints the report itself. A batch prints one report with a status per file.
{ "path": "one.pdf", "contentType": "application/pdf", "bytes": 1040 }{
"processed": 2,
"unmatched": 0,
"failed": 0,
"files": [
{ "input": "/abs/a.pdf", "ok": true, "path": "comp/a.pdf", "contentType": "application/pdf", "bytes": 1040 }
]
}A failed file has error and reason in place of path and bytes. A filter tool with no match prints { "matched": false }. pdfx list --json prints rows with id, category, group, name, description, returns, files and filter.
Which options apply to every tool?
| Option | Effect |
|---|---|
-o, --output <path> | File to write, or a directory to write into. The default is the current directory. - is standard output |
--api-base <url> | API origin. Environment variable PDFX_API_BASE, default https://pdf123.xyz |
--api-key <key> | Sent as X-API-KEY. Environment variable PDFX_API_KEY. Optional |
--input-password <pw> | Opens every password-protected input first |
--password-for <file>=<pw> | Password for one file. Repeatable |
--concurrency <n> | Files of a batch processed at once. Default 2 |
--timeout <ms> | Timeout for each request. Default 300000 |
--idempotency-key <k> | Replays the first result for 24 hours |
--json | Machine-readable output |
What are the exit codes?
| Code | Meaning |
|---|---|
0 | Success. A filter tool with no match also exits 0 and prints no match |
1 | A request failed. In a batch, at least one file failed; the others still finish |
2 | Usage error. Nothing was uploaded |
130 | Interrupted with Ctrl-C. The request in flight is aborted |
Failures print reason:, code: and hint: lines when the server supplies them, so a script can branch without matching text. A tool that finds nothing to return, such as pdf-to-csv on a PDF without tables, exits 1 with code: no_content instead of writing an empty file. The codes are listed on Error codes.
How do I point pdfx at my own server?
Set PDFX_API_BASE, or pass --api-base, to the address of a self-hosted pdfx-server. Add PDFX_API_KEY if your server needs a key. See Self-host.
export PDFX_API_BASE=http://localhost:8080
export PDFX_API_KEY=<your-key>
pdfx compress in.pdfHow do I run an endpoint the CLI does not know?
pdfx call sends a raw request to an operation id or an /api/... path. Use --field name=value for form fields and --file field=path for extra files. It sends exactly one request and validates nothing locally, so passwords and batches are refused.
pdfx call general/merge-pdfs a.pdf b.pdf -o merged.pdf
pdfx call /api/v1/misc/flatten in.pdf --field flattenOnlyForms=true -o flat.pdfRelated pages
- Developers overview with the REST API and authentication
- TypeScript SDK, the library the command line is built on
- MCP servers for AI agents
- The blog post "pdfx CLI: One Catalog, Called From Your Terminal" on the PDF123 blog
FAQ
Does pdfx work offline?
No. pdfx uploads each file to the PDF123 API or to your own server and saves the result locally, so the API base must be reachable.
Does pdfx need an API key?
No. Anonymous use works. Set PDFX_API_KEY or pass --api-key if your server requires one. The key is sent as the X-API-KEY header.
Which Node version does pdfx need?
Node 20.3 or newer, or Bun.
Will pdfx overwrite my files?
Not when it writes into a directory: an existing file is never replaced. If you name the output file yourself with -o file.pdf, that file is replaced, so choose a new name when you want to keep the original.
What happens when a filter tool finds no match?
Filter tools, whose ids start with filter-, pass the file through when their condition holds. When it does not, pdfx prints no match and exits with code 0. In a batch, such a file counts as unmatched, not as failed.