Configuration
Central configuration - validated environment variables, product constants, feature flags, and the ApplicationConfig contract.
@africaos/config is the single source of truth for configuration. Environment variables are validated once at startup, product constants and feature flags live here, and the application-config contract that every app.config.ts satisfies is defined here too.
Environment variables
packages/config/env.config.ts uses defineEnv from the envin package with Zod schemas. It splits variables into shared, server, and client (VITE_-prefixed):
| Scope | Variable | Rule |
|---|---|---|
| Shared | NODE_ENV |
`development |
| Server | DATABASE_URL |
Required, must be a valid URL. |
| Server | BETTER_AUTH_SECRET |
Required, at least 32 characters. |
| Server | BETTER_AUTH_URL |
Optional URL; blank values treated as absent. |
| Server | GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Optional; enables Google login when present. |
| Server | WORKOS_API_KEY / WORKOS_CLIENT_ID / WORKOS_REDIRECT_URI |
Optional; enables WorkOS SSO when present. |
| Client | VITE_APP_NAME |
Default "Africa OS". |
| Client | VITE_APP_VERSION |
Default "0.0.0". |
| Client | VITE_PLATFORM_URL |
Optional URL. |
Key behaviors:
- Required vs optional is encoded in the schema.
DATABASE_URLandBETTER_AUTH_SECRETare required; everything else is optional so apps that do not need a capability (the landing page has no auth) still boot. - Blank values are treated as absent.
optionalUrl()preprocesses empty strings toundefined, so copying.env.example(which leaves optional entries blank) does not break startup. - Validation is at boot time. A missing or malformed variable fails fast with a clear error rather than failing later in an obscure place.
The env object is the default export; code reads it as env.DATABASE_URL etc.
Product constants
packages/config/src/constants.ts:
export const AFRICA_OS = {
name: "Africa OS",
version: "0.0.0",
namespace: "@africaos",
} as const;
Feature flags
packages/config/src/features.ts declares the platform’s capability flags:
export const features = {
offline: true,
desktop: true,
multiTenant: true,
realtime: false,
} as const;
The ApplicationConfig contract
packages/config/src/types.ts defines the types every application’s app.config.ts uses:
export interface ApplicationFeature {
slug: string;
label: string;
icon?: string;
path?: string; // application-relative route when implemented
}
export interface ApplicationConfig {
id: string;
name: string;
icon?: string;
color?: string;
description?: string;
route?: string;
capabilities: {
offline: boolean;
desktop: boolean;
mobile: boolean;
};
features: ReadonlyArray<ApplicationFeature>;
}
export const defineAppConfig = (config: ApplicationConfig): ApplicationConfig => config;
defineAppConfig gives applications full type checking on their self-description, so the registry contract is enforced at build time. A feature with a path is implemented and becomes navigation; one without a path is declared surface (for example a planned module).
Where config is used
- The database pool reads
env.DATABASE_URL. - The auth provider reads
env.BETTER_AUTH_SECRET,BETTER_AUTH_URL, and the OAuth/WorkOS credentials. - Apps read
VITE_APP_NAME/VITE_APP_VERSIONfor the header chrome. - Application packages call
defineAppConfigin theirapp.config.ts.
Next steps
- Application registry - how
app.config.tsfeeds the registry. - Getting started - the
.env.examplewalkthrough.