How @n8n/config Manages Centralized Configuration in n8n: Decorators, Zod, and Dependency Injection
@n8n/config provides a centralized, type-safe configuration system for n8n using TypeScript decorators, Zod validation schemas, and dependency injection to aggregate all runtime settings into a single hierarchical GlobalConfig object.
The n8n workflow automation platform manages dozens of runtime settings—from database URLs to AI service endpoints—through a dedicated centralized configuration package. The @n8n/config package serves as the single source of truth for every environment variable, eliminating scattered process.env access throughout the codebase. By combining decorator-based metadata, runtime validation, and dependency injection, the system ensures that configuration is validated at startup and accessible via strongly-typed objects anywhere in the application.
The Architecture of Centralized Configuration
The @n8n/config system is built on three architectural pillars that work together to provide type safety and centralized management.
TypeScript Decorators as the Foundation
Configuration containers are defined using TypeScript classes decorated with @Config, @Env, and @Nested. These decorators attach metadata that a factory function reads during dependency resolution. In src/decorators.ts, the @Config decorator registers a factory with the @n8n/di container that walks a metadata map, reads environment variables, applies Zod schemas, and injects nested config objects.
Zod Schema Validation
Runtime type safety is enforced through Zod schemas passed to the @Env decorator. When the factory resolves a configuration value, it executes schema.safeParse(value) against the raw environment variable. If validation fails, the system falls back to the property's default value and emits a warning. This pattern is used for strict enums like protocolSchema = z.enum(['http', 'https']) in the top-level configuration.
Dependency Injection Integration
All configuration classes are registered as services within the @n8n/di container. The @Config decorator wraps the class with Service({ factory }), making the fully-populated configuration available via Container.get(GlobalConfig) or through constructor injection in other services.
Core Decorators for Configuration Management
The package exposes three primary decorators that define how environment variables map to typed properties.
@Config – The Class-Level Decorator
Applied to any class that should serve as a configuration container, the @Config decorator in src/decorators.ts (lines 37-99) registers the class with the DI container and attaches a factory function. This factory is responsible for instantiating the class, reading environment variables based on metadata keys, and recursively resolving nested configurations.
@Env – Mapping Environment Variables
The @Env decorator (lines 109-126 in src/decorators.ts) associates a class property with a specific environment variable name. It accepts an optional Zod schema for validation. During resolution, the system checks for the environment variable; if not found, it checks for a <VAR>_FILE variant for file-based secrets, then parses and validates the value before assignment.
@Nested – Building Hierarchical Configs
For complex configuration trees, the @Nested decorator (lines 101-107) declares that a property is itself a configuration class. The factory fetches this nested class from the DI container during parent resolution, enabling deep hierarchies such as config.database.url or config.ai.enabled without manual instantiation.
How GlobalConfig Aggregates All Configuration
The GlobalConfig class in src/index.ts (lines 69-132) serves as the root configuration object that aggregates every individual configuration section. It imports and nests specialized configs like AuthConfig, DatabaseConfig, AiConfig, and others from the configs/ directory.
Each nested configuration is decorated with @Nested, while primitive values use @Env with appropriate Zod schemas. For example, the protocol field uses protocolSchema = z.enum(['http', 'https']) to ensure only valid HTTP protocols are accepted. The src/index.ts file also re-exports all individual config classes and helper types, providing a single entry point: import { GlobalConfig } from '@n8n/config'.
Accessing Configuration in Your Code
Once the configuration system is initialized, you can access the centralized config through the dependency injection container.
Via Container Resolution
For one-off access or in non-service contexts, resolve the configuration directly from the DI container:
import { Container } from '@n8n/di';
import { GlobalConfig } from '@n8n/config';
// Resolve once – the DI container builds the whole hierarchy
const config = Container.get(GlobalConfig);
// Access nested properties
console.log('Server port:', config.port); // from N8N_PORT
console.log('Database URL:', config.database.url); // from DATABASE_URL (or file)
console.log('AI enabled?', config.ai.enabled); // from N8N_AI_ENABLED
Via Constructor Injection
In service classes, inject the configuration through the constructor for cleaner testing and dependency management:
import { Service } from '@n8n/di';
import { GlobalConfig } from '@n8n/config';
@Service()
export class WorkflowService {
constructor(private readonly config: GlobalConfig) {}
isFeatureEnabled(): boolean {
return this.config.ai.enabled && this.config.auth.jwtSecret !== '';
}
}
Because GlobalConfig is registered as a DI service, the container automatically provides the fully-populated instance with all environment variables resolved, validated, and nested configs injected.
Advanced Features: File-Based Secrets and Validation
The configuration system supports enterprise deployment patterns through file-based secrets and strict runtime validation.
File-Based Secrets: For sensitive values like TLS certificates or database passwords, the system checks for <VAR>_FILE environment variables before falling back to the direct variable. In src/decorators.ts (lines 19-34), the readEnv function loads file contents and trims whitespace, enabling Docker secrets or Kubernetes mounted volumes to inject configuration securely.
Zod Validation: When defining configuration properties, you can pass a Zod schema to the @Env decorator. The factory executes schema.safeParse(value) during resolution. If validation fails, the property falls back to its default value and the system emits a warning. This ensures that malformed environment variables cannot crash the application or inject invalid state.
Summary
- @n8n/config serves as the single source of truth for all n8n runtime settings, eliminating scattered
process.envaccess. - The system uses TypeScript decorators (
@Config,@Env,@Nested) to define typed configuration classes with metadata-driven resolution. - Zod schemas provide runtime validation, falling back to defaults when environment variables are malformed.
- File-based secrets (
<VAR>_FILE) enable secure injection of sensitive values via Docker or Kubernetes. - Dependency injection via
@n8n/dimakes theGlobalConfigavailable throughContainer.get()or constructor injection anywhere in the codebase.
Frequently Asked Questions
What is the primary purpose of @n8n/config?
The primary purpose of @n8n/config is to provide a centralized, type-safe configuration management system for the n8n workflow automation platform. It aggregates all runtime environment variables into a single hierarchical GlobalConfig object, ensuring that settings are validated at startup and accessible via dependency injection throughout the application without direct process.env references.
How does @n8n/config handle environment variable validation?
Environment variable validation is handled through Zod schemas passed to the @Env decorator. When the configuration factory resolves a property, it executes schema.safeParse(value) against the raw environment variable. If validation fails, the system falls back to the property's default value and emits a warning, preventing invalid configuration from crashing the application or causing undefined behavior.
Can I use file-based secrets instead of environment variables?
Yes, the configuration system supports file-based secrets through the <VAR>_FILE pattern. If an environment variable is not set directly but a corresponding <VAR>_FILE variable points to a file path, the system loads the file's contents (trimming whitespace) and uses that as the configuration value. This pattern is commonly used for Docker secrets or Kubernetes-mounted volumes to securely inject sensitive data like TLS certificates or database passwords.
How do I access the configuration in a service class?
You can access the configuration in a service class through constructor injection provided by @n8n/di. Import GlobalConfig from @n8n/config and include it in your service's constructor parameters. Because GlobalConfig is registered as a DI service via the @Config decorator, the container automatically injects the fully-populated configuration instance when resolving your service. Alternatively, you can use Container.get(GlobalConfig) for one-off access outside of service constructors.
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 →