AI-Receipt-Scanner
By (author)Haseeb Asim
Tech stack:
TS
JS
Next.js
React
Node.js
Supabase
PG
TW
This is a clean, decoupled, and production-ready implementation of an AI-powered receipt scanner. It enables users to upload grocery receipts, parses ...
read more
read more
AI Contributions:2
Published At:August 12, 2026
Files Included:11 file(s)
Updated At:October 22, 2026
Read Me
# Standalone Next.js + Gemini AI + Supabase Receipt Scanner Module
This is a clean, decoupled, and production-ready implementation of an AI-powered receipt scanner. It enables users to upload grocery receipts, parses them using Google Gemini 1.5 Flash, performs server-side fuzzy matching against a database catalog, and lets the user review and confirm items to add to their inventory.
## Features
- **Drag-and-Drop Upload UI**: A fully styled Tailwind CSS file uploader supporting PNG, JPG, WebP, and HEIC receipt formats.
- **AI Extraction (Gemini 1.5 Flash)**: Accurately identifies items, quantities, and units from receipt images using Gemini's multimodal features.
- **Graceful Developer Fallback**: Includes a local regex-based parsing fallback if Gemini credentials are not set.
- **Fuzzy Item Matching**: Server-side matcher that maps extracted items to an existing catalog.
- **Review & Edit Stage**: Interactive UI to view matched vs. unmatched items, skip items, edit unmatched item names, and modify quantities/units.
- **Supabase Integration**: Stores scan logs in `receipt_scans`, stores individual extraction results in `receipt_scan_items`, and uploads images to a private `receipts` Supabase Storage bucket.
- **Atomic Merges**: Automatically adds matching items to the inventory or increases the quantity of existing items.
---
## Directory Structure
```text
module-5-bawarchee-gig/
├── src/
│ ├── app/
│ │ └── api/
│ │ └── receipt/
│ │ ├── scan/
│ │ │ └── route.ts # POST /api/receipt/scan
│ │ └── confirm/
│ │ └── route.ts # POST /api/receipt/confirm
│ ├── components/
│ │ └── inventory/
│ │ └── ReceiptScanModal.tsx # Complete 3-step UI component
│ └── lib/
│ ├── gemini.ts # Gemini API client wrapper & prompt
│ ├── receipt-api-types.ts # Shared TypeScript definitions
│ └── supabase/
│ ├── server.ts # Supabase server-side client creator
│ └── types.ts # Database table types
├── schema.sql # Database schema & RLS policies
└── package.json # Minimal package dependencies
```
---
## Installation & Setup
### 1. Database Setup
Execute the SQL code inside `schema.sql` inside your Supabase project's SQL Editor. This will:
1. Create the `receipt_scans` and `receipt_scan_items` tables.
2. Enable Row Level Security (RLS) on both tables.
3. Add security policies restricting users to their own scan records.
4. Grant standard permissions.
### 2. Storage Setup
1. In the Supabase dashboard, go to **Storage** and create a private bucket named `receipts`.
2. Add a policy allowing users to manage their own folder. You can use the following definition:
- **Allowed operations**: `SELECT, INSERT, UPDATE, DELETE`
- **Target**: `receipts`
- **Condition**: `(role() = 'authenticated'::text) AND ((storage.foldername(name))[1] = (auth.uid())::text)`
### 3. Environment Variables
Add the following key to your `.env.local` file:
```env
GOOGLE_GENERATIVE_AI_API_KEY=your_gemini_api_key_here
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
```
### 4. Integration
1. Drop the files inside `src/` directly into your Next.js project.
2. In your dashboard or panel component, import and open the `ReceiptScanModal`:
```tsx
import { useState } from 'react';
import { ReceiptScanModal } from '@/components/inventory/ReceiptScanModal';
export default function MyDashboard() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>📷 Scan Receipt</button>
<ReceiptScanModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onItemsAdded={() => {
// Refresh your pantry/inventory list here
console.log("Inventory updated!");
}}
/>
</div>
);
}
```
---
## API Endpoints
### 1. `POST /api/receipt/scan`
- **Request**: Multipart `formData` containing a `file` field with the receipt image.
- **Response**: `200 OK`
```json
{
"scan_id": "uuid-here",
"image_url": "supabase-storage-url-here",
"items": [
{
"raw_text": "organic milk 1 gal",
"item_name": "milk",
"catalog_item_id": "catalog-item-uuid",
"category": "Dairy & Eggs",
"quantity": 1,
"unit": "gal",
"confidence": 0.95
}
]
}
```
### 2. `POST /api/receipt/confirm`
- **Request**: `application/json`
```json
{
"scan_id": "uuid-here",
"confirmed_items": [
{
"item_name": "milk",
"catalog_item_id": "catalog-item-uuid",
"category": "Dairy & Eggs",
"quantity": 1,
"unit": "gal"
}
]
}
```
- **Response**: `200 OK`
```json
{
"success": true,
"added_items_count": 1
}
```