Skip to Content
Extras

Extras

Theming

Each tenant can have its own color theme. There are 22 built-in themes to choose from:

default, neutral, stone, zinc, gray, amber, blue, cyan, emerald, fuchsia, green, indigo, lime, orange, pink, purple, red, rose, sky, teal, violet, yellow

Setting a theme

  1. Go to AdminTenants → select your tenant.
  2. Choose a theme from the Theme dropdown.
  3. Save. The frontend updates immediately (via revalidation).

How it works

  • The frontend wraps tenant pages in a TenantThemeProvider (client component).
  • The provider sets a data-tenant-theme attribute on the <html> element (e.g. data-tenant-theme="blue").
  • CSS selectors in src/styles/themes.css override color variables (using oklch) for each theme.
  • The default theme (or no theme) uses the base color scheme from globals.css.

Dark mode

All themes support dark mode. The dark variant is activated by the .dark class on <html> (standard Tailwind dark mode). Theme overrides apply to both light and dark modes.

Custom themes

To add a custom theme:

  1. Add the theme name to THEME_OPTIONS in src/lib/themes.ts.
  2. Add a CSS selector in src/styles/themes.css:
[data-tenant-theme='my-theme'] { --primary: 60% 0.15 250; --primary-foreground: 98% 0 0; /* ... other color variables */ }
  1. Run pnpm run generate:types to update the TypeScript types for the select field.

Commands reference

CommandDescription
pnpm devStart the development server
pnpm buildBuild for production
pnpm startStart the production server
pnpm run generate:typesRegenerate Payload TypeScript types after schema changes
pnpm run generate:importmapRegenerate admin import map after changing custom components
pnpm run tsTypeScript type check (tsc --noEmit)
pnpm run lintRun ESLint
pnpm run testRun integration and E2E tests

Cron / background jobs

Not included by default. If you need scheduled tasks (e.g. syncing external data, cleanup, sending digest emails), you can:

  • Add an API route (e.g. /api/cron/your-job) and call it from an external cron service (e.g. Railway cron, Vercel Cron Jobs, or cron-job.org ).
  • Protect the route with a CRON_SECRET env var and check it in the handler.

Troubleshooting

Tenant not found / blank page

The request host must exactly match a tenant’s domain field in the admin, including the port for local dev. For example, if your tenant domain is localhost:3000, visiting 127.0.0.1:3000 will not match.

Types out of date

After changing any collection, global, or field configuration:

pnpm run generate:types

This regenerates payload-types.ts so TypeScript stays in sync with your schema.

Admin components not found

After creating or modifying custom admin components:

pnpm run generate:importmap

This regenerates the import map that Payload uses to resolve component paths.

OAuth redirect mismatch

If Google sign-in fails with a redirect URI mismatch, ensure the redirect URI in Google Cloud Console matches the tenant domain exactly:

https://your-tenant-domain.com/api/auth/callback/google

You need one redirect URI per tenant domain where Google sign-in is used.

Media lost after deploy

Local file storage is ephemeral on Railway and Vercel. Use S3 storage for production so media uploads persist.

Build fails on environment variables

If the build fails because some env vars aren’t set (common in CI/Docker):

SKIP_ENV_VALIDATION=1 pnpm build

This skips the @t3-oss/env-nextjs validation at build time. Env vars are still validated at runtime.

Stripe webhooks not firing

  1. Check the webhook URL in the Stripe Dashboard  — it should be https://your-domain.com/api/webhooks/stripe.
  2. Verify the signing secret matches STRIPE_WEBHOOKS_ENDPOINT_SECRET.
  3. For local dev, use the Stripe CLI : stripe listen --forward-to localhost:3000/api/webhooks/stripe.
  4. Check the webhook logs in Stripe for error details.

Chrome redirects .dev domains to HTTPS

Chrome has .dev in its HSTS preload list, so it forces HTTPS for any .dev domain. For local development, use .local or .test TLDs instead. See Configure Tenants for details.

Database connection errors

  • Ensure your MongoDB instance is running and the connection string is correct.
  • For MongoDB Atlas, allow your IP address (or 0.0.0.0/0 for Railway/Vercel) in the Atlas network access settings.
  • The app uses DATABASE_PUBLIC_URI at build time and DATABASE_PRIVATE_URI at runtime. For local dev, both can be the same.