diff --git a/lib/config/index.ts b/lib/config/index.ts
index ae565172cf8e8ca527924be9c9e7e2167bca7a59..c5f8935b46772e208ceb8c949e6d1b6766ca56d8 100644
--- a/lib/config/index.ts
+++ b/lib/config/index.ts
@@ -1,5 +1,5 @@
 import { logger } from '../logger';
-import { get, getManagerList } from '../modules/manager';
+import { allManagersList, get } from '../modules/manager';
 import * as options from './options';
 import type {
   AllConfig,
@@ -25,7 +25,7 @@ export function getManagerConfig(
   }
   // TODO: fix types #22198
   managerConfig = mergeChildConfig(managerConfig, config[manager] as any);
-  for (const i of getManagerList()) {
+  for (const i of allManagersList) {
     delete managerConfig[i];
   }
   return managerConfig;
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 3907d8c7e5cdcacd93fc9347649a1df763d50bfa..68595b313d5a41062d92147f557c5a74042f0391 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -1,4 +1,5 @@
 import { getManagers } from '../../modules/manager';
+import { getCustomManagers } from '../../modules/manager/custom';
 import { getPlatformList } from '../../modules/platform';
 import { getVersioningList } from '../../modules/versioning';
 import type { RenovateOptions } from '../types';
@@ -2667,7 +2668,8 @@ export function getOptions(): RenovateOptions[] {
 }
 
 function loadManagerOptions(): void {
-  for (const [name, config] of getManagers().entries()) {
+  const allManagers = new Map([...getManagers(), ...getCustomManagers()]);
+  for (const [name, config] of allManagers.entries()) {
     if (config.defaultConfig) {
       const managerConfig: RenovateOptions = {
         name,
diff --git a/lib/config/validation-helpers/managers.spec.ts b/lib/config/validation-helpers/managers.spec.ts
index 981e12e2531973f47a27cd6656a4803c7fbef310..653f65bf2bf357e8a09f1a1960d7f9222f9c671b 100644
--- a/lib/config/validation-helpers/managers.spec.ts
+++ b/lib/config/validation-helpers/managers.spec.ts
@@ -3,7 +3,7 @@ import { check } from './managers';
 describe('config/validation-helpers/managers', () => {
   it('should have no errors', () => {
     const res = check({
-      resolvedRule: { matchManagers: ['npm'] },
+      resolvedRule: { matchManagers: ['npm', 'regex'] },
       currentPath: '',
     });
     expect(res).toEqual([]);
diff --git a/lib/config/validation-helpers/managers.ts b/lib/config/validation-helpers/managers.ts
index 682489f19a0c0446042eadc72f770b6a6dad5108..aecad19df3c48302e03ff7cc6aa0787ae22319b3 100644
--- a/lib/config/validation-helpers/managers.ts
+++ b/lib/config/validation-helpers/managers.ts
@@ -1,4 +1,4 @@
-import { getManagerList } from '../../modules/manager';
+import { allManagersList } from '../../modules/manager';
 import type { ValidationMessage } from '../types';
 import type { CheckManagerArgs } from './types';
 
@@ -13,14 +13,14 @@ export function check({
   if (Array.isArray(resolvedRule.matchManagers)) {
     if (
       resolvedRule.matchManagers.find(
-        (confManager) => !getManagerList().includes(confManager)
+        (confManager) => !allManagersList.includes(confManager)
       )
     ) {
       managersErrMessage = `${currentPath}:
         You have included an unsupported manager in a package rule. Your list: ${String(
           resolvedRule.matchManagers
         )}.
-        Supported managers are: (${getManagerList().join(', ')}).`;
+        Supported managers are: (${allManagersList.join(', ')}).`;
     }
   } else if (typeof resolvedRule.matchManagers !== 'undefined') {
     managersErrMessage = `${currentPath}: Managers should be type of List. You have included ${typeof resolvedRule.matchManagers}.`;
diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts
index fbeb444ce29d3b462150f0882fc4f3cc18842aa7..f1154377de7a802edfef000d1c1ae085fb03ce9b 100644
--- a/lib/config/validation.spec.ts
+++ b/lib/config/validation.spec.ts
@@ -237,7 +237,9 @@ describe('config/validation', () => {
       ['single enabled manager', { enabledManagers: ['npm'] }],
       [
         'multiple enabled managers',
-        { enabledManagers: ['npm', 'gradle', 'maven'] },
+        {
+          enabledManagers: ['npm', 'gradle', 'maven', 'regex'],
+        },
       ],
     ])('validates enabled managers for %s', async (_case, config) => {
       const { warnings, errors } = await configValidation.validateConfig(
diff --git a/lib/config/validation.ts b/lib/config/validation.ts
index 8abff1d09fa8777585a695e3d985559b9b0cf6bf..932fb2c5cf32e4b201f871cc08a474cbb36646f3 100644
--- a/lib/config/validation.ts
+++ b/lib/config/validation.ts
@@ -1,5 +1,5 @@
 import is from '@sindresorhus/is';
-import { getManagerList } from '../modules/manager';
+import { allManagersList, getManagerList } from '../modules/manager';
 import { configRegexPredicate, isConfigRegex, regEx } from '../util/regex';
 import * as template from '../util/template';
 import {
@@ -65,7 +65,7 @@ function validatePlainObject(val: Record<string, unknown>): true | string {
 
 function getUnsupportedEnabledManagers(enabledManagers: string[]): string[] {
   return enabledManagers.filter(
-    (manager) => !getManagerList().includes(manager)
+    (manager) => !allManagersList.includes(manager)
   );
 }
 
diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts
index b6d3f6e72790176b9ca75368a0be3464a131d5e2..29f35185311300193ae6187d089878ce1f6cd1c4 100644
--- a/lib/modules/manager/api.ts
+++ b/lib/modules/manager/api.ts
@@ -21,7 +21,6 @@ import * as cocoapods from './cocoapods';
 import * as composer from './composer';
 import * as conan from './conan';
 import * as cpanfile from './cpanfile';
-import * as regex from './custom/regex';
 import * as depsEdn from './deps-edn';
 import * as dockerCompose from './docker-compose';
 import * as dockerfile from './dockerfile';
@@ -161,7 +160,6 @@ api.set('pre-commit', preCommit);
 api.set('pub', pub);
 api.set('puppet', puppet);
 api.set('pyenv', pyenv);
-api.set('regex', regex);
 api.set('ruby-version', rubyVersion);
 api.set('sbt', sbt);
 api.set('setup-cfg', setupCfg);
diff --git a/lib/modules/manager/custom/api.ts b/lib/modules/manager/custom/api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..de5e051ca72ca19d2dabc5d028cda900fe6d55a7
--- /dev/null
+++ b/lib/modules/manager/custom/api.ts
@@ -0,0 +1,7 @@
+import type { ManagerApi } from '../types';
+import * as regex from './regex';
+
+const api = new Map<string, ManagerApi>();
+export default api;
+
+api.set('regex', regex);
diff --git a/lib/modules/manager/custom/index.spec.ts b/lib/modules/manager/custom/index.spec.ts
index 762390dda84be9380368463209dfb2d7ad2451a9..40332436ebd75ffdbbcd835af405807aa8be32f2 100644
--- a/lib/modules/manager/custom/index.spec.ts
+++ b/lib/modules/manager/custom/index.spec.ts
@@ -1,15 +1,14 @@
 import * as customManager from '.';
 
 describe('modules/manager/custom/index', () => {
-  it('has default config', () => {
-    expect(customManager.defaultConfig).toEqual({});
+  it('getCustomManagerList', () => {
+    expect(customManager.customManagerList).toBeArrayOf(expect.toBeString());
   });
 
-  it('gets supportedDatasources', () => {
-    expect(customManager.supportedDatasources).toEqual(['*']);
-  });
-
-  it('extractPackageFile', () => {
-    expect(customManager.extractPackageFile('', '', {})).toEqual({ deps: [] });
+  describe('isCustomManager()', () => {
+    it('works', () => {
+      expect(customManager.isCustomManager('npm')).toBe(false);
+      expect(customManager.isCustomManager('regex')).toBe(true);
+    });
   });
 });
diff --git a/lib/modules/manager/custom/index.ts b/lib/modules/manager/custom/index.ts
index b11ee7ed120621aa5e6c92cfa06eb4151a641eee..b35e64056fa2b550088ca7da86b894188ede59e0 100644
--- a/lib/modules/manager/custom/index.ts
+++ b/lib/modules/manager/custom/index.ts
@@ -1,12 +1,9 @@
-import type { ExtractConfig, PackageFileContent, Result } from '../types';
+import type { ManagerApi } from '../types';
+import customManagers from './api';
 
-// export hollow functions for validation as manager
-export const defaultConfig = {};
-export const supportedDatasources = ['*'];
-export function extractPackageFile(
-  content: string,
-  packageFile: string,
-  config: ExtractConfig
-): Result<PackageFileContent | null> {
-  return { deps: [] };
+export const customManagerList = Array.from(customManagers.keys());
+export const getCustomManagers = (): Map<string, ManagerApi> => customManagers;
+
+export function isCustomManager(manager: string): boolean {
+  return !!customManagers.has(manager);
 }
diff --git a/lib/modules/manager/custom/regex/index.spec.ts b/lib/modules/manager/custom/regex/index.spec.ts
index 0669bfa39c989e4d4670ef0bd6f1077b485f5aa6..e3fe7d34261279e9e34075723b426a1cb3906e1a 100644
--- a/lib/modules/manager/custom/regex/index.spec.ts
+++ b/lib/modules/manager/custom/regex/index.spec.ts
@@ -2,7 +2,7 @@ import { codeBlock } from 'common-tags';
 import { Fixtures } from '../../../../../test/fixtures';
 import { logger } from '../../../../logger';
 import type { CustomExtractConfig } from '../../types';
-import { defaultConfig, extractPackageFile } from '.';
+import { defaultConfig, displayName, extractPackageFile } from '.';
 
 const dockerfileContent = Fixtures.get(`Dockerfile`);
 const ansibleYamlContent = Fixtures.get(`ansible.yml`);
@@ -16,6 +16,10 @@ describe('modules/manager/custom/regex/index', () => {
     });
   });
 
+  it('has displayName', () => {
+    expect(displayName).toBe('Regex');
+  });
+
   it('extracts multiple dependencies', async () => {
     const config = {
       matchStrings: [
diff --git a/lib/modules/manager/custom/regex/index.ts b/lib/modules/manager/custom/regex/index.ts
index 5628d71f0f607e3e46f338288b47b2c7d6a00090..58a36d3b748f32f74962687900fb09aa56833d79 100644
--- a/lib/modules/manager/custom/regex/index.ts
+++ b/lib/modules/manager/custom/regex/index.ts
@@ -14,6 +14,7 @@ export const defaultConfig = {
   pinDigests: false,
 };
 export const supportedDatasources = ['*'];
+export const displayName = 'Regex';
 
 export function extractPackageFile(
   content: string,
diff --git a/lib/modules/manager/fingerprint.spec.ts b/lib/modules/manager/fingerprint.spec.ts
index 0060c790ecc370d102a1c8bd3d997c3a30da3bfa..ab9fbdc75d42e89f1efc47649baa8fd0922ec6f8 100644
--- a/lib/modules/manager/fingerprint.spec.ts
+++ b/lib/modules/manager/fingerprint.spec.ts
@@ -1,11 +1,10 @@
 import { regEx } from '../../util/regex';
-import { getManagers, hashMap } from '.';
+import { allManagersList, hashMap } from '.';
 
 describe('modules/manager/fingerprint', () => {
   it('validate manager hash', () => {
     const regex = regEx(/^[a-f0-9]{64}$/i);
-    const managers = getManagers();
-    for (const [manager] of managers) {
+    for (const manager of allManagersList) {
       const managerHash = hashMap.get(manager)!;
       expect(regex.test(managerHash)).toBeTrue();
     }
diff --git a/lib/modules/manager/index.spec.ts b/lib/modules/manager/index.spec.ts
index 18ce9f1d1e18365000339a155c1a353f1e8084c9..a2f778421f5e0e97da473ae878a8171722221231 100644
--- a/lib/modules/manager/index.spec.ts
+++ b/lib/modules/manager/index.spec.ts
@@ -1,6 +1,7 @@
 import { join } from 'upath';
 import { loadModules } from '../../util/modules';
 import { getDatasourceList } from '../datasource';
+import * as customManager from './custom';
 import type { ManagerApi } from './types';
 import * as manager from '.';
 
@@ -10,11 +11,8 @@ const datasources = getDatasourceList();
 
 describe('modules/manager/index', () => {
   describe('supportedDatasources', () => {
+    // no need to check custom managers as they support all datasources
     for (const m of manager.getManagerList()) {
-      if (m === 'regex') {
-        // regex supports any
-        continue;
-      }
       const supportedDatasources = manager.get(m, 'supportedDatasources');
 
       it(`has valid supportedDatasources for ${m}`, () => {
@@ -40,7 +38,11 @@ describe('modules/manager/index', () => {
   });
 
   it('validates', () => {
-    function validate(module: ManagerApi): boolean {
+    function validate(module: ManagerApi, moduleName: string): boolean {
+      // no need to validate custom as it is a wrapper and not an actual manager
+      if (moduleName === 'custom') {
+        return true;
+      }
       if (!module.defaultConfig) {
         return false;
       }
@@ -53,17 +55,21 @@ describe('modules/manager/index', () => {
       return true;
     }
     const mgrs = manager.getManagers();
+    const customMgrs = customManager.getCustomManagers();
 
     const loadedMgr = {
       ...loadModules(__dirname, validate), // validate built-in managers
       ...loadModules(join(__dirname, 'custom'), validate), // validate custom managers
     };
     delete loadedMgr['custom'];
-    expect(Array.from(mgrs.keys())).toEqual(Object.keys(loadedMgr).sort());
+
+    expect(Array.from([...mgrs.keys(), ...customMgrs.keys()]).sort()).toEqual(
+      Object.keys(loadedMgr).sort()
+    );
 
     for (const name of mgrs.keys()) {
       const mgr = mgrs.get(name)!;
-      expect(validate(mgr)).toBeTrue();
+      expect(validate(mgr, name)).toBeTrue();
     }
   });
 
@@ -117,6 +123,17 @@ describe('modules/manager/index', () => {
       ).toBeNull();
     });
 
+    it('handles custom managers', () => {
+      customManager.getCustomManagers().set('dummy', {
+        defaultConfig: {},
+        supportedDatasources: [],
+        extractPackageFile: () => Promise.resolve({ deps: [] }),
+      });
+      expect(
+        manager.extractPackageFile('dummy', '', 'filename', {})
+      ).not.toBeNull();
+    });
+
     it('returns non-null', () => {
       manager.getManagers().set('dummy', {
         defaultConfig: {},
diff --git a/lib/modules/manager/index.ts b/lib/modules/manager/index.ts
index 3d35007dc90841e071b594c42621b6223544995b..3e910f5899465148ce8c1cb4de4cb112adbe41ed 100644
--- a/lib/modules/manager/index.ts
+++ b/lib/modules/manager/index.ts
@@ -1,5 +1,7 @@
 import type { RangeStrategy } from '../../types';
 import managers from './api';
+import { customManagerList, isCustomManager } from './custom';
+import customManagers from './custom/api';
 import type {
   ExtractConfig,
   GlobalManagerConfig,
@@ -10,21 +12,26 @@ import type {
   Result,
 } from './types';
 export { hashMap } from './fingerprint.generated';
-const managerList = Array.from(managers.keys());
+
+const managerList = Array.from(managers.keys()); // does not include custom managers
+export const getManagerList = (): string[] => managerList;
+export const getManagers = (): Map<string, ManagerApi> => managers;
+export const allManagersList = [...managerList, ...customManagerList];
 
 export function get<T extends keyof ManagerApi>(
   manager: string,
   name: T
 ): ManagerApi[T] | undefined {
-  return managers.get(manager)?.[name];
+  return isCustomManager(manager)
+    ? customManagers.get(manager)?.[name]
+    : managers.get(manager)?.[name];
 }
-export const getManagerList = (): string[] => managerList;
-export const getManagers = (): Map<string, ManagerApi> => managers;
 
 export async function detectAllGlobalConfig(): Promise<GlobalManagerConfig> {
   let config: GlobalManagerConfig = {};
-  for (const managerName of managerList) {
-    const manager = managers.get(managerName)!;
+  for (const managerName of allManagersList) {
+    const manager =
+      managers.get(managerName)! ?? customManagers.get(managerName)!;
     if (manager.detectGlobalConfig) {
       // This should use mergeChildConfig once more than one manager is supported, but introduces a cyclic dependency
       config = { ...config, ...(await manager.detectGlobalConfig()) };
@@ -59,10 +66,11 @@ export function extractPackageFile(
   fileName: string,
   config: ExtractConfig
 ): Result<PackageFileContent | null> {
-  if (!managers.has(manager)) {
+  const m = managers.get(manager)! ?? customManagers.get(manager)!;
+  if (!m) {
     return null;
   }
-  const m = managers.get(manager)!;
+
   return m.extractPackageFile
     ? m.extractPackageFile(content, fileName, config)
     : null;
diff --git a/lib/modules/manager/metadata.spec.ts b/lib/modules/manager/metadata.spec.ts
index 4b08e34f6a58955c23dfe58a28e467beecd81468..320088f09891347f57180613fff68fbfec949aaa 100644
--- a/lib/modules/manager/metadata.spec.ts
+++ b/lib/modules/manager/metadata.spec.ts
@@ -1,40 +1,50 @@
 import fs from 'fs-extra';
+import { join } from 'upath';
+import { customManagerList as customManagers } from './custom';
 
 describe('modules/manager/metadata', () => {
   const managerList: string[] = fs
     .readdirSync(__dirname, { withFileTypes: true })
     .filter((dirent) => dirent.isDirectory())
     .map((dirent) => dirent.name)
-    .filter((name) => !name.startsWith('__') || name === 'custom')
+    .filter((name) => !name.startsWith('__') && name !== 'custom')
     .sort();
 
-  it.each(managerList)('%s has readme with no h1 or h2', async (manager) => {
-    let readme: string | undefined;
-    try {
-      readme = await fs.readFile(
-        `${__dirname}/${
-          manager === 'custom' ? 'custom/regex' : manager
-        }/readme.md`,
-        'utf8'
-      );
-    } catch (err) {
-      // do nothing
-    }
-    expect(readme).toBeDefined();
-    const lines = readme!.split('\n');
-    let isCode = false;
-    const res: string[] = [];
+  const customManagerList = fs
+    .readdirSync(join(__dirname, 'custom'), { withFileTypes: true })
+    .filter((dirent) => dirent.isDirectory())
+    .map((dirent) => dirent.name)
+    .filter((name) => !name.startsWith('__'))
+    .sort();
 
-    for (const line of lines) {
-      if (line.startsWith('```')) {
-        isCode = !isCode;
-      } else if (!isCode) {
-        res.push(line);
+  it.each([...managerList, ...customManagerList])(
+    '%s has readme with no h1 or h2',
+    async (manager) => {
+      let readme: string | undefined;
+      try {
+        const readmeFilePath = `${__dirname}/${
+          (customManagers.includes(manager) ? 'custom/' : '') + manager
+        }/readme.md`;
+        readme = await fs.readFile(readmeFilePath, 'utf8');
+      } catch (err) {
+        // do nothing
+      }
+      expect(readme).toBeDefined();
+      const lines = readme!.split('\n');
+      let isCode = false;
+      const res: string[] = [];
+
+      for (const line of lines) {
+        if (line.startsWith('```')) {
+          isCode = !isCode;
+        } else if (!isCode) {
+          res.push(line);
+        }
       }
-    }
 
-    expect(
-      res.some((line) => line.startsWith('# ') || line.startsWith('## '))
-    ).toBeFalse();
-  });
+      expect(
+        res.some((line) => line.startsWith('# ') || line.startsWith('## '))
+      ).toBeFalse();
+    }
+  );
 });
diff --git a/lib/util/package-rules/managers.spec.ts b/lib/util/package-rules/managers.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c8e855c87e7fd2ee47d486393af9bb4df028623d
--- /dev/null
+++ b/lib/util/package-rules/managers.spec.ts
@@ -0,0 +1,51 @@
+import { ManagersMatcher } from './managers';
+
+describe('util/package-rules/managers', () => {
+  const managersMatcher = new ManagersMatcher();
+
+  describe('match', () => {
+    it('should return true', () => {
+      const result = managersMatcher.matches(
+        {
+          manager: 'npm',
+        },
+        {
+          matchManagers: ['npm', 'regex'],
+        }
+      );
+      expect(result).toBeTrue();
+    });
+
+    it('should return false for no match', () => {
+      const result = managersMatcher.matches(
+        {
+          manager: 'npm',
+        },
+        {
+          matchManagers: ['docker'],
+        }
+      );
+      expect(result).toBeFalse();
+    });
+
+    it('should return null if matchManagers is undefined', () => {
+      const result = managersMatcher.matches(
+        {
+          manager: 'npm',
+        },
+        {}
+      );
+      expect(result).toBeNull();
+    });
+
+    it('should return false if no manager', () => {
+      const result = managersMatcher.matches(
+        {},
+        {
+          matchManagers: ['npm'],
+        }
+      );
+      expect(result).toBeFalse();
+    });
+  });
+});
diff --git a/lib/workers/repository/extract/extract-fingerprint-config.spec.ts b/lib/workers/repository/extract/extract-fingerprint-config.spec.ts
index c0ac40404f4e4651d75a37cf5154a87ac8d76893..13d008edeeddfeaeb31b63f2ccc4054262e7d544 100644
--- a/lib/workers/repository/extract/extract-fingerprint-config.spec.ts
+++ b/lib/workers/repository/extract/extract-fingerprint-config.spec.ts
@@ -1,6 +1,6 @@
 import { mergeChildConfig } from '../../../config';
 import { getConfig } from '../../../config/defaults';
-import { getManagerList } from '../../../modules/manager';
+import { allManagersList } from '../../../modules/manager';
 import { generateFingerprintConfig } from './extract-fingerprint-config';
 
 describe('workers/repository/extract/extract-fingerprint-config', () => {
@@ -77,7 +77,7 @@ describe('workers/repository/extract/extract-fingerprint-config', () => {
       npm: { fileMatch: ['hero.json'] },
     });
     const fingerprintConfig = generateFingerprintConfig(config);
-    expect(fingerprintConfig.managerList).toEqual(new Set(getManagerList()));
+    expect(fingerprintConfig.managerList).toEqual(new Set(allManagersList));
     expect(
       fingerprintConfig.managers.find((manager) => manager.manager === 'npm')
     ).toEqual({
diff --git a/lib/workers/repository/extract/extract-fingerprint-config.ts b/lib/workers/repository/extract/extract-fingerprint-config.ts
index 380599a5f739876e5d001053a7cfd9aa1b752fd9..31bf8d9407e74141f98edc8ff4e53aef5ece8d78 100644
--- a/lib/workers/repository/extract/extract-fingerprint-config.ts
+++ b/lib/workers/repository/extract/extract-fingerprint-config.ts
@@ -3,7 +3,8 @@ import type {
   RegexManagerTemplates,
   RenovateConfig,
 } from '../../../config/types';
-import { getManagerList } from '../../../modules/manager';
+import { allManagersList } from '../../../modules/manager';
+import { isCustomManager } from '../../../modules/manager/custom';
 import { validMatchFields } from '../../../modules/manager/custom/regex/utils';
 import type { CustomExtractConfig } from '../../../modules/manager/types';
 import type { WorkerExtractConfig } from '../../types';
@@ -37,7 +38,7 @@ function getFilteredManagerConfig(
   config: WorkerExtractConfig
 ): WorkerExtractConfig {
   return {
-    ...(config.manager === 'regex' && getRegexManagerFields(config)),
+    ...(isCustomManager(config.manager) && getRegexManagerFields(config)),
     manager: config.manager,
     fileMatch: config.fileMatch,
     npmrc: config.npmrc,
@@ -60,12 +61,13 @@ export function generateFingerprintConfig(
   if (enabledManagers?.length) {
     managerList = new Set(enabledManagers);
   } else {
-    managerList = new Set(getManagerList());
+    managerList = new Set(allManagersList);
   }
 
   for (const manager of managerList) {
     const managerConfig = getManagerConfig(config, manager);
-    if (manager === 'regex') {
+    if (isCustomManager(manager)) {
+      // TODO: filter regexManagers using customType
       for (const regexManager of config.regexManagers ?? []) {
         managerExtractConfigs.push({
           ...mergeChildConfig(managerConfig, regexManager),
diff --git a/lib/workers/repository/extract/index.spec.ts b/lib/workers/repository/extract/index.spec.ts
index b2e0b5848b67df7f6e91c13bad3fed5f4cc0cff2..74dbedef4d0fe9d2f938d8991caf2697ea911d3e 100644
--- a/lib/workers/repository/extract/index.spec.ts
+++ b/lib/workers/repository/extract/index.spec.ts
@@ -26,6 +26,7 @@ describe('workers/repository/extract/index', () => {
       managerFiles.getManagerPackageFiles.mockResolvedValue([
         partial<PackageFile<Record<string, any>>>({}),
       ]);
+      delete config.regexManagers; // for coverage
       const res = await extractAllDependencies(config);
       expect(Object.keys(res.packageFiles)).toContain('ansible');
     });
@@ -36,7 +37,9 @@ describe('workers/repository/extract/index', () => {
         partial<PackageFile<Record<string, any>>>({}),
       ]);
       const res = await extractAllDependencies(config);
-      expect(res).toMatchObject({ packageFiles: { npm: [{}] } });
+      expect(res).toMatchObject({
+        packageFiles: { npm: [{}] },
+      });
     });
 
     it('warns if no packages found for a enabled manager', async () => {
diff --git a/lib/workers/repository/extract/index.ts b/lib/workers/repository/extract/index.ts
index 9f4f45cc9bcb39f308391b40cd66f1fc09c03041..b505537335f9e3a769a9842e272a9d46a780b73b 100644
--- a/lib/workers/repository/extract/index.ts
+++ b/lib/workers/repository/extract/index.ts
@@ -2,7 +2,8 @@ import is from '@sindresorhus/is';
 import { getManagerConfig, mergeChildConfig } from '../../../config';
 import type { ManagerConfig, RenovateConfig } from '../../../config/types';
 import { logger } from '../../../logger';
-import { getManagerList, hashMap } from '../../../modules/manager';
+import { allManagersList, hashMap } from '../../../modules/manager';
+import { isCustomManager } from '../../../modules/manager/custom';
 import { scm } from '../../../modules/platform/scm';
 import type { ExtractResult, WorkerExtractConfig } from '../../types';
 import { getMatchingFiles } from './file-match';
@@ -11,7 +12,7 @@ import { getManagerPackageFiles } from './manager-files';
 export async function extractAllDependencies(
   config: RenovateConfig
 ): Promise<ExtractResult> {
-  let managerList = getManagerList();
+  let managerList = allManagersList;
   const { enabledManagers } = config;
   if (is.nonEmptyArray(enabledManagers)) {
     logger.debug('Applying enabledManagers filtering');
@@ -32,7 +33,8 @@ export async function extractAllDependencies(
   for (const manager of managerList) {
     const managerConfig = getManagerConfig(config, manager);
     managerConfig.manager = manager;
-    if (manager === 'regex') {
+    if (isCustomManager(manager)) {
+      // TODO: filter regexManagers using customType before
       for (const regexManager of config.regexManagers ?? []) {
         tryConfig(mergeChildConfig(managerConfig, regexManager));
       }
diff --git a/tools/docs/config.ts b/tools/docs/config.ts
index d668ae0f764f1b309840e8aff97864844c59d5b8..bdc89254134f931d7648921caa4bfe0c22491f45 100644
--- a/tools/docs/config.ts
+++ b/tools/docs/config.ts
@@ -1,12 +1,12 @@
 import stringify from 'json-stringify-pretty-compact';
 import { getOptions } from '../../lib/config/options';
-import { getManagerList } from '../../lib/modules/manager';
+import { allManagersList } from '../../lib/modules/manager';
 import { getCliName } from '../../lib/workers/global/config/parse/cli';
 import { getEnvName } from '../../lib/workers/global/config/parse/env';
 import { readFile, updateFile } from '../utils';
 
 const options = getOptions();
-const managers = new Set(getManagerList());
+const managers = new Set(allManagersList);
 
 /**
  * Merge string arrays one by one
diff --git a/tools/docs/manager.ts b/tools/docs/manager.ts
index a7d2833578f1e901316a5c2eb62df8fe6107aa2a..9909814923618bf633fac555db695ac8fab3ebac 100644
--- a/tools/docs/manager.ts
+++ b/tools/docs/manager.ts
@@ -1,6 +1,10 @@
 import type { RenovateConfig } from '../../lib/config/types';
 import type { Category } from '../../lib/constants';
 import { getManagers } from '../../lib/modules/manager';
+import {
+  getCustomManagers,
+  isCustomManager,
+} from '../../lib/modules/manager/custom';
 import { readFile, updateFile } from '../utils';
 import { OpenItems, generateFeatureAndBugMarkdown } from './github-query-items';
 import {
@@ -14,8 +18,8 @@ const noCategoryID = 'no-category';
 const noCategoryDisplayName = 'No Category';
 
 function getTitle(manager: string, displayName: string): string {
-  if (manager === 'regex') {
-    return `Custom Manager Support using Regex`;
+  if (isCustomManager(manager)) {
+    return `Custom Manager Support using ${displayName}`;
   }
   return `Automated Dependency Updates for ${displayName}`;
 }
@@ -55,11 +59,11 @@ export async function generateManagers(
   dist: string,
   managerIssuesMap: OpenItems
 ): Promise<void> {
-  const managers = getManagers();
+  const allManagers = [...getManagers(), ...getCustomManagers()];
 
   const allCategories: Record<string, string[]> = {};
 
-  for (const [manager, definition] of managers) {
+  for (const [manager, definition] of allManagers) {
     const { defaultConfig, supportedDatasources, urls } = definition;
     const { fileMatch } = defaultConfig as RenovateConfig;
     const displayName = getDisplayName(manager, definition);
@@ -88,7 +92,7 @@ sidebar_label: ${displayName}
     }
     md += '\n\n';
 
-    if (manager !== 'regex') {
+    if (!isCustomManager(manager)) {
       const nameWithUrl = getNameWithUrl(manager, definition);
       md += `Renovate supports updating ${nameWithUrl} dependencies.\n\n`;
       if (defaultConfig.enabled === false) {
@@ -135,10 +139,10 @@ sidebar_label: ${displayName}
     }
     const managerReadmeContent = await readFile(
       `lib/modules/manager/${
-        manager === 'regex' ? 'custom/regex' : manager
+        isCustomManager(manager) ? 'custom/' : '' + manager
       }/readme.md`
     );
-    if (manager !== 'regex') {
+    if (!isCustomManager(manager)) {
       md += '\n## Additional Information\n\n';
     }
     md += managerReadmeContent;