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 from import.meta.env.VITE_OPENHUMAN_APP_ENV at line 3.

  • CORE_RPC_URL: The endpoint for the in-process core RPC server. Defined at line 22 as export 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 from VITE_CORE_RPC_TIMEOUT_MS with type coercion to ensure valid millisecond values (line 51).

  • CHAT_ATTACHMENTS_ENABLED: Boolean feature flag that disables chat attachment handling when VITE_CHAT_ATTACHMENTS is set to 'false' (line 100).

  • GA_MEASUREMENT_ID: Google Analytics identifier used only when analytics are enabled, cast as string | undefined from VITE_GA_MEASUREMENT_ID (line 122).

  • SENTRY_DSN: Error reporting configuration sourced from VITE_SENTRY_DSN for crash telemetry integration (line 140).

  • BUILD_SHA: Short Git SHA baked at build time via VITE_BUILD_SHA for 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:

Summary

  • Centralized management in app/src/utils/config.ts prevents 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 | undefined followed by default value application ensures type safety and predictable fallbacks for missing variables.
  • Feature flags like CHAT_ATTACHMENTS_ENABLED enable 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →