All Workflows / Data & OCR
Data & OCR
Beginner 14 min READ

OCR receipts straight into a spreadsheet

Scan receipts, extract line items with OCR, and log them to Google Sheets automatically.

Target Architecture

A small pipeline that takes a photo of a receipt, sends it to an OCR (text-recognition) service to pull out the merchant, date, line items, and total, cleans up that data, and appends it to a Google Sheet as one row per item — with low-confidence scans routed to manual review instead of silently logging bad data.

INTEGRATED RUNTIMES:

Tools used

OCR API Google Sheets
Table of Contents
8 Sections

Overview

This guide explains a pattern for turning photographed receipts into clean, itemized rows in a Google Sheet, with no manual typing. A photo goes in one end; a spreadsheet row for each item comes out the other. Every technical term used along the way (OCR, service account, confidence score, and so on) is explained in the Glossary section below before it's used. Unlike a fully turnkey server setup, this guide describes the shape of the pipeline and the exact calls to the OCR and Sheets APIs — you'll need to host the small piece of code that ties them together yourself, on whatever backend you're comfortable with.

Before you begin

Budget about 30–45 minutes to read through this guide and adapt the code to your own OCR provider and hosting setup, plus however long it takes you to enable the Google Sheets API and create a service account (usually 10–15 minutes the first time). Costs vary: Google Sheets itself is free to use via the API, but most OCR providers charge per scan or per API call once you're past a free tier — check your chosen provider's pricing before processing large batches of receipts. This guide has been written from technical documentation and general best practice, but — like every guide currently on Workflow Vault — it is UNVERIFIED: it has not yet been run end-to-end and confirmed working by Workflow Vault's own testing. It also intentionally stops short of prescribing one specific OCR vendor or hosting platform, since either choice changes some of the exact setup steps; read each step as a well-researched pattern to adapt, not a guarantee or a fully turnkey deployment.

Glossary

OCR (Optical Character Recognition)
OCR is technology that reads text out of an image — for example, turning a photo of a receipt into the actual words and numbers printed on it. This guide relies on a third-party OCR service to do that reading rather than building text recognition from scratch.
OCR provider
The specific company or API you send images to for OCR, such as Google Cloud Vision or a receipt-specialized OCR service. Different providers return slightly different data shapes, so this guide uses a generic placeholder (see "ocrClient" below) that you'll connect to whichever provider you choose.
Ingest endpoint
A web address on a server you control that's ready to receive an uploaded file — in this case, a photographed receipt. This guide describes what that endpoint should do with the image once it arrives, but building and hosting the endpoint itself is left up to you, since the right choice (a Cloudflare Worker, a small Node.js server, and so on) depends on what you already use.
Service account
A special Google account meant for a program to use instead of a person. You create one in Google Cloud, download its credentials, and share your spreadsheet with its email address so your automation can write to Sheets without you typing a password anywhere.
API key
A secret string that identifies your application to a service and authorizes it to make requests. Most OCR providers issue one when you sign up; it should be stored as an environment variable, never written directly into your code.
Google Sheets API
A programmatic interface that lets code read from and write to a Google Sheet, the same way a person would by typing into cells — except done automatically, row by row, from a script.
Spreadsheet ID
A long string of letters and numbers in a Google Sheet's URL that uniquely identifies that specific spreadsheet to the Sheets API. You'll copy this out of your browser's address bar when you're viewing the target sheet.
Confidence score
A number an OCR provider returns alongside its guess at what text is in an image, indicating how sure it is that the reading is correct. A low score is a signal that the photo was blurry, dark, or otherwise hard to read, and the result may be wrong.
Line item
One individual product or charge on a receipt — for example, a single "Coffee $4.50" entry. A receipt with five purchases has five line items, each of which becomes its own row in the spreadsheet.
Normalization
Cleaning up raw extracted data into a consistent format before it's used further — for example, turning "$4.50", "4.50 USD", and "USD4.50" into the same plain number, or merging two OCR results that clearly describe the same item.

