diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 70691a6a6b2ca4d92ef5d664bec42891c4aa425e..4056537b36949ab8856bced3e260c45c4f4576d2 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -1908,6 +1908,7 @@ const options: RenovateOptions[] = [
     default: [],
     allowedValues: [
       'bundlerConservative',
+      'helmUpdateSubChartArchives',
       'gomodMassage',
       'gomodUpdateImportPaths',
       'gomodTidy',
diff --git a/lib/modules/manager/helmv3/artifacts.spec.ts b/lib/modules/manager/helmv3/artifacts.spec.ts
index dbffad7a061cc80ffe249679075706cc353045bd..f3c223931622f606a4edda94325cb95d0ce698dd 100644
--- a/lib/modules/manager/helmv3/artifacts.spec.ts
+++ b/lib/modules/manager/helmv3/artifacts.spec.ts
@@ -1,10 +1,11 @@
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../../test/exec-util';
 import { Fixtures } from '../../../../test/fixtures';
-import { env, fs, mocked } from '../../../../test/util';
+import { env, fs, git, mocked } from '../../../../test/util';
 import { GlobalConfig } from '../../../config/global';
 import type { RepoGlobalConfig } from '../../../config/types';
 import * as docker from '../../../util/exec/docker';
+import type { StatusResult } from '../../../util/git/types';
 import * as hostRules from '../../../util/host-rules';
 import * as _datasource from '../../datasource';
 import type { UpdateArtifactsConfig } from '../types';
@@ -14,6 +15,7 @@ jest.mock('../../datasource');
 jest.mock('../../../util/exec/env');
 jest.mock('../../../util/http');
 jest.mock('../../../util/fs');
+jest.mock('../../../util/git');
 
 const datasource = mocked(_datasource);
 
@@ -211,6 +213,246 @@ describe('modules/manager/helmv3/artifacts', () => {
     ]);
   });
 
+  it('add sub chart artifacts to file list if Chart.lock exists', async () => {
+    fs.readLocalFile.mockResolvedValueOnce(ociLockFile1 as never);
+    fs.getSiblingFileName.mockReturnValueOnce('Chart.lock');
+    const execSnapshots = mockExecAll();
+    fs.readLocalFile.mockResolvedValueOnce(ociLockFile2 as never);
+    fs.privateCacheDir.mockReturnValue(
+      '/tmp/renovate/cache/__renovate-private-cache'
+    );
+    fs.getParentDir.mockReturnValue('');
+
+    // sub chart artifacts
+    fs.getSiblingFileName.mockReturnValueOnce('charts');
+    git.getRepoStatus.mockResolvedValueOnce({
+      not_added: ['charts/example-1.9.2.tgz'],
+      deleted: ['charts/example-1.6.2.tgz'],
+    } as StatusResult);
+    const updatedDeps = [{ depName: 'dep1' }];
+    const test = await helmv3.updateArtifacts({
+      packageFileName: 'Chart.yaml',
+      updatedDeps,
+      newPackageFileContent: chartFile,
+      config: {
+        postUpdateOptions: ['helmUpdateSubChartArchives'],
+        ...config,
+      },
+    });
+    expect(test).toEqual([
+      {
+        file: {
+          type: 'addition',
+          path: 'Chart.lock',
+          contents: ociLockFile2,
+        },
+      },
+      {
+        file: {
+          type: 'addition',
+          path: 'charts/example-1.9.2.tgz',
+          contents: undefined,
+        },
+      },
+      {
+        file: {
+          type: 'deletion',
+          path: 'charts/example-1.6.2.tgz',
+        },
+      },
+    ]);
+    expect(execSnapshots).toMatchObject([
+      {
+        cmd: 'helm repo add repo-test --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories https://gitlab.com/api/v4/projects/xxxxxxx/packages/helm/stable',
+      },
+      {
+        cmd: "helm dependency update --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories ''",
+      },
+    ]);
+  });
+
+  it('add sub chart artifacts to file list if Chart.lock is missing', async () => {
+    fs.readLocalFile.mockResolvedValueOnce(null);
+    fs.getSiblingFileName.mockReturnValueOnce('Chart.lock');
+    const execSnapshots = mockExecAll();
+    fs.privateCacheDir.mockReturnValue(
+      '/tmp/renovate/cache/__renovate-private-cache'
+    );
+    fs.getParentDir.mockReturnValue('');
+
+    // sub chart artifacts
+    fs.getSiblingFileName.mockReturnValueOnce('charts');
+    git.getRepoStatus.mockResolvedValueOnce({
+      not_added: ['charts/example-1.9.2.tgz'],
+      deleted: ['charts/example-1.6.2.tgz'],
+    } as StatusResult);
+    const updatedDeps = [{ depName: 'dep1' }];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: chartFile,
+        config: {
+          postUpdateOptions: ['helmUpdateSubChartArchives'],
+          ...config,
+        },
+      })
+    ).toEqual([
+      {
+        file: {
+          type: 'addition',
+          path: 'charts/example-1.9.2.tgz',
+          contents: undefined,
+        },
+      },
+      {
+        file: {
+          type: 'deletion',
+          path: 'charts/example-1.6.2.tgz',
+        },
+      },
+    ]);
+    expect(execSnapshots).toMatchObject([
+      {
+        cmd: 'helm repo add repo-test --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories https://gitlab.com/api/v4/projects/xxxxxxx/packages/helm/stable',
+      },
+      {
+        cmd: "helm dependency update --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories ''",
+      },
+    ]);
+  });
+
+  it('add sub chart artifacts without old archives', async () => {
+    fs.readLocalFile.mockResolvedValueOnce(null);
+    fs.getSiblingFileName.mockReturnValueOnce('Chart.lock');
+    const execSnapshots = mockExecAll();
+    fs.privateCacheDir.mockReturnValue(
+      '/tmp/renovate/cache/__renovate-private-cache'
+    );
+    fs.getParentDir.mockReturnValue('');
+
+    // sub chart artifacts
+    fs.getSiblingFileName.mockReturnValueOnce('charts');
+    git.getRepoStatus.mockResolvedValueOnce({
+      not_added: ['charts/example-1.9.2.tgz'],
+    } as StatusResult);
+    const updatedDeps = [{ depName: 'dep1' }];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: chartFile,
+        config: {
+          postUpdateOptions: ['helmUpdateSubChartArchives'],
+          ...config,
+        },
+      })
+    ).toEqual([
+      {
+        file: {
+          type: 'addition',
+          path: 'charts/example-1.9.2.tgz',
+          contents: undefined,
+        },
+      },
+    ]);
+    expect(execSnapshots).toMatchObject([
+      {
+        cmd: 'helm repo add repo-test --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories https://gitlab.com/api/v4/projects/xxxxxxx/packages/helm/stable',
+      },
+      {
+        cmd: "helm dependency update --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories ''",
+      },
+    ]);
+  });
+
+  it('add sub chart artifacts and ignore files outside of the chart folder', async () => {
+    fs.readLocalFile.mockResolvedValueOnce(null);
+    fs.getSiblingFileName.mockReturnValueOnce('Chart.lock');
+    const execSnapshots = mockExecAll();
+    fs.privateCacheDir.mockReturnValue(
+      '/tmp/renovate/cache/__renovate-private-cache'
+    );
+    fs.getParentDir.mockReturnValue('');
+
+    // sub chart artifacts
+    fs.getSiblingFileName.mockReturnValueOnce('charts');
+    git.getRepoStatus.mockResolvedValueOnce({
+      not_added: ['charts/example-1.9.2.tgz', 'exampleFile'],
+      deleted: ['charts/example-1.6.2.tgz', 'aFolder/otherFile'],
+    } as StatusResult);
+    const updatedDeps = [{ depName: 'dep1' }];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: chartFile,
+        config: {
+          postUpdateOptions: ['helmUpdateSubChartArchives'],
+          ...config,
+        },
+      })
+    ).toEqual([
+      {
+        file: {
+          type: 'addition',
+          path: 'charts/example-1.9.2.tgz',
+          contents: undefined,
+        },
+      },
+      {
+        file: {
+          type: 'deletion',
+          path: 'charts/example-1.6.2.tgz',
+        },
+      },
+    ]);
+    expect(execSnapshots).toMatchObject([
+      {
+        cmd: 'helm repo add repo-test --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories https://gitlab.com/api/v4/projects/xxxxxxx/packages/helm/stable',
+      },
+      {
+        cmd: "helm dependency update --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories ''",
+      },
+    ]);
+  });
+
+  it('skip artifacts which are not lock files or in the chart folder', async () => {
+    fs.readLocalFile.mockResolvedValueOnce(null);
+    fs.getSiblingFileName.mockReturnValueOnce('Chart.lock');
+    const execSnapshots = mockExecAll();
+    fs.privateCacheDir.mockReturnValue(
+      '/tmp/renovate/cache/__renovate-private-cache'
+    );
+    fs.getParentDir.mockReturnValue('');
+
+    // sub chart artifacts
+    fs.getSiblingFileName.mockReturnValueOnce('charts');
+    git.getRepoStatus.mockResolvedValueOnce({
+      modified: ['example/example.tgz'],
+    } as StatusResult);
+    const updatedDeps = [{ depName: 'dep1' }];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: chartFile,
+        config: {
+          postUpdateOptions: ['helmUpdateSubChartArchives'],
+          ...config,
+        },
+      })
+    ).toBeNull();
+    expect(execSnapshots).toMatchObject([
+      {
+        cmd: 'helm repo add repo-test --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories https://gitlab.com/api/v4/projects/xxxxxxx/packages/helm/stable',
+      },
+      {
+        cmd: "helm dependency update --registry-config /tmp/renovate/cache/__renovate-private-cache/registry.json --repository-config /tmp/renovate/cache/__renovate-private-cache/repositories.yaml --repository-cache /tmp/renovate/cache/__renovate-private-cache/repositories ''",
+      },
+    ]);
+  });
+
   it('sets repositories from registryAliases', async () => {
     fs.privateCacheDir.mockReturnValue(
       '/tmp/renovate/cache/__renovate-private-cache'
diff --git a/lib/modules/manager/helmv3/artifacts.ts b/lib/modules/manager/helmv3/artifacts.ts
index 480dfb55123ecbd04340ec21875697986766dbca..c486ad423ba1d09b6e3277a100aedb432743c2d2 100644
--- a/lib/modules/manager/helmv3/artifacts.ts
+++ b/lib/modules/manager/helmv3/artifacts.ts
@@ -1,3 +1,4 @@
+import is from '@sindresorhus/is';
 import yaml from 'js-yaml';
 import { quote } from 'shlex';
 import upath from 'upath';
@@ -12,6 +13,7 @@ import {
   readLocalFile,
   writeLocalFile,
 } from '../../../util/fs';
+import { getRepoStatus } from '../../../util/git';
 import * as hostRules from '../../../util/host-rules';
 import { DockerDatasource } from '../../datasource/docker';
 import { HelmDatasource } from '../../datasource/helm';
@@ -20,6 +22,7 @@ import type { ChartDefinition, Repository, RepositoryRule } from './types';
 import {
   aliasRecordToRepositories,
   getRepositories,
+  isFileInDir,
   isOCIRegistry,
 } from './utils';
 
@@ -110,6 +113,9 @@ export async function updateArtifacts({
   logger.debug(`helmv3.updateArtifacts(${packageFileName})`);
 
   const isLockFileMaintenance = config.updateType === 'lockFileMaintenance';
+  const isUpdateOptionAddChartArchives = config.postUpdateOptions?.includes(
+    'helmUpdateSubChartArchives'
+  );
 
   if (
     !isLockFileMaintenance &&
@@ -121,14 +127,16 @@ export async function updateArtifacts({
 
   const lockFileName = getSiblingFileName(packageFileName, 'Chart.lock');
   const existingLockFileContent = await readLocalFile(lockFileName, 'utf8');
-  if (!existingLockFileContent) {
+  if (!existingLockFileContent && !isUpdateOptionAddChartArchives) {
     logger.debug('No Chart.lock found');
     return null;
   }
   try {
     // get repositories and registries defined in the package file
     const packages = yaml.load(newPackageFileContent) as ChartDefinition; //TODO #9610
-    const locks = yaml.load(existingLockFileContent) as ChartDefinition; //TODO #9610
+    const locks = existingLockFileContent
+      ? (yaml.load(existingLockFileContent) as ChartDefinition)
+      : { dependencies: [] }; //TODO #9610
 
     const chartDefinitions: ChartDefinition[] = [];
     // prioritize registryAlias naming for Helm repositories
@@ -142,7 +150,7 @@ export async function updateArtifacts({
     const repositories = getRepositories(chartDefinitions);
 
     await writeLocalFile(packageFileName, newPackageFileContent);
-    logger.debug('Updating ' + lockFileName);
+    logger.debug('Updating Helm artifacts');
     const helmToolConstraint: ToolConstraint = {
       toolName: 'helm',
       constraint: config.constraints?.helm,
@@ -158,21 +166,62 @@ export async function updateArtifacts({
       toolConstraints: [helmToolConstraint],
     };
     await helmCommands(execOptions, packageFileName, repositories);
-    logger.debug('Returning updated Chart.lock');
-    const newHelmLockContent = await readLocalFile(lockFileName, 'utf8');
-    if (existingLockFileContent === newHelmLockContent) {
-      logger.debug('Chart.lock is unchanged');
-      return null;
+    logger.debug('Returning updated Helm artifacts');
+
+    const fileChanges: UpdateArtifactsResult[] = [];
+
+    if (is.truthy(existingLockFileContent)) {
+      const newHelmLockContent = await readLocalFile(lockFileName, 'utf8');
+      const isLockFileChanged = existingLockFileContent !== newHelmLockContent;
+      if (isLockFileChanged) {
+        fileChanges.push({
+          file: {
+            type: 'addition',
+            path: lockFileName,
+            contents: newHelmLockContent,
+          },
+        });
+      } else {
+        logger.debug('Chart.lock is unchanged');
+      }
     }
-    return [
-      {
-        file: {
-          type: 'addition',
-          path: lockFileName,
-          contents: newHelmLockContent,
-        },
-      },
-    ];
+
+    // add modified helm chart archives to artifacts
+    if (is.truthy(isUpdateOptionAddChartArchives)) {
+      const chartsPath = getSiblingFileName(packageFileName, 'charts');
+      const status = await getRepoStatus();
+      const chartsAddition = status.not_added ?? [];
+      const chartsDeletion = status.deleted ?? [];
+
+      for (const file of chartsAddition) {
+        // only add artifacts in the chart sub path
+        if (!isFileInDir(chartsPath, file)) {
+          continue;
+        }
+        fileChanges.push({
+          file: {
+            type: 'addition',
+            path: file,
+            contents: await readLocalFile(file),
+          },
+        });
+      }
+
+      for (const file of chartsDeletion) {
+        // only add artifacts in the chart sub path
+        if (!isFileInDir(chartsPath, file)) {
+          continue;
+        }
+        fileChanges.push({
+          file: {
+            type: 'deletion',
+            path: file,
+          },
+        });
+      }
+    }
+
+    return fileChanges.length > 0 ? fileChanges : null;
   } catch (err) {
     // istanbul ignore if
     if (err.message === TEMPORARY_ERROR) {
diff --git a/lib/modules/manager/helmv3/readme.md b/lib/modules/manager/helmv3/readme.md
index 259998d82abe2d40fd1519fcc5994b9c3dbc5e05..5e94f2c71387a5f7ce970fa2dec256158afb7b76 100644
--- a/lib/modules/manager/helmv3/readme.md
+++ b/lib/modules/manager/helmv3/readme.md
@@ -64,3 +64,14 @@ For this you use a custom `hostRules` array.
   ],
 }
 ```
+
+### Subchart archives
+
+To get updates for subchart archives put `helmUpdateSubChartArchives` in your `postUpdateOptions` configuration.
+Renovate now updates archives in the `/chart` folder.
+
+```json
+{
+  "postUpdateOptions": ["helmUpdateSubChartArchives"]
+}
+```
diff --git a/lib/modules/manager/helmv3/utils.ts b/lib/modules/manager/helmv3/utils.ts
index e002d6c92bef3ad864e3879dacae1503d283307c..be803f67304ee2f5c4c867fdab56341ed8f45b1e 100644
--- a/lib/modules/manager/helmv3/utils.ts
+++ b/lib/modules/manager/helmv3/utils.ts
@@ -1,3 +1,4 @@
+import upath from 'upath';
 import { logger } from '../../../logger';
 import { DockerDatasource } from '../../datasource/docker';
 import type { PackageDependency } from '../types';
@@ -95,3 +96,7 @@ export function aliasRecordToRepositories(
     };
   });
 }
+
+export function isFileInDir(dir: string, file: string): boolean {
+  return upath.dirname(file) === dir;
+}