Send Invoices People Open.
A full-stack invoice builder for creating, customizing, saving, and exporting polished invoices from a responsive live editor.
Payload is a production-deployed invoice application built as a Turborepo. It combines a Next.js frontend, a Bun + Express API, PostgreSQL through Supabase, Prisma ORM, Supabase Auth, and private object storage.
The editor is designed around a simple workflow: enter invoice data, see the document update in real time, choose a visual style, save it, and export the final invoice as PDF or PNG.
Guests can create an invoice without signing in. When they later authenticate with Google, the guest invoice is migrated to their account instead of being lost.
- Live invoice editor with responsive Form, Preview, and Both view modes
- A4 PDF preview while editing
- Five invoice templates: Default, Vercel, GitHub, Stripe, and Notion
- Customizable template, font, light/dark mode, and accent color
- Company and client details with custom fields
- Logo and signature uploads
- Line items with quantity, unit price, totals, tax, discount, and additional billing details
- Payment information, notes, and terms
- PDF and PNG export
- Google OAuth through Supabase
- Guest invoice flow with account migration after sign-in
- Invoice dashboard with pagination, statuses, editing, and deletion
- Private Supabase Storage for invoice assets
- Cookie-based authentication and ownership checks on protected invoice operations
| Area | Technology |
|---|---|
| Monorepo | Turborepo, Bun workspaces |
| Frontend | Next.js 16, React, TypeScript |
| UI | Tailwind CSS v4, shadcn/ui |
| Client state | Zustand |
| HTTP client | Axios |
@react-pdf/renderer, react-pdf, pdfjs-dist, react-pdf-tailwind |
|
| Backend | Bun, Express, TypeScript |
| Validation | Zod |
| ORM | Prisma |
| Database | PostgreSQL on Supabase |
| Authentication | Supabase Auth with Google OAuth |
| Storage | Supabase Storage |
| Frontend hosting | Vercel |
| Backend hosting | Render |
| Health monitoring | UptimeRobot |
┌──────────────────────┐
│ Next.js Web │
│ Vercel │
└──────────┬────────────┘
│
Axios + credentials
│
┌──────────▼────────────┐
│ Bun + Express API │
│ Render │
└──────────┬─────────────┘
│
┌────────────┴─────────────┐
│ │
Prisma Supabase SDK
│ │
┌──────────▼──────────┐ ┌───────────▼──────────────┐
│ PostgreSQL │ │ Supabase Auth │
│ Supabase │ │ + Private Storage │
└──────────────────────┘ └───────────────────────────┘
Guest opens Payload
│
▼
guestId HttpOnly cookie set
│
▼
Guest invoice stored in PostgreSQL
│
▼
User signs in with Google
│
▼
Guest invoice migrated to the authenticated user
│
▼
guestId cookie cleared
Invoice writes are ownership-checked before database updates. Nested invoice data is persisted through Prisma transactions so related invoice fields are updated together.
payload/
├── apps/
│ ├── web/ # Next.js frontend
│ └── server/ # Bun + Express API
├── packages/
│ ├── db/ # Prisma schema and database client
│ └── ui/ # Shared UI components
├── package.json
├── turbo.json
└── bun.lock
The repository uses workspace packages so the frontend, backend, database layer, and shared UI can evolve inside one codebase.
- Node.js 20+
- Bun
- PostgreSQL database
- Supabase project
- Google provider configured in Supabase Auth
git clone https://github.com/SAHILSHANGLOO35/payload.git
cd payloadbun installBackend
PORT=8000
NODE_ENV=development
FRONTEND_URL=http://localhost:3000
GOOGLE_REDIRECT_URL=http://localhost:8000/api/v1/google/auth/callback
DATABASE_URL=
SUPABASE_URL=
SUPABASE_PUBLISHABLE_KEY=
SUPABASE_SERVICE_ROLE_KEY=
JWT_SECRET=Frontend
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000/api/v1Keep the Supabase service-role key on the server only. It must never be exposed through a
NEXT_PUBLIC_*variable.
For local development:
- Enable the Google provider in Supabase Auth
- Allow
http://localhost:8000/api/v1/google/auth/callbackas an auth redirect URL - Create a private storage bucket named
invoice-assets - Point
DATABASE_URLto the PostgreSQL database used by the project
cd packages/db
bunx prisma generate
cd ../..The database should use the schema defined by the Prisma setup in packages/db. Apply that schema using the Prisma migration workflow you use for your environment.
From the repository root:
bun devDefault local services:
- Web:
http://localhost:3000 - API:
http://localhost:8000
bun dev
bun run build
bun run lint
bun run typecheck
bun run formatThese commands are orchestrated through Turborepo across the workspaces.
The backend is mounted under /api/v1.
GET /api/v1/google/auth/login
GET /api/v1/google/auth/callback
GET /api/v1/google/auth/user
POST /api/v1/google/auth/logout
POST /api/v1/invoices
GET /api/v1/invoices
GET /api/v1/invoices/:id
PUT /api/v1/invoices/:id
PUT /api/v1/invoices/:id/status
DELETE /api/v1/invoices/:id
Invoice listing supports pagination through page and limit query parameters.
GET /health
The production health endpoint is used by UptimeRobot to monitor the Render service.
Payload uses its own JWT after Supabase completes the Google OAuth exchange.
The JWT is stored in an HttpOnly cookie, and frontend requests that require session state use credentials.
Local development uses a same-site-friendly cookie configuration. In production, the Vercel frontend and Render backend are on different sites, so cookies are configured with secure cross-site attributes.
The guest session uses the same cookie model through a separate guestId cookie.
Invoice logos and signatures are stored in the private Supabase bucket:
invoice-assets
Assets are organized per invoice and accessed through signed URLs instead of making the bucket public.
The current production setup is intentionally split by responsibility:
| Layer | Provider |
|---|---|
| Frontend | Vercel |
| Backend | Render |
| Database | Supabase PostgreSQL |
| Authentication | Supabase Auth |
| Image storage | Supabase Storage |
| Monitoring | UptimeRobot |
The backend is deployed from the monorepo root because it depends on the shared database workspace.
Build command
bun install && cd packages/db && bunx prisma generateStart command
cd apps/server && bun startProduction backend
https://payload-ogpt.onrender.com
The frontend is deployed from apps/web.
Production frontend environment variable
NEXT_PUBLIC_BACKEND_URL=https://payload-ogpt.onrender.com/api/v1Live app
https://payload-web-one.vercel.app
The important URL values are:
NODE_ENV=production
FRONTEND_URL=https://payload-web-one.vercel.app
GOOGLE_REDIRECT_URL=https://payload-ogpt.onrender.com/api/v1/google/auth/callbackProduction secrets such as the database URL, JWT secret, and Supabase server credentials should be configured through Render's environment settings rather than committed to the repository.
- JWT authentication is stored in HttpOnly cookies
- Production cookies use secure cross-site settings
- Invoice reads and writes verify user or guest ownership
- Request payloads are validated before persistence
- Nested invoice updates use Prisma transactions
- Supabase Storage remains private
- Stored assets are served using signed URLs
- Service-role credentials stay on the backend
Contributions, bug reports, and improvements are welcome.
For code changes:
git checkout -b feat/your-changeBefore opening a pull request, run:
bun run lint
bun run typecheck
bun run buildKeep pull requests focused and include enough context to explain the problem being solved.
Built and maintained by Sahil Shangloo.
If Payload is useful to you, consider starring the repository.