Litestore · Developer guide
Getting started

Configuration

This page tells you which environment variables Litestore needs, which ones are optional, and what each optional group turns on.

You configure Litestore's connections to outside services through environment variables. This page lists the ones the app needs to start and explains what each optional group adds.

env.ts defines which variables exist. It is a createEnv schema from @t3-oss/env-nextjs that validates every variable at startup. It also splits them into server-only values and NEXT_PUBLIC_* values that the browser can read.

Start your local file from .env.example. It lists every key that env.ts declares except VERCEL_URL, which Vercel injects for you. Most of its values are empty strings.

Environment variables only hold credentials and switches. The things an operator adjusts day to day are in the admin under app/admin/settings/, and they change without a deploy.

What "missing" means

Two behaviours of the schema decide whether a variable counts as set.

The schema turns on emptyStringAsUndefined, so KEY="" is treated the same as not setting KEY at all. The empty values in .env.example don't satisfy a required variable.

Validation runs when the module loads. If one required value is absent, every request fails with Invalid environment variables, and the error names the exact keys. The failure doesn't wait for the route or code path that reads the variable.

If you set SKIP_ENV_VALIDATION, the check is skipped entirely. The email script sets it, and vitest.config.ts sets it for the unit tests.

Required

Every variable in this section is required in env.ts. The app won't start without them.

NEXT_PUBLIC_SITE_URL
NEXT_PUBLIC_SITE_EMAIL
DATABASE_URL
BETTER_AUTH_URL
BETTER_AUTH_SECRET
REDIS_REST_URL
REDIS_REST_TOKEN
RESEND_API_KEY
RESEND_SENDER_EMAIL
S3_REGION
S3_BUCKET
S3_ACCESS_KEY
S3_SECRET_ACCESS_KEY

NEXT_PUBLIC_SITE_URL is the public URL of your store, and it must be a valid URL. Litestore puts it into emails, feeds, JSON-LD and checkout links, so it matters well beyond links inside the browser.

NEXT_PUBLIC_SITE_EMAIL is the public contact address, and it must be a valid email.

DATABASE_URL is the Postgres connection string, and it must be a valid URL.

BETTER_AUTH_URL is the base URL for sign-in. In most setups it is the same as the site URL. BETTER_AUTH_SECRET signs sessions.

REDIS_REST_URL and REDIS_REST_TOKEN are the Upstash Redis REST endpoint and its token.

RESEND_API_KEY matters more than its place in the list suggests. Litestore signs people in with a magic link, so without this key nobody can log in, including into the admin. RESEND_SENDER_EMAIL is the from-address for transactional email, and it must be a valid email.

The four S3_* values set up media storage. Any S3-compatible provider works.

Postgres 15+Your data
RedisCache and rate limits
S3 storageImages
LitestoreOne Next.js app
StorefrontAdmin
ResendEmail and sign-in links
InngestBackground jobs
StripeOptional
The required variables point Litestore at Postgres, Redis, S3 storage and Resend, and the optional ones add services like Stripe.

Defaults

You can leave these five variables unset, because they have defaults:

  • PORT (8000)
  • NODE_ENV (development)
  • VERCEL_ENV (development)
  • AI_PROVIDER (google)
  • ENABLE_TAXES (false)

VERCEL_URL is optional. env.ts adds https:// in front of whatever value Vercel injects.

.env.example also has DB_USER, DB_PASSWORD and DB_NAME. compose.yml reads those, and env.ts does not.

Local development

checkRateLimit in lib/rate-limit/presets.ts returns success immediately when isDev, so the limiter never calls Redis locally and the two REDIS_REST_* values only need to be set. The Self-host readiness panel does call redis.ping(), so with placeholder values it will report Redis down.

Payments with Stripe

Two variables turn on payments.

STRIPE_SECRET_KEY is what allows checkout to take payment at all.

STRIPE_WEBHOOK_SECRET is what app/api/stripe/webhooks/route.ts uses to verify incoming events.

Without Stripe, shoppers can still browse the store and fill a cart. Only checkout switches off.

Tax has its own switch, and it helps to keep the two apart. ENABLE_TAXES (default false) decides whether Litestore charges any tax at all.

When tax is on, each channel's stored taxProvider picks one of two behaviours:

  • manual charges the flat channel rate.
  • stripe_tax charges real tax for the shopper's jurisdiction. If Stripe returns any error, it falls back to the flat rate.

If the stored provider name is neither of these, Litestore logs a warning and charges the flat rate.

AI

AI_PROVIDER picks google, anthropic or openai. It defaults to google.

The key you supply has to match that choice: GOOGLE_GENERATIVE_AI_API_KEY, ANTHROPIC_API_KEY or OPENAI_API_KEY. lib/ai/status.ts maps each provider to its key name.

AI_MODEL replaces the default model, but only for the provider that AI_PROVIDER selects. It doesn't switch providers.

AI_FALLBACK_ENABLED takes "true" or "false". Fallback to another provider stays on unless the value is exactly "false". lib/ai/model.ts reads it from process.env.

