Expense capture: snap or drop a receipt, fill in the details, or let your OCR do it, and submit.
"use client"
import { toast } from "sonner"
import { ReceiptUpload } from "@/components/receipt-upload"
/** Stand-in for your OCR or AI extraction. */
async function extract() {
await new Promise((resolve) => setTimeout(resolve, 900))
return { merchant: "Blue Bottle Coffee", amount: "14.50", category: "Meals" }
}
export default function ReceiptUploadExample() {
return (
<div className="w-full max-w-xl">
<ReceiptUpload
extract={extract}
onSubmit={(expense) =>
toast.success("Expense submitted", {
description: `${expense.merchant} · ${expense.amount}`,
})
}
/>
</div>
)
}
Installation
npx shadcn@latest add @uploadcn/receipt-uploadUsage
import { ReceiptUpload } from "@/components/receipt-upload"With OCR, the form fills itself. Textract's expense model and Azure's
prebuilt-receipt return the fields directly; other engines are read from the text:
import { ocrEndpoint } from "@uploadcn/core"
;<ReceiptUpload
recognize={ocrEndpoint("/api/ocr")} // npx shadcn@latest add @uploadcn/ocr-route
onSubmit={(expense) => createExpense(expense)}
/>Or extract the fields yourself, with any API or model:
<ReceiptUpload
extract={async (item) => {
const response = await fetch(`/api/receipts/${item.result.key}/ocr`)
return response.json() // { merchant, date, amount, category }
}}
onSubmit={(expense) => createExpense(expense)}
/>On phones the picker opens the rear camera (capture="environment"). Fields the user already typed are never overwritten. receiptFromOcr(result) is exported if you want the same mapping elsewhere. See the OCR guide for providers.
Props
Prop
Type