diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index e7e5d06277b1ce16e196de4586b0096eb9cd6fd0..5f39f4e75d1d37bc9ee2482a59ea5f34cada072b 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -27,6 +27,36 @@ Also, be sure to check out Renovate's [shareable config presets](/config-presets
 
 If you have any questions about the below config options, or would like to get help/feedback about a config, please post it as an issue in [renovatebot/config-help](https://github.com/renovatebot/config-help) where we will do our best to answer your question.
 
+## addLabels
+
+The `labels` field is non-mergeable, meaning that any config setting a list of PR labels will replace any existing list.
+If you want to append labels for matched rules, then define an `addLabels` array with one (or more) label strings.
+All matched `addLabels` strings will be attached to the PR.
+
+Consider this example:
+
+```json
+{
+  "labels": ["dependencies"],
+  "packageRules": [
+    {
+      "packagePatterns": ["eslint"],
+      "labels": ["linting"]
+    },
+    {
+      "depTypeList": ["optionalDependencies"],
+      "addLabels": ["optional"]
+    }
+  ]
+}
+```
+
+With the above config:
+
+- Optional dependencies will have the labels `dependencies` and `optional`
+- ESLint dependencies will have the label `linting`
+- All other dependencies will have the label `dependencies`
+
 ## additionalBranchPrefix
 
 This value defaults to an empty string, and is typically not necessary.
diff --git a/lib/config/common.ts b/lib/config/common.ts
index 26f9205669e0659cf5b837ae8da6dca2b634c63f..3db93010aac79762747b0047f6ff696c48cc4ac0 100644
--- a/lib/config/common.ts
+++ b/lib/config/common.ts
@@ -37,6 +37,7 @@ export interface RenovateSharedConfig {
   ignoreDeps?: string[];
   ignorePaths?: string[];
   labels?: string[];
+  addLabels?: string[];
   managers?: string | string[];
   dependencyDashboardApproval?: boolean;
   npmrc?: string;
diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts
index e8c22bb2a1fb8517174c141a8ac449ad3ae83def..a2085e088e207656b2b329e00db4630fcc5ed12b 100644
--- a/lib/config/definitions.ts
+++ b/lib/config/definitions.ts
@@ -1409,9 +1409,16 @@ const options: RenovateOptions[] = [
   // Pull Request options
   {
     name: 'labels',
+    description: 'Labels to set in Pull Request',
+    type: 'array',
+    subType: 'string',
+  },
+  {
+    name: 'addLabels',
     description: 'Labels to add to Pull Request',
     type: 'array',
     subType: 'string',
+    mergeable: true,
   },
   {
     name: 'assignees',
diff --git a/lib/workers/pr/index.spec.ts b/lib/workers/pr/index.spec.ts
index f2c2290ab0bc1d380c7c29a12bf1c04bd7ea3a90..59ee12f457315767cd9be5edbe43749b413818e9 100644
--- a/lib/workers/pr/index.spec.ts
+++ b/lib/workers/pr/index.spec.ts
@@ -643,5 +643,15 @@ describe('workers/pr', () => {
         gitLabAutomerge: true,
       });
     });
+
+    it('should create a PR with set of labels and mergeable addLabels', async () => {
+      config.labels = ['deps', 'renovate'];
+      config.addLabels = ['deps', 'js'];
+      const { prResult } = await prWorker.ensurePr(config);
+      expect(prResult).toEqual(PrResult.Created);
+      expect(platform.createPr.mock.calls[0][0]).toMatchObject({
+        labels: ['deps', 'renovate', 'js'],
+      });
+    });
   });
 });
diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts
index 40a3cac68625370e62019ae9b2dace407c3b5c90..56e3489598b429231e0f64b4d384d0205c637c95 100644
--- a/lib/workers/pr/index.ts
+++ b/lib/workers/pr/index.ts
@@ -384,7 +384,7 @@ export async function ensurePr(
           targetBranch: config.baseBranch,
           prTitle,
           prBody,
-          labels: config.labels,
+          labels: [...new Set([...config.labels, ...config.addLabels])],
           platformOptions: getPlatformPrOptions(config),
           draftPR: config.draftPR,
         });