Configuration Management
Type-safe configuration for every environment. Stop scattering .env files across your codebase. Define schemas with Zod, manage three tiers of config values, and sync across your team with the CLI.
Architecture
The config system is built on three core concepts that work together to provide type-safe, environment-aware configuration.
Schemas
Define the shape and validation rules for your configuration. Each key has a type, default value, and description.
Environments
Isolate configuration by deployment stage (development, staging, production) or any custom environment.
Values
Set values per environment with three tiers: public (client-safe), secret (server-only), and feature flags.
CLI in Action
The smooai-config CLI lets you manage configuration directly from your terminal. Initialize projects, push schemas, and pull values without leaving your workflow.
pnpm add @smooai/configdefineConfig
The defineConfig function is the heart of the config system. Define your schema using Zod validators, organized into three tiers. The CLI and SDK both read from this definition.
import { defineConfig } from '@smooai/config';
import { z } from 'zod';
export default defineConfig({
public: {
API_URL: z.string().url().describe('Base URL for the API'),
APP_NAME: z.string().default('My App'),
SUPPORT_EMAIL: z.string().email().default('[email protected]'),
MAX_UPLOAD_SIZE_MB: z.number().int().positive().default(10),
},
secret: {
DATABASE_URL: z.string().describe('PostgreSQL connection string'),
JWT_SECRET: z.string().min(32).describe('Secret for signing JWTs'),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
},
featureFlags: {
NEW_DASHBOARD: z.boolean().default(false),
DARK_MODE: z.boolean().default(true),
AI_SUGGESTIONS: z.boolean().default(false),
},
});Supports any Standard Schema validator — while this example uses Zod, you can also use Valibot, ArkType, or any library that implements the Standard Schema interface.
Three-Tier Configuration
Public Config
Safe to expose to clients and browsers. Examples: API base URLs, feature names, UI theme settings. Accessible via usePublicConfig() in React.
Secret Config
Server-only values that should never be exposed to clients. Examples: API keys, database credentials, signing secrets. Only accessible in server-side code. Automatically blocked for B2M (browser) API keys.
Feature Flags
Boolean toggles for gradual rollouts and A/B testing. Enable or disable features per environment without redeploying. Use useFeatureFlag() in React.
React Integration
Wrap your app with ConfigProvider and access config values anywhere with type-safe hooks.
import { ConfigProvider, usePublicConfig, useFeatureFlag } from '@smooai/config/react';
// In your app root
function App() {
return (
<ConfigProvider schemaId="your-schema-id" environmentId="your-env-id">
<Dashboard />
</ConfigProvider>
);
}
// In any component
function Dashboard() {
const appName = usePublicConfig('APP_NAME');
const showNewDashboard = useFeatureFlag('NEW_DASHBOARD');
return (
<div>
<h1>{appName}</h1>
{showNewDashboard ? <NewDashboard /> : <LegacyDashboard />}
</div>
);
}Security: API Key Types
The Config API enforces tier-based access control depending on your API key type. Browser-to-Machine (B2M) keys are designed for frontend clients and automatically restrict access to protect sensitive data.
| Operation | B2M (Public Key) | M2M (Secret Key) |
|---|---|---|
| Read public values | Yes | Yes |
| Read feature flags | Yes | Yes |
| Read secret values | No (filtered) | Yes |
| Write config values | No (403) | Yes |
| Delete config values | No (403) | Yes |
Use B2M keys in frontend apps — they are safe to embed in browser code. Secret-tier values are automatically excluded from API responses. Use M2M keys only in server-side code where secrets are needed.
Next.js Integration
For Next.js applications, fetch config on the server and pass it to client components with zero loading flash using getConfig and SmooConfigProvider.
// app/layout.tsx (Server Component)
import { getConfig, SmooConfigProvider } from '@smooai/config/nextjs';
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const config = await getConfig({
environment: 'production',
fetchOptions: { next: { revalidate: 60 } }, // ISR: revalidate every 60s
});
return (
<html><body>
<SmooConfigProvider
initialValues={config}
baseUrl={process.env.SMOOAI_CONFIG_API_URL}
apiKey={process.env.SMOOAI_CONFIG_API_KEY}
orgId={process.env.SMOOAI_CONFIG_ORG_ID}
environment="production"
>
{children}
</SmooConfigProvider>
</body></html>
);
}
// Any client component — values available instantly (no loading flash)
'use client';
import { usePublicConfig, useFeatureFlag } from '@smooai/config/nextjs';
function Dashboard() {
const { value: apiUrl } = usePublicConfig<string>('API_URL');
const { value: showNew } = useFeatureFlag<boolean>('NEW_DASHBOARD');
return <div>{showNew ? <NewDashboard apiUrl={apiUrl} /> : <LegacyDashboard />}</div>;
}Vite React Integration
For Vite-based React apps, call preloadConfig() before mounting React to start fetching config as early as possible.
// main.tsx
import { preloadConfig } from '@smooai/config/vite';
import { ConfigProvider } from '@smooai/config/vite';
import { createRoot } from 'react-dom/client';
// Start fetching config immediately (before React renders)
preloadConfig({ environment: 'production' });
createRoot(document.getElementById('root')!).render(
<ConfigProvider
baseUrl="https://config.smooai.dev"
apiKey="your-b2m-public-key"
orgId="your-org-id"
environment="production"
>
<App />
</ConfigProvider>
);Multi-Language Support
Use the config SDK in any language. The same schema definition drives type-safe config access across your entire stack.
import { createConfigClient } from "@smooai/config";
const config = createConfigClient({
serverUrl: "https://api.smoo.ai",
token: process.env.SMOO_ACCESS_TOKEN!,
schemaId: "your-schema-id",
environmentId: "your-env-id",
});
const apiUrl = await config.get("API_URL"); // string
const retries = await config.get("MAX_RETRIES"); // number
const darkMode = await config.flag("DARK_MODE"); // booleanEnvironment Management
Each environment gets its own set of config values. Public values, secrets, and feature flags flow independently per environment so you can test features in staging without affecting production.
| Key | Development | Staging | Production |
|---|---|---|---|
| API_URL | localhost:3000 | staging.api.com | api.example.com |
| DATABASE_URL | local postgres | staging db | production db |
| NEW_DASHBOARD | true | true | false |
REST API
The Config API provides full CRUD for schemas, environments, and values. All endpoints require authentication.
Create a Config Schema
curl -X POST https://api.smoo.ai/organizations/{org_id}/config-schemas \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app-config",
"description": "Configuration for my application",
"schema": {
"API_URL": { "type": "string", "default": "https://api.example.com" },
"MAX_RETRIES": { "type": "number", "default": 3 },
"DEBUG_MODE": { "type": "boolean", "default": false }
}
}'Set Config Values
curl -X PUT https://api.smoo.ai/organizations/{org_id}/config-values \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"schemaId": "schema_id",
"environmentId": "env_id",
"values": {
"API_URL": "https://api.production.example.com",
"MAX_RETRIES": 5
}
}'SDK Usage
Use the @smooai/config SDK to read configuration values in your application with type safety and local caching.
import { createConfigClient } from "@smooai/config";
const config = createConfigClient({
serverUrl: "https://api.smoo.ai",
token: process.env.SMOO_ACCESS_TOKEN!,
schemaId: "your-schema-id",
environmentId: "your-env-id",
});
// Type-safe config access
const apiUrl = await config.get("API_URL");
const maxRetries = await config.get("MAX_RETRIES");