Documents
Capture invoice PDFs into a table
Read supplier invoices (text or scanned PDFs), extract the invoice number, dates and total, and insert them into an Invoices data table.
Suppliers email Northwind their invoices as PDFs — most generated by their accounting system, a few scanned. This workflow reads each invoice, picks out the invoice number, the dates and the total, and adds a row to an Invoices data table. Invoices it cannot read are sent to a person.
Steps: start form → Read Document (OCR for scans) → Extract from Document → Branch by Condition → Data (insert), or a User Task.
The invoice from Exotic Liquids reads:
Invoice INV-2026-0042
Date: 15 October 2026
Bill to: Exotic Liquids, accounts@exotic.example
Product Qty Amount
Chai 10 $180.00
Chang 5 $95.00
Total due: NZ$275.00 by 2026-11-141. Data table and start form
Create Invoices with the columns invoice_number (text), supplier_email (text), invoice_date (date), due_date (date), total (number), currency (text) and pages (number). Give the workflow a start form with an Attachments field invoice (PDF only, at most 1 file).
2. Read the invoice
{
"type": "doc_read",
"label": "Read invoice",
"config": {
"source": "{{form.invoice}}",
"ocr": "auto",
"ocr_languages": "eng",
"output_var": "invoice"
}
}The canvas shows OCR auto → invoice. With OCR: Pages without text, a PDF from an accounting system is read from its text layer (OCR never starts), and a scan is recognised page by page. For a text PDF the run shows:
{ "variable": "invoice", "file": "INV-2026-0042.pdf", "format": "pdf", "pages": 2, "words": 33, "characters": 219, "rows": 2, "tables": 1, "truncated": false, "text_preview": "Invoice INV-2026-0042 Date: 15 October 2026 Bill to: Exotic Liquids, accounts@exotic.example Product Qty Amount Chai 10 $180.00 Chang 5 $95.00 Total due: NZ$275.00 by 2026-11-14 Page two: terms and conditions apply." }For a scan, each page in {{invoice.structure.pages}} has has_text_layer: false and an ocr entry with its confidence. OCR needs a provider on the instance; see Scanned PDFs and OCR.
3. Extract the values
Extract from Document takes text as well as files, so give it the text Read Document already has — the PDF is not read, or recognised, a second time:
{
"type": "doc_extract",
"label": "Extract invoice fields",
"config": {
"source": "{{invoice_text}}",
"extract_tables": false,
"patterns": [
{ "name": "invoice_number", "pattern": "Invoice\\s+(INV-[\\d-]+)" },
{ "name": "total_text", "pattern": "Total due:\\s*(\\S+)" }
],
"finders": ["emails", "dates", "amounts"],
"date_order": "DMY",
"output_var": "invoice_data"
}
}The canvas shows 2 patterns · emails, dates, amounts. The result:
{
"file": "text.txt",
"rows": [],
"values": { "invoice_number": "INV-2026-0042", "total_text": "NZ$275.00" },
"found": {
"emails": ["accounts@exotic.example"],
"dates": [{ "text": "15 October 2026", "date": "2026-10-15" }, { "text": "2026-11-14", "date": "2026-11-14" }],
"amounts": [
{ "text": "$180.00", "value": 180, "currency": "USD" },
{ "text": "$95.00", "value": 95, "currency": "USD" },
{ "text": "NZ$275.00", "value": 275, "currency": "NZD" }
]
}
}{{invoice_data_invoice_number}}isINV-2026-0042.- The amounts finder turns
NZ$275.00into a number and a currency. The total is the last amount on this layout:{{=LAST(PLUCK(invoice_data.found.amounts, "value"))}}is275and{{=LAST(PLUCK(invoice_data.found.amounts, "currency"))}}isNZD. The line items before it are read asUSDbecause a plain$is US dollars — another reason to take the total from the "Total due" line. - The dates finder gives ISO dates in the order they appear: the invoice date first, the due date last. Date order: Day first reads
03/04/2026as 3 April.
Patterns match within one line and go through the ReDoS guard. Adjust them to each supplier's wording (Invoice No\.?\s*(\S+), Amount due\s*(\S+)), or add one Extract step per supplier behind a condition on {{invoice_data.found.emails.0}}.
4. Check, then insert
Add Branch by Condition: when {{invoice_data_invoice_number}} is empty, the invoice could not be read — send it to a User Task for manual entry. You can also route scans with low OCR confidence there: {{=MIN(PLUCK(invoice.structure.pages, "ocr.confidence"))}} less than 70.
On the Else path, insert the row:
{
"type": "update_data_table",
"label": "Save invoice",
"config": {
"table_id": "<invoices-table-id>",
"table_name": "Invoices",
"operation": "insert",
"column_values": {
"invoice_number": "{{invoice_data_invoice_number}}",
"supplier_email": "{{invoice_data.found.emails.0}}",
"invoice_date": "{{=FIRST(PLUCK(invoice_data.found.dates, \"date\"))}}",
"due_date": "{{=LAST(PLUCK(invoice_data.found.dates, \"date\"))}}",
"total": "{{=LAST(PLUCK(invoice_data.found.amounts, \"value\"))}}",
"currency": "{{=LAST(PLUCK(invoice_data.found.amounts, \"currency\"))}}",
"pages": "{{invoice.metadata.pages}}"
}
}
}The run ends with a new Invoices row: INV-2026-0042, accounts@exotic.example, 2026-10-15, 2026-11-14, 275, NZD, 2.
Line items too
To store the lines as well, add a second Extract step on the file itself ("source": "{{form.invoice}}", "to_table": true) into an Invoice lines table: the table Product / Qty / Amount is found from the positions of the text on the page. That works for text PDFs only — tables are not detected on OCR'd pages — and, being best effort, for simple grids. See Extract from Document.
Messy layouts: use an agent
When suppliers' layouts vary too much for patterns, replace step 3 with Run Agent: put {{invoice.markdown}} in the prompt ("Return the invoice number, invoice date, due date, total and currency of this invoice") and give the agent declared outputs invoice_number, invoice_date, due_date, total and currency. They become workflow variables you map in the Data step the same way. Keep the condition in step 4: check that the values are there before you insert them.