diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts
index e6288c3b78ea05fc487f9aab5744fc2eff56b203..7dfabbecbbb28c412dc7e2008fa904c310efa058 100644
--- a/lib/config/migration.spec.ts
+++ b/lib/config/migration.spec.ts
@@ -435,5 +435,26 @@ describe('config/migration', () => {
       expect(res.isMigrated).toBe(false);
       expect(res.migratedConfig).toMatchObject({ semanticCommits: 'disabled' });
     });
+    it('it migrates preset strings to array', () => {
+      let config: RenovateConfig;
+      let res: MigratedConfig;
+
+      config = { extends: ':js-app' } as never;
+      res = configMigration.migrateConfig(config);
+      expect(res.isMigrated).toBe(true);
+      expect(res.migratedConfig).toMatchObject({ extends: ['config:js-app'] });
+
+      config = { extends: 'foo' } as never;
+      res = configMigration.migrateConfig(config);
+      expect(res.isMigrated).toBe(true);
+      expect(res.migratedConfig).toMatchObject({ extends: ['foo'] });
+
+      config = { extends: ['foo', ':js-app', 'bar'] } as never;
+      res = configMigration.migrateConfig(config);
+      expect(res.isMigrated).toBe(true);
+      expect(res.migratedConfig).toMatchObject({
+        extends: ['foo', 'config:js-app', 'bar'],
+      });
+    });
   });
 });
diff --git a/lib/config/migration.ts b/lib/config/migration.ts
index 82cd988842494ac54a23bc3e44656156304be3e5..d98e552f11045780640b88cd7121984e123e6376 100644
--- a/lib/config/migration.ts
+++ b/lib/config/migration.ts
@@ -237,20 +237,29 @@ export function migrateConfig(
         } else {
           migratedConfig.semanticCommitScope = null;
         }
-      } else if (key === 'extends' && is.array<string>(val)) {
-        for (let i = 0; i < val.length; i += 1) {
-          if (val[i] === 'config:application' || val[i] === ':js-app') {
-            isMigrated = true;
-            migratedConfig.extends[i] = 'config:js-app';
-          } else if (val[i] === ':library' || val[i] === 'config:library') {
-            isMigrated = true;
-            migratedConfig.extends[i] = 'config:js-lib';
-          } else if (is.string(val[i]) && val[i].startsWith(':masterIssue')) {
-            isMigrated = true;
-            migratedConfig.extends[i] = val[i].replace(
-              'masterIssue',
-              'dependencyDashboard'
-            );
+      } else if (
+        key === 'extends' &&
+        (is.array<string>(val) || is.string(val))
+      ) {
+        if (is.string(migratedConfig.extends)) {
+          migratedConfig.extends = [migratedConfig.extends];
+          isMigrated = true;
+        }
+        const presets = migratedConfig.extends;
+        for (let i = 0; i < presets.length; i += 1) {
+          let preset = presets[i];
+          if (is.string(preset)) {
+            if (preset === 'config:application' || preset === ':js-app') {
+              isMigrated = true;
+              preset = 'config:js-app';
+            } else if (preset === ':library' || preset === 'config:library') {
+              isMigrated = true;
+              preset = 'config:js-lib';
+            } else if (preset.startsWith(':masterIssue')) {
+              isMigrated = true;
+              preset = preset.replace('masterIssue', 'dependencyDashboard');
+            }
+            presets[i] = preset;
           }
         }
       } else if (key === 'versionScheme') {
diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts
index d1f88c081dee95da9f17d65b4f0f0b045ada604c..258b392e724c1491523acd71910627d6f527e592 100644
--- a/lib/config/validation.spec.ts
+++ b/lib/config/validation.spec.ts
@@ -434,5 +434,17 @@ describe('config/validation', () => {
       expect(errors).toMatchSnapshot();
       expect(warnings).toMatchSnapshot();
     });
+
+    it('validates preset values', async () => {
+      const config = {
+        extends: ['foo', 'bar', 42] as never,
+      };
+      const { warnings, errors } = await configValidation.validateConfig(
+        config,
+        true
+      );
+      expect(warnings).toHaveLength(0);
+      expect(errors).toHaveLength(1);
+    });
   });
 });
diff --git a/lib/config/validation.ts b/lib/config/validation.ts
index 58eb98421ea869634835d1c4eb12c08c3014acc1..a61a2805c4c908de57fde848cbc4ca1f8d165f53 100644
--- a/lib/config/validation.ts
+++ b/lib/config/validation.ts
@@ -214,17 +214,24 @@ export async function validateConfig(
             if (key === 'extends') {
               const tzRe = /^:timezone\((.+)\)$/;
               for (const subval of val) {
-                if (is.string(subval) && tzRe.test(subval)) {
-                  const [, timezone] = tzRe.exec(subval);
-                  const [validTimezone, errorMessage] = hasValidTimezone(
-                    timezone
-                  );
-                  if (!validTimezone) {
-                    errors.push({
-                      depName: 'Configuration Error',
-                      message: `${currentPath}: ${errorMessage}`,
-                    });
+                if (is.string(subval)) {
+                  if (tzRe.test(subval)) {
+                    const [, timezone] = tzRe.exec(subval);
+                    const [validTimezone, errorMessage] = hasValidTimezone(
+                      timezone
+                    );
+                    if (!validTimezone) {
+                      errors.push({
+                        depName: 'Configuration Error',
+                        message: `${currentPath}: ${errorMessage}`,
+                      });
+                    }
                   }
+                } else {
+                  errors.push({
+                    depName: 'Configuration Warning',
+                    message: `${currentPath}: preset value is not a string`,
+                  });
                 }
               }
             }