Guests scan a QR code, type their name, and upload photos, videos, or voice messages. Files go straight to a Google Drive folder you own; metadata lands in Cloudflare D1. Runs entirely on Cloudflare's free tier.
- Upload page: photos (≤ 25 MB), videos (≤ 50 MB), voice notes (≤ 3 min), written messages.
- Offline-resilient queue — blobs persist in IndexedDB across tab closes, browser restarts, and long disconnects; auto-resumes when the connection returns.
- Shared gallery with infinite scroll, kind filters, 30-second live refresh, and a lightbox with download/share (Web Share API on mobile,
<a download>fallback on desktop). - Admin page for hiding or deleting entries; deletion removes the file from Drive too.
- Per-table QR codes (
?table=7auto-tags uploads). - Gallery thumbnails served by Google's CDN — zero Worker traffic per image load.
Everything fits in free tiers for a typical one-day event.
| Free tier | |
|---|---|
| Cloudflare Pages + Functions | 100k req/day |
| Cloudflare D1 | 5 GB, 25M reads/day |
| Google Drive | 15 GB |
Edit public/config.js before deploying:
export const WEDDING = {
names: { pl: "Anna i Jan", en: "Anna & Jan" },
eventDate: { pl: "12.09.2026", en: "Sep 12, 2026" },
lead: { pl: "Podziel się zdjęciem…", en: "Drop in photos…" },
themeColor: "#6d7c3d",
};config.example.js has annotated placeholders to copy from.
npm install
- console.cloud.google.com → new project → enable Google Drive API.
- OAuth consent screen → External → scope:
drive.file→ Publish app (stays in Production so tokens don't expire after 7 days). - Credentials → Create OAuth client ID → Desktop app → download JSON as
oauth-client.json(git-ignored). - Create a Drive folder for uploads; copy its ID from the URL.
npm run auth
Browser opens, you approve, terminal prints the refresh token.
npx wrangler login
npx wrangler d1 create wedding-photos # paste the returned database_id into wrangler.toml
npm run db:init
Set secrets:
npx wrangler pages secret put GOOGLE_CLIENT_ID
npx wrangler pages secret put GOOGLE_CLIENT_SECRET
npx wrangler pages secret put GOOGLE_REFRESH_TOKEN
npx wrangler pages secret put DRIVE_FOLDER_ID
npx wrangler pages secret put ADMIN_TOKEN # any random 32-char string
npm run db:init:local
npm run dev
Open http://localhost:8788.
npm run deploy
node scripts/make-qr.mjs https://your-app.pages.dev # one general QR
node scripts/make-qr.mjs https://your-app.pages.dev 1 10 # + one per table
Output lands in qr-output/ as PNG and SVG. Use SVG for print.
/admin.html?token=<ADMIN_TOKEN> — hide entries from the gallery or delete them (and the Drive file) permanently.
Drive files are granted anyone with link: reader so Google's CDN can serve gallery thumbnails. "Hide" removes an entry from the feed but does not revoke the Drive link — use Delete to fully remove content.
The admin token travels in the query string and will appear in browser history and server logs. The backend also accepts an x-admin-token header if you want to avoid that.
Playwright e2e suite covering upload, offline queue, gallery, lightbox, and download. No Cloudflare credentials needed — API calls are mocked.
npx playwright install chromium # one-time
npm test
npm run test:ui # opens the UI runner
Specs live in tests/e2e/. Failing runs keep a trace at test-results/<name>/trace.zip.
MIT