Documentation
Everything you need to go from download to deployed SaaS.
Your First 30 Minutes
Follow these steps in order. You'll have a running SaaS with auth and billing by the end.
1. Unzip and install
2. Start a local database
You need PostgreSQL running locally. The easiest way is Docker:
Or use Homebrew (brew install postgresql@16), a hosted DB (Supabase, Neon), or any PostgreSQL instance.
3. Configure environment
Open .env and fill in the minimum to get started:
You can add Google OAuth, Stripe, and Resend keys later. The app runs without them — you just won't have OAuth, billing, or email features until configured.
4. Create database tables
This creates the users, accounts, sessions, and subscriptions tables. Run this again whenever you modify src/lib/server/db/schema.ts.
5. Start the dev server
Open http://localhost:5173. You should see the landing page. Magic link sign-in works once you add Resend keys. Google OAuth works once you add Google credentials.
6. Verify it works
- ☐ Landing page loads at localhost:5173
- ☐
/loginshows sign-in form - ☐
/pricingshows Free and Pro tiers - ☐ No errors in the terminal
Project Structure
src/ ├── lib/server/ │ ├── auth/ Auth.js config (Google + Email) │ ├── db/ Drizzle schema + connection │ ├── email/ Resend templates │ └── stripe/ Checkout, portal, webhooks ├── routes/ │ ├── +page.svelte Landing page │ ├── +layout.svelte Root layout with footer │ ├── login/ Sign-in (OAuth + magic link) │ ├── dashboard/ Auth-gated app with stats + quick actions │ ├── pricing/ Free/Pro with monthly/annual toggle │ ├── settings/ Profile + billing management │ ├── admin/ Role-gated admin panel │ └── api/ │ ├── billing/ POST /checkout, GET /portal │ └── webhooks/stripe/ Webhook handler ├── app.css Tailwind styles ├── app.html HTML shell └── hooks.server.ts Auth middleware
Stripe Billing Setup
Complete walkthrough to get subscriptions working.
1. Create a product and prices
- Go to Stripe Dashboard → Products
- Click "Add product" — name it (e.g., "Pro Plan")
- Add a recurring price — e.g., $24/month
- Optionally add a second price for annual billing (e.g., $228/year = 20% off)
- Copy each Price ID (
price_xxx) into your.env:
The Price ID is on the product detail page, not the product ID.
2. Set up webhooks (local dev)
Install the Stripe CLI and run:
Copy the whsec_xxx signing secret it prints into .env as STRIPE_WEBHOOK_SECRET.
3. Set up webhooks (production)
- Go to Stripe Dashboard → Developers → Webhooks
- Click "Add endpoint"
- URL:
https://yourdomain.com/api/webhooks/stripe - Select events:
checkout.session.completed,customer.subscription.updated,customer.subscription.deleted - Copy the signing secret into your production
STRIPE_WEBHOOK_SECRET
4. Test the full flow
- Sign in to your app
- Go to
/pricingand click "Upgrade to Pro" - Use Stripe test card:
4242 4242 4242 4242(any future date, any CVC) - After checkout, you should be redirected to
/dashboard?payment=success - Your plan should show "Pro" on the dashboard
- Check the terminal — the webhook should log the event
5. Go live
When ready for real payments:
- Switch from
sk_test_tosk_live_inSTRIPE_SECRET_KEY - Create live-mode prices and update
STRIPE_PRICE_MONTHLY/STRIPE_PRICE_ANNUAL - Add a live-mode webhook endpoint with the production URL
Authentication Setup
Two sign-in methods out of the box. Add more with one line of code.
Google OAuth
- Go to Google Cloud Console → Credentials
- Click "Create Credentials" → "OAuth client ID"
- Application type: Web application
- Authorized redirect URI:
http://localhost:5173/auth/callback/google - Copy the Client ID and Client Secret into
.env
In production, add your domain as an additional redirect URI: https://yourdomain.com/auth/callback/google
Magic Link Email
Works automatically once Resend is configured (see Email Setup below). Users enter their email, receive a one-time sign-in link, and click to authenticate.
Adding other providers
Auth.js supports 80+ providers. To add GitHub, for example:
Making a user admin
Connect to your database and run:
Admin users see the "Admin" link in the dashboard sidebar and can access /admin.
Email Setup
Transactional email via Resend. Required for magic link sign-in.
- Create a Resend account
- Verify your sending domain (so emails come from
[email protected]) - Copy your API key into
.envasRESEND_API_KEY - Set
FROM_EMAILto an address on your verified domain
Email templates live in src/lib/server/email/index.ts. Add new templates by creating new async functions — see the inline comments for examples.
Customization
Make the template your own.
Branding
- Search for
YourAppand replace with your app name (landing page, dashboard sidebar, pricing page) - Update
PUBLIC_APP_NAMEin.env(used in email subjects) - Change colors: search for
blue-500andblue-400acrosssrc/routes/and replace with your brand color - Edit the footer in
src/routes/+layout.svelte
Adding pages
Create a new directory in src/routes/ with a +page.svelte file. To require authentication, add a +page.server.ts that checks the session:
Adding database tables
- Define the table in
src/lib/server/db/schema.ts - Run
npx drizzle-kit push - Import and query in your routes — see the inline comments in schema.ts for an example
Updating pricing
Edit src/routes/pricing/+page.svelte to change the displayed prices, feature list, and plan names. Make sure the amounts match your Stripe prices.
Environment Variables
Database
DATABASE_URL PostgreSQL connection stringAuthentication
AUTH_SECRET Random string for session encryption — generate with: openssl rand -base64 32GOOGLE_CLIENT_ID Google OAuth client IDGOOGLE_CLIENT_SECRET Google OAuth client secretStripe
STRIPE_SECRET_KEY Stripe secret API key (sk_test_ for dev, sk_live_ for prod)STRIPE_WEBHOOK_SECRET Webhook signing secret (whsec_...)STRIPE_PRICE_MONTHLY Price ID for monthly plan (price_...)STRIPE_PRICE_ANNUAL Price ID for annual plan (price_...)RESEND_API_KEY Resend API key for sending emailFROM_EMAIL Sender email address (must be on a verified Resend domain)App
PUBLIC_APP_NAME App name used in email subjectsPUBLIC_APP_URL Your app URL (e.g. https://myapp.com)Deployment
Deploy to any platform that runs Node.js or Docker.
Docker (any platform)
Works with AWS ECS, Google Cloud Run, Kubernetes, or any Docker host.
Railway
- Push code to GitHub/GitLab
- Create a new project on Railway and connect your repo
- Add a PostgreSQL database service
- Railway auto-detects the Dockerfile — set all env vars in the Railway dashboard
- Railway provides
DATABASE_URLautomatically when you link the DB service
Fly.io
Production checklist
- Switch Stripe keys from
sk_test_tosk_live_ - Create live-mode Stripe prices and update env vars
- Add production webhook endpoint in Stripe Dashboard
- Add production domain to Google OAuth redirect URIs
- Set
PUBLIC_APP_URLto your production URL - Verify Resend domain for production email sending