first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,17 @@
import { Container } from '@n8n/di';
import { AiConfig } from '../ai.config';
describe('AiConfig', () => {
beforeEach(() => {
Container.reset();
jest.clearAllMocks();
});
it('should not poison openAiDefaultHeaders object globally when modified', () => {
const { openAiDefaultHeaders } = Container.get(AiConfig);
openAiDefaultHeaders.test = 'ok';
expect(openAiDefaultHeaders.test).toBe('ok');
expect(Container.get(AiConfig).openAiDefaultHeaders.test).toBeFalsy();
});
});
@@ -0,0 +1,67 @@
import { Container } from '@n8n/di';
import { UserManagementConfig } from '../user-management.config';
describe('UserManagementConfig', () => {
beforeEach(() => {
Container.reset();
jest.clearAllMocks();
});
const originalEnv = process.env;
afterEach(() => {
process.env = originalEnv;
});
test('with refresh timout > session, sets refresh timout to `0`', () => {
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
process.env = {
N8N_USER_MANAGEMENT_JWT_DURATION_HOURS: '1',
N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS: '2',
};
const config = Container.get(UserManagementConfig);
expect(config.jwtRefreshTimeoutHours).toBe(0);
expect(consoleWarnSpy).toHaveBeenCalledWith(
'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS needs to be smaller than N8N_USER_MANAGEMENT_JWT_DURATION_HOURS. Setting N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS to 0.',
);
consoleWarnSpy.mockRestore();
});
test('with refresh timout == session, sets refresh timout to `0`', () => {
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
process.env = {
N8N_USER_MANAGEMENT_JWT_DURATION_HOURS: '1',
N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS: '1',
};
const config = Container.get(UserManagementConfig);
expect(config.jwtRefreshTimeoutHours).toBe(0);
expect(consoleWarnSpy).toHaveBeenCalledWith(
'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS needs to be smaller than N8N_USER_MANAGEMENT_JWT_DURATION_HOURS. Setting N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS to 0.',
);
consoleWarnSpy.mockRestore();
});
test('with refresh timout < session, keeps refresh timout intact', () => {
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
process.env = {
N8N_USER_MANAGEMENT_JWT_DURATION_HOURS: '10',
N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS: '5',
};
const config = Container.get(UserManagementConfig);
expect(config.jwtRefreshTimeoutHours).toBe(5);
expect(consoleWarnSpy).not.toHaveBeenCalled();
consoleWarnSpy.mockRestore();
});
});
@@ -0,0 +1,11 @@
import { Config, Env } from '../decorators';
@Config
export class AiAssistantConfig {
/**
* Base URL of the AI assistant service.
* When set, requests are sent to this URL instead of the default provider endpoint.
*/
@Env('N8N_AI_ASSISTANT_BASE_URL')
baseUrl: string = '';
}
@@ -0,0 +1,11 @@
import { Config, Env } from '../decorators';
@Config
export class AiBuilderConfig {
/**
* API key for the Anthropic (Claude) provider used by the AI workflow builder.
* When set, enables AI-powered workflow and node building.
*/
@Env('N8N_AI_ANTHROPIC_KEY')
apiKey: string = '';
}
@@ -0,0 +1,32 @@
import { Config, Env } from '../decorators';
@Config
export class AiConfig {
/** Whether AI features (such as AI nodes and AI assistant) are enabled globally. */
@Env('N8N_AI_ENABLED')
enabled: boolean = false;
/**
* Maximum time in milliseconds to wait for an HTTP response from an AI service.
* Matches the maximum workflow execution timeout, EXECUTIONS_TIMEOUT_MAX (1 hour) so AI calls do not outlive executions.
* Default: 3600000 (1 hour).
*/
@Env('N8N_AI_TIMEOUT_MAX')
timeout: number = 3600000;
/**
* Whether workflow and node parameter values may be sent to AI providers.
* When false, only structure or placeholders are sent.
*/
@Env('N8N_AI_ALLOW_SENDING_PARAMETER_VALUES')
allowSendingParameterValues: boolean = true;
/** Whether to persist AI workflow builder sessions to the database. */
@Env('N8N_AI_PERSIST_BUILDER_SESSIONS')
persistBuilderSessions: boolean = false;
get openAiDefaultHeaders(): Record<string, string> {
// eslint-disable-next-line @typescript-eslint/naming-convention
return { 'openai-platform': 'org-qkmJQuJ2WnvoIKMr2UJwIJkZ' };
}
}
@@ -0,0 +1,24 @@
import { z } from 'zod';
import { Config, Env, Nested } from '../decorators';
const samesiteSchema = z.enum(['strict', 'lax', 'none']);
type Samesite = z.infer<typeof samesiteSchema>;
@Config
class CookieConfig {
/** Whether to set the `Secure` flag on the n8n authentication cookie (recommended for HTTPS). */
@Env('N8N_SECURE_COOKIE')
secure: boolean = true;
/** Value for the `SameSite` attribute on the n8n authentication cookie (`strict`, `lax`, or `none`). */
@Env('N8N_SAMESITE_COOKIE', samesiteSchema)
samesite: Samesite = 'lax';
}
@Config
export class AuthConfig {
@Nested
cookie: CookieConfig;
}
@@ -0,0 +1,41 @@
import { z } from 'zod';
import { Config, Env, Nested } from '../decorators';
const cacheBackendSchema = z.enum(['memory', 'redis', 'auto']);
type CacheBackend = z.infer<typeof cacheBackendSchema>;
@Config
class MemoryConfig {
/** Maximum size of the in-memory cache in bytes. Default: 3 MiB. */
@Env('N8N_CACHE_MEMORY_MAX_SIZE')
maxSize: number = 3 * 1024 * 1024; // 3 MiB
/** Time to live in milliseconds for entries in the memory cache. Default: 1 hour. */
@Env('N8N_CACHE_MEMORY_TTL')
ttl: number = 3600 * 1000; // 1 hour
}
@Config
class RedisConfig {
/** Key prefix for cache entries stored in Redis. */
@Env('N8N_CACHE_REDIS_KEY_PREFIX')
prefix: string = 'cache';
/** Time to live in milliseconds for Redis cache entries. Set to 0 to disable expiry. Default: 1 hour. */
@Env('N8N_CACHE_REDIS_TTL')
ttl: number = 3600 * 1000; // 1 hour
}
@Config
export class CacheConfig {
/** Cache backend: `memory`, `redis`, or `auto` (choose based on deployment). */
@Env('N8N_CACHE_BACKEND', cacheBackendSchema)
backend: CacheBackend = 'auto';
@Nested
memory: MemoryConfig;
@Nested
redis: RedisConfig;
}
@@ -0,0 +1,23 @@
import { Config, Env } from '../decorators';
@Config
export class ChatHubConfig {
/**
* Time to live in seconds for execution context in Chat Hub.
* Maximum duration for a single non-streaming Workflow Agent execution, including wait time.
* After this TTL, responses from those executions are no longer captured or sent to the client.
*/
@Env('N8N_CHAT_HUB_EXECUTION_CONTEXT_TTL')
executionContextTtl: number = 3600;
/**
* Time to live in seconds for stream state in Chat Hub.
* Inactive streams are cleaned up after this duration.
*/
@Env('N8N_CHAT_HUB_STREAM_STATE_TTL')
streamStateTtl: number = 300;
/** Maximum number of response chunks to buffer per stream for reconnection in Chat Hub. */
@Env('N8N_CHAT_HUB_MAX_BUFFERED_CHUNKS')
maxBufferedChunks: number = 1000;
}
@@ -0,0 +1,33 @@
import { Config, Env, Nested } from '../decorators';
@Config
class CredentialsOverwrite {
/**
* JSON object of prefilled credential data (overwrites). End users cannot view or edit these values.
* Format: `{ "CREDENTIAL_NAME": { "PARAMETER": "VALUE" } }`.
*/
@Env('CREDENTIALS_OVERWRITE_DATA')
data: string = '{}';
/** Endpoint of an internal API that returns overwritten credential definitions. When set, overwrites are loaded from this endpoint. */
@Env('CREDENTIALS_OVERWRITE_ENDPOINT')
endpoint: string = '';
/** Token used to authenticate requests to the credentials overwrite endpoint. */
@Env('CREDENTIALS_OVERWRITE_ENDPOINT_AUTH_TOKEN')
endpointAuthToken: string = '';
/** Whether to persist credential overwrites so they survive restarts. */
@Env('CREDENTIALS_OVERWRITE_PERSISTENCE')
persistence: boolean = false;
}
@Config
export class CredentialsConfig {
/** Default name suggested when creating new credentials. */
@Env('CREDENTIALS_DEFAULT_NAME')
defaultName: string = 'My credentials';
@Nested
overwrite: CredentialsOverwrite;
}
@@ -0,0 +1,53 @@
import { tmpdir } from 'node:os';
import path from 'node:path';
import { Config, Env } from '../decorators';
@Config
export class DataTableConfig {
/** Maximum total size in bytes allowed for data tables. Default: 50 MiB. */
@Env('N8N_DATA_TABLES_MAX_SIZE_BYTES')
maxSize: number = 50 * 1024 * 1024;
/**
* Size in bytes at which to warn that a data table is nearing capacity.
* If unset, defaults to 80% of maxSize.
*/
@Env('N8N_DATA_TABLES_WARNING_THRESHOLD_BYTES')
warningThreshold?: number;
/**
* Duration in milliseconds to cache data table size checks.
* Reduces database load when validating size repeatedly.
*/
@Env('N8N_DATA_TABLES_SIZE_CHECK_CACHE_DURATION_MS')
sizeCheckCacheDuration: number = 5 * 1000;
/**
* Maximum file size in bytes for CSV uploads to data tables.
* If unset, the limit is the remaining available storage.
*/
@Env('N8N_DATA_TABLES_UPLOAD_MAX_FILE_SIZE_BYTES')
uploadMaxFileSize?: number;
/** Interval in milliseconds between cleanup runs for orphaned upload files. Default: 60000 milliseconds. */
@Env('N8N_DATA_TABLES_CLEANUP_INTERVAL_MS')
cleanupIntervalMs: number = 60 * 1000;
/**
* Age in milliseconds after which an uploaded file is treated as orphaned and deleted during cleanup.
* Default: 2 minutes.
*/
@Env('N8N_DATA_TABLES_FILE_MAX_AGE_MS')
fileMaxAgeMs: number = 2 * 60 * 1000;
/**
* Directory for temporary CSV uploads before import. Files in this directory are pruned by cleanup (see fileMaxAgeMs).
* Resolved as `<system-tmp-dir>/n8nDataTableUploads` (for example, `/tmp/n8nDataTableUploads`).
*/
readonly uploadDir: string;
constructor() {
this.uploadDir = path.join(tmpdir(), 'n8nDataTableUploads');
}
}
@@ -0,0 +1,143 @@
import { z } from 'zod';
import { Config, Env, Nested } from '../decorators';
const dbLoggingOptionsSchema = z.enum(['query', 'error', 'schema', 'warn', 'info', 'log', 'all']);
type DbLoggingOptions = z.infer<typeof dbLoggingOptionsSchema>;
@Config
class LoggingConfig {
/** Whether database logging is enabled. */
@Env('DB_LOGGING_ENABLED')
enabled: boolean = false;
/**
* Database logging verbosity. Only applies when `DB_LOGGING_MAX_EXECUTION_TIME` is greater than 0.
*/
@Env('DB_LOGGING_OPTIONS', dbLoggingOptionsSchema)
options: DbLoggingOptions = 'error';
/** Only log queries that run longer than this many milliseconds. Set to 0 to disable slow-query logging. */
@Env('DB_LOGGING_MAX_EXECUTION_TIME')
maxQueryExecutionTime: number = 0;
}
@Config
class PostgresSSLConfig {
/**
* Whether to use SSL/TLS for the Postgres connection.
* Defaults to true if any of the SSL cert/key/CA environment variables are set.
*/
@Env('DB_POSTGRESDB_SSL_ENABLED')
enabled: boolean = false;
/** Path or contents of the CA certificate for Postgres SSL. */
@Env('DB_POSTGRESDB_SSL_CA')
ca: string = '';
/** Path or contents of the client certificate for Postgres SSL. */
@Env('DB_POSTGRESDB_SSL_CERT')
cert: string = '';
/** Path or contents of the client private key for Postgres SSL. */
@Env('DB_POSTGRESDB_SSL_KEY')
key: string = '';
/** Whether to reject Postgres connections when the server certificate cannot be verified. */
@Env('DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED')
rejectUnauthorized: boolean = true;
}
@Config
class PostgresConfig {
/** Postgres database name. */
@Env('DB_POSTGRESDB_DATABASE')
database: string = 'n8n';
/** Postgres database host. */
@Env('DB_POSTGRESDB_HOST')
host: string = 'localhost';
/** Postgres database password. */
@Env('DB_POSTGRESDB_PASSWORD')
password: string = '';
/** Postgres database port. */
@Env('DB_POSTGRESDB_PORT')
port: number = 5432;
/** Postgres user name. */
@Env('DB_POSTGRESDB_USER')
user: string = 'postgres';
/** Postgres schema to use. */
@Env('DB_POSTGRESDB_SCHEMA')
schema: string = 'public';
/** Maximum number of connections in the Postgres connection pool. */
@Env('DB_POSTGRESDB_POOL_SIZE')
poolSize: number = 2;
/** Timeout in milliseconds when establishing a new Postgres connection. */
@Env('DB_POSTGRESDB_CONNECTION_TIMEOUT')
connectionTimeoutMs: number = 20_000;
/** Time in milliseconds after which an idle connection in the pool is closed. */
@Env('DB_POSTGRESDB_IDLE_CONNECTION_TIMEOUT')
idleTimeoutMs: number = 30_000;
/** Maximum time in milliseconds for a single query. Queries exceeding this are cancelled. Set to 0 to disable. */
@Env('DB_POSTGRESDB_STATEMENT_TIMEOUT')
statementTimeoutMs: number = 5 * 60 * 1000; // 5 minutes
@Nested
ssl: PostgresSSLConfig;
}
const sqlitePoolSizeSchema = z.coerce.number().int().gte(1);
@Config
export class SqliteConfig {
/** Path to the SQLite database file. */
@Env('DB_SQLITE_DATABASE')
database: string = 'database.sqlite';
/** Number of connections in the SQLite connection pool. Must be at least 1. */
@Env('DB_SQLITE_POOL_SIZE', sqlitePoolSizeSchema)
poolSize: number = 3;
/**
* Whether to run SQLite VACUUM on startup to reclaim space and optimize the file.
*
* @warning Blocking operation; can significantly increase startup time.
*/
@Env('DB_SQLITE_VACUUM_ON_STARTUP')
executeVacuumOnStartup: boolean = false;
}
const dbTypeSchema = z.enum(['sqlite', 'postgresdb']);
type DbType = z.infer<typeof dbTypeSchema>;
@Config
export class DatabaseConfig {
/** Database type: `sqlite` or `postgresdb`. */
@Env('DB_TYPE', dbTypeSchema)
type: DbType = 'sqlite';
/** Prefix prepended to all n8n table names (useful for shared databases). */
@Env('DB_TABLE_PREFIX')
tablePrefix: string = '';
/** Interval in seconds between health-check pings to the database. */
@Env('DB_PING_INTERVAL_SECONDS')
pingIntervalSeconds: number = 2;
@Nested
logging: LoggingConfig;
@Nested
postgresdb: PostgresConfig;
@Nested
sqlite: SqliteConfig;
}
@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class DeploymentConfig {
/** Deployment type identifier (for example, `default`, `cloud`). Used for telemetry and feature behavior. */
@Env('N8N_DEPLOYMENT_TYPE')
type: string = 'default';
}
@@ -0,0 +1,30 @@
import { Config, Env, Nested } from '../decorators';
@Config
class PostHogConfig {
/** PostHog project API key for product analytics. */
@Env('N8N_DIAGNOSTICS_POSTHOG_API_KEY')
apiKey: string = 'phc_4URIAm1uYfJO7j8kWSe0J8lc8IqnstRLS7Jx8NcakHo';
/** PostHog API host URL. */
@Env('N8N_DIAGNOSTICS_POSTHOG_API_HOST')
apiHost: string = 'https://us.i.posthog.com';
}
@Config
export class DiagnosticsConfig {
/** Whether anonymous diagnostics and telemetry are enabled for this instance. */
@Env('N8N_DIAGNOSTICS_ENABLED')
enabled: boolean = true;
/** Telemetry endpoint config for the frontend (format: key;baseUrl). */
@Env('N8N_DIAGNOSTICS_CONFIG_FRONTEND')
frontendConfig: string = '1zPn9bgWPzlQc0p8Gj1uiK6DOTn;https://telemetry.n8n.io';
/** Telemetry endpoint config for the backend (format: key;baseUrl). */
@Env('N8N_DIAGNOSTICS_CONFIG_BACKEND')
backendConfig: string = '1zPn7YoGC3ZXE9zLeTKLuQCB4F6;https://telemetry.n8n.io';
@Nested
posthogConfig: PostHogConfig;
}
@@ -0,0 +1,12 @@
import { Config, Env } from '../decorators';
@Config
export class DynamicBannersConfig {
/** URL to fetch dynamic banner content from (for example, in-app announcements). */
@Env('N8N_DYNAMIC_BANNERS_ENDPOINT')
endpoint: string = 'https://api.n8n.io/api/banners';
/** Whether to fetch and show dynamic banners (for example, announcements) from the endpoint. */
@Env('N8N_DYNAMIC_BANNERS_ENABLED')
enabled: boolean = true;
}
@@ -0,0 +1,147 @@
import { z } from 'zod';
import { Config, Env, Nested } from '../decorators';
@Config
class PrometheusMetricsConfig {
/** Whether to enable the `/metrics` endpoint to expose Prometheus metrics. */
@Env('N8N_METRICS')
enable: boolean = false;
/** Prefix for Prometheus metric names. */
@Env('N8N_METRICS_PREFIX')
prefix: string = 'n8n_';
/** Whether to expose system and Node.js metrics. See: https://www.npmjs.com/package/prom-client */
@Env('N8N_METRICS_INCLUDE_DEFAULT_METRICS')
includeDefaultMetrics: boolean = true;
/** Whether to include a label for workflow ID on workflow metrics. */
@Env('N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL')
includeWorkflowIdLabel: boolean = false;
/** Whether to include a label for node type on node metrics. */
@Env('N8N_METRICS_INCLUDE_NODE_TYPE_LABEL')
includeNodeTypeLabel: boolean = false;
/** Whether to include a label for credential type on credential metrics. */
@Env('N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL')
includeCredentialTypeLabel: boolean = false;
/** Whether to expose metrics for API endpoints. See: https://www.npmjs.com/package/express-prom-bundle */
@Env('N8N_METRICS_INCLUDE_API_ENDPOINTS')
includeApiEndpoints: boolean = false;
/** Whether to include a label for the path of API endpoint calls. */
@Env('N8N_METRICS_INCLUDE_API_PATH_LABEL')
includeApiPathLabel: boolean = false;
/** Whether to include a label for the HTTP method of API endpoint calls. */
@Env('N8N_METRICS_INCLUDE_API_METHOD_LABEL')
includeApiMethodLabel: boolean = false;
/** Whether to include a label for the status code of API endpoint calls. */
@Env('N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL')
includeApiStatusCodeLabel: boolean = false;
/** Whether to include metrics for cache hits and misses. */
@Env('N8N_METRICS_INCLUDE_CACHE_METRICS')
includeCacheMetrics: boolean = false;
/** Whether to include metrics derived from n8n's internal events */
@Env('N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS')
includeMessageEventBusMetrics: boolean = false;
/** Whether to include metrics for jobs in scaling mode. Not supported in multi-main setup. */
@Env('N8N_METRICS_INCLUDE_QUEUE_METRICS')
includeQueueMetrics: boolean = false;
/** How often (in seconds) to update queue metrics. */
@Env('N8N_METRICS_QUEUE_METRICS_INTERVAL')
queueMetricsInterval: number = 20;
/** How often (in seconds) to update active workflow metric */
@Env('N8N_METRICS_ACTIVE_WORKFLOW_METRIC_INTERVAL')
activeWorkflowCountInterval: number = 60;
/** Whether to include a label for workflow name on workflow metrics. */
@Env('N8N_METRICS_INCLUDE_WORKFLOW_NAME_LABEL')
includeWorkflowNameLabel: boolean = false;
/** Whether to include workflow execution statistics as metrics. */
@Env('N8N_METRICS_INCLUDE_WORKFLOW_STATISTICS')
includeWorkflowStatistics: boolean = false;
/** How often (in seconds) to update workflow statistics metrics. */
@Env('N8N_METRICS_WORKFLOW_STATISTICS_INTERVAL')
workflowStatisticsInterval: number = 300;
}
@Config
export class EndpointsConfig {
/** Maximum request payload size in MiB for the API. */
@Env('N8N_PAYLOAD_SIZE_MAX')
payloadSizeMax: number = 16;
/** Maximum size in MiB for a single file in multipart/form-data webhook payloads. */
@Env('N8N_FORMDATA_FILE_SIZE_MAX')
formDataFileSizeMax: number = 200;
@Nested
metrics: PrometheusMetricsConfig;
/** Path segment for REST API endpoints. */
@Env('N8N_ENDPOINT_REST')
rest: string = 'rest';
/** Path segment for form endpoints. */
@Env('N8N_ENDPOINT_FORM')
form: string = 'form';
/** Path segment for test form endpoints. */
@Env('N8N_ENDPOINT_FORM_TEST')
formTest: string = 'form-test';
/** Path segment for waiting form endpoints. */
@Env('N8N_ENDPOINT_FORM_WAIT')
formWaiting: string = 'form-waiting';
/** Path segment for webhook endpoints. */
@Env('N8N_ENDPOINT_WEBHOOK')
webhook: string = 'webhook';
/** Path segment for test webhook endpoints. */
@Env('N8N_ENDPOINT_WEBHOOK_TEST')
webhookTest: string = 'webhook-test';
/** Path segment for waiting webhook endpoints. */
@Env('N8N_ENDPOINT_WEBHOOK_WAIT')
webhookWaiting: string = 'webhook-waiting';
/** Path segment for MCP endpoints. */
@Env('N8N_ENDPOINT_MCP')
mcp: string = 'mcp';
/** Path segment for test MCP endpoints. */
@Env('N8N_ENDPOINT_MCP_TEST')
mcpTest: string = 'mcp-test';
/** Whether to disable n8n's UI (frontend). */
@Env('N8N_DISABLE_UI')
disableUi: boolean = false;
/** Whether to disable production webhooks on the main process, when using webhook-specific processes. */
@Env('N8N_DISABLE_PRODUCTION_MAIN_PROCESS')
disableProductionWebhooksOnMainProcess: boolean = false;
/** Colon-separated list of path segments that should not serve the UI (for example, health or webhook-only routes). */
@Env('N8N_ADDITIONAL_NON_UI_ROUTES')
additionalNonUIRoutes: string = '';
/** Path for the health check endpoint. */
@Env(
'N8N_ENDPOINT_HEALTH',
z.string().transform((val) => (val.startsWith('/') ? val : `/${val}`)),
)
health: string = '/healthz';
}
@@ -0,0 +1,36 @@
import { z } from 'zod';
import { Config, Env, Nested } from '../decorators';
@Config
class LogWriterConfig {
/** Number of event log files to retain; older files are rotated out. */
@Env('N8N_EVENTBUS_LOGWRITER_KEEPLOGCOUNT')
keepLogCount: number = 3;
/** Maximum size in KB of a single event log file before rotation. Default: 10 MB. */
@Env('N8N_EVENTBUS_LOGWRITER_MAXFILESIZEINKB')
maxFileSizeInKB: number = 10240; // 10 MB
/** Base filename for event log files (extension and rotation suffix are added). */
@Env('N8N_EVENTBUS_LOGWRITER_LOGBASENAME')
logBaseName: string = 'n8nEventLog';
}
const recoveryModeSchema = z.enum(['simple', 'extensive']);
type RecoveryMode = z.infer<typeof recoveryModeSchema>;
@Config
export class EventBusConfig {
/** Interval in milliseconds to check for and resend unsent event-bus messages. Set to 0 to disable (may rarely allow duplicate sends when non-zero). */
@Env('N8N_EVENTBUS_CHECKUNSENTINTERVAL')
checkUnsentInterval: number = 0;
/** Endpoint to retrieve n8n version information from */
@Nested
logWriter: LogWriterConfig;
/** After a crash: `extensive` recovers full execution details; `simple` only marks executions as crashed. */
@Env('N8N_EVENTBUS_RECOVERY_MODE', recoveryModeSchema)
crashRecoveryMode: RecoveryMode = 'extensive';
}
@@ -0,0 +1,130 @@
import z from 'zod';
import { Config, Env, Nested } from '../decorators';
@Config
class PruningIntervalsConfig {
/** How often (minutes) execution data should be hard-deleted. */
@Env('EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL')
hardDelete: number = 15;
/** How often (minutes) execution data should be soft-deleted. */
@Env('EXECUTIONS_DATA_PRUNE_SOFT_DELETE_INTERVAL')
softDelete: number = 60;
}
@Config
class ConcurrencyConfig {
/**
* Max production executions allowed to run concurrently. `-1` means unlimited.
*
* Default for scaling mode is taken from the worker's `--concurrency` flag.
*/
@Env('N8N_CONCURRENCY_PRODUCTION_LIMIT')
productionLimit: number = -1;
/** Max evaluation executions allowed to run concurrently. `-1` means unlimited. */
@Env('N8N_CONCURRENCY_EVALUATION_LIMIT')
evaluationLimit: number = -1;
}
@Config
class QueueRecoveryConfig {
/** How often (minutes) to check for queue recovery. */
@Env('N8N_EXECUTIONS_QUEUE_RECOVERY_INTERVAL')
interval: number = 180;
/** Size of batch of executions to check for queue recovery. */
@Env('N8N_EXECUTIONS_QUEUE_RECOVERY_BATCH')
batchSize: number = 100;
}
@Config
class RecoveryConfig {
/**
* Number of last executions to check when determining if a workflow should be deactivated
* when all of the last N executions have crashed.
*/
@Env('N8N_WORKFLOW_AUTODEACTIVATION_MAX_LAST_EXECUTIONS')
maxLastExecutions: number = 3;
/**
* Whether to automatically deactivate workflows that have all their last executions crashed.
*/
@Env('N8N_WORKFLOW_AUTODEACTIVATION_ENABLED')
workflowDeactivationEnabled: boolean = false;
}
const executionModeSchema = z.enum(['regular', 'queue']);
export type ExecutionMode = z.infer<typeof executionModeSchema>;
@Config
export class ExecutionsConfig {
/** Whether to run executions in regular mode (in-process) or scaling mode (in workers). */
@Env('EXECUTIONS_MODE', executionModeSchema)
mode: ExecutionMode = 'regular';
/**
* How long (seconds) a workflow execution may run for before timeout.
* On timeout, the execution will be forcefully stopped. `-1` for unlimited.
* Currently unlimited by default - this default will change in a future version.
*/
@Env('EXECUTIONS_TIMEOUT')
timeout: number = -1;
/** Upper bound in seconds for execution timeout. Default: 1 hour. */
@Env('EXECUTIONS_TIMEOUT_MAX')
maxTimeout: number = 3600; // 1h
/** Whether to delete past executions on a rolling basis. */
@Env('EXECUTIONS_DATA_PRUNE')
pruneData: boolean = true;
/** How old (hours) a finished execution must be to qualify for soft-deletion. */
@Env('EXECUTIONS_DATA_MAX_AGE')
pruneDataMaxAge: number = 336;
/**
* Max number of finished executions to keep in database. Does not necessarily
* prune to the exact max number. `0` for unlimited.
*/
@Env('EXECUTIONS_DATA_PRUNE_MAX_COUNT')
pruneDataMaxCount: number = 10_000;
/**
* How old (hours) a finished execution must be to qualify for hard-deletion.
* This buffer by default excludes recent executions as the user may need
* them while building a workflow.
*/
@Env('EXECUTIONS_DATA_HARD_DELETE_BUFFER')
pruneDataHardDeleteBuffer: number = 1;
@Nested
pruneDataIntervals: PruningIntervalsConfig;
@Nested
concurrency: ConcurrencyConfig;
@Nested
queueRecovery: QueueRecoveryConfig;
@Nested
recovery: RecoveryConfig;
/** Whether to save execution data for failed production executions. This default can be overridden at a workflow level. */
@Env('EXECUTIONS_DATA_SAVE_ON_ERROR')
saveDataOnError: 'all' | 'none' = 'all';
/** Whether to save execution data for successful production executions. This default can be overridden at a workflow level. */
@Env('EXECUTIONS_DATA_SAVE_ON_SUCCESS')
saveDataOnSuccess: 'all' | 'none' = 'all';
/** Whether to save execution data as each node executes. This default can be overridden at a workflow level. */
@Env('EXECUTIONS_DATA_SAVE_ON_PROGRESS')
saveExecutionProgress: boolean = false;
/** Whether to save execution data for manual executions. This default can be overridden at a workflow level. */
@Env('EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS')
saveDataManualExecutions: boolean = true;
}
@@ -0,0 +1,9 @@
import { ColonSeparatedStringArray } from '../custom-types';
import { Config, Env } from '../decorators';
@Config
export class ExternalHooksConfig {
/** Paths to files that define external lifecycle hooks. Colon-separated for multiple files. */
@Env('EXTERNAL_HOOK_FILES')
files: ColonSeparatedStringArray = [];
}
@@ -0,0 +1,21 @@
import { z } from 'zod';
import { Config, Env } from '../decorators';
const releaseChannelSchema = z.enum(['stable', 'beta', 'nightly', 'dev', 'rc']);
type ReleaseChannel = z.infer<typeof releaseChannelSchema>;
@Config
export class GenericConfig {
/** Default timezone for the instance. Can be overridden per workflow. */
@Env('GENERIC_TIMEZONE')
timezone: string = 'America/New_York';
/** Release channel (for example, stable, beta, nightly). Affects update checks and some defaults. */
@Env('N8N_RELEASE_TYPE', releaseChannelSchema)
releaseChannel: ReleaseChannel = 'dev';
/** Seconds to wait for graceful shutdown (for example, finishing executions) before the process exits. */
@Env('N8N_GRACEFUL_SHUTDOWN_TIMEOUT')
gracefulShutdownTimeout: number = 30;
}
@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class HiringBannerConfig {
/** Whether to show the hiring/recruitment message in the browser devtools console. */
@Env('N8N_HIRING_BANNER_ENABLED')
enabled: boolean = true;
}
@@ -0,0 +1,34 @@
import path from 'node:path';
import { Config, Env } from '../decorators';
import { getN8nFolder } from '../utils/utils';
@Config
export class InstanceSettingsConfig {
/**
* Whether to enforce that n8n settings file doesn't have overly wide permissions.
* If set to true, n8n will check the permissions of the settings file and
* attempt change them to 0600 (only owner has rw access) if they are too wide.
*/
@Env('N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS')
enforceSettingsFilePermissions: boolean = true;
/**
* Encryption key to use for encrypting and decrypting credentials.
* If none is provided, a random key will be generated and saved to the settings file on the first launch.
* Can be provided directly via N8N_ENCRYPTION_KEY or via a file path using N8N_ENCRYPTION_KEY_FILE.
*/
@Env('N8N_ENCRYPTION_KEY')
encryptionKey: string = '';
/** User home directory path; falls back to current working directory if not available. */
readonly userHome: string;
/** n8n data directory (for example, ~/.n8n), used for settings, credentials, and local files. */
readonly n8nFolder: string;
constructor() {
this.n8nFolder = getN8nFolder();
this.userHome = path.dirname(this.n8nFolder);
}
}
@@ -0,0 +1,28 @@
import { Config, Env } from '../decorators';
@Config
export class LicenseConfig {
/** URL of the license server used to validate and refresh licenses. */
@Env('N8N_LICENSE_SERVER_URL')
serverUrl: string = 'https://license.n8n.io/v1';
/** Whether to automatically renew licenses before they expire. */
@Env('N8N_LICENSE_AUTO_RENEW_ENABLED')
autoRenewalEnabled: boolean = true;
/** Activation key used to activate or upgrade the instance license. */
@Env('N8N_LICENSE_ACTIVATION_KEY')
activationKey: string = '';
/** Whether to release floating entitlements back to the pool when the instance shuts down. */
@Env('N8N_LICENSE_DETACH_FLOATING_ON_SHUTDOWN')
detachFloatingOnShutdown: boolean = true;
/** Tenant identifier for the license SDK (for example, self-hosted, sandbox, embed, cloud). */
@Env('N8N_LICENSE_TENANT_ID')
tenantId: number = 1;
/** Ephemeral license certificate. See: https://github.com/n8n-io/license-management?tab=readme-ov-file#concept-ephemeral-entitlements */
@Env('N8N_LICENSE_CERT')
cert: string = '';
}
@@ -0,0 +1,136 @@
import { z } from 'zod';
import { CommaSeparatedStringArray } from '../custom-types';
import { Config, Env, Nested } from '../decorators';
/** Scopes (areas of functionality) to filter logs by. */
export const LOG_SCOPES = [
'concurrency',
'external-secrets',
'license',
'mcp',
'multi-main-setup',
'pruning',
'pubsub',
'push',
'quick-connect',
'redis',
'scaling',
'waiting-executions',
'task-runner',
'task-runner-js',
'task-runner-py',
'insights',
'workflow-activation',
'ssh-client',
'data-table',
'cron',
'community-nodes',
'chat-hub',
'breaking-changes',
'circuit-breaker',
'source-control',
'dynamic-credentials',
'workflow-history-compaction',
] as const;
export type LogScope = (typeof LOG_SCOPES)[number];
@Config
export class CronLoggingConfig {
/**
* Interval in minutes to log currently active cron jobs. Set to `0` to disable.
*
* @example `N8N_LOG_CRON_ACTIVE_INTERVAL=30` will log active crons every 30 minutes.
*/
@Env('N8N_LOG_CRON_ACTIVE_INTERVAL')
activeInterval: number = 0;
}
@Config
class FileLoggingConfig {
/**
* Max number of log files to keep, or max number of days to keep logs for.
* Once the limit is reached, the oldest log files will be rotated out.
* If using days, append a `d` suffix. Only for `file` log output.
*
* @example `N8N_LOG_FILE_COUNT_MAX=7` will keep at most 7 files.
* @example `N8N_LOG_FILE_COUNT_MAX=7d` will keep at most 7 days worth of files.
*/
@Env('N8N_LOG_FILE_COUNT_MAX')
fileCountMax: number = 100;
/** Max size (in MiB) for each log file. Only for `file` log output. */
@Env('N8N_LOG_FILE_SIZE_MAX')
fileSizeMax: number = 16;
/** Location of the log files inside `~/.n8n`. Only for `file` log output. */
@Env('N8N_LOG_FILE_LOCATION')
location: string = 'logs/n8n.log';
}
const logLevelSchema = z.enum(['error', 'warn', 'info', 'debug', 'silent']);
type LogLevel = z.infer<typeof logLevelSchema>;
@Config
export class LoggingConfig {
/**
* Minimum level of logs to output. Logs with this or higher level will be output;
* logs with lower levels will not. Exception: `silent` disables all logging.
*
* @example `N8N_LOG_LEVEL=info` will output `error`, `warn` and `info` logs, but not `debug`.
*/
@Env('N8N_LOG_LEVEL', logLevelSchema)
level: LogLevel = 'info';
/**
* Where to output logs to. Options are: `console` or `file` or both in a comma separated list.
*
* @example `N8N_LOG_OUTPUT=console,file` will output to both console and file.
*/
@Env('N8N_LOG_OUTPUT')
outputs: CommaSeparatedStringArray<'console' | 'file'> = ['console'];
/**
* What format the logs should have.
* `text` is only printing the human readable messages.
* `json` is printing one JSON object per line containing the message, level,
* timestamp and all the metadata.
*/
@Env('N8N_LOG_FORMAT')
format: 'text' | 'json' = 'text';
@Nested
file: FileLoggingConfig;
@Nested
cron: CronLoggingConfig;
/**
* Scopes to filter logs by. Nothing is filtered by default.
*
* Supported log scopes:
*
* - `concurrency`
* - `external-secrets`
* - `license`
* - `multi-main-setup`
* - `pruning`
* - `pubsub`
* - `push`
* - `redis`
* - `scaling`
* - `waiting-executions`
* - `task-runner-js`
* - `task-runner-py`
* - `workflow-activation`
* - `insights`
* - `chat-hub`
*
* @example
* `N8N_LOG_SCOPES=license`
* `N8N_LOG_SCOPES=license,waiting-executions`
*/
@Env('N8N_LOG_SCOPES')
scopes: CommaSeparatedStringArray<LogScope> = [];
}
@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class MfaConfig {
/** Whether multi-factor authentication (MFA) is enabled for the instance. */
@Env('N8N_MFA_ENABLED')
enabled: boolean = true;
}
@@ -0,0 +1,16 @@
import { Config, Env } from '../decorators';
@Config
export class MultiMainSetupConfig {
/** Whether to enable multi-main setup when using scaling mode (requires license). */
@Env('N8N_MULTI_MAIN_SETUP_ENABLED')
enabled: boolean = false;
/** Time to live in seconds for the leader lock key; the current leader must renew before this expires. */
@Env('N8N_MULTI_MAIN_SETUP_KEY_TTL')
ttl: number = 10;
/** Interval in seconds between leader eligibility checks in multi-main setup. */
@Env('N8N_MULTI_MAIN_SETUP_CHECK_INTERVAL')
interval: number = 3;
}
@@ -0,0 +1,45 @@
import { Config, Env } from '../decorators';
function isStringArray(input: unknown): input is string[] {
return Array.isArray(input) && input.every((item) => typeof item === 'string');
}
class JsonStringArray extends Array<string> {
constructor(str: string) {
super();
let parsed: unknown;
try {
parsed = JSON.parse(str);
} catch {
return [];
}
return isStringArray(parsed) ? parsed : [];
}
}
@Config
export class NodesConfig {
/** Node types to load. If empty, all available nodes are loaded. Example: `["n8n-nodes-base.hackerNews"]`. */
@Env('NODES_INCLUDE')
include: JsonStringArray = [];
/**
* Node types to exclude from loading. Default excludes `ExecuteCommand` and `LocalFileTrigger` for security.
* Set to an empty array to allow all node types.
*
* @example '["n8n-nodes-base.hackerNews"]'
*/
@Env('NODES_EXCLUDE')
exclude: JsonStringArray = ['n8n-nodes-base.executeCommand', 'n8n-nodes-base.localFileTrigger'];
/** Node type name used as the default error trigger when workflow execution fails. */
@Env('NODES_ERROR_TRIGGER_TYPE')
errorTriggerType: string = 'n8n-nodes-base.errorTrigger';
/** Whether to enable Python execution on the Code node. */
@Env('N8N_PYTHON_ENABLED')
pythonEnabled: boolean = true;
}
@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class PersonalizationConfig {
/** Whether to enable personalization features. */
@Env('N8N_PERSONALIZATION_ENABLED')
enabled: boolean = true;
}
@@ -0,0 +1,16 @@
import { Config, Env } from '../decorators';
@Config
export class PublicApiConfig {
/** When true, the public API is disabled and its routes are not registered. */
@Env('N8N_PUBLIC_API_DISABLED')
disabled: boolean = false;
/** URL path segment for the Public API (for example, /api/v1/...). */
@Env('N8N_PUBLIC_API_ENDPOINT')
path: string = 'api';
/** When true, the Swagger UI for the Public API is not served. */
@Env('N8N_PUBLIC_API_SWAGGERUI_DISABLED')
swaggerUiDisabled: boolean = false;
}
@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class RedisConfig {
/** Key prefix for all Redis keys used by n8n (avoids clashes when sharing a Redis instance). */
@Env('N8N_REDIS_KEY_PREFIX')
prefix: string = 'n8n';
}
@@ -0,0 +1,73 @@
import { z } from 'zod';
import { Config, Env } from '../decorators';
const runnerModeSchema = z.enum(['internal', 'external']);
export type TaskRunnerMode = z.infer<typeof runnerModeSchema>;
@Config
export class TaskRunnersConfig {
/**
* How the task runner runs: `internal` (child process of n8n) or `external` (separate process).
*/
@Env('N8N_RUNNERS_MODE', runnerModeSchema)
mode: TaskRunnerMode = 'internal';
/** URL path segment where the task runner service is exposed (for example, `/runners`). */
@Env('N8N_RUNNERS_PATH')
path: string = '/runners';
/** Shared secret used to authenticate runner processes with the broker. */
@Env('N8N_RUNNERS_AUTH_TOKEN')
authToken: string = '';
/** Port the task runner broker listens on for runner connections. */
@Env('N8N_RUNNERS_BROKER_PORT')
port: number = 5679;
/** IP address the task runner broker binds to. */
@Env('N8N_RUNNERS_BROKER_LISTEN_ADDRESS')
listenAddress: string = '127.0.0.1';
/** Maximum size in bytes of a payload sent to a runner. Default: 1 GiB. */
@Env('N8N_RUNNERS_MAX_PAYLOAD')
maxPayload: number = 1024 * 1024 * 1024;
/** Node.js `--max-old-space-size` value in MB for the runner process. Empty lets Node choose based on memory. */
@Env('N8N_RUNNERS_MAX_OLD_SPACE_SIZE')
maxOldSpaceSize: string = '';
/** Maximum number of tasks a single runner can execute concurrently. */
@Env('N8N_RUNNERS_MAX_CONCURRENCY')
maxConcurrency: number = 10;
/**
* How long (in seconds) a task is allowed to take for completion, else the
* task will be aborted. (In internal mode, the runner will also be
* restarted.) Must be greater than 0.
*
* Kept high for backwards compatibility - n8n v3 will reduce this to `60`
*/
@Env('N8N_RUNNERS_TASK_TIMEOUT')
taskTimeout: number = 300; // 5 minutes
/**
* How long (in seconds) a task request can wait for a runner to become
* available before timing out. This prevents workflows from hanging
* indefinitely when no runners are available. Must be greater than 0.
*/
@Env('N8N_RUNNERS_TASK_REQUEST_TIMEOUT')
taskRequestTimeout: number = 60;
/** Interval in seconds between heartbeats from runner to broker; missing heartbeats abort the task (and restart the runner in internal mode). Must be > 0. */
@Env('N8N_RUNNERS_HEARTBEAT_INTERVAL')
heartbeatInterval: number = 30;
/**
* Whether to disable all security measures in the task runner. **Discouraged for production use.**
* Set to `true` for compatibility with modules that rely on insecure JS features.
*/
@Env('N8N_RUNNERS_INSECURE_MODE')
insecureMode: boolean = false;
}
@@ -0,0 +1,134 @@
import { Config, Env, Nested } from '../decorators';
@Config
class HealthConfig {
/**
* Whether to enable worker health endpoints: `/healthz` (liveness) and `/healthz/readiness` (DB and Redis ready).
*/
@Env('QUEUE_HEALTH_CHECK_ACTIVE')
active: boolean = false;
/** Port the worker HTTP server listens on for health checks. */
@Env('QUEUE_HEALTH_CHECK_PORT')
port: number = 5678;
/** IP address the worker server binds to. Use `::` for all interfaces. */
@Env('N8N_WORKER_SERVER_ADDRESS')
address: string = '::';
}
@Config
class RedisConfig {
/** Redis database for Bull queue. */
@Env('QUEUE_BULL_REDIS_DB')
db: number = 0;
/** Redis host for Bull queue. */
@Env('QUEUE_BULL_REDIS_HOST')
host: string = 'localhost';
/** Password to authenticate with Redis. */
@Env('QUEUE_BULL_REDIS_PASSWORD')
password: string = '';
/** Port for Redis to listen on. */
@Env('QUEUE_BULL_REDIS_PORT')
port: number = 6379;
/** Max cumulative timeout (in milliseconds) of connection retries before process exit. */
@Env('QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD')
timeoutThreshold: number = 10_000;
/** Slot refresh timeout (in milliseconds) before a timeout occurs while refreshing slots from the cluster. */
@Env('QUEUE_BULL_REDIS_SLOT_REFRESH_TIMEOUT')
slotsRefreshTimeout: number = 1_000;
/** Slot refresh interval (in milliseconds) between every automatic slot refresh. */
@Env('QUEUE_BULL_REDIS_SLOT_REFRESH_INTERVAL')
slotsRefreshInterval: number = 5_000;
/** Redis username. Redis 6.0 or higher required. */
@Env('QUEUE_BULL_REDIS_USERNAME')
username: string = '';
/** Redis cluster startup nodes, as comma-separated list of `{host}:{port}` pairs. @example 'redis-1:6379,redis-2:6379' */
@Env('QUEUE_BULL_REDIS_CLUSTER_NODES')
clusterNodes: string = '';
/** Whether to enable TLS on Redis connections. */
@Env('QUEUE_BULL_REDIS_TLS')
tls: boolean = false;
/**
* DNS resolution strategy for Redis hostnames on initial client connection.
* - `LOOKUP` (default): Use system DNS resolver to resolve hostnames to IP addresses.
* - `NONE`: Disable DNS resolution and pass hostnames directly to Redis client.
*
* DNS lookups can be error prone, especially in combination with TLS certificates.
* Especially AWS Elasticache cluster connections often lead to invalid certificate errors due to hostname/ip mismatches.
* For AWS ElastiCache clusters with TLS, it is recommended to set this option to `NONE`.
* @see https://github.com/redis/ioredis?tab=readme-ov-file#special-note-aws-elasticache-clusters-with-tls
*/
@Env('QUEUE_BULL_REDIS_DNS_LOOKUP_STRATEGY')
dnsResolveStrategy: 'LOOKUP' | 'NONE' = 'LOOKUP';
/** Whether to enable dual-stack hostname resolution for Redis connections. */
@Env('QUEUE_BULL_REDIS_DUALSTACK')
dualStack: boolean = false;
/** Whether to enable TCP keep-alive on Redis connections. */
@Env('QUEUE_BULL_REDIS_KEEP_ALIVE')
keepAlive: boolean = false;
/** TCP keep-alive initial delay in milliseconds. */
@Env('QUEUE_BULL_REDIS_KEEP_ALIVE_DELAY')
keepAliveDelay: number = 5000;
/** TCP keep-alive interval in milliseconds. */
@Env('QUEUE_BULL_REDIS_KEEP_ALIVE_INTERVAL')
keepAliveInterval: number = 5000;
/** Whether to reconnect to Redis on READONLY errors i.e., failover events. */
@Env('QUEUE_BULL_REDIS_RECONNECT_ON_FAILOVER')
reconnectOnFailover: boolean = true;
}
@Config
class SettingsConfig {
/** How long (in milliseconds) is the lease period for a worker processing a job. */
@Env('QUEUE_WORKER_LOCK_DURATION')
lockDuration: number = 60_000;
/** How often (in milliseconds) a worker must renew the lease. */
@Env('QUEUE_WORKER_LOCK_RENEW_TIME')
lockRenewTime: number = 10_000;
/** How often (in milliseconds) Bull must check for stalled jobs. `0` to disable. */
@Env('QUEUE_WORKER_STALLED_INTERVAL')
stalledInterval: number = 30_000;
}
@Config
class BullConfig {
/** Prefix for Bull keys on Redis. @example 'bull:jobs:23' */
@Env('QUEUE_BULL_PREFIX')
prefix: string = 'bull';
@Nested
redis: RedisConfig;
/** @deprecated How long (in seconds) a worker must wait for active executions to finish before exiting. Use `N8N_GRACEFUL_SHUTDOWN_TIMEOUT` instead */
@Env('QUEUE_WORKER_TIMEOUT')
gracefulShutdownTimeout: number = 30;
@Nested
settings: SettingsConfig;
}
@Config
export class ScalingModeConfig {
@Nested
health: HealthConfig;
@Nested
bull: BullConfig;
}
@@ -0,0 +1,88 @@
import z from 'zod';
import { Config, Env } from '../decorators';
const crossOriginOpenerPolicySchema = z.enum(['same-origin', 'same-origin-allow-popups']);
@Config
export class SecurityConfig {
/**
* Dirs that the `ReadWriteFile` and `ReadBinaryFiles` nodes are allowed to access. Separate multiple dirs with semicolon `;`.
* Set to an empty string to disable restrictions (insecure, not recommended for production).
*
* @example N8N_RESTRICT_FILE_ACCESS_TO=/home/john/my-n8n-files
*/
@Env('N8N_RESTRICT_FILE_ACCESS_TO')
restrictFileAccessTo: string = '~/.n8n-files';
/**
* Whether to block nodes from accessing files at dirs internally used by n8n:
* - `~/.n8n`
* - `~/.cache/n8n/public`
* - any dirs specified by `N8N_CONFIG_FILES`, `N8N_CUSTOM_EXTENSIONS`, `N8N_BINARY_DATA_STORAGE_PATH`, `N8N_UM_EMAIL_TEMPLATES_INVITE`, and `UM_EMAIL_TEMPLATES_PWRESET`.
*/
@Env('N8N_BLOCK_FILE_ACCESS_TO_N8N_FILES')
blockFileAccessToN8nFiles: boolean = true;
/**
* Regex patterns for files and folders that `ReadWriteFile` and `ReadBinaryFiles` nodes cannot access.
* Separate multiple patterns with semicolons. Default blocks `.git`. Set to empty to disable pattern-based blocking.
*/
@Env('N8N_BLOCK_FILE_PATTERNS')
blockFilePatterns: string = '^(.*\\/)*\\.git(\\/.*)*$';
/**
* In a [security audit](https://docs.n8n.io/hosting/securing/security-audit/), how many days for a workflow to be considered abandoned if not executed.
*/
@Env('N8N_SECURITY_AUDIT_DAYS_ABANDONED_WORKFLOW')
daysAbandonedWorkflow: number = 90;
/**
* Set [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) headers as [helmet.js](https://helmetjs.github.io/#content-security-policy) nested directives object.
* Example: { "frame-ancestors": ["http://localhost:3000"] }
*/
// TODO: create a new type that parses and validates this string into a strongly-typed object
@Env('N8N_CONTENT_SECURITY_POLICY')
contentSecurityPolicy: string = '{}';
/**
* Whether to set the `Content-Security-Policy-Report-Only` header instead of `Content-Security-Policy`.
*/
@Env('N8N_CONTENT_SECURITY_POLICY_REPORT_ONLY')
contentSecurityPolicyReportOnly: boolean = false;
/**
* Configuration for the `Cross-Origin-Opener-Policy` header.
*/
@Env('N8N_CROSS_ORIGIN_OPENER_POLICY', crossOriginOpenerPolicySchema)
crossOriginOpenerPolicy: z.infer<typeof crossOriginOpenerPolicySchema> = 'same-origin';
/**
* Whether to disable HTML sandboxing for webhooks. The sandboxing mechanism uses CSP headers now,
* but the name is kept for backwards compatibility.
*/
@Env('N8N_INSECURE_DISABLE_WEBHOOK_IFRAME_SANDBOX')
disableWebhookHtmlSandboxing: boolean = false;
/**
* Whether to disable bare repositories support in the Git node.
*/
@Env('N8N_GIT_NODE_DISABLE_BARE_REPOS')
disableBareRepos: boolean = true;
/** Whether to allow access to AWS system credentials, e.g. in awsAssumeRole credentials */
@Env('N8N_AWS_SYSTEM_CREDENTIALS_ACCESS_ENABLED')
awsSystemCredentialsAccess: boolean = false;
/**
* Whether to enable hooks (like pre-commit hooks) for the Git node.
*/
@Env('N8N_GIT_NODE_ENABLE_HOOKS')
enableGitNodeHooks: boolean = false;
/**
* Whether to enable arbitrary git config keys.
*/
@Env('N8N_GIT_NODE_ENABLE_ALL_CONFIG_KEYS')
enableGitNodeAllConfigKeys: boolean = false;
}
@@ -0,0 +1,63 @@
import { z } from 'zod';
import { Config, Env } from '../decorators';
/** Schema for sample rates (0.0 to 1.0). */
export const sampleRateSchema = z.number({ coerce: true }).min(0).max(1);
@Config
export class SentryConfig {
/** Sentry DSN (data source name) for the backend. */
@Env('N8N_SENTRY_DSN')
backendDsn: string = '';
/** Sentry DSN (data source name) for the frontend. */
@Env('N8N_FRONTEND_SENTRY_DSN')
frontendDsn: string = '';
/**
* Sample rate for Sentry traces (0.0 to 1.0).
* This determines whether tracing is enabled and what percentage of
* transactions are traced.
*
* @default 0 (disabled)
*/
@Env('N8N_SENTRY_TRACES_SAMPLE_RATE', sampleRateSchema)
tracesSampleRate: number = 0;
/**
* Sample rate for Sentry profiling (0.0 to 1.0).
* This determines whether profiling is enabled and what percentage of
* transactions are profiled.
*
* @default 0 (disabled)
*/
@Env('N8N_SENTRY_PROFILES_SAMPLE_RATE', sampleRateSchema)
profilesSampleRate: number = 0;
/**
* Threshold in milliseconds for event loop block detection.
* When the event loop is blocked for longer than this threshold,
* Sentry will report it.
*
* @default 500
*/
@Env('N8N_SENTRY_EVENT_LOOP_BLOCK_THRESHOLD', z.number({ coerce: true }).int().positive())
eventLoopBlockThreshold: number = 500;
/**
* Environment of the n8n instance.
*
* @example 'production'
*/
@Env('ENVIRONMENT')
environment: string = '';
/**
* Name of the deployment, e.g. cloud account name.
*
* @example 'janober'
*/
@Env('DEPLOYMENT_NAME')
deploymentName: string = '';
}
@@ -0,0 +1,76 @@
import { Config, Env, Nested } from '../decorators';
@Config
class SamlConfig {
/** Whether SAML-based single sign-on is enabled. */
@Env('N8N_SSO_SAML_LOGIN_ENABLED')
loginEnabled: boolean = false;
/** Label shown on the login button for SAML (for example, "Sign in with SAML"). */
@Env('N8N_SSO_SAML_LOGIN_LABEL')
loginLabel: string = '';
}
@Config
class OidcConfig {
/** Whether OIDC-based single sign-on is enabled. */
@Env('N8N_SSO_OIDC_LOGIN_ENABLED')
loginEnabled: boolean = false;
}
@Config
class LdapConfig {
/** Whether LDAP-based single sign-on is enabled. */
@Env('N8N_SSO_LDAP_LOGIN_ENABLED')
loginEnabled: boolean = false;
/** Label shown on the login button for LDAP (for example, "Sign in with LDAP"). */
@Env('N8N_SSO_LDAP_LOGIN_LABEL')
loginLabel: string = '';
}
@Config
class ProvisioningConfig {
/** Whether to set the user's instance role from an SSO claim during login. */
@Env('N8N_SSO_SCOPES_PROVISION_INSTANCE_ROLE')
scopesProvisionInstanceRole: boolean = false;
/** Whether to set projectrole mappings from an SSO claim during login. */
@Env('N8N_SSO_SCOPES_PROVISION_PROJECT_ROLES')
scopesProvisionProjectRoles: boolean = false;
/** Name of the OAuth scope to request for SSO provisioning. */
@Env('N8N_SSO_SCOPES_NAME')
scopesName: string = 'n8n';
/** Name of the SSO claim that contains the user's instance role (for provisioning). */
@Env('N8N_SSO_SCOPES_INSTANCE_ROLE_CLAIM_NAME')
scopesInstanceRoleClaimName: string = 'n8n_instance_role';
/** Name of the SSO claim that contains projectrole mappings (for provisioning). */
@Env('N8N_SSO_SCOPES_PROJECTS_ROLES_CLAIM_NAME')
scopesProjectsRolesClaimName: string = 'n8n_projects';
}
@Config
export class SsoConfig {
/** Whether to automatically create user accounts when someone signs in via SSO for the first time. */
@Env('N8N_SSO_JUST_IN_TIME_PROVISIONING')
justInTimeProvisioning: boolean = true;
/** Whether the login screen redirects directly to SSO instead of showing email/password. */
@Env('N8N_SSO_REDIRECT_LOGIN_TO_SSO')
redirectLoginToSso: boolean = true;
@Nested
saml: SamlConfig;
@Nested
oidc: OidcConfig;
@Nested
ldap: LdapConfig;
@Nested
provisioning: ProvisioningConfig;
}
@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class TagsConfig {
/** When true, workflow tags are disabled (no tagging UI or filtering by tag). */
@Env('N8N_WORKFLOW_TAGS_DISABLED')
disabled: boolean = false;
}
@@ -0,0 +1,16 @@
import { Config, Env } from '../decorators';
@Config
export class TemplatesConfig {
/** Whether to enable loading and showing workflow templates. */
@Env('N8N_TEMPLATES_ENABLED')
enabled: boolean = true;
/** Base URL for the workflow templates API. */
@Env('N8N_TEMPLATES_HOST')
host: string = 'https://api.n8n.io/api/';
/** Base URL for fetching dynamic (contextual) templates. */
@Env('N8N_DYNAMIC_TEMPLATES_HOST')
dynamicTemplatesHost: string = 'https://dynamic-templates.n8n.io/templates';
}
@@ -0,0 +1,137 @@
import { z } from 'zod';
import { Config, Env, Nested } from '../decorators';
@Config
class SmtpAuth {
/** SMTP login username */
@Env('N8N_SMTP_USER')
user: string = '';
/** SMTP login password */
@Env('N8N_SMTP_PASS')
pass: string = '';
/** SMTP OAuth Service Client */
@Env('N8N_SMTP_OAUTH_SERVICE_CLIENT')
serviceClient: string = '';
/** SMTP OAuth Private Key */
@Env('N8N_SMTP_OAUTH_PRIVATE_KEY')
privateKey: string = '';
}
@Config
class SmtpConfig {
/** SMTP server host */
@Env('N8N_SMTP_HOST')
host: string = '';
/** SMTP server port */
@Env('N8N_SMTP_PORT')
port: number = 465;
/** Whether to use SSL for SMTP */
@Env('N8N_SMTP_SSL')
secure: boolean = true;
/** Whether to use STARTTLS for SMTP when SSL is disabled */
@Env('N8N_SMTP_STARTTLS')
startTLS: boolean = true;
/** How to display sender name */
@Env('N8N_SMTP_SENDER')
sender: string = '';
@Nested
auth: SmtpAuth;
}
@Config
export class TemplateConfig {
/** Overrides default HTML template for inviting new people (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_INVITE')
'user-invited': string = '';
/** Overrides default HTML template for resetting password (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_PWRESET')
'password-reset-requested': string = '';
/** Overrides default HTML template for notifying that a workflow was shared (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_SHARED')
'workflow-shared': string = '';
/** Overrides default HTML template for notifying that a workflow was deactivated (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_AUTODEACTIVATED')
'workflow-deactivated': string = '';
/** Overrides default HTML template for notifying that credentials were shared (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_CREDENTIALS_SHARED')
'credentials-shared': string = '';
/** Overrides default HTML template for notifying that credentials were shared (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_PROJECT_SHARED')
'project-shared': string = '';
/** Overrides default HTML template for notifying that a workflow failed in production (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_FAILURE')
'workflow-failure': string = '';
}
const emailModeSchema = z.enum(['', 'smtp']);
type EmailMode = z.infer<typeof emailModeSchema>;
@Config
class EmailConfig {
/** Email delivery method: `smtp` or empty (disabled). */
@Env('N8N_EMAIL_MODE', emailModeSchema)
mode: EmailMode = 'smtp';
@Nested
smtp: SmtpConfig;
@Nested
template: TemplateConfig;
}
const INVALID_JWT_REFRESH_TIMEOUT_WARNING =
'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS needs to be smaller than N8N_USER_MANAGEMENT_JWT_DURATION_HOURS. Setting N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS to 0.';
@Config
export class UserManagementConfig {
@Nested
emails: EmailConfig;
/** JWT secret to use. If unset, n8n will generate its own. */
@Env('N8N_USER_MANAGEMENT_JWT_SECRET')
jwtSecret: string = '';
/** How long (in hours) before the JWT expires. */
@Env('N8N_USER_MANAGEMENT_JWT_DURATION_HOURS')
jwtSessionDurationHours: number = 168;
/**
* Security Control: Invite Link Exposure Prevention
*
* When enabled, prevents exposure of invite URLs in API responses to users
* with 'user:create' permission, mitigating account takeover risks via
* invite link leakage (e.g., compromised admin accounts, network interception).
*/
@Env('N8N_INVITE_LINKS_EMAIL_ONLY')
inviteLinksEmailOnly: boolean = false;
/**
* How long (in hours) before expiration to automatically refresh it.
* - `0` means 25% of `N8N_USER_MANAGEMENT_JWT_DURATION_HOURS`.
* - `-1` means it will never refresh. This forces users to log back in after expiration.
*/
@Env('N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS')
jwtRefreshTimeoutHours: number = 0;
sanitize() {
if (this.jwtRefreshTimeoutHours >= this.jwtSessionDurationHours) {
console.warn(INVALID_JWT_REFRESH_TIMEOUT_WARNING);
this.jwtRefreshTimeoutHours = 0;
}
}
}
@@ -0,0 +1,24 @@
import { Config, Env } from '../decorators';
@Config
export class VersionNotificationsConfig {
/** Whether to check for and show in-app notifications about new n8n versions. */
@Env('N8N_VERSION_NOTIFICATIONS_ENABLED')
enabled: boolean = true;
/** URL used to fetch current n8n version information. */
@Env('N8N_VERSION_NOTIFICATIONS_ENDPOINT')
endpoint: string = 'https://api.n8n.io/api/versions/';
/** Whether to fetch and show "What's New" content. Requires version notifications to be enabled. */
@Env('N8N_VERSION_NOTIFICATIONS_WHATS_NEW_ENABLED')
whatsNewEnabled: boolean = true;
/** URL used to fetch "What's New" articles. */
@Env('N8N_VERSION_NOTIFICATIONS_WHATS_NEW_ENDPOINT')
whatsNewEndpoint: string = 'https://api.n8n.io/api/whats-new';
/** URL linked from the versions panel (for example, upgrade instructions). */
@Env('N8N_VERSION_NOTIFICATIONS_INFO_URL')
infoUrl: string = 'https://docs.n8n.io/hosting/installation/updating/';
}
@@ -0,0 +1,50 @@
import { Config, Env } from '../decorators';
/**
* Config for the workflow history compaction service, which compacts recent versions and trims old ones to manage storage.
*/
@Config
export class WorkflowHistoryCompactionConfig {
/**
* Minimum age in hours before a workflow version is eligible for optimization.
* Versions compacted are those with `createdAt` between `optimizingMinimumAgeHours - optimizingTimeWindowHours` and `optimizingMinimumAgeHours`.
*/
@Env('N8N_WORKFLOW_HISTORY_OPTIMIZING_MINIMUM_AGE_HOURS')
optimizingMinimumAgeHours: number = 0.25;
/**
* Time window in hours used when selecting versions to optimize.
* Optimization runs roughly every `optimizingTimeWindowHours / 2` hours.
*/
@Env('N8N_WORKFLOW_HISTORY_OPTIMIZING_TIME_WINDOW_HOURS')
optimizingTimeWindowHours: number = 2;
/**
* Minimum age in days before a workflow version is eligible for trimming.
* Versions trimmed are those with `createdAt` between `trimmingMinimumAgeDays - trimmingTimeWindowDays` and `trimmingMinimumAgeDays`.
*/
@Env('N8N_WORKFLOW_HISTORY_TRIMMING_MINIMUM_AGE_DAYS')
trimmingMinimumAgeDays: number = 7;
/**
* Time window in days used when selecting versions to trim. Trimming runs once per day.
*/
@Env('N8N_WORKFLOW_HISTORY_TRIMMING_TIME_WINDOW_DAYS')
trimmingTimeWindowDays: number = 2;
/** Maximum number of workflow versions to process per workflow before waiting `batchDelayMs` before the next workflow. */
@Env('N8N_WORKFLOW_HISTORY_COMPACTION_BATCH_SIZE')
batchSize: number = 100;
/** Delay in milliseconds after processing `batchSize` versions before moving to the next workflow. */
@Env('N8N_WORKFLOW_HISTORY_COMPACTION_BATCH_DELAY_MS')
batchDelayMs: number = 1_000;
/**
* Whether to run a trim pass on startup (for example, to fix existing history or for development).
*
* @warning Blocking; can significantly increase startup time.
*/
@Env('N8N_WORKFLOW_HISTORY_COMPACTION_TRIM_ON_START_UP')
trimOnStartUp: boolean = false;
}
@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class WorkflowHistoryConfig {
/** How long in hours to keep workflow history versions before pruning. Use `-1` to keep forever. */
@Env('N8N_WORKFLOW_HISTORY_PRUNE_TIME')
pruneTime: number = -1;
}
@@ -0,0 +1,29 @@
import { z } from 'zod';
import { Config, Env } from '../decorators';
const callerPolicySchema = z.enum(['any', 'none', 'workflowsFromAList', 'workflowsFromSameOwner']);
type CallerPolicy = z.infer<typeof callerPolicySchema>;
@Config
export class WorkflowsConfig {
/** Default name suggested when creating a new workflow. */
@Env('WORKFLOWS_DEFAULT_NAME')
defaultName: string = 'My workflow';
/** Default policy for which workflows are allowed to call this workflow (for example, same owner, any, none). */
@Env('N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION', callerPolicySchema)
callerPolicyDefaultOption: CallerPolicy = 'workflowsFromSameOwner';
/** Number of workflows to activate in parallel during startup. */
@Env('N8N_WORKFLOW_ACTIVATION_BATCH_SIZE')
activationBatchSize: number = 1;
/** Whether to build and maintain workflow dependency indexes (for example, for subworkflow callers). */
@Env('N8N_WORKFLOWS_INDEXING_ENABLED')
indexingEnabled: boolean = true;
/** Whether to use the workflow publication service. Still under development. */
@Env('N8N_USE_WORKFLOW_PUBLICATION_SERVICE')
useWorkflowPublicationService: boolean = false;
}