Turn Scanned Documents into Searchable, Structured Data

Introduction

A folder of scanned receipts is data you can't actually use. The numbers are locked inside pixels — you can't search them, total them, or look anything up.

The goal here is to take a single scanned receipt image and turn it into a record you can query with SQL-like expressions. We'll OCR the image with aux4/image, archive it as a searchable PDF, pull the text back out with aux4/pdf, and store the structured result in aux4/repository.

Install the packages

aux4 aux4 pkger install aux4/image-text aux4/pdf aux4/repository

aux4/image-text adds OCR commands (text, pdf) under the image profile and relies on Tesseract, which it installs for you.

Step 1: read text straight off an image

The fastest path from pixels to text is aux4 image text. Point it at an image and it prints what it recognizes to stdout:

aux4 image text receipt.png
GROCERY MART
742 Market Street
Milk 3.99
Bread 2.49
Eggs 4.25
Total 10.73

The page-segmentation mode matters for odd layouts. A single line of text — a label, a SKU — reads better with --psm 7:

aux4 image text label.png --psm 7
SKU-48217

Use --lang to OCR a different language (--lang eng is the default; install extra Tesseract language packs for others).

Step 2: archive the scan as a searchable PDF

Raw text is great for a pipeline, but for archiving you usually want to keep the original image and be able to search it. aux4 image pdf does both — it lays an invisible OCR text layer over the image and writes a PDF:

aux4 image pdf receipt.png --output receipt
receipt.pdf

The output name is given without the .pdf extension. Multi-page documents are just multiple inputs, one --input per page:

aux4 image pdf \
  --input page1.png --input page2.png --input page3.png \
  --output statement

You now have statement.pdf — looks like the scan, but every word is selectable and searchable.

Step 3: pull structure back out with aux4/pdf

Once you have a PDF — whether scanned or a real fillable form — aux4/pdf turns it back into data. The simplest command extracts plain text:

aux4 pdf text receipt.pdf
GROCERY MART
742 Market Street
Milk 3.99
Bread 2.49
Eggs 4.25
Total 10.73

For fillable forms, aux4 pdf parse is the powerful one: it returns a JSON array of pages, each with its text and every form field — name, type, value, options for dropdowns, and a bounding box (ref):

aux4 pdf parse invoice.pdf
[
  {
    "page": 1,
    "text": "Please enter your name:  [Field:Name] ...",
    "fields": [
      {
        "name": "Name",
        "alternativeText": "",
        "value": "",
        "type": "TextField",
        "ref": {
          "x": 202.468,
          "y": 587.91,
          "width": 209.821,
          "height": 22
        }
      },
      {
        "name": "Dropdown2",
        "alternativeText": "",
        "value": ["Choice 1"],
        "type": "Dropdown",
        "options": ["Choice 1", "Choice 2", "Choice 3", "Choice 4"],
        "ref": {
          "x": 71.6528,
          "y": 524.831,
          "width": 72.0002,
          "height": 20
        }
      }
    ]
  }
]

A couple of neighbors worth knowing: aux4 pdf count returns the page count, aux4 pdf search <file> --term <word> finds a term with its page and coordinates, and aux4 pdf image <file> --page 2 --image page2.png renders a page back to an image. (There's no pdf render — it's pdf image.)

Step 4: store the structured record

Now the payoff: persist the extracted fields somewhere you can query them. aux4/repository is a local JSON document store backed by SQLite — no server, no schema migrations. Each "repository" is a table; it's created the first time you write to it, and the database file defaults to .local.db in the current directory.

After shaping the OCR/parse output into a clean object (with jq, or by hand), write it in:

aux4 repository write invoices --id inv-001 \
  --data '{"vendor":"Acme","total":129.50,"date":"2025-12-17"}' \
  --metadata '{"source":"scan","ocr":"tesseract"}'
inv-001

The id becomes the primary key, and writes are upserts — re-running with the same id updates the record instead of duplicating it. You can also pipe JSON straight in and let it generate a UUID:

echo '{"vendor":"Globex","total":58.20}' | aux4 repository write invoices

Step 5: query like a database

This is what the whole pipeline was for. aux4 repository find takes a SQL-like expression where bare field names resolve into the stored JSON:

aux4 repository find invoices --expr "total > 100 and vendor = 'Acme'"
[
  { "id": "inv-001", "vendor": "Acme", "total": 129.50, "date": "2025-12-17" }
]

It supports the comparisons you'd expect (=, !=, <, >, <=, >=), like for fuzzy text, and/or, and parentheses:

aux4 repository find invoices --expr "vendor like '%Acme%' or total < 60"
aux4 repository read invoices --id inv-001        # fetch one by id

Your folder of dead scans is now a searchable ledger.

Conclusion

The path from scan to searchable data is four short hops:

  1. aux4 image text — OCR the image to plain text.
  2. aux4 image pdf — archive it as a searchable PDF.
  3. aux4 pdf parse / pdf text — extract text and form fields as JSON.
  4. aux4 repository write / find — store the structured record and query it.

Every step is a small, focused command, so you can stop at any point — just OCR, just archive, or go the whole way to a queryable store. Point it at receipts, invoices, forms, or a backlog of scanned paperwork, and the documents finally become data.

See Also