Docket identifies what kind of document you've uploaded, extracts its text, and returns the key fields as clean, structured data.
Plenty of documents carry the same handful of fields every time, yet getting that data out usually means opening each file and retyping it into another system. That doesn't scale, and it's an easy place for typos to creep in.
Docket automates that step. Upload a PDF or image and it works out what kind of document it is, extracts the text, pulls the fields specific to that document type, and hands back JSON instead of a document someone has to read.
It isn't locked to the document types it ships with. Each type is a small parser module, and a dispatcher routes every document to the right one. To support a new document, add a parser. The detection flow, the output format, and the core pipeline stay the same.
Docket runs each upload through four stages.
1. File type detection Reads the file's actual content instead of trusting its extension, so a renamed or mislabeled file is still handled correctly. Empty, unreadable, and unsupported files are caught here with a clear error rather than failing deeper in the pipeline.
2. Text extraction Works page by page, since a single PDF can mix text-based and scanned pages. Pages with a real text layer are read directly with pdfplumber. Pages without one are rendered as images and run through PaddleOCR. Image uploads go straight to OCR. Scanned and mixed documents go through the same flow with no extra steps.
3. Document type detection Searches the extracted text for keywords unique to each supported document type and returns a type label.
4. Parsing Routes the text to the matching parser, which returns the document's fields along with any warnings. If an expected field can't be found, the response includes a warning for it instead of quietly leaving it out.
| Document | Extraction approach |
|---|---|
| Emirates ID | MRZ parsing, plus a front-page lookup for the national ID number |
| Passport | MRZ parsing |
| Title deed | Label-based extraction |
| Listing form | Position-based extraction |
There's no single extraction method that works for every document, so each parser uses whichever technique its layout makes reliable.
-
Label-based extraction (title deed): The document has clearly labeled fields, so the parser finds each label and reads the value that belongs to it.
-
MRZ parsing (Emirates ID, passport): These documents carry a machine-readable zone with a standardized format, so the fields can be decoded from that rather than from the visual layout. The parser handles both the 3-line format used on ID cards and the 2-line format used on passports. On the Emirates ID, the MRZ holds the card number rather than the national ID number printed on the front, so the national ID number is picked up separately from the front-page text.
-
Position-based extraction (listing form): Fields sit side by side, and PDF text extraction can reorder them, which made label-based matching grab the wrong values. This parser uses word coordinates to work out which value belongs to which label.
The backend exposes one endpoint.
curl -X POST http://localhost:8000/extract -F "file=@document.pdf"Example response (sample data):
{
"doc_type": "passport",
"fields": {
"name": "RIMSHA ALI",
"document_number": "AB1234567",
"nationality": "PAK",
"date_of_birth": "01/01/2000",
"sex": "F",
"expiry_date": "01/01/2030",
"country": "PAK"
},
"warnings": []
}If something goes wrong, the response is {"error": "message"}.
Adding a new document type is straightforward and doesn't require changes to the core pipeline:
-
Create a parser module in
backend/parsers/with aparse(text, file_path)function that returns(fields, warnings)for the extracted document data. -
Add the relevant identifying keywords to
detect_doc_typeinutils/doc_type_detector.pyso Docket can recognize the new document type. -
Register the new parser in the
PARSERSdictionary inmain.pyto connect it to the processing pipeline.
docket/
├── backend/
│ ├── main.py
│ ├── app.py
│ ├── Dockerfile
│ ├── parsers/
│ └── utils/
│ ├── file_type.py
│ ├── extract_text.py
│ ├── doc_type_detector.py
│ ├── mrz_parser.py
│ ├── json_builder.py
│ └── warnings_helper.py
├── frontend/
└── assets/
Prerequisites: Python 3.11+, Node.js, npm, and a few GB of free RAM.
git clone https://github.com/imanabshar/docket.git
cd docketcd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn app:app --reload --port 8000Note: PaddleOCR downloads its models on the first run, which may take some time.
- CLI usage (optional):
python main.py path/to/document.pdfExtracted fields are printed to the terminal and saved as JSON in output/.
cd frontend
npm install
echo "VITE_API_URL=http://localhost:8000" > .env
npm run dev- OCR uses 150 DPI instead of 300. Lower resolution reduces RAM usage and prevents crashes, with a slight trade-off in accuracy.
- Scanned pages are slower by design. OCR is the slow step, so scanned and image uploads take noticeably longer than documents with a real text layer.
- CORS is an allowlist. If you host the frontend somewhere new, add its origin to the allowed origins in
app.py, or uploads from it will be blocked. - Temporary file handling. Uploaded files are written to a temporary location for processing and are deleted immediately after processing is complete.
Try it here: docket-opal.vercel.app
The backend runs on Render's free tier (512 MB RAM), which is not enough for PaddleOCR. Only the first request succeeds. After that server runs out of memory and restarts, so later uploads may fail until it's back up.
