How to Manage Kaneo Configurations: A Complete Guide to Environment Variables and API-First Settings

Kaneo configurations are managed through environment variables that are read at runtime, exposed via a public /config API endpoint, and consumed reactively by the frontend through TanStack Query hooks.

Kaneo is an open-source project management platform built with a modern stack. Understanding how to manage Kaneo configurations is essential for self-hosting, customizing integrations, and extending the platform. This guide walks through the complete configuration lifecycle—from environment variables to React components—based on the actual source code in the usekaneo/kaneo repository.

How Kaneo's Configuration System Works

Kaneo uses a thin, API-first configuration layer rather than static config files. This design choice enables runtime flexibility and seamless frontend reactivity without rebuilds.

The Three-Layer Architecture

  1. Environment layer – process.env variables serve as the single source of truth
  2. API layer – GET /config endpoint exposes typed settings as JSON
  3. Client layer – TanStack Query hooks provide cached, reactive access

Each layer is intentionally simple. The getSettings() helper in apps/api/src/utils/get-settings.ts performs the only transformation: parsing environment strings into typed values.

Reading Configuration Settings on the Server

All server-side configuration flows through one function.

The getSettings() Helper

In apps/api/src/utils/get-settings.ts, the getSettings() function maps environment variables to a typed object:

function getSettings() {
  return {
    isDemoMode: process.env.DEMO_MODE === "true",
    hasSmtp: isSmtpConfigured(),
    hasGithubSignIn: !!process.env.GITHUB_CLIENT_ID,
    hasGoogleSignIn: !!process.env.GOOGLE_CLIENT_ID,
    // ... additional flags
  };
}

Key characteristics:

  • Returns a plain JavaScript object with boolean and string properties
  • Performs no caching—reads fresh process.env values on every call
  • Delegates complex logic (like SMTP validation) to specialized utilities

SMTP-Specific Configuration

Email settings receive special handling in packages/email/src/smtp-config.ts. The getSmtpTransportOptions() function constructs a complete Nodemailer configuration object, while isSmtpConfigured() performs a quick check for required variables.

Exposing Configurations via the API

The GET /config endpoint in apps/api/src/config/index.ts is the bridge between environment variables and the frontend.

The Config Route Implementation

import { configSchema } from "@/schemas";
import { getSettings } from "@/utils/get-settings";

app.get("/config", (c) => {
  const settings = getSettings();
  return c.json(settings);
});

OpenAPI integration – The response shape is governed by configSchema in apps/api/src/schemas.ts, defined with Valibot validators:

export const configSchema = v.object({
  isDemoMode: v.boolean(),
  hasSmtp: v.boolean(),
  hasGithubSignIn: v.boolean(),
  hasGoogleSignIn: v.boolean(),
  // ... additional fields
});

Thanks to hono-openapi decorators, this schema automatically generates documentation and runtime validation without manual synchronization.

Consuming Kaneo Configurations in the Frontend

The frontend accesses configurations through a typed API client and reactive hooks.

Fetching Configurations with the API Client

The base URL resolution happens in packages/libs/src/api-url.ts via resolveApiBaseUrl(), which handles both development and production environments. The fetcher in apps/web/src/fetchers/config/get-config.ts wraps the Hono client:

import { client } from "@kaneo/libs";

export async function getConfig() {
  const response = await client.config.$get();
  
  if (!response.ok) {
    throw new Error("Failed to fetch config");
  }
  
  return response.json();
}

Using the useGetConfig React Hook

For reactive access, useGetConfig in apps/web/src/hooks/queries/config/use-get-config.ts wraps the fetcher in TanStack Query:

import useGetConfig from "@/hooks/queries/config/use-get-config";

export default function ConfigDisplay() {
  const { data: cfg, isLoading, error } = useGetConfig();

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Error loading config</p>;

  return (
    <ul>
      <li>Demo mode: {cfg?.isDemoMode ? "Yes" : "No"}</li>
      <li>SMTP configured: {cfg?.hasSmtp ? "Yes" : "No"}</li>
      <li>GitHub sign‑in: {cfg?.hasGithubSignIn ? "Enabled" : "Disabled"}</li>
    </ul>
  );
}

This pattern provides automatic caching, background refetching, and error handling without additional boilerplate.

Adding a New Kaneo Configuration Flag

Extending the configuration system requires coordinated changes across three files.

Step 1: Add the Environment Variable

Document the new variable in .env.example:


# Enable real-time chat features

ENABLE_CHAT="true"

Step 2: Expose in getSettings()

Update apps/api/src/utils/get-settings.ts:

function getSettings() {
  return {
    // …existing flags
    enableChat: process.env.ENABLE_CHAT === "true",
  };
}

Step 3: Extend the OpenAPI Schema

Update apps/api/src/schemas.ts:

export const configSchema = v.object({
  // …existing fields
  enableChat: v.boolean(),
});

The frontend automatically receives the new property through getConfig() and useGetConfig() without additional changes.

Integration-Specific Configuration Patterns

Kaneo uses consistent patterns for third-party integrations.

Authentication Providers

Each OAuth provider exposes a boolean flag indicating configuration status:

  • hasGithubSignIn – checks for GITHUB_CLIENT_ID
  • hasGoogleSignIn – checks for GOOGLE_CLIENT_ID
  • hasDiscordSignIn – checks for DISCORD_CLIENT_ID

These flags enable conditional UI rendering (show/hide sign-in buttons) without exposing sensitive credentials.

Email Configuration

SMTP settings are the most complex configuration area. The packages/email/src/smtp-config.ts module provides:

  • isSmtpConfigured() – quick boolean check for UI purposes
  • getSmtpTransportOptions() – full Nodemailer configuration for sending

This separation allows the API to report email capability without constructing transport objects unnecessarily.

Summary

  • Single source of truth – All Kaneo configurations originate from environment variables read via process.env
  • Server entry point – The getSettings() function in apps/api/src/utils/get-settings.ts parses and types all environment variables
  • Public API – The GET /config endpoint in apps/api/src/config/index.ts exposes settings with automatic OpenAPI documentation
  • Frontend access – Use useGetConfig() for reactive state or getConfig() for imperative fetching
  • Extensibility – Adding flags requires updating three files: environment documentation, getSettings(), and configSchema

Frequently Asked Questions

Where does Kaneo store its configuration files?

Kaneo does not use static configuration files. All settings are read from environment variables at runtime through process.env, making it ideal for containerized deployments and secret management systems.

How do I add a custom configuration option to Kaneo?

Add your variable to .env.example, parse it in apps/api/src/utils/get-settings.ts, and extend configSchema in apps/api/src/schemas.ts. The frontend will automatically receive the new value through existing hooks.

Why does Kaneo expose configurations through an API endpoint instead of baking them into the build?

The API-first approach enables runtime configuration changes without recompiling the frontend. This is critical for Docker deployments where the same image runs across different environments (staging, production) with different settings.

How can I check if email notifications are configured in Kaneo?

Query the public config endpoint or use useGetConfig() in a React component. The hasSmtp boolean indicates whether SMTP credentials are present, while packages/email/src/smtp-config.ts contains the detailed transport configuration used when sending actual emails.

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 →