How Configuration is Managed in the OpenHuman Frontend: Vite Environment Variables and Type-Safe Constants
The OpenHuman frontend centralizes all build-time and runtime settings in a single TypeScript module that reads Vite environment variables via import.meta.env and exports typed constants for type-safe consumption across the application.
In the tinyhumansai/openhuman repository, the React-based UI follows a strict centralized configuration pattern that prevents drift between components. All environment-specific values—from RPC endpoints to feature flags—flow through app/src/utils/config.ts, which processes Vite-prefixed variables and exposes them as strictly typed constants with sensible defaults.
The Central Configuration Module
The entire frontend configuration strategy revolves around app/src/utils/config.ts, which acts as the single source of truth for all environment variables. This module exclusively reads variables prefixed with VITE_ (Vite's required naming convention), applies type casting and fallback logic, and exports immutable constants that the rest of the application imports.
Unlike scattered process.env accesses, this architecture ensures that missing variables trigger predictable defaults rather than runtime undefined errors, while TypeScript's static analysis catches type mismatches at compile time.
Key Configuration Constants
The file exports typed constants that control critical application behavior:
-
APP_ENV: Determines the deployment environment (development, staging, or production), sourced fromimport.meta.env.VITE_OPENHUMAN_APP_ENVat line 3. -
CORE_RPC_URL: The endpoint for the in-process core RPC server. Defined at line 22 asexport const CORE_RPC_URL = import.meta.env.VITE_OPENHUMAN_CORE_RPC_URL || 'http://127.0.0.1:7788/rpc', providing a localhost fallback when the variable is unset. -
CORE_RPC_TIMEOUT_MS: Numeric timeout for RPC calls, parsed fromVITE_CORE_RPC_TIMEOUT_MSwith type coercion to ensure valid millisecond values (line 51). -
CHAT_ATTACHMENTS_ENABLED: Boolean feature flag that disables chat attachment handling whenVITE_CHAT_ATTACHMENTSis set to'false'(line 100). -
GA_MEASUREMENT_ID: Google Analytics identifier used only when analytics are enabled, cast asstring | undefinedfromVITE_GA_MEASUREMENT_ID(line 122). -
SENTRY_DSN: Error reporting configuration sourced fromVITE_SENTRY_DSNfor crash telemetry integration (line 140). -
BUILD_SHA: Short Git SHA baked at build time viaVITE_BUILD_SHAfor debugging and version tracking, defaulting to an empty string when unavailable (line 195). -
LATEST_APP_DOWNLOAD_URL: Direct download link for desktop app updates, with whitespace trimming and fallback logic applied (line 226).
Type Safety and Default Value Patterns
Each constant follows a consistent extraction pattern that leverages TypeScript's type system. The code casts import.meta.env values to string | undefined before applying default values, preventing implicit any types and enabling compile-time verification.
For example, the definition of CORE_RPC_URL guarantees that consuming code receives a definitive string rather than potentially undefined values, while boolean flags like CHAT_ATTACHMENTS_ENABLED use explicit string comparison rather than truthy checks to avoid parsing errors.
Consuming Configuration Values
Components and services import constants directly from the config module rather than accessing environment variables themselves. This import-based approach ensures that app/src/utils/config.ts remains the single source of truth, allowing developers to trace configuration usage through static imports rather than global variable searches.
Service-Level Configuration
The RPC client uses timeout and URL values to initialize connections without hardcoding endpoints:
import { CORE_RPC_TIMEOUT_MS } from '@/utils/config';
import { coreRpcClient } from '@/services/coreRpcClient';
export async function fetchUserProfile() {
// The client respects the global timeout configuration
return await coreRpcClient.invoke('user/profile', {}, { timeoutMs: CORE_RPC_TIMEOUT_MS });
}
Feature Flags in UI Components
Boolean flags enable conditional rendering without prop drilling:
import { DERIVED_TRANSCRIPT_ENABLED } from '@/utils/config';
import DerivedTranscript from '@/components/DerivedTranscript';
export default function ChatPane() {
return (
<>
{/* ...chat UI... */}
{DERIVED_TRANSCRIPT_ENABLED && <DerivedTranscript />}
</>
);
}
Basic Configuration Access
Standard imports provide immediate access to environment-dependent values:
import { CORE_RPC_URL, CHAT_ATTACHMENTS_ENABLED } from '@/utils/config';
console.log('Core RPC endpoint:', CORE_RPC_URL);
if (!CHAT_ATTACHMENTS_ENABLED) {
// UI component can hide the attachment button completely
}
Runtime Overrides and Deployment Flexibility
The Vite-based configuration approach allows operators to override settings at container runtime or build time without modifying source code. While web bundles bake import.meta.env values at compile time, Tauri desktop contexts can read certain variables at application startup, enabling the same binary to target different backend endpoints (such as VITE_BACKEND_URL) through environment variable changes alone.
This pattern proves particularly valuable for feature toggles like CHAT_ATTACHMENTS_ENABLED, supporting A/B testing and progressive rollouts through deployment configuration rather than code deployments.
Supporting Configuration Infrastructure
While config.ts handles static environment values, the application includes specialized utilities for dynamic and native configuration:
app/src/utils/configPersistence.ts: Manages user-specific overrides such as theme preferences and onboarding completion status, persisting data across sessions.app/src/utils/tauriCommands/config.ts: Bridges frontend configuration to the Rust core when native access is required, mirroring values to the Tauri backend.app/src/services/coreRpcClient.ts: ConsumesCORE_RPC_URLandCORE_RPC_TIMEOUT_MSto establish and manage connections with the backend RPC server.
Summary
- Centralized management in
app/src/utils/config.tsprevents configuration drift across the React component tree by serving as the sole ingestion point for all environment variables. - Vite environment variables prefixed with
VITE_provide build-time and runtime flexibility for different deployment targets, including web and Tauri desktop contexts. - TypeScript casting to
string | undefinedfollowed by default value application ensures type safety and predictable fallbacks for missing variables. - Feature flags like
CHAT_ATTACHMENTS_ENABLEDenable progressive feature rollouts and A/B testing through simple environment variable changes without code modifications. - Immutable exported constants create a strict contract between the configuration layer and consuming services, making dependencies explicit through ES6 imports rather than global state.
Frequently Asked Questions
How does OpenHuman handle missing environment variables?
The app/src/utils/config.ts module provides sensible defaults for critical infrastructure (such as defaulting CORE_RPC_URL to http://127.0.0.1:7788/rpc) while allowing optional values (like GA_MEASUREMENT_ID and SENTRY_DSN) to remain undefined when not configured. This prevents runtime crashes while allowing optional telemetry features to gracefully degrade.
Can configuration values change after the application is built?
For standard web deployments, Vite replaces import.meta.env references at build time, making values immutable in the shipped bundle. However, in Tauri desktop contexts, certain environment variables may be read at application startup, allowing runtime overrides when launching the binary with different environment settings or when using the configuration bridge in app/src/utils/tauriCommands/config.ts.
What is the difference between config.ts and configPersistence.ts?
While config.ts manages static environment configuration injected at build or deployment time, configPersistence.ts handles mutable user preferences (such as UI themes and onboarding completion status) that persist across sessions via local storage or Tauri's native storage mechanisms. The former is read-only for environment variables; the latter supports read-write operations for user settings.
How are feature flags implemented in the frontend?
Feature flags appear as exported boolean constants (e.g., CHAT_ATTACHMENTS_ENABLED, DERIVED_TRANSCRIPT_ENABLED) that evaluate environment variables against string values like 'false'. Components conditionally render functionality based on these constants, enabling feature toggles through simple environment variable changes without requiring code redeployment or conditional compilation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →