Configuration

Every setting you can change, where it lives, and what it affects.

Onekit starts with one Bun/TypeScript app targeting web, iOS, and Android. One provides routes and the web server; Tamagui provides shared UI; Better Auth and Drizzle use PostgreSQL; SST deploys the server and API to AWS. Native binaries have a separate Expo/EAS release process.

These are editable source and environment settings. An interactive setup wizard and interchangeable billing/storage modules are not implemented.

Configuration map

ChoiceEditCurrent behavior
Name, slug, descriptionsrc/config/app.tsPublic metadata and shared branding
Native URL scheme and identifierssrc/config/app.ts, app.config.tsonekit scheme; sh.onekit.app identifiers (replace with your own)
Default appearance and accentsrc/config/app.tsSystem appearance, blue accent; screens use the configured accent where explicitly wrapped in a theme
Tokens, fonts, themes, responsive rulessrc/tamagui/config.tsTamagui default configuration with long-form style props enabled
Appearance persistencesrc/tamagui/provider.tsx, src/tamagui/theme-storage*Browser local storage; native SecureStore; key is onekit-theme
Navigation and screensapp/, src/components/screen.tsxFile-based routes and shared shell
Web rendering and native bundlervite.config.tsSPA default, explicit SSG home route, Node deployment target, Metro native bundler
Auth policy and sessionssrc/server/auth.tsEmail/password; optional Google/Apple; seven-day session, one-day refresh interval; reset, verification, and deletion hooks
Email delivery and templatessrc/server/email.tsConsole logger locally, Amazon SES when deployed; three plain-text/HTML templates
Plans and billingsrc/config/billing.ts, src/server/billing.tsStripe subscriptions by lookup key; local entitlement table
Database schema and migrationssrc/server/db/schema.ts, drizzle/, drizzle.config.tsPostgreSQL with Better Auth tables and reviewed SQL migrations
Local databasecompose.yamlPostgreSQL 17, loopback port 54329, persistent named volume
AWS infrastructureinfra/config.ts, sst.config.tsRegion, service sizing, scaling, architecture, OAuth secret toggles, deployment stages
Container runtimeDockerfile, package.jsonNode server, public origin build arguments, runtime secret injection
Native build variantsapp.config.ts, eas.jsonDevelopment, preview, production
Dependency/tool versions and commandspackage.json, lockfilePinned application dependencies and Bun version
Type checking and code styletsconfig.json, biome.jsonStrict TypeScript, Biome formatting/linting

src/config/app.ts is bundled into clients. It must not contain credentials. Changing a name does not automatically rename the database, SST stack, package, theme storage key, or application-store listing; review those separately when adapting the starter.

Local environment

Create an untracked .env from .env.example, generate a fresh auth secret with openssl rand -base64 32, and fill in the following values. Never reuse the example database credentials in a hosted environment.

