diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index 10f6c161de366f489bc8c04005ee500e4dabc110..3d4b58ed791786bb86e92a289b8a6d556c85f1be 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -1232,6 +1232,15 @@ Typically you would encrypt it and put it inside the `encrypted` object.
 
 See [Private npm module support](https://docs.renovatebot.com/getting-started/private-packages) for details on how this is used.
 
+## npmrcMerge
+
+This option exists to provide flexibility about whether `npmrc` strings in config should override `.npmrc` files in the repo, or be merged with them.
+In some situations you need the ability to force override `.npmrc` contents in a repo (`npmMerge=false`) while in others you might want to simply supplement the settings already in the `.npmrc` (`npmMerge=true`).
+A use case for the latter is if you are a Renovate bot admin and wish to provide a default token for `npmjs.org` without removing any other `.npmrc` settings which individual repositories have configured (such as scopes/registries).
+
+If `false` (default), it means that defining `config.npmrc` will result in any `.npmrc` file in the repo being overridden and therefore its values ignored.
+If configured to `true`, it means that any `.npmrc` file in the repo will have `config.npmrc` prepended to it before running `npm`.
+
 ## packageRules
 
 `packageRules` is a powerful feature that lets you apply rules to individual packages or to groups of packages using regex pattern matching.
diff --git a/docs/usage/getting-started/private-packages.md b/docs/usage/getting-started/private-packages.md
index 9007d04028df64198fe424364e31329f302640a9..0af1ac31133e9c976c9047c3ba0277da8457cec2 100644
--- a/docs/usage/getting-started/private-packages.md
+++ b/docs/usage/getting-started/private-packages.md
@@ -180,6 +180,8 @@ You can add an `.npmrc` authentication line to your Renovate config under the fi
 ```
 
 If configured like this, Renovate will use this to authenticate with npm and will ignore any `.npmrc` files(s) it finds checked into the repository.
+If you wish for the values in your `config.npmrc` to be _merged_ (prepended) with any values found in repos then also set `config.npmrcMerge=true`.
+This merge approach is similar to how `npm` itself behaves if `.npmrc` is found in both the user home directory as well as a project.
 
 #### Add npmToken to Renovate config
 
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 7a757b04d8cec1f986b72716fdf8e05e0cabf8a3..90105c4e4de306a2eeca332db1cdd8e88eda983c 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -589,6 +589,14 @@ const options: RenovateOptions[] = [
     stage: 'branch',
     type: 'string',
   },
+  {
+    name: 'npmrcMerge',
+    description:
+      'Whether to merge config.npmrc with repo .npmrc content if both are found.',
+    stage: 'branch',
+    type: 'boolean',
+    default: false,
+  },
   {
     name: 'npmToken',
     description: 'npm token used for authenticating with the default registry.',
diff --git a/lib/config/types.ts b/lib/config/types.ts
index 6094917fa250c2fcbf374de2441804ae7d15958e..a773d15247b84110c7e709d16933716a8e5b296b 100644
--- a/lib/config/types.ts
+++ b/lib/config/types.ts
@@ -44,6 +44,7 @@ export interface RenovateSharedConfig {
   dependencyDashboardApproval?: boolean;
   hashedBranchLength?: number;
   npmrc?: string;
+  npmrcMerge?: boolean;
   platform?: string;
   postUpgradeTasks?: PostUpgradeTasks;
   prBodyColumns?: string[];
diff --git a/lib/manager/npm/extract/index.spec.ts b/lib/manager/npm/extract/index.spec.ts
index e513d19e92813b2a63ca5fe14e251322611d1618..538c8fbea66e10c3d58e6124e8dab4e36ff47e4f 100644
--- a/lib/manager/npm/extract/index.spec.ts
+++ b/lib/manager/npm/extract/index.spec.ts
@@ -122,7 +122,7 @@ describe('manager/npm/extract/index', () => {
       );
       expect(res.npmrc).toBeDefined();
     });
-    it('ignores .npmrc when config.npmrc is defined', async () => {
+    it('ignores .npmrc when config.npmrc is defined and npmrcMerge=false', async () => {
       fs.readLocalFile = jest.fn((fileName) => {
         if (fileName === '.npmrc') {
           return 'some-npmrc\n';
@@ -136,6 +136,20 @@ describe('manager/npm/extract/index', () => {
       );
       expect(res.npmrc).toBeUndefined();
     });
+    it('reads .npmrc when config.npmrc is merged', async () => {
+      fs.readLocalFile = jest.fn((fileName) => {
+        if (fileName === '.npmrc') {
+          return 'repo-npmrc\n';
+        }
+        return null;
+      });
+      const res = await npmExtract.extractPackageFile(
+        input01Content,
+        'package.json',
+        { npmrc: 'config-npmrc', npmrcMerge: true }
+      );
+      expect(res.npmrc).toEqual(`config-npmrc\nrepo-npmrc\n`);
+    });
     it('finds and filters .npmrc with variables', async () => {
       fs.readLocalFile = jest.fn((fileName) => {
         if (fileName === '.npmrc') {
diff --git a/lib/manager/npm/extract/index.ts b/lib/manager/npm/extract/index.ts
index 4cf396790500b0d26b1578db351d50f29f6dd538..e3a7bf30a47bf3e256d5f09bfcaf7374fe3bae9c 100644
--- a/lib/manager/npm/extract/index.ts
+++ b/lib/manager/npm/extract/index.ts
@@ -96,29 +96,33 @@ export async function extractPackageFile(
 
   let npmrc: string;
   const npmrcFileName = getSiblingFileName(fileName, '.npmrc');
-  const npmrcContent = await readLocalFile(npmrcFileName, 'utf8');
-  if (is.string(npmrcContent)) {
-    if (is.string(config.npmrc)) {
+  let repoNpmrc = await readLocalFile(npmrcFileName, 'utf8');
+  if (is.string(repoNpmrc)) {
+    if (is.string(config.npmrc) && !config.npmrcMerge) {
       logger.debug(
         { npmrcFileName },
-        'Repo .npmrc file is ignored due to presence of config.npmrc'
+        'Repo .npmrc file is ignored due to config.npmrc with config.npmrcMerge=force'
       );
     } else {
-      npmrc = npmrcContent;
-      if (npmrc?.includes('package-lock')) {
+      npmrc = config.npmrc || '';
+      if (npmrc.length) {
+        npmrc = npmrc.replace(/\n?$/, '\n');
+      }
+      if (repoNpmrc?.includes('package-lock')) {
         logger.debug('Stripping package-lock setting from .npmrc');
-        npmrc = npmrc.replace(/(^|\n)package-lock.*?(\n|$)/g, '\n');
+        repoNpmrc = repoNpmrc.replace(/(^|\n)package-lock.*?(\n|$)/g, '\n');
       }
-      if (npmrc.includes('=${') && !getGlobalConfig().exposeAllEnv) {
+      if (repoNpmrc.includes('=${') && !getGlobalConfig().exposeAllEnv) {
         logger.debug(
           { npmrcFileName },
           'Stripping .npmrc file of lines with variables'
         );
-        npmrc = npmrc
+        repoNpmrc = repoNpmrc
           .split('\n')
           .filter((line) => !line.includes('=${'))
           .join('\n');
       }
+      npmrc += repoNpmrc;
     }
   }
 
diff --git a/lib/manager/types.ts b/lib/manager/types.ts
index 69358a103eb96af0d3e02a26d7a77767edc14370..5e1842c53d4130703f573980eb4817b3f807eee9 100644
--- a/lib/manager/types.ts
+++ b/lib/manager/types.ts
@@ -20,6 +20,7 @@ export interface ExtractConfig {
   gradle?: { timeout?: number };
   aliases?: Record<string, string>;
   npmrc?: string;
+  npmrcMerge?: boolean;
   skipInstalls?: boolean;
   updateInternalDeps?: boolean;
   deepExtract?: boolean;