If there is no key for the selected provider, isProviderConfigured returns false and the admin hides its AI features.

You set how much the AI may do on its own, and which tools it may use, in Settings → AI, not in the environment.

Sign-in with Google

AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET add Google as a sign-in option in lib/auth.ts. Litestore only adds it when both values are present.

Without them, people can only sign in with a magic link.

Storage details

Two optional variables change how Litestore uses the S3 credentials.

Set S3_ENDPOINT when you use R2 or MinIO. Leave it out for AWS.

Set S3_PUBLIC_URL when the public address for your media is different from the bucket endpoint.

Analytics and tracking

There are two sets of analytics variables, and they do different jobs.

The NEXT_PUBLIC_* values load tracking pixels in the browser. Each pixel only loads after the shopper accepts the matching consent category.

  • NEXT_PUBLIC_PLAUSIBLE_DOMAIN, NEXT_PUBLIC_PLAUSIBLE_URL
  • NEXT_PUBLIC_POSTHOG_API_KEY, NEXT_PUBLIC_POSTHOG_HOST
  • NEXT_PUBLIC_GA_MEASUREMENT_ID
  • NEXT_PUBLIC_FACEBOOK_PIXEL
  • NEXT_PUBLIC_TIKTOK_PIXEL
  • NEXT_PUBLIC_CURRENCY_API_KEY for currency rates

The server-side keys work from your server instead:

  • GA4_MEASUREMENT_ID with GA4_API_SECRET, and META_PIXEL_ID with META_ACCESS_TOKEN, send events from your server to Google and Meta.
  • PLAUSIBLE_API_KEY, POSTHOG_API_KEY and POSTHOG_PROJECT_ID pull stats back into the admin.

You can configure the browser side, the server side, both or neither.

Newsletter providers

Configure one of these providers. If you configure more than one, Litestore uses the first one in this list.

  • Klaviyo: KLAVIYO_API_KEY, KLAVIYO_LIST_ID
  • Mailchimp: MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID
  • ConvertKit: CONVERTKIT_API_KEY, CONVERTKIT_FORM_ID
  • Buttondown: BUTTONDOWN_API_KEY
  • Loops: LOOPS_API_KEY
  • Beehiiv: BEEHIIV_API_KEY, BEEHIIV_PUBLICATION_ID

To send subscribers to any other platform, set NEWSLETTER_WEBHOOK_URL. Litestore posts each new subscriber to that URL.

None of this affects transactional email, which always goes through Resend.

Integrations and observability

To import a catalog from Shopify through Admin OAuth, you need five values:

SHOPIFY_APP_URL
SHOPIFY_API_KEY
SHOPIFY_API_SECRET
SHOPIFY_SCOPES
SHOPIFY_API_VERSION

Two variables have a required length instead of a required format.

SERVICE_API_KEY is a fixed key that other servers use to call Litestore. It must be at least 32 characters long.

ENCRYPTION_KEY encrypts stored credentials and API keys. It must be exactly 64 characters long. You can generate one with openssl rand -hex 32.

RESEND_WEBHOOK_SECRET is the Svix signing secret for Resend delivery events. Without it, /api/webhooks/resend answers 503, and Litestore never records bounces or complaints.

ADMIN_ALERT_EMAIL is the address that receives admin alert emails. JINA_API_KEY lets Litestore scrape content from web pages.

Sentry error reporting uses NEXT_PUBLIC_SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT and SENTRY_AUTH_TOKEN. next.config.ts only applies withSentryConfig on a production Vercel build, and it only uploads source maps in CI.

ENABLE_TAXES is false by default. While it is off, getTaxProvider returns the zero-tax provider and Litestore charges no tax on any order.

Inngest is not in the schema

env.ts doesn't declare any Inngest variable. services/inngest.ts creates the client with only an id and a Prisma middleware.

The Inngest SDK reads INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY from process.env on its own. A missing key therefore fails when Litestore sends an event, not at startup.

In development this is deliberate. isInlineFallbackEnabled in events/emitter.ts checks for NODE_ENV=development with no INNGEST_EVENT_KEY. When it finds that combination, Litestore runs the domain-event handlers inline instead of publishing them to Inngest.

Verifying your configuration

Open Settings → Developer in the admin.

The check in server/admin/settings/self-host-readiness.ts runs a live SELECT 1 against Postgres and a redis.ping(), each with a 3-second timeout.

It then reports whether the public site URL uses https and whether Resend, S3, Stripe and the AI provider are configured. The first five items are marked critical. Stripe and AI are not.

Where a setting belongs

Put a value in the environment when it is a credential or an endpoint. That means it identifies an outside service or authorises a call to it.

Put it in Settings in the admin when it is a business decision. The admin settings cover site, appearance, checkout, payments, taxes, delivery, fulfillment, SEO, roles, alerts, notifications, integrations and AI.

The difference is not how sensitive a value is. It is who changes the value and how often.

Changing an environment variable needs a deploy. Changing an admin setting does not.

Next Steps

On this page