Prerequisites

  • [You'll need this already] A Google Cloud project with the Sheets API enabled, and a service account with edit access to your target spreadsheet. See the Glossary above for what a service account is.
  • [You'll need this already] An account and API key with an OCR provider capable of reading receipts, such as Google Cloud Vision or a dedicated receipt-OCR API. This guide does not pick a provider for you — you'll need to sign up with one and get its credentials before adapting the code below to match its actual SDK or REST API.
  • [You'll need this already] A small server or endpoint you control that can receive an uploaded image over the web. This guide describes what that endpoint should do once a photo arrives, but does not provide ready-to-deploy endpoint code — you can build one with any backend you're comfortable with, such as a Cloudflare Worker or a small Node.js/Express app.
  • [You'll need this already] Basic comfort reading JavaScript and making API calls from code. You do not need prior experience with OCR or the Sheets API specifically — every term used is explained in the Glossary above.
  • [Optional, not required] A mobile shortcut or share-sheet action for quickly sending a phone photo to your ingest endpoint, if you want hands-free capture instead of uploading through a form.

Where to Run This Automation

Where should you run this automation?

Don't have 24/7 hardware at home? Choose the setup that fits your budget and technical comfort.

Hardware & Cloud Guide

Best for Freelancers, Agencies, and 24/7 Client Pipelines

If you don't have a dedicated server running at home, a Virtual Private Server (VPS) gives you a dedicated Linux server in the cloud that stays online 24/7 even when your personal laptop is closed.

  • Cost: ~$4 to $6 / month (predictable flat fee, no per-execution surprises).
  • Providers: Hetzner Cloud, DigitalOcean, Linode / Akamai, Railway, or OVH.
  • Key advantage: You get a permanent public IP address and free SSL certificates, which are essential for receiving incoming Stripe, Airtable, or GitHub webhooks reliably.
Quick 1-liner to install Docker on any Ubuntu/Debian VPS: curl -fsSL https://get.docker.com | sh && sudo usermod -aG docker $USER
RECOMMENDED SPECS
RAM: 1GB – 2GB
vCPU: 1 Core
Storage: 20GB SSD
Uptime: 99.9%

Best for Hobbyists, Testing & Zero-Cost Experimentation

You can run this automation completely free on your everyday computer (Mac, Windows, or Linux) or on low-cost home hardware like a Raspberry Pi 4/5 or repurposed mini PC.

  • Cost: $0 / month (100% free).
  • Software needed: Docker Desktop (free for personal use) or native Node.js / Python.
  • Consideration: Automations will only execute while your computer is powered on and awake. To receive incoming webhooks locally, use tools like ngrok or Cloudflare Tunnels for secure local tunneling.
LOCAL CHECKLIST
Docker Desktop: Installed
RAM Overhead: ~400MB
Webhooks: Tunnel required

Best for Zero-Maintenance 1-Click Operations

If you don't want to manage Docker containers or terminal commands, you can run automations directly on managed serverless platforms or official SaaS plans:

  • Cloudflare Workers: Free tier includes 100,000 requests per day with 0ms cold starts.
  • n8n Cloud / Make / Zapier: Fully managed hosted platforms with automated backups and visual scenario builders.
  • Railway / Render: 1-click Git container deploys with auto-healing and managed PostgreSQL databases.
SERVERLESS TIERS
Cloudflare: 100k req/day free
GitHub CI: 2,000 min/mo free
Maintenance: Zero OS updates

Step-by-step implementation

Click the numbered phase buttons as you complete each step to track your live progress.

Phase Progress 0/5 completed (0%)
PHASE 1

Capture the receipt

Set up a way to get a photo of the receipt to your ingest endpoint — an HTTP address on a server you control that's ready to accept an image upload. The simplest option is a basic web form with a file input; a more convenient option, once the pipeline works, is a mobile shortcut that posts the photo directly from your phone's camera roll.

Why this matters

Every later step depends on the raw image being available somewhere your code can reach it. Starting with the simplest possible upload path (a form, or a single shortcut action) keeps this first step easy to test in isolation before you connect it to OCR.

Expected Output

After uploading a test photo, your server-side code should have the image's raw bytes available in memory or in a temporary file — confirm this by logging the file size, which should roughly match the photo's actual file size and not be zero.

PHASE 2

Run OCR extraction

Send the captured image to your OCR provider and ask for structured fields (merchant, date, line items, total) rather than a single block of raw text, if your provider supports that mode — it saves you from having to parse plain text yourself. The `ocrClient` in the code below is a stand-in for whichever SDK or REST client you set up for your chosen provider; replace it with the real client once you've signed up and installed it.

javascript
const result = await ocrClient.extractReceipt(imageBuffer);
// result: { merchant, date, items: [{ name, price }], total }
Why this matters

Requesting structured fields up front, instead of a wall of raw text, pushes the hardest part of the parsing work onto the OCR provider, which is generally better at it than a hand-rolled regex would be.

Expected Output

The response should be an object shaped roughly like the comment in the code below: a merchant name, a date, an array of items each with a name and price, and a total. Log this object once for a real receipt and manually check the numbers against the physical receipt before moving on.

PHASE 3

Normalize the data

Before anything touches the spreadsheet, clean up the OCR output: strip currency symbols and standardize decimal formatting so every price is a plain number (4.99, not "$4.99" or "4,99"), and check for near-duplicate line items that OCR sometimes splits into two rows by mistake.

Why this matters

Bad formatting is easy to fix once, here, before it becomes fifty inconsistent rows in a shared spreadsheet that someone else has to clean up by hand later.

Expected Output

After normalizing, log the item list once more and confirm every price is a plain number (not a string with a currency symbol) and that the count of items roughly matches what's actually printed on the receipt.

PHASE 4

Append to Google Sheets

Using an authenticated Sheets API client (built from your service account's credentials), append one row per line item to the target sheet, tagging each row with the receipt's merchant and date so rows from the same receipt can be grouped later. The `sheets` object in the code below refers to that already-authenticated client — setting up that authentication is a one-time step handled by whichever Google API client library you use (for example, the `googleapis` npm package with your service account key file).

javascript
await sheets.spreadsheets.values.append({
  spreadsheetId,
  range: 'Receipts!A:D',
  valueInputOption: 'USER_ENTERED',
  requestBody: {
    values: result.items.map(i => [result.date, result.merchant, i.name, i.price])
  }
});
Why this matters

Appending rather than overwriting means every run adds new data without touching what's already there, so a failed or repeated run can't accidentally erase previous receipts.

Expected Output

After the call resolves without throwing, refresh the actual spreadsheet in your browser — one new row per line item should appear in the Receipts sheet, in columns A through D, with the merchant and date matching the receipt you scanned.

PHASE 5

Flag low-confidence scans

If your OCR provider returns a confidence score below a threshold you choose, route that receipt to a manual review queue (even something as simple as a separate "Needs review" sheet tab) instead of writing it to the main spreadsheet.

Why this matters

A low-confidence result is often wrong in ways that aren't obvious at a glance — silently logging it would quietly pollute the spreadsheet with bad numbers that are harder to catch later than an obviously-blank row would be.

Expected Output

Test this by deliberately photographing a receipt at a steep angle or in dim light. That scan should land in your review queue instead of the main Receipts sheet — if it still writes to the main sheet, your confidence threshold is set too low.

Workflow architecture

Images flow through a single ingest endpoint (which you build and host) into the OCR provider, then through a normalization step before touching the spreadsheet. Keeping normalization separate from extraction means you can swap OCR providers later without changing how data lands in Sheets. The confidence check sits as a gate right before the Sheets write, so low-quality reads never make it into the shared spreadsheet in the first place.

Final result

Receipts logged as clean, itemized rows in Google Sheets within seconds of being photographed, with no manual data entry — and receipts OCR struggled to read routed to a review queue instead of silently corrupting the sheet. As noted above, this guide is unverified by Workflow Vault; treat the outcome as expected-but-not-yet-confirmed until you've run it yourself against a real OCR provider.

Recommended Extensions & Scaling

  • Add category tagging, such as meals, travel, or supplies, to each logged expense.
  • Send a weekly summary email of logged spend.
  • Support multi-currency receipts with automatic conversion.

Troubleshooting

⚠ OCR returns garbled or empty text
Root Cause

The photo is low-resolution, poorly lit, or the receipt was captured at a steep angle.

Solution

Add basic image preprocessing such as cropping and deskewing before sending to the OCR provider, or prompt the user to retake the photo.

⚠ Sheets API call fails with a 403
Root Cause

The service account was never given edit access to the specific spreadsheet.

Solution

Share the spreadsheet with the service account's email address as an Editor.

⚠ Line item totals don't match the receipt total
Root Cause

OCR occasionally splits one line item into two separate rows.

Solution

Add a validation step that sums extracted line items and flags the receipt for manual review if it doesn't match the extracted total.

⚠ OCR calls fail with an authentication error
Root Cause

The OCR provider's API key is missing from the environment, wasn't loaded before the request fired, or was typed into the code directly instead of read from an environment variable.

Solution

Confirm the key is set as an environment variable in your hosting platform's settings and that your code reads it from there, then redeploy or restart the process.