diff --git a/lib/config/presets/index.spec.ts b/lib/config/presets/index.spec.ts
index ccfd5dd8169102bdf42970c61dd73bc4409cde92..2cc78c86dc5ebb31f0ba04e8db0c4b9ae24d68fc 100644
--- a/lib/config/presets/index.spec.ts
+++ b/lib/config/presets/index.spec.ts
@@ -513,485 +513,6 @@ describe('config/presets/index', () => {
     });
   });
 
-  describe('parsePreset', () => {
-    // default namespace
-    it('returns default package name', () => {
-      expect(presets.parsePreset(':base')).toEqual({
-        repo: 'default',
-        params: undefined,
-        presetName: 'base',
-        presetPath: undefined,
-        presetSource: 'internal',
-      });
-    });
-
-    it('parses github', () => {
-      expect(presets.parsePreset('github>some/repo')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'github',
-      });
-    });
-
-    it('handles special chars', () => {
-      expect(presets.parsePreset('github>some/repo:foo+bar')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'foo+bar',
-        presetPath: undefined,
-        presetSource: 'github',
-      });
-    });
-
-    it('parses github subfiles', () => {
-      expect(presets.parsePreset('github>some/repo:somefile')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile',
-        presetPath: undefined,
-        presetSource: 'github',
-      });
-    });
-
-    it('parses github subfiles with preset name', () => {
-      expect(
-        presets.parsePreset('github>some/repo:somefile/somepreset'),
-      ).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile/somepreset',
-        presetPath: undefined,
-        presetSource: 'github',
-      });
-    });
-
-    it('parses github file with preset name with .json extension', () => {
-      expect(presets.parsePreset('github>some/repo:somefile.json')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile.json',
-        presetPath: undefined,
-        presetSource: 'github',
-        tag: undefined,
-      });
-    });
-
-    it('parses github file with preset name with .json5 extension', () => {
-      expect(presets.parsePreset('github>some/repo:somefile.json5')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile.json5',
-        presetPath: undefined,
-        presetSource: 'github',
-        tag: undefined,
-      });
-    });
-
-    it('parses github subfiles with preset name with .json extension', () => {
-      expect(
-        presets.parsePreset('github>some/repo:somefile.json/somepreset'),
-      ).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile.json/somepreset',
-        presetPath: undefined,
-        presetSource: 'github',
-        tag: undefined,
-      });
-    });
-
-    it('parses github subfiles with preset name with .json5 extension', () => {
-      expect(
-        presets.parsePreset('github>some/repo:somefile.json5/somepreset'),
-      ).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile.json5/somepreset',
-        presetPath: undefined,
-        presetSource: 'github',
-        tag: undefined,
-      });
-    });
-
-    it('parses github subfiles with preset and sub-preset name', () => {
-      expect(
-        presets.parsePreset(
-          'github>some/repo:somefile/somepreset/somesubpreset',
-        ),
-      ).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile/somepreset/somesubpreset',
-        presetPath: undefined,
-        presetSource: 'github',
-      });
-    });
-
-    it('parses github subdirectories', () => {
-      expect(
-        presets.parsePreset('github>some/repo//somepath/somesubpath/somefile'),
-      ).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile',
-        presetPath: 'somepath/somesubpath',
-        presetSource: 'github',
-      });
-    });
-
-    it('parses github toplevel file using subdirectory syntax', () => {
-      expect(presets.parsePreset('github>some/repo//somefile')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'somefile',
-        presetPath: undefined,
-        presetSource: 'github',
-      });
-    });
-
-    it('parses gitlab', () => {
-      expect(presets.parsePreset('gitlab>some/repo')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'gitlab',
-      });
-    });
-
-    it('parses gitea', () => {
-      expect(presets.parsePreset('gitea>some/repo')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'gitea',
-      });
-    });
-
-    it('parses local', () => {
-      expect(presets.parsePreset('local>some/repo')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'local',
-      });
-    });
-
-    it('parses local with spaces', () => {
-      expect(presets.parsePreset('local>A2B CD/A2B_Renovate')).toEqual({
-        repo: 'A2B CD/A2B_Renovate',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'local',
-      });
-    });
-
-    it('parses local with subdirectory', () => {
-      expect(
-        presets.parsePreset('local>some-group/some-repo//some-dir/some-file'),
-      ).toEqual({
-        repo: 'some-group/some-repo',
-        params: undefined,
-        presetName: 'some-file',
-        presetPath: 'some-dir',
-        presetSource: 'local',
-      });
-    });
-
-    it('parses local with spaces and subdirectory', () => {
-      expect(
-        presets.parsePreset('local>A2B CD/A2B_Renovate//some-dir/some-file'),
-      ).toEqual({
-        repo: 'A2B CD/A2B_Renovate',
-        params: undefined,
-        presetName: 'some-file',
-        presetPath: 'some-dir',
-        presetSource: 'local',
-      });
-    });
-
-    it('parses local with sub preset and tag', () => {
-      expect(
-        presets.parsePreset(
-          'local>some-group/some-repo:some-file/subpreset#1.2.3',
-        ),
-      ).toEqual({
-        repo: 'some-group/some-repo',
-        params: undefined,
-        presetName: 'some-file/subpreset',
-        presetPath: undefined,
-        presetSource: 'local',
-        tag: '1.2.3',
-      });
-    });
-
-    it('parses local with subdirectory and tag', () => {
-      expect(
-        presets.parsePreset(
-          'local>some-group/some-repo//some-dir/some-file#1.2.3',
-        ),
-      ).toEqual({
-        repo: 'some-group/some-repo',
-        params: undefined,
-        presetName: 'some-file',
-        presetPath: 'some-dir',
-        presetSource: 'local',
-        tag: '1.2.3',
-      });
-    });
-
-    it('parses local with subdirectory and branch/tag with a slash', () => {
-      expect(
-        presets.parsePreset(
-          'local>PROJECT/repository//path/to/preset#feature/branch',
-        ),
-      ).toEqual({
-        repo: 'PROJECT/repository',
-        params: undefined,
-        presetName: 'preset',
-        presetPath: 'path/to',
-        presetSource: 'local',
-        tag: 'feature/branch',
-      });
-    });
-
-    it('parses local with sub preset and branch/tag with a slash', () => {
-      expect(
-        presets.parsePreset(
-          'local>PROJECT/repository:preset/subpreset#feature/branch',
-        ),
-      ).toEqual({
-        repo: 'PROJECT/repository',
-        params: undefined,
-        presetName: 'preset/subpreset',
-        presetPath: undefined,
-        presetSource: 'local',
-        tag: 'feature/branch',
-      });
-    });
-
-    it('parses no prefix as local', () => {
-      expect(presets.parsePreset('some/repo')).toEqual({
-        repo: 'some/repo',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'local',
-      });
-    });
-
-    it('parses local Bitbucket user repo with preset name', () => {
-      expect(presets.parsePreset('local>~john_doe/repo//somefile')).toEqual({
-        repo: '~john_doe/repo',
-        params: undefined,
-        presetName: 'somefile',
-        presetPath: undefined,
-        presetSource: 'local',
-      });
-    });
-
-    it('parses local Bitbucket user repo', () => {
-      expect(presets.parsePreset('local>~john_doe/renovate-config')).toEqual({
-        repo: '~john_doe/renovate-config',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'local',
-      });
-    });
-
-    it('returns default package name with params', () => {
-      expect(presets.parsePreset(':group(packages/eslint, eslint)')).toEqual({
-        repo: 'default',
-        params: ['packages/eslint', 'eslint'],
-        presetName: 'group',
-        presetPath: undefined,
-        presetSource: 'internal',
-      });
-    });
-
-    // scoped namespace
-    it('returns simple scope', () => {
-      expect(presets.parsePreset('@somescope')).toEqual({
-        repo: '@somescope/renovate-config',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns simple scope and params', () => {
-      expect(presets.parsePreset('@somescope(param1)')).toEqual({
-        repo: '@somescope/renovate-config',
-        params: ['param1'],
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns scope with repo and default', () => {
-      expect(presets.parsePreset('@somescope/somepackagename')).toEqual({
-        repo: '@somescope/somepackagename',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns scope with repo and params and default', () => {
-      expect(
-        presets.parsePreset(
-          '@somescope/somepackagename(param1, param2, param3)',
-        ),
-      ).toEqual({
-        repo: '@somescope/somepackagename',
-        params: ['param1', 'param2', 'param3'],
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns scope with presetName', () => {
-      expect(presets.parsePreset('@somescope:somePresetName')).toEqual({
-        repo: '@somescope/renovate-config',
-        params: undefined,
-        presetName: 'somePresetName',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns scope with presetName and params', () => {
-      expect(presets.parsePreset('@somescope:somePresetName(param1)')).toEqual({
-        repo: '@somescope/renovate-config',
-        params: ['param1'],
-        presetName: 'somePresetName',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns scope with repo and presetName', () => {
-      expect(
-        presets.parsePreset('@somescope/somepackagename:somePresetName'),
-      ).toEqual({
-        repo: '@somescope/somepackagename',
-        params: undefined,
-        presetName: 'somePresetName',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns scope with repo and presetName and params', () => {
-      expect(
-        presets.parsePreset(
-          '@somescope/somepackagename:somePresetName(param1, param2)',
-        ),
-      ).toEqual({
-        repo: '@somescope/somepackagename',
-        params: ['param1', 'param2'],
-        presetName: 'somePresetName',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    // non-scoped namespace
-    it('returns non-scoped default', () => {
-      expect(presets.parsePreset('somepackage')).toEqual({
-        repo: 'renovate-config-somepackage',
-        params: undefined,
-        presetName: 'default',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns non-scoped package name', () => {
-      expect(presets.parsePreset('somepackage:webapp')).toEqual({
-        repo: 'renovate-config-somepackage',
-        params: undefined,
-        presetName: 'webapp',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('returns non-scoped package name full', () => {
-      expect(presets.parsePreset('renovate-config-somepackage:webapp')).toEqual(
-        {
-          repo: 'renovate-config-somepackage',
-          params: undefined,
-          presetName: 'webapp',
-          presetPath: undefined,
-          presetSource: 'npm',
-        },
-      );
-    });
-
-    it('returns non-scoped package name with params', () => {
-      expect(presets.parsePreset('somepackage:webapp(param1)')).toEqual({
-        repo: 'renovate-config-somepackage',
-        params: ['param1'],
-        presetName: 'webapp',
-        presetPath: undefined,
-        presetSource: 'npm',
-      });
-    });
-
-    it('parses HTTPS URLs', () => {
-      expect(
-        presets.parsePreset(
-          'https://my.server/gitea/renovate-config/raw/branch/main/default.json',
-        ),
-      ).toEqual({
-        repo: 'https://my.server/gitea/renovate-config/raw/branch/main/default.json',
-        params: undefined,
-        presetName: '',
-        presetPath: undefined,
-        presetSource: 'http',
-      });
-    });
-
-    it('parses HTTP URLs', () => {
-      expect(
-        presets.parsePreset(
-          'http://my.server/users/me/repos/renovate-presets/raw/default.json?at=refs%2Fheads%2Fmain',
-        ),
-      ).toEqual({
-        repo: 'http://my.server/users/me/repos/renovate-presets/raw/default.json?at=refs%2Fheads%2Fmain',
-        params: undefined,
-        presetName: '',
-        presetPath: undefined,
-        presetSource: 'http',
-      });
-    });
-
-    it('parses HTTPS URLs with parameters', () => {
-      expect(
-        presets.parsePreset(
-          'https://my.server/gitea/renovate-config/raw/branch/main/default.json(param1)',
-        ),
-      ).toEqual({
-        repo: 'https://my.server/gitea/renovate-config/raw/branch/main/default.json',
-        params: ['param1'],
-        presetName: '',
-        presetPath: undefined,
-        presetSource: 'http',
-      });
-    });
-  });
-
   describe('getPreset', () => {
     it('handles removed presets with a migration', async () => {
       const res = await presets.getPreset(':base', {});
diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts
index 9a96ed1f6f5aab1b1d00ff7d14d7ca84fcd35a9f..349db47089fe75f27d18dada56b855f908727ae3 100644
--- a/lib/config/presets/index.ts
+++ b/lib/config/presets/index.ts
@@ -24,7 +24,8 @@ import * as http from './http';
 import * as internal from './internal';
 import * as local from './local';
 import * as npm from './npm';
-import type { ParsedPreset, Preset, PresetApi } from './types';
+import { parsePreset } from './parse';
+import type { Preset, PresetApi } from './types';
 import {
   PRESET_DEP_NOT_FOUND,
   PRESET_INVALID,
@@ -46,13 +47,6 @@ const presetSources: Record<string, PresetApi> = {
 
 const presetCacheNamespace = 'preset';
 
-const nonScopedPresetWithSubdirRegex = regEx(
-  /^(?<repo>~?[\w\-. /]+?)\/\/(?:(?<presetPath>[\w\-./]+)\/)?(?<presetName>[\w\-.]+)(?:#(?<tag>[\w\-./]+?))?$/,
-);
-const gitPresetRegex = regEx(
-  /^(?<repo>~?[\w\-. /]+)(?::(?<presetName>[\w\-.+/]+))?(?:#(?<tag>[\w\-./]+?))?$/,
-);
-
 export function replaceArgs(
   obj: string,
   argMapping: Record<string, any>,
@@ -105,120 +99,6 @@ export function replaceArgs(
   return obj;
 }
 
-export function parsePreset(input: string): ParsedPreset {
-  let str = input;
-  let presetSource: string | undefined;
-  let presetPath: string | undefined;
-  let repo: string;
-  let presetName: string;
-  let tag: string | undefined;
-  let params: string[] | undefined;
-  if (str.startsWith('github>')) {
-    presetSource = 'github';
-    str = str.substring('github>'.length);
-  } else if (str.startsWith('gitlab>')) {
-    presetSource = 'gitlab';
-    str = str.substring('gitlab>'.length);
-  } else if (str.startsWith('gitea>')) {
-    presetSource = 'gitea';
-    str = str.substring('gitea>'.length);
-  } else if (str.startsWith('local>')) {
-    presetSource = 'local';
-    str = str.substring('local>'.length);
-  } else if (str.startsWith('http://') || str.startsWith('https://')) {
-    presetSource = 'http';
-  } else if (
-    !str.startsWith('@') &&
-    !str.startsWith(':') &&
-    str.includes('/')
-  ) {
-    presetSource = 'local';
-  }
-  str = str.replace(regEx(/^npm>/), '');
-  presetSource = presetSource ?? 'npm';
-  if (str.includes('(')) {
-    params = str
-      .slice(str.indexOf('(') + 1, -1)
-      .split(',')
-      .map((elem) => elem.trim());
-    str = str.slice(0, str.indexOf('('));
-  }
-  if (presetSource === 'http') {
-    return { presetSource, repo: str, presetName: '', params };
-  }
-  const presetsPackages = [
-    'compatibility',
-    'config',
-    'customManagers',
-    'default',
-    'docker',
-    'group',
-    'helpers',
-    'mergeConfidence',
-    'monorepo',
-    'npm',
-    'packages',
-    'preview',
-    'replacements',
-    'schedule',
-    'security',
-    'workarounds',
-  ];
-  if (
-    presetsPackages.some((presetPackage) => str.startsWith(`${presetPackage}:`))
-  ) {
-    presetSource = 'internal';
-    [repo, presetName] = str.split(':');
-  } else if (str.startsWith(':')) {
-    // default namespace
-    presetSource = 'internal';
-    repo = 'default';
-    presetName = str.slice(1);
-  } else if (str.startsWith('@')) {
-    // scoped namespace
-    [, repo] = regEx(/(@.*?)(:|$)/).exec(str)!;
-    str = str.slice(repo.length);
-    if (!repo.includes('/')) {
-      repo += '/renovate-config';
-    }
-    if (str === '') {
-      presetName = 'default';
-    } else {
-      presetName = str.slice(1);
-    }
-  } else if (str.includes('//')) {
-    // non-scoped namespace with a subdirectory preset
-
-    // Validation
-    if (str.includes(':')) {
-      throw new Error(PRESET_PROHIBITED_SUBPRESET);
-    }
-    if (!nonScopedPresetWithSubdirRegex.test(str)) {
-      throw new Error(PRESET_INVALID);
-    }
-    ({ repo, presetPath, presetName, tag } =
-      nonScopedPresetWithSubdirRegex.exec(str)!.groups!);
-  } else {
-    ({ repo, presetName, tag } = gitPresetRegex.exec(str)!.groups!);
-
-    if (presetSource === 'npm' && !repo.startsWith('renovate-config-')) {
-      repo = `renovate-config-${repo}`;
-    }
-    if (!is.nonEmptyString(presetName)) {
-      presetName = 'default';
-    }
-  }
-
-  return {
-    presetSource,
-    presetPath,
-    repo,
-    presetName,
-    tag,
-    params,
-  };
-}
-
 export async function getPreset(
   preset: string,
   baseConfig?: RenovateConfig,
diff --git a/lib/config/presets/parse.spec.ts b/lib/config/presets/parse.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..76cd8ddcee47c0254667b20178b77b30922bde82
--- /dev/null
+++ b/lib/config/presets/parse.spec.ts
@@ -0,0 +1,462 @@
+import { parsePreset } from './parse';
+
+describe('config/presets/parse', () => {
+  describe('parsePreset', () => {
+    // default namespace
+    it('returns default package name', () => {
+      expect(parsePreset(':base')).toEqual({
+        repo: 'default',
+        params: undefined,
+        presetName: 'base',
+        presetPath: undefined,
+        presetSource: 'internal',
+      });
+    });
+
+    it('parses github', () => {
+      expect(parsePreset('github>some/repo')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'github',
+      });
+    });
+
+    it('handles special chars', () => {
+      expect(parsePreset('github>some/repo:foo+bar')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'foo+bar',
+        presetPath: undefined,
+        presetSource: 'github',
+      });
+    });
+
+    it('parses github subfiles', () => {
+      expect(parsePreset('github>some/repo:somefile')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'somefile',
+        presetPath: undefined,
+        presetSource: 'github',
+      });
+    });
+
+    it('parses github subfiles with preset name', () => {
+      expect(parsePreset('github>some/repo:somefile/somepreset')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'somefile/somepreset',
+        presetPath: undefined,
+        presetSource: 'github',
+      });
+    });
+
+    it('parses github file with preset name with .json extension', () => {
+      expect(parsePreset('github>some/repo:somefile.json')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'somefile.json',
+        presetPath: undefined,
+        presetSource: 'github',
+        tag: undefined,
+      });
+    });
+
+    it('parses github file with preset name with .json5 extension', () => {
+      expect(parsePreset('github>some/repo:somefile.json5')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'somefile.json5',
+        presetPath: undefined,
+        presetSource: 'github',
+        tag: undefined,
+      });
+    });
+
+    it('parses github subfiles with preset name with .json extension', () => {
+      expect(parsePreset('github>some/repo:somefile.json/somepreset')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'somefile.json/somepreset',
+        presetPath: undefined,
+        presetSource: 'github',
+        tag: undefined,
+      });
+    });
+
+    it('parses github subfiles with preset name with .json5 extension', () => {
+      expect(parsePreset('github>some/repo:somefile.json5/somepreset')).toEqual(
+        {
+          repo: 'some/repo',
+          params: undefined,
+          presetName: 'somefile.json5/somepreset',
+          presetPath: undefined,
+          presetSource: 'github',
+          tag: undefined,
+        },
+      );
+    });
+
+    it('parses github subfiles with preset and sub-preset name', () => {
+      expect(
+        parsePreset('github>some/repo:somefile/somepreset/somesubpreset'),
+      ).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'somefile/somepreset/somesubpreset',
+        presetPath: undefined,
+        presetSource: 'github',
+      });
+    });
+
+    it('parses github subdirectories', () => {
+      expect(
+        parsePreset('github>some/repo//somepath/somesubpath/somefile'),
+      ).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'somefile',
+        presetPath: 'somepath/somesubpath',
+        presetSource: 'github',
+      });
+    });
+
+    it('parses github toplevel file using subdirectory syntax', () => {
+      expect(parsePreset('github>some/repo//somefile')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'somefile',
+        presetPath: undefined,
+        presetSource: 'github',
+      });
+    });
+
+    it('parses gitlab', () => {
+      expect(parsePreset('gitlab>some/repo')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'gitlab',
+      });
+    });
+
+    it('parses gitea', () => {
+      expect(parsePreset('gitea>some/repo')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'gitea',
+      });
+    });
+
+    it('parses local', () => {
+      expect(parsePreset('local>some/repo')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'local',
+      });
+    });
+
+    it('parses local with spaces', () => {
+      expect(parsePreset('local>A2B CD/A2B_Renovate')).toEqual({
+        repo: 'A2B CD/A2B_Renovate',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'local',
+      });
+    });
+
+    it('parses local with subdirectory', () => {
+      expect(
+        parsePreset('local>some-group/some-repo//some-dir/some-file'),
+      ).toEqual({
+        repo: 'some-group/some-repo',
+        params: undefined,
+        presetName: 'some-file',
+        presetPath: 'some-dir',
+        presetSource: 'local',
+      });
+    });
+
+    it('parses local with spaces and subdirectory', () => {
+      expect(
+        parsePreset('local>A2B CD/A2B_Renovate//some-dir/some-file'),
+      ).toEqual({
+        repo: 'A2B CD/A2B_Renovate',
+        params: undefined,
+        presetName: 'some-file',
+        presetPath: 'some-dir',
+        presetSource: 'local',
+      });
+    });
+
+    it('parses local with sub preset and tag', () => {
+      expect(
+        parsePreset('local>some-group/some-repo:some-file/subpreset#1.2.3'),
+      ).toEqual({
+        repo: 'some-group/some-repo',
+        params: undefined,
+        presetName: 'some-file/subpreset',
+        presetPath: undefined,
+        presetSource: 'local',
+        tag: '1.2.3',
+      });
+    });
+
+    it('parses local with subdirectory and tag', () => {
+      expect(
+        parsePreset('local>some-group/some-repo//some-dir/some-file#1.2.3'),
+      ).toEqual({
+        repo: 'some-group/some-repo',
+        params: undefined,
+        presetName: 'some-file',
+        presetPath: 'some-dir',
+        presetSource: 'local',
+        tag: '1.2.3',
+      });
+    });
+
+    it('parses local with subdirectory and branch/tag with a slash', () => {
+      expect(
+        parsePreset('local>PROJECT/repository//path/to/preset#feature/branch'),
+      ).toEqual({
+        repo: 'PROJECT/repository',
+        params: undefined,
+        presetName: 'preset',
+        presetPath: 'path/to',
+        presetSource: 'local',
+        tag: 'feature/branch',
+      });
+    });
+
+    it('parses local with sub preset and branch/tag with a slash', () => {
+      expect(
+        parsePreset('local>PROJECT/repository:preset/subpreset#feature/branch'),
+      ).toEqual({
+        repo: 'PROJECT/repository',
+        params: undefined,
+        presetName: 'preset/subpreset',
+        presetPath: undefined,
+        presetSource: 'local',
+        tag: 'feature/branch',
+      });
+    });
+
+    it('parses no prefix as local', () => {
+      expect(parsePreset('some/repo')).toEqual({
+        repo: 'some/repo',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'local',
+      });
+    });
+
+    it('parses local Bitbucket user repo with preset name', () => {
+      expect(parsePreset('local>~john_doe/repo//somefile')).toEqual({
+        repo: '~john_doe/repo',
+        params: undefined,
+        presetName: 'somefile',
+        presetPath: undefined,
+        presetSource: 'local',
+      });
+    });
+
+    it('parses local Bitbucket user repo', () => {
+      expect(parsePreset('local>~john_doe/renovate-config')).toEqual({
+        repo: '~john_doe/renovate-config',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'local',
+      });
+    });
+
+    it('returns default package name with params', () => {
+      expect(parsePreset(':group(packages/eslint, eslint)')).toEqual({
+        repo: 'default',
+        params: ['packages/eslint', 'eslint'],
+        presetName: 'group',
+        presetPath: undefined,
+        presetSource: 'internal',
+      });
+    });
+
+    // scoped namespace
+    it('returns simple scope', () => {
+      expect(parsePreset('@somescope')).toEqual({
+        repo: '@somescope/renovate-config',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns simple scope and params', () => {
+      expect(parsePreset('@somescope(param1)')).toEqual({
+        repo: '@somescope/renovate-config',
+        params: ['param1'],
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns scope with repo and default', () => {
+      expect(parsePreset('@somescope/somepackagename')).toEqual({
+        repo: '@somescope/somepackagename',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns scope with repo and params and default', () => {
+      expect(
+        parsePreset('@somescope/somepackagename(param1, param2, param3)'),
+      ).toEqual({
+        repo: '@somescope/somepackagename',
+        params: ['param1', 'param2', 'param3'],
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns scope with presetName', () => {
+      expect(parsePreset('@somescope:somePresetName')).toEqual({
+        repo: '@somescope/renovate-config',
+        params: undefined,
+        presetName: 'somePresetName',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns scope with presetName and params', () => {
+      expect(parsePreset('@somescope:somePresetName(param1)')).toEqual({
+        repo: '@somescope/renovate-config',
+        params: ['param1'],
+        presetName: 'somePresetName',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns scope with repo and presetName', () => {
+      expect(parsePreset('@somescope/somepackagename:somePresetName')).toEqual({
+        repo: '@somescope/somepackagename',
+        params: undefined,
+        presetName: 'somePresetName',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns scope with repo and presetName and params', () => {
+      expect(
+        parsePreset(
+          '@somescope/somepackagename:somePresetName(param1, param2)',
+        ),
+      ).toEqual({
+        repo: '@somescope/somepackagename',
+        params: ['param1', 'param2'],
+        presetName: 'somePresetName',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    // non-scoped namespace
+    it('returns non-scoped default', () => {
+      expect(parsePreset('somepackage')).toEqual({
+        repo: 'renovate-config-somepackage',
+        params: undefined,
+        presetName: 'default',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns non-scoped package name', () => {
+      expect(parsePreset('somepackage:webapp')).toEqual({
+        repo: 'renovate-config-somepackage',
+        params: undefined,
+        presetName: 'webapp',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns non-scoped package name full', () => {
+      expect(parsePreset('renovate-config-somepackage:webapp')).toEqual({
+        repo: 'renovate-config-somepackage',
+        params: undefined,
+        presetName: 'webapp',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('returns non-scoped package name with params', () => {
+      expect(parsePreset('somepackage:webapp(param1)')).toEqual({
+        repo: 'renovate-config-somepackage',
+        params: ['param1'],
+        presetName: 'webapp',
+        presetPath: undefined,
+        presetSource: 'npm',
+      });
+    });
+
+    it('parses HTTPS URLs', () => {
+      expect(
+        parsePreset(
+          'https://my.server/gitea/renovate-config/raw/branch/main/default.json',
+        ),
+      ).toEqual({
+        repo: 'https://my.server/gitea/renovate-config/raw/branch/main/default.json',
+        params: undefined,
+        presetName: '',
+        presetPath: undefined,
+        presetSource: 'http',
+      });
+    });
+
+    it('parses HTTP URLs', () => {
+      expect(
+        parsePreset(
+          'http://my.server/users/me/repos/renovate-presets/raw/default.json?at=refs%2Fheads%2Fmain',
+        ),
+      ).toEqual({
+        repo: 'http://my.server/users/me/repos/renovate-presets/raw/default.json?at=refs%2Fheads%2Fmain',
+        params: undefined,
+        presetName: '',
+        presetPath: undefined,
+        presetSource: 'http',
+      });
+    });
+
+    it('parses HTTPS URLs with parameters', () => {
+      expect(
+        parsePreset(
+          'https://my.server/gitea/renovate-config/raw/branch/main/default.json(param1)',
+        ),
+      ).toEqual({
+        repo: 'https://my.server/gitea/renovate-config/raw/branch/main/default.json',
+        params: ['param1'],
+        presetName: '',
+        presetPath: undefined,
+        presetSource: 'http',
+      });
+    });
+  });
+});
diff --git a/lib/config/presets/parse.ts b/lib/config/presets/parse.ts
new file mode 100644
index 0000000000000000000000000000000000000000..09ac031b26ce9ed183ee23f5e93b4c1d7f00a5a5
--- /dev/null
+++ b/lib/config/presets/parse.ts
@@ -0,0 +1,125 @@
+import is from '@sindresorhus/is';
+import { regEx } from '../../util/regex';
+import type { ParsedPreset } from './types';
+import { PRESET_INVALID, PRESET_PROHIBITED_SUBPRESET } from './util';
+
+const nonScopedPresetWithSubdirRegex = regEx(
+  /^(?<repo>~?[\w\-. /]+?)\/\/(?:(?<presetPath>[\w\-./]+)\/)?(?<presetName>[\w\-.]+)(?:#(?<tag>[\w\-./]+?))?$/,
+);
+const gitPresetRegex = regEx(
+  /^(?<repo>~?[\w\-. /]+)(?::(?<presetName>[\w\-.+/]+))?(?:#(?<tag>[\w\-./]+?))?$/,
+);
+
+export function parsePreset(input: string): ParsedPreset {
+  let str = input;
+  let presetSource: string | undefined;
+  let presetPath: string | undefined;
+  let repo: string;
+  let presetName: string;
+  let tag: string | undefined;
+  let params: string[] | undefined;
+  if (str.startsWith('github>')) {
+    presetSource = 'github';
+    str = str.substring('github>'.length);
+  } else if (str.startsWith('gitlab>')) {
+    presetSource = 'gitlab';
+    str = str.substring('gitlab>'.length);
+  } else if (str.startsWith('gitea>')) {
+    presetSource = 'gitea';
+    str = str.substring('gitea>'.length);
+  } else if (str.startsWith('local>')) {
+    presetSource = 'local';
+    str = str.substring('local>'.length);
+  } else if (str.startsWith('http://') || str.startsWith('https://')) {
+    presetSource = 'http';
+  } else if (
+    !str.startsWith('@') &&
+    !str.startsWith(':') &&
+    str.includes('/')
+  ) {
+    presetSource = 'local';
+  }
+  str = str.replace(regEx(/^npm>/), '');
+  presetSource = presetSource ?? 'npm';
+  if (str.includes('(')) {
+    params = str
+      .slice(str.indexOf('(') + 1, -1)
+      .split(',')
+      .map((elem) => elem.trim());
+    str = str.slice(0, str.indexOf('('));
+  }
+  if (presetSource === 'http') {
+    return { presetSource, repo: str, presetName: '', params };
+  }
+  const presetsPackages = [
+    'compatibility',
+    'config',
+    'customManagers',
+    'default',
+    'docker',
+    'group',
+    'helpers',
+    'mergeConfidence',
+    'monorepo',
+    'npm',
+    'packages',
+    'preview',
+    'replacements',
+    'schedule',
+    'security',
+    'workarounds',
+  ];
+  if (
+    presetsPackages.some((presetPackage) => str.startsWith(`${presetPackage}:`))
+  ) {
+    presetSource = 'internal';
+    [repo, presetName] = str.split(':');
+  } else if (str.startsWith(':')) {
+    // default namespace
+    presetSource = 'internal';
+    repo = 'default';
+    presetName = str.slice(1);
+  } else if (str.startsWith('@')) {
+    // scoped namespace
+    [, repo] = regEx(/(@.*?)(:|$)/).exec(str)!;
+    str = str.slice(repo.length);
+    if (!repo.includes('/')) {
+      repo += '/renovate-config';
+    }
+    if (str === '') {
+      presetName = 'default';
+    } else {
+      presetName = str.slice(1);
+    }
+  } else if (str.includes('//')) {
+    // non-scoped namespace with a subdirectory preset
+
+    // Validation
+    if (str.includes(':')) {
+      throw new Error(PRESET_PROHIBITED_SUBPRESET);
+    }
+    if (!nonScopedPresetWithSubdirRegex.test(str)) {
+      throw new Error(PRESET_INVALID);
+    }
+    ({ repo, presetPath, presetName, tag } =
+      nonScopedPresetWithSubdirRegex.exec(str)!.groups!);
+  } else {
+    ({ repo, presetName, tag } = gitPresetRegex.exec(str)!.groups!);
+
+    if (presetSource === 'npm' && !repo.startsWith('renovate-config-')) {
+      repo = `renovate-config-${repo}`;
+    }
+    if (!is.nonEmptyString(presetName)) {
+      presetName = 'default';
+    }
+  }
+
+  return {
+    presetSource,
+    presetPath,
+    repo,
+    presetName,
+    tag,
+    params,
+  };
+}