The problem
An expense claim spreadsheet starts as a favor. Someone lists date, employee, merchant, category, amount and a link to a receipt. It works for a few claims a month. Then the company adds currencies, project codes, client billing, cost centers and partial approvals. The sheet grows columns. Finance starts each month by emailing people to ask where the receipts are.
Shared editing is the next problem. Two approvers update the same row. A formula gets overwritten. A hidden column holds a private note. The file is shared with a broad group, so anyone can see every claim. Receipt images sit in a folder with weak permissions. No one can say who changed an amount or when a claim moved from submitted to approved.
A spreadsheet has no workflow. It cannot block a claim that has no receipt. It cannot send a claim to a cost center owner. It cannot show a clean status to the employee. Expense claims need a state: draft, submitted, in review, approved, rejected, paid. Without that state, finance rebuilds the truth from email threads. Month end becomes a reconciliation exercise, not a close.
What a good solution looks like
The app should have one claim form with line items. Each line holds the date, merchant, category, amount, currency, receipt, note and coding. Coding means cost center, project or client. The app calculates the claim total from lines. It stores amounts as whole cents, not floating point numbers, so totals do not drift.
Status should be visible on every claim. A short timeline shows who submitted, who approved, what changed and when. Comments stay with the claim. Approvers can approve the full claim or individual lines. A rejection needs a reason. Delegation handles vacations. Notifications go to email or chat, with a link back to the claim.
The finance view is the part most spreadsheets get wrong. Finance needs filters by period, employee, category, cost center and status. It needs a CSV export with stable claim and line IDs. It needs a mark as paid action that records the payment date and reference. It needs duplicate warnings based on receipt hash, employee, date, amount and merchant. Receipts should open through short lived signed links, not public file shares.
Keep the employee experience small. A phone camera upload for receipts saves more time than a complex policy engine. Save a draft. Clone a repeated claim. Show the last status without a login maze. The goal is to remove the email chase, not to build a full travel and expense platform.
How to build it
Start with a data model that matches the claim, not the spreadsheet. Use tables for users, claims, claim lines, receipts, approvals, comments and payments. A claim row holds the employee, period, status, submit time and total. A line row holds the claim ID, expense date, category, amount in minor units, currency, converted amount if you need it, cost center, project and receipt ID. Store the original currency and the rate you applied, so finance can audit the conversion later.
Build the workflow on the server. Every status change goes through one function that checks the current state and the user role. For example, only a manager can approve a submitted claim, and only finance can mark it paid. Write the audit entry in the same database transaction as the status change. Use your identity provider for sign in, then map groups to roles: employee, approver, finance admin. Scope queries by role. An employee sees only their claims. An approver sees their team. Finance sees all.
Receipts need their own path. Upload the file straight to object storage with a presigned URL, then record the storage key in the database. Scan uploads for malware. Accept PDF, JPEG and PNG. Keep the original file and make a thumbnail for the list view. If you add OCR, let it suggest the merchant, date and total, then let the employee correct the values. Never make OCR the only source of truth.
For the first release, import the current spreadsheet and keep it read only. Map old categories to new ones. Run a dry import, check row counts and totals, then load. Build one CSV export that finance signs off on before go live. Add a written runbook for common admin tasks: adding a category, changing an approver, fixing a failed export and closing a period. A short training session with a live claim beats a long policy document.
Risks and how to manage them
Receipts are sensitive records. They can show card digits, home addresses, travel patterns and client names. Use private storage, encryption in transit and at rest, and short lived signed URLs. Restrict who can download receipts and log the download. Set a retention rule with finance and stick to it. Delete test data before launch.
Approval routing will break on edge cases. New hires may not have a manager yet. A manager may be the claimant. A contractor may sit outside the normal reporting line. Build an admin fallback queue for finance, with an audit entry for every override. Do not hardcode approvers in code. Read them from your identity provider or a small admin screen.
Duplicate claims need a warning, not an accusation. Flag a claim when a receipt hash already exists, or when the same employee, date, merchant and amount appear twice. Let finance clear the flag with a note. Keep rejected claims in the database. Deleting them destroys the history you need for a clean audit.
Cutover is a people problem. Some employees will keep emailing receipts. Give them a reply template with the app link and a short instruction. Run the old spreadsheet in read only mode after the cutover date. Do not let two systems accept new claims for long. Review the first close with finance and fix the export before adding new features.
How to measure success
Measure the claim cycle from submission to payment. Track how many claims arrive without a receipt, how many finance touches each claim needs, and how many duplicate flags are opened and cleared. These numbers come from the app, so keep the events you need. Compare the first month with your last spreadsheet month.
Track adoption by counting claims submitted in the app against claims that still arrive by email or chat. Check approver response time by step. Look at the support queue: if people ask how to split a line or find an old claim, the interface needs work. If finance asks for a new export column every week, the data model is missing a field.
For finance, the main test is reconciliation. The export totals should match the accounting ledger for the period. Payment references should be complete. Corrections after approval should be rare and visible in the audit trail. If a claim is changed after approval, the app should require a new approval.
If you want outside help, Cloudverg's Internal tool sprint at $16,500 fixed price builds one workflow in four weeks, with sign in, roles, history, import and exports. It is one option among others. A small custom app is one path. It is a direct one when the spreadsheet has become a daily drag on finance and employees.
Frequently asked questions
Can I replace an expense spreadsheet with a no-code tool?
Yes, for simple claims with one approver and basic receipt uploads. Check the tool for row level permissions, audit history, receipt access control and a clean finance export before you commit. No-code tools often hit limits when approval routing and accounting exports get specific.
What should an expense claim app store for each line?
Store the date, merchant, category, amount in minor units, currency, cost center or project, receipt link and note. Keep the claim level details separate: employee, period, approver, status and payment reference. This split makes exports and reporting much easier.
How do I stop duplicate expense claims?
Hash each receipt file and warn when the same hash appears again. Also compare employee, date, merchant and amount on submit. Let finance clear a flag with a note instead of blocking the claim outright.