Litestore · Developer docs
Getting started

Configuration

Set the environment variables Litestore needs and choose which optional services to turn on.

You configure Litestore's connections to outside services through environment variables. The app needs some of them to start, and each optional group adds a feature.

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 and S3 storage (and Resend in production), and the optional ones add services like Stripe.

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. Most of its values are empty strings.

Environment variables only hold credentials and switches. The few business settings a store owner changes live in the admin under app/admin/settings/ and take effect without a deploy. Timings, thresholds and other tuning are typed constants in platform/config/, which you change in code.

Missing values

The schema turns on emptyStringAsUndefined, so Litestore treats KEY="" 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, including requests to routes that never read that variable. The error message names the exact keys.

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

Required variables

The app won't start until you set every variable in this table.

VariableWhat it does
NEXT_PUBLIC_SITE_URLThis is the public URL of the store, and it must be a valid URL. Litestore also uses it in emails, feeds, JSON-LD and checkout links.
NEXT_PUBLIC_SITE_EMAILThis is the public contact address, and it must be a valid email.
DATABASE_URLThis is the Postgres connection string, and it must be a valid URL.
BETTER_AUTH_URLThis is the base URL for sign-in. In most setups it is the same as the site URL.
BETTER_AUTH_SECRETLitestore signs sessions with this secret.
REDIS_REST_URLThis is the Upstash Redis REST endpoint.
REDIS_REST_TOKENThis is the token for that endpoint.
RESEND_API_KEYRequired only in production. Litestore signs people in with a magic link. Outside production no email is sent at all: every email, including the sign-in link, is printed in the terminal running bun run dev.
RESEND_SENDER_EMAILRequired only in production. This is the from-address for transactional email, and it must be a valid email.
S3_REGION, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_ACCESS_KEYThese four values set up media storage, and any S3-compatible provider works.

Local development

compose.yml runs Redis behind an Upstash-compatible REST proxy on port 8079, and .env.example already points the two REDIS_REST_* values at it. The rate limiter, the checkout payment lock and coupon reservations use it the same way they use Upstash in production.

Defaults

These five variables have defaults, so they're optional.

VariableDefault
PORT8000
NODE_ENVdevelopment
VERCEL_ENVdevelopment
AI_PROVIDERgoogle
ENABLE_TAXESfalse

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.

Payments and tax

STRIPE_SECRET_KEY lets checkout take payment at all. app/api/stripe/webhooks/route.ts uses STRIPE_WEBHOOK_SECRET to verify incoming events. Without Stripe, shoppers can still browse the store and fill a cart, and only checkout switches off.

Tax is controlled separately from payments. ENABLE_TAXES (default false) controls whether Litestore charges any tax at all. While it is off, getTaxProvider returns the zero-tax provider and Litestore charges no tax on any order.

When tax is on, each channel's stored taxProvider picks how Litestore calculates it.

taxProviderWhat Litestore charges
manualThe flat channel rate.
stripe_taxReal tax for the shopper's jurisdiction. If Stripe returns any error, Litestore falls back to the flat rate.
Any other valueLitestore logs a warning and charges the flat rate.

AI

AI_PROVIDER picks google, anthropic or openai, and it defaults to google. The key you supply has to match that choice, and modules/assistant/model.ts maps each provider to its key name.

AI_PROVIDERKey you set
googleGOOGLE_GENERATIVE_AI_API_KEY
anthropicANTHROPIC_API_KEY
openaiOPENAI_API_KEY

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". Litestore keeps fallback to another provider on unless the value is exactly "false", and modules/assistant/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 far the assistant may go (read only, or read and propose) under Settings → AI in the admin. Rate limits and the spend budget are constants in platform/config/ai.ts.

Sign-in with Google

AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET add Google as a sign-in option in platform/auth/server.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, and 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

The two sets of analytics variables do different jobs, and the browser side, the server side, both or neither can be configured.

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

ServiceVariables
PlausibleNEXT_PUBLIC_PLAUSIBLE_DOMAIN, NEXT_PUBLIC_PLAUSIBLE_URL
PostHogNEXT_PUBLIC_POSTHOG_API_KEY, NEXT_PUBLIC_POSTHOG_HOST
Google AnalyticsNEXT_PUBLIC_GA_MEASUREMENT_ID
MetaNEXT_PUBLIC_FACEBOOK_PIXEL
TikTokNEXT_PUBLIC_TIKTOK_PIXEL
Currency ratesNEXT_PUBLIC_CURRENCY_API_KEY

Newsletter providers

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

ProviderVariables
KlaviyoKLAVIYO_API_KEY, KLAVIYO_LIST_ID
MailchimpMAILCHIMP_API_KEY, MAILCHIMP_LIST_ID
ConvertKitCONVERTKIT_API_KEY, CONVERTKIT_FORM_ID
ButtondownBUTTONDOWN_API_KEY
LoopsLOOPS_API_KEY
BeehiivBEEHIIV_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

VariableWhat it does
SHOPIFY_APP_URL, SHOPIFY_API_KEY, SHOPIFY_API_SECRET, SHOPIFY_SCOPES, SHOPIFY_API_VERSIONYou need all five to import a catalog from Shopify through Admin OAuth.
SERVICE_API_KEYA fallback service key for development and simple setups: a Bearer token equal to it is accepted with the read, write:orders and write:webhooks scopes. It must be at least 32 characters long. For real integrations, create scoped keys under Settings → Developer.
ENCRYPTION_KEYLitestore encrypts stored credentials and API keys with it. It must be exactly 64 characters long, and openssl rand -hex 32 generates one.
RESEND_WEBHOOK_SECRETThis 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_EMAILThis address receives operational store emails when the Admin notification email setting is empty.
JINA_API_KEYLitestore reads web pages through Jina Reader (for example when it imports a product from a URL); the key lifts Jina's anonymous rate limit.
NEXT_PUBLIC_SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKENSentry error reporting uses these. next.config.ts only applies withSentryConfig on a production Vercel build, and it only uploads source maps in CI.

Inngest variables

env.ts doesn't declare any Inngest variable. platform/inngest.ts creates the client.

The Inngest SDK reads INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY from process.env on its own. If a key is missing, the error appears when Litestore sends an event rather than at startup.

Litestore handles a missing key on purpose. isInlineFallbackEnabled in platform/events/emitter.ts checks for a missing INNGEST_EVENT_KEY. In that case, Litestore runs the same domain-event handlers inline, once, instead of letting the events go nowhere. In production without a key it skips the send and goes straight to the inline path; in development it tries a key-less Inngest dev server first.

Verification

Open /api/health. The route in app/api/health/route.ts runs a live SELECT 1 against Postgres and a redis.ping(), each with a 3-second timeout. It answers ok, degraded (Redis down) or down (database down, with status 503), so an uptime monitor can poll it.

The admin home's setup guide checks six things against the store's real state: a first product, payments, shipping, taxes, store details and email sending.

Environment and admin settings

Put a value in the environment when it is a credential or an endpoint, because it identifies an outside service or authorises a call to it. Put it in Settings in the admin when a store owner would change it: the admin has settings pages for site, currency, payments, notifications, integrations, AI, roles and developer keys. Everything else, such as timings, thresholds, windows and copy defaults, is a typed constant in platform/config/.

Where a value goes depends on who changes it and how often, rather than how sensitive it is.

Changing an environment variable or a platform/config/ constant needs a deploy. Changing an admin setting does not.

Next steps

On this page