diff --git a/lib/modules/manager/npm/extract/__snapshots__/monorepo.spec.ts.snap b/lib/modules/manager/npm/extract/__snapshots__/monorepo.spec.ts.snap
index 2fd7c3706f5c4c787cbc62978902ee53acd1dc85..190c157e32019671762c870b89dcf6e952d64cf4 100644
--- a/lib/modules/manager/npm/extract/__snapshots__/monorepo.spec.ts.snap
+++ b/lib/modules/manager/npm/extract/__snapshots__/monorepo.spec.ts.snap
@@ -26,9 +26,6 @@ Array [
       "lernaJsonFile": "lerna.json",
     },
     "packageFile": "package.json",
-    "packages": Array [
-      "packages/*",
-    ],
   },
   Object {
     "deps": Array [
@@ -45,6 +42,7 @@ Array [
     ],
     "lernaClient": undefined,
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": "lerna.json",
       "yarnZeroInstall": undefined,
     },
@@ -57,6 +55,7 @@ Array [
   Object {
     "lernaClient": undefined,
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": "lerna.json",
       "yarnZeroInstall": undefined,
     },
@@ -95,9 +94,6 @@ Array [
       "lernaJsonFile": "lerna.json",
     },
     "packageFile": "package.json",
-    "packages": Array [
-      "packages/*",
-    ],
   },
   Object {
     "deps": Array [
@@ -114,6 +110,7 @@ Array [
     ],
     "lernaClient": undefined,
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": "lerna.json",
       "yarnZeroInstall": undefined,
     },
@@ -126,6 +123,7 @@ Array [
   Object {
     "lernaClient": undefined,
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": "lerna.json",
       "yarnZeroInstall": undefined,
     },
@@ -156,6 +154,7 @@ Array [
   Object {
     "lernaClient": "yarn",
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": "lerna.json",
       "yarnZeroInstall": undefined,
     },
@@ -168,6 +167,7 @@ Array [
   Object {
     "lernaClient": "yarn",
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": "lerna.json",
       "yarnZeroInstall": undefined,
     },
@@ -192,6 +192,7 @@ Array [
     "hasYarnWorkspaces": true,
     "lernaClient": undefined,
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": undefined,
       "yarnZeroInstall": undefined,
     },
@@ -206,6 +207,7 @@ Array [
   Object {
     "lernaClient": undefined,
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": undefined,
       "yarnZeroInstall": undefined,
     },
@@ -234,6 +236,7 @@ Array [
     "hasYarnWorkspaces": true,
     "lernaClient": undefined,
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": undefined,
       "yarnZeroInstall": true,
     },
@@ -248,6 +251,7 @@ Array [
   Object {
     "lernaClient": undefined,
     "managerData": Object {
+      "hasPackageManager": undefined,
       "lernaJsonFile": undefined,
       "yarnZeroInstall": true,
     },
diff --git a/lib/modules/manager/npm/extract/locked-versions.spec.ts b/lib/modules/manager/npm/extract/locked-versions.spec.ts
index 8b90657b7bb130bdbb8af1272fd1f1967ba6966f..d228834e482a91afde01e6cf691a3f4c6addacb3 100644
--- a/lib/modules/manager/npm/extract/locked-versions.spec.ts
+++ b/lib/modules/manager/npm/extract/locked-versions.spec.ts
@@ -211,6 +211,46 @@ describe('modules/manager/npm/extract/locked-versions', () => {
       ]);
     });
 
+    it("uses yarn.lock but doesn't override contraints", async () => {
+      const yarnVersion = '3.2.0';
+      const lockfileVersion = 8;
+      const isYarn1 = false;
+      yarn.getYarnLock.mockReturnValue({
+        isYarn1,
+        lockfileVersion,
+        lockedVersions,
+      });
+      const packageFiles = getPackageFiles(yarnVersion);
+      packageFiles[0].constraints = { yarn: '3.2.0' };
+      await getLockedVersions(packageFiles);
+      expect(packageFiles).toEqual([
+        {
+          constraints: { yarn: '3.2.0' },
+          deps: [
+            { currentValue: '1.0.0', depName: 'a', lockedVersion: '1.0.0' },
+            { currentValue: '2.0.0', depName: 'b', lockedVersion: '2.0.0' },
+            {
+              currentValue: '^3.2.0',
+              depName: 'yarn',
+              depType: 'engines',
+              lockedVersion: undefined,
+              packageName: '@yarnpkg/cli',
+            },
+            {
+              currentValue: '3.2.0',
+              depName: 'yarn',
+              depType: 'packageManager',
+              lockedVersion: undefined,
+              packageName: '@yarnpkg/cli',
+            },
+          ],
+          lockFiles: ['yarn.lock'],
+          npmLock: 'package-lock.json',
+          yarnLock: 'yarn.lock',
+        },
+      ]);
+    });
+
     it('uses package-lock.json with npm v6.0.0', async () => {
       npm.getNpmLock.mockReturnValue({
         lockedVersions: { a: '1.0.0', b: '2.0.0', c: '3.0.0' },
diff --git a/lib/modules/manager/npm/extract/locked-versions.ts b/lib/modules/manager/npm/extract/locked-versions.ts
index 442ae37e2df2274cd03df655a12bc6be50e397e9..3c1d9f1cac671fae473d168ecbc9ef964a571710 100644
--- a/lib/modules/manager/npm/extract/locked-versions.ts
+++ b/lib/modules/manager/npm/extract/locked-versions.ts
@@ -21,7 +21,7 @@ export async function getLockedVersions(
         lockFileCache[yarnLock] = await getYarnLock(yarnLock);
       }
       const { lockfileVersion, isYarn1 } = lockFileCache[yarnLock];
-      if (!isYarn1) {
+      if (!isYarn1 && !packageFile.constraints?.yarn) {
         if (lockfileVersion && lockfileVersion >= 8) {
           // https://github.com/yarnpkg/berry/commit/9bcd27ae34aee77a567dd104947407532fa179b3
           // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
diff --git a/lib/modules/manager/npm/extract/monorepo.spec.ts b/lib/modules/manager/npm/extract/monorepo.spec.ts
index 68c91a4717d7c2035687ef25d716c000acdb6843..c75bac3a2abaf1d0f928285df1c27f167222f1e7 100644
--- a/lib/modules/manager/npm/extract/monorepo.spec.ts
+++ b/lib/modules/manager/npm/extract/monorepo.spec.ts
@@ -1,3 +1,4 @@
+import type { PackageFile } from '../../types';
 import { detectMonorepos } from './monorepo';
 
 jest.mock('./pnpm');
@@ -5,14 +6,13 @@ jest.mock('./pnpm');
 describe('modules/manager/npm/extract/monorepo', () => {
   describe('.extractPackageFile()', () => {
     it('uses lerna package settings', async () => {
-      const packageFiles = [
+      const packageFiles: Partial<PackageFile>[] = [
         {
           packageFile: 'package.json',
           managerData: {
             lernaJsonFile: 'lerna.json',
           },
           lernaPackages: ['packages/*'],
-          packages: ['packages/*'],
           deps: [
             {
               depName: '@org/a',
@@ -47,10 +47,10 @@ describe('modules/manager/npm/extract/monorepo', () => {
           packageFile: 'packages/b/package.json',
           packageJsonName: '@org/b',
         },
-      ] as any;
+      ];
       await detectMonorepos(packageFiles);
       expect(packageFiles).toMatchSnapshot();
-      expect(packageFiles[1].managerData.lernaJsonFile).toBe('lerna.json');
+      expect(packageFiles[1].managerData?.lernaJsonFile).toBe('lerna.json');
       expect(
         packageFiles.some((packageFile) =>
           packageFile.deps?.some((dep) => dep.isInternal)
@@ -59,14 +59,13 @@ describe('modules/manager/npm/extract/monorepo', () => {
     });
 
     it('updates internal packages', async () => {
-      const packageFiles = [
+      const packageFiles: Partial<PackageFile>[] = [
         {
           packageFile: 'package.json',
           managerData: {
             lernaJsonFile: 'lerna.json',
           },
           lernaPackages: ['packages/*'],
-          packages: ['packages/*'],
           deps: [
             {
               depName: '@org/a',
@@ -101,10 +100,10 @@ describe('modules/manager/npm/extract/monorepo', () => {
           packageFile: 'packages/b/package.json',
           packageJsonName: '@org/b',
         },
-      ] as any;
+      ];
       await detectMonorepos(packageFiles);
       expect(packageFiles).toMatchSnapshot();
-      expect(packageFiles[1].managerData.lernaJsonFile).toBe('lerna.json');
+      expect(packageFiles[1].managerData?.lernaJsonFile).toBe('lerna.json');
       expect(
         packageFiles.some((packageFile) =>
           packageFile.deps?.some((dep) => dep.isInternal)
@@ -113,7 +112,7 @@ describe('modules/manager/npm/extract/monorepo', () => {
     });
 
     it('uses yarn workspaces package settings with lerna', async () => {
-      const packageFiles = [
+      const packageFiles: Partial<PackageFile>[] = [
         {
           packageFile: 'package.json',
           managerData: {
@@ -134,11 +133,11 @@ describe('modules/manager/npm/extract/monorepo', () => {
       ];
       await detectMonorepos(packageFiles);
       expect(packageFiles).toMatchSnapshot();
-      expect(packageFiles[1].managerData.lernaJsonFile).toBe('lerna.json');
+      expect(packageFiles[1].managerData?.lernaJsonFile).toBe('lerna.json');
     });
 
     it('uses yarn workspaces package settings without lerna', async () => {
-      const packageFiles = [
+      const packageFiles: Partial<PackageFile>[] = [
         {
           packageFile: 'package.json',
           npmrc: '@org:registry=//registry.some.org\n',
@@ -162,8 +161,53 @@ describe('modules/manager/npm/extract/monorepo', () => {
       ]);
     });
 
+    it('uses yarn workspaces package settings with contraints', async () => {
+      const packageFiles: Partial<PackageFile>[] = [
+        {
+          packageFile: 'package.json',
+          yarnWorkspacesPackages: ['docs'],
+          skipInstalls: true, // coverage
+          constraints: {
+            node: '^14.15.0 || >=16.13.0',
+            yarn: '3.2.1',
+          },
+          yarnLock: 'yarn.lock',
+          managerData: {
+            hasPackageManager: true,
+          },
+        },
+        {
+          packageFile: 'docs/package.json',
+          packageJsonName: 'docs',
+          yarnLock: 'yarn.lock',
+          constraints: { yarn: '^3.2.0' },
+        },
+      ];
+      await detectMonorepos(packageFiles);
+      expect(packageFiles).toMatchObject([
+        {
+          constraints: {
+            node: '^14.15.0 || >=16.13.0',
+            yarn: '3.2.1',
+          },
+          managerData: {
+            hasPackageManager: true,
+          },
+        },
+        {
+          constraints: {
+            node: '^14.15.0 || >=16.13.0',
+            yarn: '^3.2.0',
+          },
+          managerData: {
+            hasPackageManager: true,
+          },
+        },
+      ]);
+    });
+
     it('uses yarnZeroInstall and skipInstalls from yarn workspaces package settings', async () => {
-      const packageFiles = [
+      const packageFiles: Partial<PackageFile>[] = [
         {
           packageFile: 'package.json',
           managerData: {
diff --git a/lib/modules/manager/npm/extract/monorepo.ts b/lib/modules/manager/npm/extract/monorepo.ts
index ea7aec797c1d7893f1a6ff466daea59cbd6dcb8e..31581102b35876ce716422d62a5a515587d3eb24 100644
--- a/lib/modules/manager/npm/extract/monorepo.ts
+++ b/lib/modules/manager/npm/extract/monorepo.ts
@@ -22,7 +22,8 @@ export async function detectMonorepos(
       yarnWorkspacesPackages,
       skipInstalls,
     } = p;
-    const { lernaJsonFile, yarnZeroInstall } = managerData;
+    const { lernaJsonFile, yarnZeroInstall, hasPackageManager } = managerData;
+
     const packages = yarnWorkspacesPackages || lernaPackages;
     if (packages?.length) {
       const internalPackagePatterns = (
@@ -51,6 +52,7 @@ export async function detectMonorepos(
         subPackage.managerData = subPackage.managerData || {};
         subPackage.managerData.lernaJsonFile = lernaJsonFile;
         subPackage.managerData.yarnZeroInstall = yarnZeroInstall;
+        subPackage.managerData.hasPackageManager = hasPackageManager;
         subPackage.lernaClient = lernaClient;
         subPackage.yarnLock = subPackage.yarnLock || yarnLock;
         subPackage.npmLock = subPackage.npmLock || npmLock;
@@ -60,6 +62,13 @@ export async function detectMonorepos(
           subPackage.npmrc = subPackage.npmrc || npmrc;
         }
 
+        if (p.constraints) {
+          subPackage.constraints = {
+            ...p.constraints,
+            ...subPackage.constraints,
+          };
+        }
+
         subPackage.deps?.forEach((dep) => {
           if (internalPackageNames.includes(dep.depName)) {
             dep.isInternal = true;