A screenshot of a table should just be a spreadsheet.
Drop a photo of a table. Get CSV.
$9 lifetime. Processing stays in the browser.
Sheetshot is a tiny paid web product: you drop a screenshot (or phone photo) of a table — bank statement, marks sheet, price list, dashboard, Excel on a monitor — and get an editable grid you can download as CSV or .xlsx. OCR and grid reconstruction run entirely in the browser with Tesseract.js. The image never leaves the device.
npm install
cp .env.example .env.local # optional until you hook Google + Polar
npm run devOpen http://localhost:3000. The landing page has a live dropzone; the app lives at /app. Click Try a sample to run the pipeline without uploading a file.
npm run build
npm start
npm testSample table images are generated with:
python3 scripts/generate-samples.py(pillow is required for that script: pip install pillow.)
- The image is downscaled, EXIF-rotated, contrast-stretched, inverted if it looks like a dark screenshot, and lightly deskewed.
- Tesseract.js (WebAssembly) returns words plus bounding boxes.
- Rows are clustered by Y, columns by X. Nearby numeric fragments such as
1,00,000are glued into1,00,000. - You edit cells, then download CSV /
.xlsxor copy TSV.
The first three exports need no account. Lifetime unlock is a Polar order on the Google account you sign in with.
| Variable | Purpose |
|---|---|
POLAR_ACCESS_TOKEN |
Organization access token from Polar (checkouts:write, orders:read, customers:read) |
POLAR_PRODUCT_ID |
Product ID for Sheetshot Lifetime |
SUCCESS_URL |
Absolute URL Polar redirects to after payment, e.g. https://your-domain.com/success?checkout_id={CHECKOUT_ID} |
POLAR_SERVER |
sandbox or production |
AUTH_SECRET |
Auth.js secret (npx auth secret). Required for real sign-in |
AUTH_GOOGLE_ID |
Google OAuth client ID |
AUTH_GOOGLE_SECRET |
Google OAuth client secret |
AUTH_URL |
Canonical site URL. Production: https://sheetshot-five.vercel.app. Local: http://localhost:3000 |
If Polar vars are missing, /checkout does not crash. It redirects to /unlock?reason=not-configured. If Google vars are missing, Sign in and Unlock go to /unlock?reason=google-not-configured instead of crashing. In NODE_ENV=development a clearly labeled Local DEV unlock writes sheetshot_unlocked=true so you can test the paid UI without Polar.
- Google Cloud Console → APIs & Services → Credentials → OAuth client ID (Web application).
- Authorized JavaScript origins:
https://sheetshot-five.vercel.appandhttp://localhost:3000. - Authorized redirect URIs:
https://sheetshot-five.vercel.app/api/auth/callback/googlehttp://localhost:3000/api/auth/callback/google
- Copy the client ID and secret into
AUTH_GOOGLE_ID/AUTH_GOOGLE_SECRET. SetAUTH_SECRET. SetAUTH_URLto the origin you are running.
Sign-in is Auth.js / NextAuth with the Google provider (next-auth). The header shows Sign in, then avatar + Sign out.
-
Create an account at Polar. Use sandbox.polar.sh while testing (
POLAR_SERVER=sandbox). -
Create an organization.
-
Products → New product
- Name:
Sheetshot Lifetime - Pricing: one-time, $9 USD (not a subscription)
- Name:
-
Copy the product ID (from the product page / URL). That is
POLAR_PRODUCT_ID. -
Organization settings → developers / access tokens → create an organization access token with checkout, orders read, and customers read. That is
POLAR_ACCESS_TOKEN. -
Set
SUCCESS_URLto your deployed origin plus/success?checkout_id={CHECKOUT_ID}. Polar substitutes the checkout id. Locally:SUCCESS_URL=http://localhost:3000/success?checkout_id={CHECKOUT_ID} -
Restart
npm run dev. Sign in with Google, then visit/checkout(or click Unlock). Checkout sends PolarcustomerEmail(and name) from the Google session. After payment Polar sends the buyer to/success, which does not set a localStorage paid flag. It verifies the signed-in email against Polar orders/customers. Visiting/successlogged out prompts Google sign-in, then the same Polar lookup.
On any later device: Sign in with Google → Sheetshot looks up Polar by that email → if there is a paid Sheetshot Lifetime order, the session is unlocked (paid JWT claim).
Checkout is the Polar Next.js adapter at /checkout (@polar-sh/nextjs), following Polar’s Next.js adapter docs. The route injects ?products=$POLAR_PRODUCT_ID plus the signed-in email.
Three successful exports (CSV, .xlsx, or Copy TSV) per browser, counted in localStorage as sheetshot_export_count. No login required for those three. After that, this browser cannot start a new OCR run, edit or copy the last grid, or download until the Google account is unlocked via Polar. A paywall modal points at /checkout. Closing it with Not now leaves those locks in place.
Teams, history sync, server-side OCR, subscriptions, Chrome extension.