diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index 650433f09028e984f0f944ef37af174d6a5af50c..17799486a4475be54feac452dc0d17642864089c 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -2323,6 +2323,24 @@ This works because Renovate will add a "renovate/stability-days" pending status
 
 Add to this object if you wish to define rules that apply only to minor updates.
 
+## mode
+
+This configuration option was created primarily for use with Mend's hosted app, but can also be useful for some self-hosted use cases.
+
+It enables a new `silent` mode to allow repos to be scanned for updates _and_ for users to be able to request such updates be opened in PRs _on demand_ through the Mend UI, without needing the Dependency Dashboard issue in the repo.
+
+Although similar, the options `mode=silent` and `dryRun` can be used together.
+When both are configured, `dryRun` takes precedence, so for example PRs won't be created.
+
+Configuring `silent` mode is quite similar to `dryRun=lookup` except:
+
+- It will bypass onboarding checks (unlike when performing a dry run on a non-onboarded repo) similar to `requireConfig=optional`
+- It can create branches/PRs if `checkedBranches` is set
+- It will keep any existing branches up-to-date (e.g. ones created previously using `checkedBranches`)
+
+When in `silent` mode, Renovate does not create issues (such as Dependency Dashboard, or due to config errors) or Config Migration PRs, even if enabled.
+It also does not prune/close any which already exist.
+
 ## npmToken
 
 See [Private npm module support](./getting-started/private-packages.md) for details on how this is used.
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 5a2aa34c339fd4d68d5a9e014c6c91af0ee4def6..af3688da7dd1074ebf2493ded4fedb0ced068508 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -5,6 +5,13 @@ import { getVersioningList } from '../../modules/versioning';
 import type { RenovateOptions } from '../types';
 
 const options: RenovateOptions[] = [
+  {
+    name: 'mode',
+    description: 'Mode of operation.',
+    type: 'string',
+    default: 'full',
+    allowedValues: ['full', 'silent'],
+  },
   {
     name: 'allowedHeaders',
     description:
diff --git a/lib/workers/repository/config-migration/index.spec.ts b/lib/workers/repository/config-migration/index.spec.ts
index 9f107189397a1af0d5f2e621b2826508db72584d..3067573bedbeb8da6ba1e90d00e53b9a2869b6b3 100644
--- a/lib/workers/repository/config-migration/index.spec.ts
+++ b/lib/workers/repository/config-migration/index.spec.ts
@@ -28,6 +28,13 @@ describe('workers/repository/config-migration/index', () => {
     });
   });
 