VariableRequired forValue or rule
DATABASE_URLDatabase and auth requests; migration commandsPostgreSQL connection URL; local Compose uses postgresql://onekit:onekit@localhost:54329/onekit
DATABASE_POOL_MAXOptional connection tuningInteger 1–100; default 10 per server process
DATABASE_SSL_CAProviders with a private CAPEM certificate to trust, for example Supabase’s; verification stays enabled
ZERO_UPSTREAM_DBzero-cache, when DATABASE_URL is pooledDirect connection URL as a role with REPLICATION; default DATABASE_URL. See Database
ZERO_DB_PASSWORDbun run db:migrate, optionalAlso create zero-cache’s onekit_zero role with this password, plus its zero_cvr and zero_change databases. The deploy’s migrate task always sets it
ONEKIT_DATABASESST deploymentrds (default) creates PostgreSQL in the stack; external uses the DatabaseUrl secret
ONE_PUBLIC_ZERO_URLEvery signed-in clientzero-cache’s public origin, embedded in client builds; default http://localhost:4848. SST sets https://sync.<domain>
ZERO_APP_URLLocal zero-cache (bun run zero:serve, zero:dev)Where zero-cache reaches this app’s /api/zero/*; default the dev server, http://localhost:8081
ZERO_PORTLocal zero-cachePort zero-cache listens on; default 4848
BETTER_AUTH_SECRETAuthenticationRandom secret at least 32 characters long; keep stable between releases
BETTER_AUTH_URLAuthenticationCanonical HTTP(S) origin without a path; local default http://localhost:8081; production requires HTTPS
ONE_SERVER_URLOne build/server originUse the same canonical public server origin
ONE_PUBLIC_SERVER_URLNative auth/API clientPublic origin embedded in client builds; must be reachable from the device; native release builds require HTTPS
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETOptional Google loginSet both to enable; leave both empty to disable
APPLE_CLIENT_ID, APPLE_CLIENT_SECRETOptional Apple loginSet both to enable; the secret is a signed JWT requiring renewal
MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRETOptional Microsoft loginSet both to enable
MICROSOFT_TENANT_IDOptional Microsoft tenant restrictionEntra tenant GUID; empty allows any Microsoft account (common)
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETOptional GitHub loginSet both to enable
EMAIL_PROVIDEREmail delivery, and the default sign-in routeconsole (local only), file (local only, appends to EMAIL_OUTBOX), ses, or empty — empty also disables the emailed code, reset, and verification
EMAIL_FROMSES senderVerified identity such as Onekit <no-reply@example.com>; optional for console and file
EMAIL_OUTBOXEMAIL_PROVIDER=filePath the messages are appended to as JSON lines, for reading a code back from a script or test
EMAIL_REGIONSES regionFalls back to AWS_REGION
AUTH_EMAIL_SIGN_INSign-in policyoff, code, or link; empty is code wherever EMAIL_PROVIDER is set and AUTH_PASSWORD is not true. link is not implemented yet and is refused
AUTH_PASSWORDSign-in policytrue adds the password form and leaves AUTH_EMAIL_SIGN_IN off unless you name it; empty is off, except where it would be the only way in
AUTH_PHONE_SIGN_INSign-in policytrue accepts a phone number in the sign-in field and texts a code; needs SMS_PROVIDER. Never a way in on its own — a number is verified from the profile page
SMS_PROVIDERSMS deliveryconsole or file (local only), twilio, or empty to disable
SMS_FROMSMS senderE.164 number such as +15550100000, or a Twilio Messaging Service SID; optional for console and file
SMS_OUTBOXSMS_PROVIDER=filePath the texts are appended to as JSON lines
TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKENSMS_PROVIDER=twilioBoth required
STORAGE_PROVIDERUploaded imageslocal (development only, refused in production), s3, or empty to hide the upload control
STORAGE_ROOTSTORAGE_PROVIDER=localDirectory the files are written to; default .onekit/uploads
STORAGE_BUCKETSTORAGE_PROVIDER=s3One private bucket; keys are prefixed per owner
STORAGE_REGIONSTORAGE_PROVIDER=s3Falls back to AWS_REGION
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETOptional billingSet both to enable checkout; see Billing
AUTH_REQUIRE_EMAIL_VERIFICATIONSign-in policytrue refuses sign-in until the address is confirmed; needs EMAIL_PROVIDER
APP_VARIANTNative app configurationdevelopment (default), preview, or production
ONEKIT_DOMAINSST deploymentHostname in the selected AWS account’s Route 53 zone, such as app.example.com
AWS_PROFILEOptional local AWS credential selectionExisting profile for the intended account

Server configuration is validated when used. Static page generation does not require database or auth secrets. In local development, native devices need a reachable server address; use one canonical origin consistently for auth and API configuration. Restart the server or rebuild clients after changing values that are read at startup or bundled into the app.

Uploaded images go to one private bucket with keys prefixed per owner, never a bucket per workspace: an AWS account gets 100 buckets by default, creating one is a slow control-plane call nobody wants inside a signup path, and isolation here is the application refusing to serve a key it does not own. Bytes go through the service rather than a presigned URL, which is what lets the server cap the size and establish what a file actually is — the stored content type is the one it sniffed, never the one the browser claimed, and SVG is refused outright because it is XML that can carry script. Set uploads: true in infra/config.ts to deploy the bucket; SST’s link gives the task role that bucket’s policy, so no keys are configured by hand.

Onekit ships passwordless: with EMAIL_PROVIDER set and nothing else named, the sign-in screen asks for an address and emails a six-digit code. See Authentication for the whole truth table, including what happens when neither switch nor transport nor OAuth pair is configured.

The browser auth client uses the current website origin. Native auth uses ONE_PUBLIC_SERVER_URL and SecureStore. OAuth callback URLs use ${BETTER_AUTH_URL}/api/auth/callback/google or /api/auth/callback/apple. See Authentication for provider setup and current limitations.

Brand and native variants

Set your final bundle/package identifiers before configuring provider applications or store listings. Development and preview append .development and .preview to the base iOS/Android identifiers; production uses the base identifiers. Names also include the variant outside production. The URL scheme is shared by the current variants, so validate deep links with the intended build installed.

Changing the URL scheme, package identifiers, plugins, or native configuration requires rebuilding the native app. EAS profiles do not provision project ownership, signing credentials, store records, or a production API origin; configure those for your own account. Align Apple provider configuration with the actual build identifier. Update public/favicon.svg and add native icons/splash assets before release.

The shared theme preference can be changed in Settings. To create a custom visual system, edit Tamagui tokens/themes and the consuming components together; the accent field is not an automatic recoloring mechanism for every element.

Auth and database policy

Email/password is enabled with a minimum password length of eight. Password reset, confirmation, password change, and deletion work when an email provider is configured; see Email. Organizations and role management require additional schema and application work.

Generate migrations after editing the schema, review the SQL, then apply them explicitly to the intended database. Deployment does not run migrations automatically. Use separate databases and secrets per stage, and size each process’s connection pool against the total service replica count. See Database.

Row-level security

Workspace tenancy is enforced in the application: every query over the eight CRM tables carries organization_id, in one place, and tests/tenancy.test.ts holds that rule. The eight tables also carry a PostgreSQL row-level policy, as a floor under a mistake rather than as the mechanism — drizzle/0013_row_level_security.sql.

Each request runs in one transaction whose first statement is select set_config('onekit.org_id', …, true), and the policies compare organization_id against that setting. Two consequences worth planning for:

  • A request holds a pooled connection for its whole life, so DATABASE_POOL_MAX now bounds concurrent requests rather than concurrent queries. Size it against expected concurrency per process, not against query volume.
  • The setting is transaction-local, so this is correct behind RDS Proxy or PgBouncer in transaction pooling mode as well as session mode. The value is discarded at commit and cannot reach the next request on the same backend. Nothing in the kit issues a bare SET, which would be silently wrong under transaction pooling — do not add one.

The policies do nothing unless DATABASE_URL connects as an ordinary role. PostgreSQL bypasses row-level security for a superuser or a role with BYPASSRLS no matter what the tables say, and the kit’s Compose file ships a single superuser for convenience. The server logs one line at startup when it detects this; tenancy still holds, but the backstop is inert. To get it, create a dedicated non-owner role, run migrations as the owner, and point the app at the role:

create role onekit_app login password 'choose-a-strong-one';
grant usage on schema public to onekit_app;
grant select, insert, update, delete on all tables in schema public to onekit_app;
-- So tables created by later migrations are reachable too.
alter default privileges in schema public grant select, insert, update, delete on tables to onekit_app;

FORCE ROW LEVEL SECURITY is set on all eight tables, so a deployment that points DATABASE_URL at the owning role is still subject to the policies — ownership is safe, only the two bypasses are not.

SST settings

infra/config.ts defaults to us-east-1, ARM64, port 3000, and local development port 8081. Production requests 0.5 vCPU/1 GB with one to two instances; other stages request 0.25 vCPU/0.5 GB with one instance. The deployed service and load balancer have ongoing infrastructure costs. PostgreSQL is Amazon RDS in the same stack by default (infraConfig.database); ONEKIT_DATABASE=external uses your own instead.

SST requires BetterAuthSecret, ZeroDbPassword and ZeroAdminPassword secrets for each stage, plus DatabaseUrl and ZeroUpstreamDb with an external database. Enable infraConfig.oauth.google or .apple and set the corresponding provider secret pair to deploy social login. Set infraConfig.email.from to a verified SES identity to deploy email delivery with the ses:SendEmail task permission. Setting only local .env values does not change the deployed service.

The domain determines the deployed public/auth origins. Production is protected and retains resources on removal; other stages use removal behavior. Review stack and stage identity before renaming either. Changing ports also requires keeping package scripts, Docker configuration, and health checks aligned. Follow Deployment for exact commands and prerequisites.

Next configuration rounds

DecisionStatus
Core framework, package manager, database/ORM/authAccepted defaults implemented in source
Billing provider and plans for apps built with the kitStripe subscriptions implemented; plans in src/config/billing.ts
Transactional email provider and templatesAmazon SES implemented with console fallback; other providers can replace the transport in src/server/email.ts
Managed PostgreSQL hostChoose a compatible provider and supply its URL
Teams/organizations, storage, analytics, pushOptional future modules; absent
Onekit’s own pricing, purchase model, license, source delivery, supportProduct decisions; see product brief

Introduce additional options as complete, documented configurations. Test supported combinations before treating them as a buyer-facing feature.

Onekit / A little less setup.Web · iOS · Android