Extract and inspect PDFs in Clojure with Apache PDFBox.
This library is the Clojure counterpart to Python's pdfplumber.
It extracts text, tables, and geometry from digitally generated PDFs as plain,
EDN/JSON-friendly data.
Try it live - a hosted table extractor for this library. Upload a PDF to view detected table regions on each page. Set the detection strategy. Download the rows as CSV or JSON. The service processes files in memory and does not store them.
Stable. Data shapes follow semantic versioning. A breaking change to a returned map requires a major bump.
Covers the Python pdfplumber extraction surface for text, words, chars, objects, tables, and crop. It also provides:
The parity workflow measures parity weekly. It fetches the upstream
jsvine/pdfplumber test corpus. It extracts each PDF with Python pdfplumber to
make a baseline. It compares page count, text similarity, word count, and table
cell content. The repository does not commit the corpus. Run
dev/fetch-corpus.sh, then dev/gen_golden.py.
deps.edn
net.clojars.savya/pdfplumber-clj {:mvn/version "1.0.1"}
Leiningen
[net.clojars.savya/pdfplumber-clj "1.0.1"]
Requires JDK 17+.
(require '[pdfplumber.core :as pdf])
(pdf/with-pdf [doc "statement.pdf"]
(pdf/text doc {:page 1})) ; => "Account statement\n..."
;; Supply :password for password-protected PDFs.
(pdf/with-pdf [doc "statement.pdf" {:password "hunter2"}]
(pdf/text doc {:page 1}))
(pdf/with-pdf [doc "statement.pdf"]
(pdf/words doc {:page 1})) ; => [{:text "Account" :x0 .. :top .. :x1 .. :bottom ..} ...]
(pdf/with-pdf [doc "invoice.pdf"]
(pdf/extract-table doc {:page 1 :strategy :lines}))
The reducible-* functions in pdfplumber.core return IReduceInit streams.
They extract one page at a time. Transducers can stop early without extracting
later pages. pdfplumber.reducible also exports these functions.
(into []
(comp (filter #(> (:size %) 10)) (take 100))
(pdf/reducible-chars doc))
(transduce (take 20) conj [] (pdf/reducible-words doc))
extract-tables returns independent table regions on a page. It orders them
top-to-bottom, then left-to-right. extract-table returns the first region.
Set each axis with :vertical-strategy and
:horizontal-strategy; each accepts :lines, :lines-strict, :text, or
:explicit. The legacy :strategy option sets both axes.
(pdf/extract-tables doc
{:page 1
:vertical-strategy :explicit
:horizontal-strategy :lines
:explicit-vertical-lines [70 170 260]
:snap-tolerance 3.0
:join-tolerance 3.0
:edge-min-length 3.0
:intersection-tolerance 3.0
:min-words-vertical 3
:min-words-horizontal 1})
Use :explicit-horizontal-lines with :horizontal-strategy :explicit.
Explicit lines can be coordinates or maps with bounded line coordinates.
table->maps converts an extracted table or raw rows to a sequence of
header-keyed maps.
(-> (pdf/extract-table doc {:page 1})
pdf/table->maps)
The result can go directly to tech.v3.dataset/->dataset with no added dependency.
Use :keywordize? true for keyword keys. Set :header to :first, the
default, an explicit key vector, or false for integer keys.
pdfplumber.core/to-image renders a page with PDFBox and returns a PageImage.
pdfplumber.core/page-image? identifies a PageImage. pdfplumber.image has
functions to overlay, reset, copy, save, and display images.
(require '[pdfplumber.image :as image])
(pdf/with-pdf [doc "invoice.pdf"]
(-> (pdf/to-image doc {:page 1 :resolution 144})
(image/outline-words)
(image/draw-rect [72 72 240 160])
(image/save "debug.png")))
(pdf/with-pdf [doc "invoice.pdf"]
(-> (pdf/to-image doc {:page 1})
(image/debug-tablefinder {:vertical-strategy :lines})
(image/save "tables.png")))
More verbs in pdfplumber.image:
draw-line, draw-vline, draw-hline, draw-rect, draw-rects, draw-circle, draw-circlesoutline-words, outline-charsreset, copy, save, showstructure-tree returns the nested logical structure of a tagged PDF.
page-structure-tree limits it to a 1-based page. Untagged PDFs return [].
(pdf/structure-tree doc)
(pdf/page-structure-tree doc 1)
form-fields returns terminal AcroForm field maps. They include values,
constraints, options, and first-widget geometry. field-values returns the
name-to-value map.
(pdf/form-fields doc)
;; => [{:name "customer.email", :type :text, :value "ada@example.com",
;; :required? true, :read-only? false, :page-number 1,
;; :bbox [72.0 120.0 288.0 140.0]}]
(pdf/field-values doc)
;; => {"customer.email" "ada@example.com"}
Widget annotations from annots also carry :field-name, :field-value, and
:field-type.
outline returns nested bookmarks with resolved 1-based page numbers.
(pdf/outline doc)
;; => [{:title "Introduction", :page-number 1, :children []}]
attachments returns embedded-file metadata. Set :include-data? true to add
decoded :bytes.
(pdf/attachments doc)
;; => [{:name "data.csv", :size 128, :mime-type "text/csv"}]
(pdf/attachments doc {:include-data? true})
permissions reports encryption state and effective access flags.
(pdf/permissions doc)
;; => {:encrypted? true, :can-print? true, :can-modify? false, ...}
signatures returns signature metadata and a :covers-whole-document?
integrity signal. signed? reports the presence of a signature dictionary.
These APIs do not validate cryptographic signatures, certificates, or trust.
(pdf/signatures doc)
;; => [{:name "Ada Lovelace", :byte-range [0 1024 2048 512],
;; :covers-whole-document? true}]
(pdf/signed? doc)
;; => true
Dump selected PDF objects as CSV or JSON:
clojure -M -m pdfplumber.cli statement.pdf \
--format json --pages 1,2 --types char,line,rect,curve,image,annot \
--precision 2 --indent 2
images returns drawn image objects. They also appear as :image entries in
objects. Each object has:
:bbox, plus pixel :width and :height:colorspace and :bits:mask? and :smask?Decoded PNG bytes are omitted by default.
(pdf/images doc {:page 1})
(pdf/images doc {:page 1 :include-image-data? true}) ; adds :bytes
Public coordinates use a top-left origin, like pdfplumber. Bounding boxes
are [x0 top x1 bottom] in PDF user-space points. The library converts PDFBox's
native bottom-left coordinates.
In:
Not in scope, as in Python pdfplumber: PDF generation, OCR, scanned/image PDFs, and layout ML.
Two caveats:
:text strategy is heuristic for digitally generated PDFs.Copyright © 2026 Savyasachi.
Distributed under the Eclipse Public License 2.0.
Can you improve this documentation? These fine people already did:
Savyasachi & Savyasachi JagadeeshanEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |