Turn a pile of scanned pages into a searchable, structured archive — without
sending a single page to an external OCR API. Raw scans land in
Backblaze B2,
PaddleOCR runs locally over each
page (text detection → angle classification → recognition), and three derived
artifacts are written back to B2 per page: a structured result.json (boxes +
text + confidence), a rendered overlay.png for visual QA, and a searchable
text.txt. A lightweight SQLite index — rebuildable entirely from those
B2-resident artifacts — powers keyword search. B2 is the single source of
truth; the only credential you need is a B2 key.
Built for archivists, legal teams, and digital-preservation workflows that need on-device OCR over large scan collections, and as a reference for using B2 as the durable, S3-compatible backbone of a high-write-amplification AI workload (each ingested page fans out to ~4 B2 objects).
Dashboard — archive metrics (documents, pages processed, pending, average recognition confidence, storage used), a 7-day OCR-throughput chart, and the most recent OCR runs.
Archive — the scoped explorer listing every scanned document with its collection, status, and recognition confidence, plus per-document Run OCR / view / edit / delete and a keyword search box.
Document detail — the scan beside its PaddleOCR detection overlay, the recognized full text, and per-region confidence scores.
Keyword search — searching recognized text opens a results panel with matching pages and highlighted snippets, served from the rebuildable B2-backed index.
Ingest — a dropzone plus per-scan OCR configuration (language, page-orientation detection, and a collection label) for adding new pages to the archive.
- Ingest a scan (TIFF / JPEG / PNG) → stored at
raw-scans/<doc-id>on B2 with a config sidecar; the document starts as pending. - Run OCR locally → PaddleOCR recognizes the page and writes
ocr-results/<doc-id>/{result.json, overlay.png, text.txt}to B2; status flips to processed. - Search recognized text → the SQLite keyword index returns matching pages with snippets.
- Review / edit / delete → inspect the scan next to its detection overlay, adjust OCR config for the next run, or remove the document (scan + all artifacts).
- Local OCR (PaddleOCR) — text detection, angle classification, and recognition run entirely on-device. No second API key, no per-page cost, no data egress.
- Scoped Archive explorer (
/archive) — the app's primary surface: list documents with status, confidence, collection, and a thumbnail; run OCR, view, edit, or delete each; keyword search box on top. - Full-bucket File explorer (
/files) — the reusable B2 browse / preview / download / delete surface, kept from the starter kit. - OCR overlay — detected bounding boxes drawn over the scan and stored on B2 for visual detection QA.
- Keyword full-text search — a rebuildable SQLite index over recognized text; a "Reindex" action repopulates it from B2.
- Archive dashboard — documents ingested, pages processed, pending vs processed, average recognition confidence, storage used, and an OCR-throughput chart.
You need: Node.js >= 20, pnpm >= 9, Python >= 3.11, Docker (the OCR service
runs in a container — on macOS start it with Colima:
colima start), and a free
Backblaze B2 account.
1. Install frontend dependencies
pnpm install2. Set up the backend venv (for tests + lint)
cd services/api
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt # core API — enough for `pnpm test:api` / lint (OCR engine mocked)
cd ../..The heavy OCR deps (PaddleOCR + PaddlePaddle) are not installed on the host — they run in the Docker container (see Running the OCR service below). The host venv only needs
requirements.txtfor the mocked test suite and linting.
3. Add your B2 credentials
cp .env.example .envIn the Backblaze B2 dashboard:
- Create a bucket → paste its Bucket Unique Name into
B2_BUCKET_NAME. SetB2_REGIONto the bucket's region slug (e.g.us-west-004); the S3 endpoint is derived ashttps://s3.<B2_REGION>.backblazeb2.com. - Create an application key with Read and Write → paste keyID into
B2_APPLICATION_KEY_IDand applicationKey intoB2_APPLICATION_KEY(shown once).
4. Run it
colima start # start Docker (macOS) if it isn't already running
pnpm devFrontend at localhost:3000, API at localhost:8000. pnpm dev runs pnpm doctor first — a preflight that catches a missing venv, placeholder .env, wrong tool versions, and busy ports.
pnpm dev builds and runs the FastAPI OCR service in a linux/arm64 container
(the web app stays on the host and talks to it over HTTP via NEXT_PUBLIC_API_URL).
Docker is a prerequisite — on macOS start it with colima start.
Why containerized: the macOS-arm64 paddlepaddle CPU wheel hangs at 100% CPU
on the first kernel dispatch on Apple Silicon. Running the exact same code in a
linux/arm64 container (via Colima) dodges that broken wheel and executes paddle
natively-fast on the same Apple Silicon hardware.
- First build takes ~5–10 min — it installs paddle + deps and bakes the OCR models into the image so the first request doesn't pay the download cost. Later starts are fast.
- The SQLite index + counters under
/app/datapersist in a named Docker volume (ocr-data). - Tear the container down with
pnpm dev:api:down. - Tests and lint still run on the host venv (
pnpm test:api,pnpm lint:api) with the OCR engine mocked — no Docker needed for those.
| Command | What it does |
|---|---|
pnpm dev |
Start frontend (host) + OCR API (Docker container) |
pnpm dev:web |
Frontend only |
pnpm dev:api |
OCR API only — builds + runs the linux/arm64 container |
pnpm dev:api:down |
Stop + remove the OCR API container |
pnpm build |
Type-check + build frontend |
pnpm lint |
Lint frontend |
pnpm lint:api |
Lint backend (ruff) |
pnpm test:api |
Backend tests (OCR engine is mocked — no ML install needed) |
pnpm check:structure |
Verify layering rules |
pnpm test:e2e |
Playwright e2e tests (run pnpm --filter @paddleocr-document-archive/web exec playwright install chromium once first) |
- TypeScript, Next.js 16, React 19, Tailwind v4, shadcn/ui, Recharts, TanStack Query
- Python 3.11+, FastAPI, boto3, Pydantic v2, Pillow
- PaddleOCR + PaddlePaddle (local OCR, isolated in
services/api/requirements-ml.txt) - SQLite (stdlib) for the rebuildable keyword index
- Backblaze B2 (S3-compatible object storage) — single source of truth
- pnpm workspaces (monorepo)
- Document Ingest — scan ingest + per-document OCR config
- OCR Recognition — local PaddleOCR pipeline, device autodetect, artifacts
- Document Archive — scoped explorer + full document lifecycle
- Full-Text Search — SQLite keyword index + reindex from B2
- File Browser — full-bucket browse / preview / download / delete
- Dashboard — OCR-archive metrics and throughput
- Design System — tokens, primitives, loader, error/empty states. Live at
/design.
| Doc | Purpose |
|---|---|
| AGENTS.md | Agent table of contents — start here |
| ARCHITECTURE.md | System layout, layering, data flows |
| docs/features/ | Feature docs |
| docs/app-workflows.md | User journeys |
| docs/dev-workflows.md | Engineering workflows and testing |
| docs/SECURITY.md | Security principles |
| docs/RELIABILITY.md | Reliability expectations |
| docs/exec-plans/ | Execution plans and tech debt tracker |
MIT License — see LICENSE for details.
Manage Backblaze B2 from your terminal using natural language (list/search, audits, stale or large file detection, security checks, safe cleanup).
Repo: https://github.com/backblaze-b2-samples/claude-skill-b2-cloud-storage