+  it('does nothing when in silent mode', async () => {
+    await configMigration({ ...config, mode: 'silent' }, []);
+    expect(MigratedDataFactory.getAsync).toHaveBeenCalledTimes(0);
+    expect(checkConfigMigrationBranch).toHaveBeenCalledTimes(0);
+    expect(ensureConfigMigrationPr).toHaveBeenCalledTimes(0);
+  });
+
   it('does nothing when config migration is disabled', async () => {
     await configMigration({ ...config, configMigration: false }, []);
     expect(MigratedDataFactory.getAsync).toHaveBeenCalledTimes(0);
diff --git a/lib/workers/repository/config-migration/index.ts b/lib/workers/repository/config-migration/index.ts
index 0746ccdf8d77bee40b291f15d4c88c7973eac319..856b0ceb2772683a0c57da608f75f50e4fb0e9a0 100644
--- a/lib/workers/repository/config-migration/index.ts
+++ b/lib/workers/repository/config-migration/index.ts
@@ -1,4 +1,5 @@
 import type { RenovateConfig } from '../../../config/types';
+import { logger } from '../../../logger';
 import { checkConfigMigrationBranch } from './branch';
 import { MigratedDataFactory } from './branch/migrated-data';
 import { ensureConfigMigrationPr } from './pr';
@@ -8,6 +9,12 @@ export async function configMigration(
   branchList: string[],
 ): Promise<void> {
   if (config.configMigration) {
+    if (config.mode === 'silent') {
+      logger.debug(
+        'Config migration issues are not created, updated or closed when mode=silent',
+      );
+      return;
+    }
     const migratedConfigData = await MigratedDataFactory.getAsync();
     const migrationBranch = await checkConfigMigrationBranch(
       config,
diff --git a/lib/workers/repository/dependency-dashboard.spec.ts b/lib/workers/repository/dependency-dashboard.spec.ts
index 8b12cbfba9e73d95c8c1e039f398f2f43e62c924..54be528026093104117ced934f8dfe235b2c3019 100644
--- a/lib/workers/repository/dependency-dashboard.spec.ts
+++ b/lib/workers/repository/dependency-dashboard.spec.ts
@@ -241,6 +241,17 @@ describe('workers/repository/dependency-dashboard', () => {
       logger.getProblems.mockReturnValue([]);
     });
 
+    it('does nothing if mode=silent', async () => {
+      const branches: BranchConfig[] = [];
+      config.mode = 'silent';
+      await dependencyDashboard.ensureDependencyDashboard(config, branches);
+      expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0);
+      expect(platform.ensureIssue).toHaveBeenCalledTimes(0);
+
+      // same with dry run
+      await dryRun(branches, platform, 0, 0);
+    });
+
     it('do nothing if dependencyDashboard is disabled', async () => {
       const branches: BranchConfig[] = [];
       await dependencyDashboard.ensureDependencyDashboard(config, branches);
diff --git a/lib/workers/repository/dependency-dashboard.ts b/lib/workers/repository/dependency-dashboard.ts
index 92a9bbf6dc4acae7b01e1bc19634b8262502d62e..17d895c35b22ad7465d2c20ec1c537c1278fb28f 100644
--- a/lib/workers/repository/dependency-dashboard.ts
+++ b/lib/workers/repository/dependency-dashboard.ts
@@ -181,6 +181,12 @@ export async function ensureDependencyDashboard(
   packageFiles: Record<string, PackageFile[]> = {},
 ): Promise<void> {
   logger.debug('ensureDependencyDashboard()');
+  if (config.mode === 'silent') {
+    logger.debug(
+      'Dependency Dashboard issue is not created, updated or closed when mode=silent',
+    );
+    return;
+  }
   // legacy/migrated issue
   const reuseTitle = 'Update Dependencies (Renovate Bot)';
   const branches = allBranches.filter(
diff --git a/lib/workers/repository/error-config.spec.ts b/lib/workers/repository/error-config.spec.ts
index f08d0fad23d6e34c516065b7282c092779ccb844..f3dc7fb6f05dfdc12721a48d0671f230c2a8b4c9 100644
--- a/lib/workers/repository/error-config.spec.ts
+++ b/lib/workers/repository/error-config.spec.ts
@@ -29,6 +29,20 @@ describe('workers/repository/error-config', () => {
       GlobalConfig.reset();
     });
 
+    it('returns if mode is silent', async () => {
+      config.mode = 'silent';
+
+      const res = await raiseConfigWarningIssue(
+        config,
+        new Error(CONFIG_VALIDATION),
+      );
+
+      expect(res).toBeUndefined();
+      expect(logger.debug).toHaveBeenCalledWith(
+        'Config warning issues are not created, updated or closed when mode=silent',
+      );
+    });
+
     it('creates issues', async () => {
       const expectedBody = `There are missing credentials for the authentication-required feature. As a precaution, Renovate will pause PRs until it is resolved.
 
diff --git a/lib/workers/repository/error-config.ts b/lib/workers/repository/error-config.ts
index 3654ef90db65bbb72264f220601cc749ce15186a..fb5c735bde65a4072471cbe4355c048c955ca37c 100644
--- a/lib/workers/repository/error-config.ts
+++ b/lib/workers/repository/error-config.ts
@@ -33,6 +33,12 @@ async function raiseWarningIssue(
   initialBody: string,
   error: Error,
 ): Promise<void> {
+  if (config.mode === 'silent') {
+    logger.debug(
+      `Config warning issues are not created, updated or closed when mode=silent`,
+    );
+    return;
+  }
   let body = initialBody;
   if (error.validationSource) {
     body += `Location: \`${error.validationSource}\`\n`;
diff --git a/lib/workers/repository/init/index.spec.ts b/lib/workers/repository/init/index.spec.ts
index 1927900bcd683f062d1289261a725dd8e235e2f5..4f96f0181189050898b0358915922df521195835 100644
--- a/lib/workers/repository/init/index.spec.ts
+++ b/lib/workers/repository/init/index.spec.ts
@@ -38,7 +38,7 @@ describe('workers/repository/init/index', () => {
     it('runs', async () => {
       apis.initApis.mockResolvedValue(partial<_apis.WorkerPlatformConfig>());
       onboarding.checkOnboardingBranch.mockResolvedValueOnce({});
-      config.getRepoConfig.mockResolvedValueOnce({});
+      config.getRepoConfig.mockResolvedValueOnce({ mode: 'silent' });
       merge.mergeRenovateConfig.mockResolvedValueOnce({});
       secrets.applySecretsToConfig.mockReturnValueOnce(
         partial<RenovateConfig>(),
diff --git a/lib/workers/repository/init/index.ts b/lib/workers/repository/init/index.ts
index f4d3ac84dbeab04fe948007b50661bf57b7cf94d..8f21f936db2e407a50de721e5e7c990493adfe40 100644
--- a/lib/workers/repository/init/index.ts
+++ b/lib/workers/repository/init/index.ts
@@ -54,6 +54,11 @@ export async function initRepo(
   await initializeCaches(config as WorkerPlatformConfig);
   config = await getRepoConfig(config);
   setRepositoryLogLevelRemaps(config.logLevelRemap);
+  if (config.mode === 'silent') {
+    logger.info(
+      'Repository is running with mode=silent and will not make Issues or PRs by default',
+    );
+  }
   checkIfConfigured(config);
   warnOnUnsupportedOptions(config);
   config = applySecretsToConfig(config);
diff --git a/lib/workers/repository/init/merge.spec.ts b/lib/workers/repository/init/merge.spec.ts
index 51fb01fe249936300054deafbc9cc208c7fc3b6b..7cc7a92a057cecdfcc156aa9ca793dd11fa13f1a 100644
--- a/lib/workers/repository/init/merge.spec.ts
+++ b/lib/workers/repository/init/merge.spec.ts
@@ -280,6 +280,18 @@ describe('workers/repository/init/merge', () => {
       });
     });
 
+    it('uses onboarding config if silent', async () => {
+      scm.getFileList.mockResolvedValue([]);
+      migrateAndValidate.migrateAndValidate.mockResolvedValue({
+        warnings: [],
+        errors: [],
+      });
+      config.mode = 'silent';
+      config.repository = 'some-org/some-repo';
+      const res = await mergeRenovateConfig(config);
+      expect(res).toBeDefined();
+    });
+
     it('throws error if misconfigured', async () => {
       scm.getFileList.mockResolvedValue(['package.json', '.renovaterc.json']);
       fs.readLocalFile.mockResolvedValue('{}');
diff --git a/lib/workers/repository/init/merge.ts b/lib/workers/repository/init/merge.ts
index c8ced5681b9e3a8015c3b20b752767cff858ca81..dff41b852fafe7e0735c202b3c0ed70bbc25d6ec 100644
--- a/lib/workers/repository/init/merge.ts
+++ b/lib/workers/repository/init/merge.ts
@@ -23,6 +23,8 @@ import { readLocalFile } from '../../../util/fs';
 import * as hostRules from '../../../util/host-rules';
 import * as queue from '../../../util/http/queue';
 import * as throttle from '../../../util/http/throttle';
+import { getOnboardingConfig } from '../onboarding/branch/config';
+import { getDefaultConfigFileName } from '../onboarding/branch/create';
 import {
   getOnboardingConfigFromCache,
   getOnboardingFileNameFromCache,
@@ -58,7 +60,7 @@ export async function detectConfigFile(): Promise<string | null> {
 export async function detectRepoFileConfig(): Promise<RepoFileConfig> {
   const cache = getCache();
   let { configFileName } = cache;
-  if (configFileName) {
+  if (is.nonEmptyString(configFileName)) {
     let configFileRaw: string | null;
     try {
       configFileRaw = await platform.getRawFile(configFileName);
@@ -89,6 +91,7 @@ export async function detectRepoFileConfig(): Promise<RepoFileConfig> {
 
   if (!configFileName) {
     logger.debug('No renovate config file found');
+    cache.configFileName = '';
     return {};
   }
   cache.configFileName = configFileName;
@@ -171,6 +174,16 @@ export async function mergeRenovateConfig(
   if (config.requireConfig !== 'ignored') {
     repoConfig = await detectRepoFileConfig();
   }
+  if (!repoConfig.configFileParsed && config.mode === 'silent') {
+    logger.debug(
+      'When mode=silent and repo has no config file, we use the onboarding config as repo config',
+    );
+    const configFileName = getDefaultConfigFileName(config);
+    repoConfig = {
+      configFileName,
+      configFileParsed: await getOnboardingConfig(config),
+    };
+  }
   const configFileParsed = repoConfig?.configFileParsed || {};
   if (is.nonEmptyArray(returnConfig.extends)) {
     configFileParsed.extends = [
diff --git a/lib/workers/repository/onboarding/branch/check.spec.ts b/lib/workers/repository/onboarding/branch/check.spec.ts
index d4130c9a1db812675dfc1fdd8c6d9d2e532e6308..1761e95932037c39aff2503671076d1ee1447469 100644
--- a/lib/workers/repository/onboarding/branch/check.spec.ts
+++ b/lib/workers/repository/onboarding/branch/check.spec.ts
@@ -25,6 +25,11 @@ describe('workers/repository/onboarding/branch/check', () => {
     onboarding: true,
   });
 
+  it('returns true if in silent mode', async () => {
+    const res = await isOnboarded({ ...config, mode: 'silent' });
+    expect(res).toBeTrue();
+  });
+
   it('skips normal onboarding check if onboardingCache is valid', async () => {
     cache.getCache.mockReturnValueOnce({
       onboardingBranchCache: {
diff --git a/lib/workers/repository/onboarding/branch/check.ts b/lib/workers/repository/onboarding/branch/check.ts
index c3773f789b9dbb0764b376c359eb5f05f6fc9238..e4699ac03cb181896947fcc244b012cf34aab7e0 100644
--- a/lib/workers/repository/onboarding/branch/check.ts
+++ b/lib/workers/repository/onboarding/branch/check.ts
@@ -63,6 +63,12 @@ export async function isOnboarded(config: RenovateConfig): Promise<boolean> {
   logger.debug('isOnboarded()');
   const title = `Action required: Add a Renovate config`;
 
+  // Repo is onboarded if in silent mode
+  if (config.mode === 'silent') {
+    logger.debug('Silent mode enabled so repo is considered onboarded');
+    return true;
+  }
+
   // Repo is onboarded if global config is bypassing onboarding and does not require a
   // configuration file.
   // The repo is considered "not onboarded" if:
diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts
index 708d82cc33f1674827f5e03605a9f2a84349f05e..8070cfed2a5831a759054f0b8fac6acb997e6de1 100644
--- a/lib/workers/repository/onboarding/branch/create.ts
+++ b/lib/workers/repository/onboarding/branch/create.ts
@@ -9,22 +9,27 @@ import { getOnboardingConfigContents } from './config';
 
 const defaultConfigFile = configFileNames[0];
 
-export async function createOnboardingBranch(
+export function getDefaultConfigFileName(
   config: Partial<RenovateConfig>,
-): Promise<string | null> {
+): string {
   // TODO #22198
-  const configFile = configFileNames.includes(config.onboardingConfigFileName!)
-    ? config.onboardingConfigFileName
+  return configFileNames.includes(config.onboardingConfigFileName!)
+    ? config.onboardingConfigFileName!
     : defaultConfigFile;
+}
 
+export async function createOnboardingBranch(
+  config: Partial<RenovateConfig>,
+): Promise<string | null> {
   logger.debug('createOnboardingBranch()');
+  const configFile = getDefaultConfigFileName(config);
   // TODO #22198
-  const contents = await getOnboardingConfigContents(config, configFile!);
+  const contents = await getOnboardingConfigContents(config, configFile);
   logger.debug('Creating onboarding branch');
 
   const commitMessageFactory = new OnboardingCommitMessageFactory(
     config,
-    configFile!,
+    configFile,
   );
   let commitMessage = commitMessageFactory.create().toString();
 
@@ -51,7 +56,7 @@ export async function createOnboardingBranch(
       {
         type: 'addition',
         // TODO #22198
-        path: configFile!,
+        path: configFile,
         contents,
       },
     ],
diff --git a/lib/workers/repository/process/deprecated.spec.ts b/lib/workers/repository/process/deprecated.spec.ts
index a0981c7177202eb521e27e628949018444250b98..1f15b73f6db1fed670cb30b6af3f46c5f5487c37 100644
--- a/lib/workers/repository/process/deprecated.spec.ts
+++ b/lib/workers/repository/process/deprecated.spec.ts
@@ -16,6 +16,15 @@ describe('workers/repository/process/deprecated', () => {
       await expect(raiseDeprecationWarnings(config, {})).resolves.not.toThrow();
     });
 
+    it('returns if in silent mode', async () => {
+      const config: RenovateConfig = {
+        repoIsOnboarded: true,
+        suppressNotifications: [],
+        mode: 'silent',
+      };
+      await expect(raiseDeprecationWarnings(config, {})).resolves.not.toThrow();
+    });
+
     it('raises deprecation warnings', async () => {
       const config: RenovateConfig = {
         repoIsOnboarded: true,
diff --git a/lib/workers/repository/process/deprecated.ts b/lib/workers/repository/process/deprecated.ts
index 43c699cbf43386a2bd0154ecce5357957b623dd0..0a27d71b53157227744995f93cb5f9678041a34c 100644
--- a/lib/workers/repository/process/deprecated.ts
+++ b/lib/workers/repository/process/deprecated.ts
@@ -15,6 +15,12 @@ export async function raiseDeprecationWarnings(
   if (config.suppressNotifications?.includes('deprecationWarningIssues')) {
     return;
   }
+  if (config.mode === 'silent') {
+    logger.debug(
+      `Deprecation warning issues are not created, updated or closed when mode=silent`,
+    );
+    return;
+  }
   for (const [manager, files] of Object.entries(packageFiles)) {
     const deprecatedPackages: Record<
       string,
diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts
index e5f17516c6bb98b56823a8886ba60c29e8714c80..e71f943e5f72496d67fda840601ecb74aa899527 100644
--- a/lib/workers/repository/update/branch/index.spec.ts
+++ b/lib/workers/repository/update/branch/index.spec.ts
@@ -543,6 +543,42 @@ describe('workers/repository/update/branch/index', () => {
       });
     });
 
+    it('returns if branch does not exist and in silent mode', async () => {
+      getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
+        ...updatedPackageFiles,
+      });
+      npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
+        artifactErrors: [],
+        updatedArtifacts: [],
+      });
+      scm.branchExists.mockResolvedValue(false);
+      GlobalConfig.set({ ...adminConfig });
+      config.mode = 'silent';
+      expect(await branchWorker.processBranch(config)).toEqual({
+        branchExists: false,
+        prNo: undefined,
+        result: 'needs-approval',
+      });
+    });
+
+    it('returns if branch needs dependencyDashboardApproval', async () => {
+      getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
+        ...updatedPackageFiles,
+      });
+      npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
+        artifactErrors: [],
+        updatedArtifacts: [],
+      });
+      scm.branchExists.mockResolvedValue(false);
+      GlobalConfig.set({ ...adminConfig });
+      config.dependencyDashboardApproval = true;
+      expect(await branchWorker.processBranch(config)).toEqual({
+        branchExists: false,
+        prNo: undefined,
+        result: 'needs-approval',
+      });
+    });
+
     it('returns if pr creation limit exceeded and branch exists', async () => {
       getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
         ...updatedPackageFiles,
diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts
index 762d55622c86d46d83ab2cfefc51e7f4f89d1160..08a1fabd2bea275d68088a4b6718edf0d4f8022e 100644
--- a/lib/workers/repository/update/branch/index.ts
+++ b/lib/workers/repository/update/branch/index.ts
@@ -183,12 +183,21 @@ export async function processBranch(
         result: 'pending',
       };
     }
-    // istanbul ignore if
-    if (!branchExists && config.dependencyDashboardApproval) {
-      if (dependencyDashboardCheck) {
-        logger.debug(`Branch ${config.branchName} is approved for creation`);
-      } else {
-        logger.debug(`Branch ${config.branchName} needs approval`);
+    if (!branchExists) {
+      if (config.mode === 'silent' && !dependencyDashboardCheck) {
+        logger.debug(
+          `Branch ${config.branchName} creation is disabled because mode=silent`,
+        );
+        return {
+          branchExists,
+          prNo: branchPr?.number,
+          result: 'needs-approval',
+        };
+      }
+      if (config.dependencyDashboardApproval && !dependencyDashboardCheck) {
+        logger.debug(
+          `Branch ${config.branchName} creation is disabled because dependencyDashboardApproval=true`,
+        );
         return {
           branchExists,
           prNo: branchPr?.number,