Skip to content

Instantly share code, notes, and snippets.

@tjbcg
Last active February 26, 2026 20:30
Show Gist options
  • Select an option

  • Save tjbcg/5385c85a7dfbe5802fcfaa76be3e3149 to your computer and use it in GitHub Desktop.

Select an option

Save tjbcg/5385c85a7dfbe5802fcfaa76be3e3149 to your computer and use it in GitHub Desktop.
No license check for n8n

Below is a patch tested on 2.9.4 that bypasses license checks on n8n, nothing else. Apply to the git repo, build with pnpm build:docker (or build:n8n if dockerless).

There is also a self contained script that build the n8nio/n8n:local docker image with this patch applied to the latest stable release. This can be run on a cronjob to keep the image updated regularly.

#!/usr/bin/env bash
set -euo pipefail
PATCH_LINK="https://gist.githubusercontent.com/tjbcg/5385c85a7dfbe5802fcfaa76be3e3149/raw/4c71a142a210c3f4225a038a1885491ec80499c3/n8n-no-license-check.patch"
UPSTREAM_REPO="https://github.com/n8n-io/n8n.git"
REPO_DIR="./n8n"
PATCH_FILE="n8n-no-license-check.patch"
export NODE_OPTIONS="--max-old-space-size=4096"
# Clone repo if not already present
if [ ! -d "$REPO_DIR/.git" ]; then
echo "Cloning n8n repository into $REPO_DIR..."
git clone "$UPSTREAM_REPO" "$REPO_DIR"
else
echo "Repository already exists in $REPO_DIR, skipping clone."
fi
cd "$REPO_DIR"
# Fetch tags
echo "Fetching latest tags from upstream..."
git fetch origin --tags
# Find highest semver-like tag of the form n8n@x.y.z,
# that is reachable from the 'stable' branch, excluding experimental (exp) tags
LATEST_TAG="$(
set +e
comm -12 \
<(git tag -l 'n8n@*' --sort=v:refname | grep -v 'exp' | sort) \
<(git tag --merged stable | sort) \
| tail -n 1
)"
if [ -z "$LATEST_TAG" ]; then
echo "No non-experimental n8n@* tags found; cannot determine latest version." >&2
exit 1
fi
echo "Latest non-experimental n8n tag detected: $LATEST_TAG"
# Check out the tag in detached HEAD, local changes are discarded
git checkout -f --detach "$LATEST_TAG"
# Clean untracked/ignored files but keep the .turbo cache
git clean -fdx -e .turbo/
# Download patch
echo "Downloading patch..."
curl -fsSL "$PATCH_LINK" -o "$PATCH_FILE"
# Ensure patch applies cleanly
echo "Checking patch applicability..."
git apply --check "$PATCH_FILE"
echo "Applying patch..."
git apply "$PATCH_FILE"
echo "Patch applied successfully."
# Add "concurrency": 1, to turbo.json in repo root
echo "Setting concurrency=1 in turbo.json..."
tmpfile="$(mktemp)"
jq '.concurrency = "1"' turbo.json > "$tmpfile"
mv "$tmpfile" turbo.json
# Enable pnpm
echo "Enabling pnpm..."
corepack enable
corepack prepare pnpm@10.22.0 --activate
# Install dependencies
echo "Running pnpm install..."
pnpm install
# Build Docker image
echo "Running pnpm build:docker..."
pnpm build:docker
echo "Done."
diff --git a/packages/@n8n/backend-common/src/license-state.ts b/packages/@n8n/backend-common/src/license-state.ts
index d78ba53eb6..b869c492c9 100644
--- a/packages/@n8n/backend-common/src/license-state.ts
+++ b/packages/@n8n/backend-common/src/license-state.ts
@@ -30,24 +30,27 @@ export class LicenseState {
* If the feature is a string. checks if the feature is licensed
* If the feature is an array of strings, it checks if any of the features are licensed
*/
- isLicensed(feature: BooleanLicenseFeature | BooleanLicenseFeature[]) {
- this.assertProvider();
-
- if (typeof feature === 'string') return this.licenseProvider.isLicensed(feature);
-
- for (const featureName of feature) {
- if (this.licenseProvider.isLicensed(featureName)) {
- return true;
- }
- }
-
- return false;
+ isLicensed(_feature: BooleanLicenseFeature | BooleanLicenseFeature[]) {
+ return true;
}
getValue<T extends keyof FeatureReturnType>(feature: T): FeatureReturnType[T] {
+ if (feature === 'planName') {
+ return 'Enterprise' as FeatureReturnType[T];
+ }
+
+ if (feature.toString().includes('quota:')) {
+ return UNLIMITED_LICENSE_QUOTA as FeatureReturnType[T];
+ }
+
this.assertProvider();
- return this.licenseProvider.getValue(feature);
+ const realValue = this.licenseProvider.getValue(feature);
+ if (realValue !== undefined && realValue !== null) {
+ return realValue;
+ } else {
+ return UNLIMITED_LICENSE_QUOTA as FeatureReturnType[T];
+ }
}
// --------------------
diff --git a/packages/cli/src/license.ts b/packages/cli/src/license.ts
index 196528fd33..08cce3171a 100644
--- a/packages/cli/src/license.ts
+++ b/packages/cli/src/license.ts
@@ -251,8 +251,8 @@ export class License implements LicenseProvider {
this.logger.debug('License shut down');
}
- isLicensed(feature: BooleanLicenseFeature) {
- return this.manager?.hasFeatureEnabled(feature) ?? false;
+ isLicensed(feature: BooleanLicenseFeature): boolean {
+ return feature !== LICENSE_FEATURES.SHOW_NON_PROD_BANNER;
}
/** @deprecated Use `LicenseState.isDynamicCredentialsLicensed` instead. */
@@ -380,7 +380,20 @@ export class License implements LicenseProvider {
}
getValue<T extends keyof FeatureReturnType>(feature: T): FeatureReturnType[T] {
- return this.manager?.getFeatureValue(feature) as FeatureReturnType[T];
+ if (feature === 'planName') {
+ return 'Enterprise' as FeatureReturnType[T];
+ }
+
+ if (feature.toString().includes('quota:')) {
+ return UNLIMITED_LICENSE_QUOTA as FeatureReturnType[T];
+ }
+
+ const realValue = this.manager?.getFeatureValue(feature) as FeatureReturnType[T];
+ if (realValue !== undefined && realValue !== null) {
+ return realValue;
+ }
+
+ return UNLIMITED_LICENSE_QUOTA as FeatureReturnType[T];
}
getManagementJwt(): string {
diff --git a/packages/frontend/editor-ui/src/app/utils/rbac/checks/isEnterpriseFeatureEnabled.ts b/packages/frontend/editor-ui/src/app/utils/rbac/checks/isEnterpriseFeatureEnabled.ts
index 00df351894..913aee702c 100644
--- a/packages/frontend/editor-ui/src/app/utils/rbac/checks/isEnterpriseFeatureEnabled.ts
+++ b/packages/frontend/editor-ui/src/app/utils/rbac/checks/isEnterpriseFeatureEnabled.ts
@@ -4,16 +4,5 @@ import type { RBACPermissionCheck, EnterprisePermissionOptions } from '@/app/typ
export const isEnterpriseFeatureEnabled: RBACPermissionCheck<EnterprisePermissionOptions> = (
options,
) => {
- if (!options?.feature) {
- return true;
- }
-
- const features = Array.isArray(options.feature) ? options.feature : [options.feature];
- const settingsStore = useSettingsStore();
- const mode = options.mode ?? 'allOf';
- if (mode === 'allOf') {
- return features.every((feature) => settingsStore.isEnterpriseFeatureEnabled[feature]);
- } else {
- return features.some((feature) => settingsStore.isEnterpriseFeatureEnabled[feature]);
- }
+ return true;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment