diff --git a/docs/usage/getting-started/running.md b/docs/usage/getting-started/running.md
index a19079c12ef5f8e8be8bca1045b3f5ab85635520..cff201ef9c54e73c75670293e4baa140a609c3c2 100644
--- a/docs/usage/getting-started/running.md
+++ b/docs/usage/getting-started/running.md
@@ -92,7 +92,7 @@ WhiteSource Renovate On-Premises and WhiteSource Remediate both run as long-live
 
 ### Global config
 
-Renovate's server-side/admin config is referred to as its "global" config, and can be specified using either a config file (`config.js` or `config.json`), environment variables, or CLI parameters.
+Renovate's server-side/admin config is referred to as its "global" config, and can be specified using either a config file (`config.js`, `config.json`, `config.json5`, `config.yaml` or `config.yml`), environment variables, or CLI parameters.
 
 Some config is global-only, meaning that either it is only applicable to the bot administrator or it can only be controlled by the administrator and not repository users.
 Those are documented in [Self-hosted Configuration](../self-hosted-configuration.md).
@@ -108,6 +108,21 @@ If you combine both of the above then any single config option in the environmen
 
 Note: it's also possible to change the default prefix from `RENOVATE_` using `ENV_PREFIX`. e.g. `ENV_PREFIX=RNV_ RNV_TOKEN=abc123 renovate`.
 
+#### Using `config.js`
+
+If you use a `config.js`, it will be expected to export a configuration via `module.exports`.
+The value can be either a plain JavaScript object like in this example where `config.js` exports a plain object:
+
+```javascript
+module.exports = {
+  token: 'abcdefg',
+};
+```
+
+`config.js` may also export a `Promise` of such an object, or a function that will return either a plain Javascript object or a `Promise` of such an object.
+This allows one to include the results of asynchronous operations in the exported value.
+An example of a `config.js` that exports an async function (which is a function that returns a `Promise`) can be seen in a comment for [#10011: Allow autodiscover filtering for repo topic](https://github.com/renovatebot/renovate/issues/10011#issuecomment-992568583) and more examples can be seen in [`file.spec.ts`](https://github.com/renovatebot/renovate/blob/main/lib/workers/global/config/parse/file.spec.ts).
+
 ### Authentication
 
 Regardless of platform, you need to select a user account for `renovate` to assume the identity of, and generate a Personal Access Token.
diff --git a/lib/workers/global/config/parse/__fixtures__/config-async-function.js b/lib/workers/global/config/parse/__fixtures__/config-async-function.js
new file mode 100644
index 0000000000000000000000000000000000000000..5d5b3c5d9244abf8231af66c22ac1d7bba3090f5
--- /dev/null
+++ b/lib/workers/global/config/parse/__fixtures__/config-async-function.js
@@ -0,0 +1,8 @@
+// This is functionally equivalent to config-function-promise.js but syntactically different
+
+// @ts-ignore
+module.exports = async function () {
+  return {
+        token: 'abcdefg',
+  }
+};
diff --git a/lib/workers/global/config/parse/__fixtures__/config-function-promise.js b/lib/workers/global/config/parse/__fixtures__/config-function-promise.js
new file mode 100644
index 0000000000000000000000000000000000000000..946c9f62939ef5019afef571403df939e8c88f8e
--- /dev/null
+++ b/lib/workers/global/config/parse/__fixtures__/config-function-promise.js
@@ -0,0 +1,10 @@
+// This is functionally equivalent to config-async-function.js but syntactically different
+
+// @ts-ignore
+module.exports = function () {
+  return new Promise(resolve => {
+    resolve({
+          token: 'abcdefg',
+    })
+  });
+};
diff --git a/lib/workers/global/config/parse/__fixtures__/config-function.js b/lib/workers/global/config/parse/__fixtures__/config-function.js
new file mode 100644
index 0000000000000000000000000000000000000000..e9e2acb9b89f7b8c96a66b349b56ad592a200941
--- /dev/null
+++ b/lib/workers/global/config/parse/__fixtures__/config-function.js
@@ -0,0 +1,6 @@
+// @ts-ignore
+module.exports = function () {
+  return {
+        token: 'abcdefg',
+  }
+};
diff --git a/lib/workers/global/config/parse/__fixtures__/config-promise.js b/lib/workers/global/config/parse/__fixtures__/config-promise.js
new file mode 100644
index 0000000000000000000000000000000000000000..a8c19e3adae43c79221495e6dc5ab473bf6b6a85
--- /dev/null
+++ b/lib/workers/global/config/parse/__fixtures__/config-promise.js
@@ -0,0 +1,8 @@
+// @ts-ignore
+module.exports = new Promise( resolve => {
+  resolve(
+    {
+      token: 'abcdefg',
+    }
+  );
+});
diff --git a/lib/workers/global/config/parse/__fixtures__/file.js b/lib/workers/global/config/parse/__fixtures__/config.js
similarity index 100%
rename from lib/workers/global/config/parse/__fixtures__/file.js
rename to lib/workers/global/config/parse/__fixtures__/config.js
diff --git a/lib/workers/global/config/parse/__fixtures__/file2.js b/lib/workers/global/config/parse/__fixtures__/config2.js
similarity index 100%
rename from lib/workers/global/config/parse/__fixtures__/file2.js
rename to lib/workers/global/config/parse/__fixtures__/config2.js
diff --git a/lib/workers/global/config/parse/file.spec.ts b/lib/workers/global/config/parse/file.spec.ts
index cf497566a42a59308cf81bf249ddbcc1d71d5e07..a0826752160183ceef4526e309618e8d2b07a3f0 100644
--- a/lib/workers/global/config/parse/file.spec.ts
+++ b/lib/workers/global/config/parse/file.spec.ts
@@ -2,7 +2,7 @@ import fs from 'fs';
 import { DirectoryResult, dir } from 'tmp-promise';
 import upath from 'upath';
 import { logger } from '../../../../logger';
-import customConfig from './__fixtures__/file';
+import customConfig from './__fixtures__/config';
 import * as file from './file';
 
 describe('workers/global/config/parse/file', () => {
@@ -18,7 +18,18 @@ describe('workers/global/config/parse/file', () => {
 
   describe('.getConfig()', () => {
     it.each([
-      ['custom config file with extension', 'file.js'],
+      ['custom js config file', 'config.js'],
+      ['custom js config file exporting a Promise', 'config-promise.js'],
+      ['custom js config file exporting a function', 'config-function.js'],
+      // The next two are different syntactic ways of expressing the same thing
+      [
+        'custom js config file exporting a function returning a Promise',
+        'config-function-promise.js',
+      ],
+      [
+        'custom js config file exporting an async function',
+        'config-async-function.js',
+      ],
       ['JSON5 config file', 'config.json5'],
       ['YAML config file', 'config.yaml'],
     ])('parses %s', async (fileType, filePath) => {
@@ -29,7 +40,7 @@ describe('workers/global/config/parse/file', () => {
     });
 
     it('migrates', async () => {
-      const configFile = upath.resolve(__dirname, './__fixtures__/file2.js');
+      const configFile = upath.resolve(__dirname, './__fixtures__/config2.js');
       const res = await file.getConfig({ RENOVATE_CONFIG_FILE: configFile });
       expect(res).toMatchSnapshot();
       expect(res.rangeStrategy).toBe('bump');
diff --git a/lib/workers/global/config/parse/file.ts b/lib/workers/global/config/parse/file.ts
index 103d24d8692f4ec7f0095ebbeff5eabd4aa55565..24a0e31df7d6c0adf61ebff5c9f814f6c1ea24ef 100644
--- a/lib/workers/global/config/parse/file.ts
+++ b/lib/workers/global/config/parse/file.ts
@@ -1,3 +1,4 @@
+import is from 'is';
 import { load } from 'js-yaml';
 import JSON5 from 'json5';
 import upath from 'upath';
@@ -18,7 +19,12 @@ export async function getParsedContent(file: string): Promise<RenovateConfig> {
       return JSON5.parse(await readFile(file, 'utf8'));
     case '.js': {
       const tmpConfig = await import(file);
-      return tmpConfig.default ? tmpConfig.default : tmpConfig;
+      let config = tmpConfig.default ? tmpConfig.default : tmpConfig;
+      // Allow the config to be a function
+      if (is.fn(config)) {
+        config = config();
+      }
+      return config;
     }
     default:
       throw new Error('Unsupported file type');