diff --git a/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap b/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap
index f9b6e422bb0f756d00582144b4535356c3a3745a..06024603d866fc4bcc98c46c3b40bb8c72a2daf9 100644
--- a/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap
+++ b/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap
@@ -2,6 +2,7 @@
 
 exports[`manager/npm/extract .extractPackageFile() catches invalid names 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [
     Object {
       "depName": "kgabis/parson",
@@ -29,6 +30,11 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() extracts engines 1`] = `
 Object {
+  "compatibility": Object {
+    "node": ">= 8.9.2",
+    "npm": "^8.0.0",
+    "yarn": "disabled",
+  },
   "deps": Array [
     Object {
       "currentValue": "1.6.0",
@@ -131,6 +137,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() extracts non-npmjs 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [
     Object {
       "currentValue": "github:owner/a",
@@ -292,6 +299,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() extracts npm package alias 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [
     Object {
       "currentValue": "1",
@@ -339,6 +347,9 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() extracts volta 1`] = `
 Object {
+  "compatibility": Object {
+    "node": "8.9.2",
+  },
   "deps": Array [
     Object {
       "commitMessageTopic": "Node.js",
@@ -401,6 +412,9 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() extracts volta yarn unknown-version 1`] = `
 Object {
+  "compatibility": Object {
+    "node": "8.9.2",
+  },
   "deps": Array [
     Object {
       "commitMessageTopic": "Node.js",
@@ -457,6 +471,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() finds "npmClient":"npm" in lerna.json 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [
     Object {
       "currentValue": "6.5.0",
@@ -591,6 +606,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() finds "npmClient":"yarn" in lerna.json 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [
     Object {
       "currentValue": "6.5.0",
@@ -725,6 +741,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() finds a lock file 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [
     Object {
       "currentValue": "6.5.0",
@@ -859,6 +876,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() finds complex yarn workspaces 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [],
   "ignoreNpmrcFile": undefined,
   "lernaClient": "npm",
@@ -881,6 +899,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() finds lerna 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [
     Object {
       "currentValue": "6.5.0",
@@ -1015,6 +1034,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() finds simple yarn workspaces 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [],
   "ignoreNpmrcFile": undefined,
   "lernaClient": "npm",
@@ -1037,6 +1057,7 @@ Object {
 
 exports[`manager/npm/extract .extractPackageFile() returns an array of dependencies 1`] = `
 Object {
+  "compatibility": Object {},
   "deps": Array [
     Object {
       "currentValue": "6.5.0",
diff --git a/lib/manager/npm/extract/index.ts b/lib/manager/npm/extract/index.ts
index e2cd307971f788859ce4239c74426c5ebb3e0bd8..2bd6c2316ce910c6d668621ad32b673e3eba2110 100644
--- a/lib/manager/npm/extract/index.ts
+++ b/lib/manager/npm/extract/index.ts
@@ -148,6 +148,8 @@ export async function extractPackageFile(
     resolutions: 'resolutions',
   };
 
+  const compatibility: Record<string, any> = {};
+
   function extractDependency(
     depType: string,
     depName: string,
@@ -168,12 +170,15 @@ export async function extractPackageFile(
         dep.datasource = datasourceGithubTags.id;
         dep.lookupName = 'nodejs/node';
         dep.versioning = nodeVersioning.id;
+        compatibility.node = dep.currentValue;
       } else if (depName === 'yarn') {
         dep.datasource = datasourceNpm.id;
         dep.commitMessageTopic = 'Yarn';
+        compatibility.yarn = dep.currentValue;
       } else if (depName === 'npm') {
         dep.datasource = datasourceNpm.id;
         dep.commitMessageTopic = 'npm';
+        compatibility.npm = dep.currentValue;
       } else {
         dep.skipReason = SkipReason.UnknownEngines;
       }
@@ -351,6 +356,7 @@ export async function extractPackageFile(
     lernaPackages,
     skipInstalls,
     yarnWorkspacesPackages,
+    compatibility,
   };
 }
 
diff --git a/lib/manager/npm/post-update/node-version.spec.ts b/lib/manager/npm/post-update/node-version.spec.ts
index f287dbbccc0b65c5488fd73a36f53229fb2c3446..e529d1d3236f6923b7209d9cba3d26e4605614bb 100644
--- a/lib/manager/npm/post-update/node-version.spec.ts
+++ b/lib/manager/npm/post-update/node-version.spec.ts
@@ -5,41 +5,42 @@ import { getNodeConstraint } from './node-version';
 const fs = mocked(fs_);
 
 describe('getNodeConstraint', () => {
+  const config = {
+    packageFile: 'package.json',
+    compatibility: { node: '^12.16.0' },
+  };
   it('returns package.json range', async () => {
     fs.readLocalFile = jest.fn();
     fs.readLocalFile.mockResolvedValueOnce(null);
     fs.readLocalFile.mockResolvedValueOnce(null);
-    fs.readLocalFile.mockResolvedValueOnce('{"engines":{"node":"^12.16.0"}}');
-    const res = await getNodeConstraint({ packageFile: 'package.json' });
+    const res = await getNodeConstraint(config);
     expect(res).toEqual('^12.16.0');
   });
   it('returns .node-version value', async () => {
     fs.readLocalFile = jest.fn();
     fs.readLocalFile.mockResolvedValueOnce(null);
     fs.readLocalFile.mockResolvedValueOnce('12.16.1\n');
-    const res = await getNodeConstraint({ packageFile: 'package.json' });
+    const res = await getNodeConstraint(config);
     expect(res).toEqual('12.16.1');
   });
   it('returns .nvmrc value', async () => {
     fs.readLocalFile = jest.fn();
     fs.readLocalFile.mockResolvedValueOnce('12.16.2\n');
-    const res = await getNodeConstraint({ packageFile: 'package.json' });
+    const res = await getNodeConstraint(config);
     expect(res).toEqual('12.16.2');
   });
   it('ignores unusable ranges in dotfiles', async () => {
     fs.readLocalFile = jest.fn();
     fs.readLocalFile.mockResolvedValueOnce('latest');
     fs.readLocalFile.mockResolvedValueOnce('lts');
-    fs.readLocalFile.mockResolvedValueOnce('{"engines":{"node":"^12.16.0"}}');
-    const res = await getNodeConstraint({ packageFile: 'package.json' });
+    const res = await getNodeConstraint(config);
     expect(res).toEqual('^12.16.0');
   });
   it('returns no constraint', async () => {
     fs.readLocalFile = jest.fn();
     fs.readLocalFile.mockResolvedValueOnce(null);
     fs.readLocalFile.mockResolvedValueOnce(null);
-    fs.readLocalFile.mockResolvedValueOnce('{}');
-    const res = await getNodeConstraint({ packageFile: 'package.json' });
+    const res = await getNodeConstraint({ ...config, compatibility: null });
     expect(res).toBeNull();
   });
 });
diff --git a/lib/manager/npm/post-update/node-version.ts b/lib/manager/npm/post-update/node-version.ts
index 2c664e2a8e992a8faf60a0e3f0cb4b65c7dae294..b929499b63137e88f8dc17426cdbfcab2bdb9106 100644
--- a/lib/manager/npm/post-update/node-version.ts
+++ b/lib/manager/npm/post-update/node-version.ts
@@ -18,18 +18,13 @@ async function getNodeFile(filename: string): Promise<string> | null {
   return null;
 }
 
-async function getPackageJsonConstraint(
-  filename: string
+function getPackageJsonConstraint(
+  config: PostUpdateConfig
 ): Promise<string> | null {
-  try {
-    const pj = JSON.parse(await readLocalFile(filename, 'utf8'));
-    const constraint = pj?.engines?.node;
-    if (constraint && validRange(constraint)) {
-      logger.debug(`Using node constraint "${constraint}" from package.json`);
-      return constraint;
-    }
-  } catch (err) {
-    // do nothing
+  const constraint = config.compatibility?.node;
+  if (constraint && validRange(constraint)) {
+    logger.debug(`Using node constraint "${constraint}" from package.json`);
+    return constraint;
   }
   return null;
 }
@@ -41,7 +36,7 @@ export async function getNodeConstraint(
   const constraint =
     (await getNodeFile(getSiblingFileName(packageFile, '.nvmrc'))) ||
     (await getNodeFile(getSiblingFileName(packageFile, '.node-version'))) ||
-    (await getPackageJsonConstraint(packageFile));
+    getPackageJsonConstraint(config);
   if (!constraint) {
     logger.debug('No node constraint found - using latest');
   }
diff --git a/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap b/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap
index 8d00958e6fe702c0d756db97af61c174d495bdc1..bdb8c7cc7f2215c0632400dbfbc107498f266430 100644
--- a/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap
+++ b/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap
@@ -3,6 +3,7 @@
 exports[`workers/repository/extract/manager-files getManagerPackageFiles() returns files with extractAllPackageFiles 1`] = `
 Array [
   Object {
+    "compatibility": Object {},
     "deps": Array [
       Object {
         "currentValue": "2.0.0",