This page covers the license lifecycle in n8n: the License service, LicenseState facade, configuration, feature flag checks, certificate persistence, auto-renewal, and how feature gates are enforced in controllers and surfaced to the frontend.
For information about the general configuration system (env-var mappings, GlobalConfig), see 7.1. For role-based access control enforced alongside license checks, see 3.5.
n8n uses a proprietary license SDK (@n8n_io/license-sdk) whose LicenseManager class handles activation, renewal, and entitlement checking. The n8n codebase wraps this SDK in two layers:
License (packages/cli/src/license.ts) ā the concrete DI service that owns the LicenseManager instance and persists certificates.LicenseState (packages/@n8n/backend-common/src/license-state.ts) ā a thin, named-method facade over a LicenseProvider interface; the preferred query surface for the rest of the app.Feature identifiers are defined in @n8n/constants and are the canonical strings that the SDK uses internally.
LicenseConfig)LicenseConfig is a @Config-decorated class in packages/@n8n/config/src/configs/license.config.ts and is nested under GlobalConfig.license.
| Field | Env variable | Default | Description |
|---|---|---|---|
serverUrl | N8N_LICENSE_SERVER_URL | https://license.n8n.io/v1 | License server endpoint |
autoRenewalEnabled | N8N_LICENSE_AUTO_RENEWAL_ENABLED | true | Enables periodic background renewal |
detachFloatingOnShutdown | N8N_LICENSE_DETACH_FLOATING_ON_SHUTDOWN | true | Releases floating entitlements on shutdown |
activationKey | N8N_LICENSE_ACTIVATION_KEY | '' | Key used for initial activation |
tenantId | N8N_LICENSE_TENANT_ID | 1 | Tenant identifier; 1 = production |
cert | N8N_LICENSE_CERT | '' | Ephemeral license cert (bypasses DB storage) |
Sources: packages/@n8n/config/test/config.test.ts320-327
Feature identifiers live in packages/@n8n/constants/src/index.ts as two constant objects.
LICENSE_FEATURES ā boolean capabilities, prefixed feat:
| Constant key | SDK string |
|---|---|
SHARING | feat:sharing |
LDAP | feat:ldap |
SAML | feat:saml |
OIDC | feat:oidc |
LOG_STREAMING | feat:logStreaming |
VARIABLES | feat:variables |
SOURCE_CONTROL | feat:sourceControl |
EXTERNAL_SECRETS | feat:externalSecrets |
ADVANCED_PERMISSIONS | feat:advancedPermissions |
MULTIPLE_MAIN_INSTANCES | feat:multipleMainInstances |
AI_ASSISTANT | feat:aiAssistant |
AI_CREDITS | feat:aiCredits |
FOLDERS | feat:folders |
WORKFLOW_DIFFS | feat:workflowDiffs |
| (and ~20 more) |
LICENSE_QUOTAS ā numeric limits, prefixed quota:
| Constant key | SDK string | Sentinel |
|---|---|---|
USERS_LIMIT | quota:users | -1 = unlimited |
VARIABLES_LIMIT | quota:maxVariables | -1 = unlimited |
TRIGGER_LIMIT | quota:activeWorkflows | -1 = unlimited |
TEAM_PROJECT_LIMIT | quota:maxTeamProjects | 0 = none |
AI_CREDITS | quota:aiCredits | 0 = none |
WORKFLOW_HISTORY_PRUNE_LIMIT | quota:workflowHistoryPrune | -1 = unlimited |
UNLIMITED_LICENSE_QUOTA = -1 is exported alongside these constants as the canonical sentinel value.
Sources: packages/@n8n/constants/src/index.ts8-63
License ServiceDiagram: License class structure and SDK wiring
Sources: packages/cli/src/license.ts36-494
License.init() is called during process startup via BaseCommand.initLicense(). All three process types call it:
packages/cli/src/commands/start.ts ā main processpackages/cli/src/commands/worker.ts ā worker processpackages/cli/src/commands/webhook.ts ā webhook processBehavior varies by instance type:
| Setting | Main (leader) | Main (follower) | Worker / Webhook |
|---|---|---|---|
offlineMode | false | false | true |
autoRenewEnabled | depends on autoRenewalEnabled config | depends on config | false |
renewOnInit | same as autoRenewEnabled | same | false |
saveCertStr | saves to DB | saves to DB | no-op |
onFeatureChange | broadcasts PubSub | broadcasts PubSub | no-op |
onExpirySoon | undefined (leader does not self-reload) | defined (triggers reload) | defined |
The productIdentifier passed to the SDK is n8n-${N8N_VERSION}, so the server can track version.
deviceFingerprint returns instanceSettings.instanceId, a stable UUID tied to the n8n data directory.
Sources: packages/cli/src/license.ts53-129 packages/cli/src/commands/start.ts222-227 packages/cli/src/commands/worker.ts93-94 packages/cli/src/commands/webhook.ts70-71
Diagram: cert load/save flow
The key constant SETTINGS_LICENSE_CERT_KEY = 'license.cert' is defined in packages/cli/src/constants.ts.
Sources: packages/cli/src/license.ts131-174 packages/cli/src/constants.ts82
Diagram: renewal coordination across instances
When a main instance loses leadership (@OnLeaderStepdown), disableAutoRenewals() is called. When it gains leadership (@OnLeaderTakeover), enableAutoRenewals() is called. This ensures exactly one instance renews at a time in multi-main setups.
For non-leader instances, the onExpirySoon callback (fires 120 minutes before expiry) triggers reload() from the DB, which picks up the refreshed cert saved by the leader.
Sources: packages/cli/src/license.ts155-161 packages/cli/src/license.ts469-494
LicenseState ā Named Query FacadeLicenseState (in packages/@n8n/backend-common/src/license-state.ts) is a @Service() that wraps any LicenseProvider implementor. It provides named convenience methods so callers don't need to import LICENSE_FEATURES strings.
LicenseProvider interface (implemented by License):
isLicensed(feature: BooleanLicenseFeature): boolean
getValue<T>(feature: T): FeatureReturnType[T]
Example methods on LicenseState:
| Method | Delegates to |
|---|---|
isSharingLicensed() | isLicensed('feat:sharing') |
isOidcLicensed() | isLicensed('feat:oidc') |
isMFAEnforcementLicensed() | isLicensed('feat:mfaEnforcement') |
isWorkflowDiffsLicensed() | isLicensed('feat:workflowDiffs') |
isProvisioningLicensed() | isLicensed(['feat:saml', 'feat:oidc']) ā any match |
getMaxUsers() | getValue('quota:users') ?? -1 |
getMaxVariables() | getValue('quota:maxVariables') ?? -1 |
getMaxTeamProjects() | getValue('quota:maxTeamProjects') ?? 0 |
getMaxWorkflowsWithEvaluations() | getValue('quota:evaluations:maxWorkflows') ?? 1 |
The many methods on License that directly call isLicensed() are marked @deprecated in favor of LicenseState.
Sources: packages/@n8n/backend-common/src/license-state.ts1-240
Diagram: how feature gates flow from config ā License ā service ā controller
Pattern 1: @Licensed decorator on controller route
Controllers use @Licensed(LICENSE_FEATURES.SOME_FEATURE) to automatically return 403 FeatureNotLicensedError if the feature is not licensed. For more on this decorator, see 10.2.
Pattern 2: Inline check in service methods
Services inject License or LicenseState and call methods like licenseState.isVariablesLicensed() before executing business logic.
Pattern 3: FeatureNotLicensedError
Thrown when a licensed feature is required but not present. Example from startup:
Sources: packages/cli/src/commands/start.ts225-227 packages/cli/src/license.ts254-376 packages/@n8n/backend-common/src/license-state.ts56-240
FrontendService.getSettings() (in packages/cli/src/services/frontend.service.ts) queries the License service and LicenseState on every settings request and populates the enterprise block of FrontendSettings.
Diagram: FrontendSettings.enterprise population
The FrontendSettings.license.environment field is derived from tenantId: tenantId === 1 ā 'production', otherwise 'staging'.
The frontend settingsStore exposes isEnterpriseFeatureEnabled as a computed property over settings.enterprise, and the EnterpriseEditionFeature map in packages/frontend/editor-ui/src/app/constants/enterprise.ts maps UI constant names to the enterprise object keys.
Sources: packages/cli/src/services/frontend.service.ts443-477 packages/cli/src/services/frontend.service.ts339-342 packages/@n8n/api-types/src/frontend-settings.ts36-64 packages/frontend/editor-ui/src/app/constants/enterprise.ts1-26
License.activate() accepts an activation key plus optional EULA URI and user email (for EULA acceptance flows), then delegates to LicenseManager.activate(). After activation the cert is persisted via saveCertStr().
The backend exposes this through a REST controller endpoint (not shown in the provided files) that calls License.activate(). The frontend routes the owner through the activation UI when license.consumerId === 'unknown'.
Sources: packages/cli/src/license.ts201-210
License.shutdown() is decorated with @OnShutdown() so it runs automatically during process teardown. It calls LicenseManager.shutdown(), which releases any floating entitlements back to the pool (if detachFloatingOnShutdown is true). The in-memory entitlements remain valid for the duration of the shutdown sequence.
Sources: packages/cli/src/license.ts240-252
In E2E tests, E2EController (packages/cli/src/controllers/e2e.controller.ts) monkey-patches the License instance at construction time:
Tests can toggle features and quotas via PATCH /e2e/feature and PATCH /e2e/quota endpoints without requiring a real license server. The E2EController maintains local enabledFeatures and numericFeatures maps that mirror the full set of LICENSE_FEATURES and LICENSE_QUOTAS.
The testServer.license helper in integration tests (used in files like packages/cli/test/integration/variables.test.ts) wraps license.setDefaults(), license.enable(), and license.disable() to manipulate the mock license state per-test.
Sources: packages/cli/src/controllers/e2e.controller.ts89-191 packages/cli/test/integration/variables.test.ts33-38
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.