diff --git a/cmd/helm/dependency.go b/cmd/helm/dependency.go
index 2cc4c5045..3de3ef014 100644
--- a/cmd/helm/dependency.go
+++ b/cmd/helm/dependency.go
@@ -82,7 +82,7 @@ the contents of a chart.
 This will produce an error if the chart cannot be loaded.
 `
 
-func newDependencyCmd(out io.Writer) *cobra.Command {
+func newDependencyCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:     "dependency update|build|list",
 		Aliases: []string{"dep", "dependencies"},
@@ -92,7 +92,7 @@ func newDependencyCmd(out io.Writer) *cobra.Command {
 	}
 
 	cmd.AddCommand(newDependencyListCmd(out))
-	cmd.AddCommand(newDependencyUpdateCmd(out))
+	cmd.AddCommand(newDependencyUpdateCmd(cfg, out))
 	cmd.AddCommand(newDependencyBuildCmd(out))
 
 	return cmd
diff --git a/cmd/helm/dependency_update.go b/cmd/helm/dependency_update.go
index 9855afb92..ad0188f17 100644
--- a/cmd/helm/dependency_update.go
+++ b/cmd/helm/dependency_update.go
@@ -43,7 +43,7 @@ in the Chart.yaml file, but (b) at the wrong version.
 `
 
 // newDependencyUpdateCmd creates a new dependency update command.
-func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
+func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 	client := action.NewDependency()
 
 	cmd := &cobra.Command{
@@ -63,6 +63,7 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
 				Keyring:          client.Keyring,
 				SkipUpdate:       client.SkipRefresh,
 				Getters:          getter.All(settings),
+				RegistryClient:   cfg.RegistryClient,
 				RepositoryConfig: settings.RepositoryConfig,
 				RepositoryCache:  settings.RepositoryCache,
 				Debug:            settings.Debug,
diff --git a/cmd/helm/dependency_update_test.go b/cmd/helm/dependency_update_test.go
index bf27c7b6c..ce93e5c41 100644
--- a/cmd/helm/dependency_update_test.go
+++ b/cmd/helm/dependency_update_test.go
@@ -40,6 +40,23 @@ func TestDependencyUpdateCmd(t *testing.T) {
 	defer srv.Stop()
 	t.Logf("Listening on directory %s", srv.Root())
 
+	ociSrv, err := repotest.NewOCIServer(t, srv.Root())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ociChartName := "oci-depending-chart"
+	c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL)
+	if err := chartutil.SaveDir(c, ociSrv.Dir); err != nil {
+		t.Fatal(err)
+	}
+	ociSrv.Run(t, repotest.WithDependingChart(c))
+
+	err = os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
+	if err != nil {
+		t.Fatal("failed to set environment variable enabling OCI support")
+	}
+
 	if err := srv.LinkIndices(); err != nil {
 		t.Fatal(err)
 	}
@@ -115,6 +132,22 @@ func TestDependencyUpdateCmd(t *testing.T) {
 	if _, err := os.Stat(unexpected); err == nil {
 		t.Fatalf("Unexpected %q", unexpected)
 	}
+
+	// test for OCI charts
+	cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json",
+		dir(ociChartName),
+		dir("repositories.yaml"),
+		dir(),
+		dir())
+	_, out, err = executeActionCommand(cmd)
+	if err != nil {
+		t.Logf("Output: %s", out)
+		t.Fatal(err)
+	}
+	expect = dir(ociChartName, "charts/oci-dependent-chart")
+	if _, err := os.Stat(expect); err != nil {
+		t.Fatal(err)
+	}
 }
 
 func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
@@ -193,6 +226,19 @@ func createTestingMetadata(name, baseURL string) *chart.Chart {
 	}
 }
 
+func createTestingMetadataForOCI(name, registryURL string) *chart.Chart {
+	return &chart.Chart{
+		Metadata: &chart.Metadata{
+			APIVersion: chart.APIVersionV2,
+			Name:       name,
+			Version:    "1.2.3",
+			Dependencies: []*chart.Dependency{
+				{Name: "oci-dependent-chart", Version: "0.1.0", Repository: fmt.Sprintf("oci://%s/u/ocitestuser", registryURL)},
+			},
+		},
+	}
+}
+
 // createTestingChart creates a basic chart that depends on reqtest-0.1.0
 //
 // The baseURL can be used to point to a particular repository server.
diff --git a/cmd/helm/pull.go b/cmd/helm/pull.go
index 3f62bf0c7..ded0609e5 100644
--- a/cmd/helm/pull.go
+++ b/cmd/helm/pull.go
@@ -20,6 +20,7 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"strings"
 
 	"github.com/spf13/cobra"
 
@@ -42,8 +43,8 @@ file, and MUST pass the verification process. Failure in any part of this will
 result in an error, and the chart will not be saved locally.
 `
 
-func newPullCmd(out io.Writer) *cobra.Command {
-	client := action.NewPull()
+func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
+	client := action.NewPull(cfg)
 
 	cmd := &cobra.Command{
 		Use:     "pull [chart URL | repo/chartname] [...]",
@@ -64,6 +65,14 @@ func newPullCmd(out io.Writer) *cobra.Command {
 				client.Version = ">0.0.0-0"
 			}
 
+			if strings.HasPrefix(args[0], "oci://") {
+				if !FeatureGateOCI.IsEnabled() {
+					return FeatureGateOCI.Error()
+				}
+
+				client.OCI = true
+			}
+
 			for i := 0; i < len(args); i++ {
 				output, err := client.Run(args[i])
 				if err != nil {
diff --git a/cmd/helm/pull_test.go b/cmd/helm/pull_test.go
index 1d439e873..51cdfdfa4 100644
--- a/cmd/helm/pull_test.go
+++ b/cmd/helm/pull_test.go
@@ -32,6 +32,13 @@ func TestPullCmd(t *testing.T) {
 	}
 	defer srv.Stop()
 
+	os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
+	ociSrv, err := repotest.NewOCIServer(t, srv.Root())
+	if err != nil {
+		t.Fatal(err)
+	}
+	ociSrv.Run(t)
+
 	if err := srv.LinkIndices(); err != nil {
 		t.Fatal(err)
 	}
@@ -139,23 +146,70 @@ func TestPullCmd(t *testing.T) {
 			failExpect: "Failed to fetch chart version",
 			wantError:  true,
 		},
+		{
+			name:       "Fetch OCI Chart",
+			args:       fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0", ociSrv.RegistryURL),
+			expectFile: "./oci-dependent-chart-0.1.0.tgz",
+		},
+		{
+			name:       "Fetch OCI Chart with untar",
+			args:       fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --untar", ociSrv.RegistryURL),
+			expectFile: "./oci-dependent-chart",
+			expectDir:  true,
+		},
+		{
+			name:       "Fetch OCI Chart with untar and untardir",
+			args:       fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --untar --untardir ocitest2", ociSrv.RegistryURL),
+			expectFile: "./ocitest2",
+			expectDir:  true,
+		},
+		{
+			name:         "OCI Fetch untar when dir with same name existed",
+			args:         fmt.Sprintf("oci-test-chart oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --untar --untardir ocitest2 --untar --untardir ocitest2", ociSrv.RegistryURL),
+			wantError:    true,
+			wantErrorMsg: fmt.Sprintf("failed to untar: a file or directory with the name %s already exists", filepath.Join(srv.Root(), "ocitest2")),
+		},
+		{
+			name:       "Fail fetching non-existent OCI chart",
+			args:       fmt.Sprintf("oci://%s/u/ocitestuser/nosuchthing --version 0.1.0", ociSrv.RegistryURL),
+			failExpect: "Failed to fetch",
+			wantError:  true,
+		},
+		{
+			name:         "Fail fetching OCI chart without version specified",
+			args:         fmt.Sprintf("oci://%s/u/ocitestuser/nosuchthing", ociSrv.RegistryURL),
+			wantErrorMsg: "Error: --version flag is explicitly required for OCI registries",
+			wantError:    true,
+		},
+		{
+			name:         "Fail fetching OCI chart without version specified",
+			args:         fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0", ociSrv.RegistryURL),
+			wantErrorMsg: "Error: --version flag is explicitly required for OCI registries",
+			wantError:    true,
+		},
+		{
+			name:      "Fail fetching OCI chart without version specified",
+			args:      fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0 --version 0.1.0", ociSrv.RegistryURL),
+			wantError: true,
+		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			outdir := srv.Root()
-			cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s ",
+			cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s",
 				tt.args,
 				outdir,
 				filepath.Join(outdir, "repositories.yaml"),
 				outdir,
+				filepath.Join(outdir, "config.json"),
 			)
 			// Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182
 			if tt.existFile != "" {
 				file := filepath.Join(outdir, tt.existFile)
 				_, err := os.Create(file)
 				if err != nil {
-					t.Fatal("err")
+					t.Fatal(err)
 				}
 			}
 			if tt.existDir != "" {
diff --git a/cmd/helm/root.go b/cmd/helm/root.go
index f2be0b5a9..8025a9ddf 100644
--- a/cmd/helm/root.go
+++ b/cmd/helm/root.go
@@ -153,12 +153,22 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 	flags.ParseErrorsWhitelist.UnknownFlags = true
 	flags.Parse(args)
 
+	registryClient, err := registry.NewClient(
+		registry.ClientOptDebug(settings.Debug),
+		registry.ClientOptWriter(out),
+		registry.ClientOptCredentialsFile(settings.RegistryConfig),
+	)
+	if err != nil {
+		return nil, err
+	}
+	actionConfig.RegistryClient = registryClient
+
 	// Add subcommands
 	cmd.AddCommand(
 		// chart commands
 		newCreateCmd(out),
-		newDependencyCmd(out),
-		newPullCmd(out),
+		newDependencyCmd(actionConfig, out),
+		newPullCmd(actionConfig, out),
 		newShowCmd(out),
 		newLintCmd(out),
 		newPackageCmd(out),
@@ -188,15 +198,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 	)
 
 	// Add *experimental* subcommands
-	registryClient, err := registry.NewClient(
-		registry.ClientOptDebug(settings.Debug),
-		registry.ClientOptWriter(out),
-		registry.ClientOptCredentialsFile(settings.RegistryConfig),
-	)
-	if err != nil {
-		return nil, err
-	}
-	actionConfig.RegistryClient = registryClient
 	cmd.AddCommand(
 		newRegistryCmd(actionConfig, out),
 		newChartCmd(actionConfig, out),
diff --git a/cmd/helm/testdata/testcharts/oci-dependent-chart-0.1.0.tgz b/cmd/helm/testdata/testcharts/oci-dependent-chart-0.1.0.tgz
new file mode 100644
index 000000000..7b4cbeccc
Binary files /dev/null and b/cmd/helm/testdata/testcharts/oci-dependent-chart-0.1.0.tgz differ
diff --git a/internal/experimental/registry/client.go b/internal/experimental/registry/client.go
index 5756030c0..55b34d68f 100644
--- a/internal/experimental/registry/client.go
+++ b/internal/experimental/registry/client.go
@@ -17,6 +17,7 @@ limitations under the License.
 package registry // import "helm.sh/helm/v3/internal/experimental/registry"
 
 import (
+	"bytes"
 	"context"
 	"fmt"
 	"io"
@@ -24,14 +25,16 @@ import (
 	"net/http"
 	"sort"
 
+	"helm.sh/helm/v3/pkg/chart"
+	"helm.sh/helm/v3/pkg/helmpath"
+
+	"github.com/deislabs/oras/pkg/content"
+
 	auth "github.com/deislabs/oras/pkg/auth/docker"
 	"github.com/deislabs/oras/pkg/oras"
 	"github.com/gosuri/uitable"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
-
-	"helm.sh/helm/v3/pkg/chart"
-	"helm.sh/helm/v3/pkg/helmpath"
 )
 
 const (
@@ -144,7 +147,57 @@ func (c *Client) PushChart(ref *Reference) error {
 }
 
 // PullChart downloads a chart from a registry
-func (c *Client) PullChart(ref *Reference) error {
+func (c *Client) PullChart(ref *Reference) (*bytes.Buffer, error) {
+	buf := bytes.NewBuffer(nil)
+
+	if ref.Tag == "" {
+		return buf, errors.New("tag explicitly required")
+	}
+
+	fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Tag, ref.Repo)
+
+	store := content.NewMemoryStore()
+	fullname := ref.FullName()
+	_ = fullname
+	_, layerDescriptors, err := oras.Pull(ctx(c.out, c.debug), c.resolver, ref.FullName(), store,
+		oras.WithPullEmptyNameAllowed(),
+		oras.WithAllowedMediaTypes(KnownMediaTypes()))
+	if err != nil {
+		return buf, err
+	}
+
+	numLayers := len(layerDescriptors)
+	if numLayers < 1 {
+		return buf, errors.New(
+			fmt.Sprintf("manifest does not contain at least 1 layer (total: %d)", numLayers))
+	}
+
+	var contentLayer *ocispec.Descriptor
+	for _, layer := range layerDescriptors {
+		layer := layer
+		switch layer.MediaType {
+		case HelmChartContentLayerMediaType:
+			contentLayer = &layer
+
+		}
+	}
+
+	if contentLayer == nil {
+		return buf, errors.New(
+			fmt.Sprintf("manifest does not contain a layer with mediatype %s",
+				HelmChartContentLayerMediaType))
+	}
+
+	_, b, ok := store.Get(*contentLayer)
+	if !ok {
+		return buf, errors.Errorf("Unable to retrieve blob with digest %s", contentLayer.Digest)
+	}
+
+	buf = bytes.NewBuffer(b)
+	return buf, nil
+}
+
+func (c *Client) PullChartToCache(ref *Reference) error {
 	if ref.Tag == "" {
 		return errors.New("tag explicitly required")
 	}
diff --git a/internal/experimental/registry/client_test.go b/internal/experimental/registry/client_test.go
index 2d208b7b9..0d5d508d5 100644
--- a/internal/experimental/registry/client_test.go
+++ b/internal/experimental/registry/client_test.go
@@ -202,13 +202,13 @@ func (suite *RegistryClientTestSuite) Test_4_PullChart() {
 	// non-existent ref
 	ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
 	suite.Nil(err)
-	err = suite.RegistryClient.PullChart(ref)
+	_, err = suite.RegistryClient.PullChart(ref)
 	suite.NotNil(err)
 
 	// existing ref
 	ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
 	suite.Nil(err)
-	err = suite.RegistryClient.PullChart(ref)
+	_, err = suite.RegistryClient.PullChart(ref)
 	suite.Nil(err)
 }
 
@@ -245,7 +245,7 @@ func (suite *RegistryClientTestSuite) Test_8_ManInTheMiddle() {
 	suite.Nil(err)
 
 	// returns content that does not match the expected digest
-	err = suite.RegistryClient.PullChart(ref)
+	_, err = suite.RegistryClient.PullChart(ref)
 	suite.NotNil(err)
 	suite.True(errdefs.IsFailedPrecondition(err))
 }
diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go
index c72a39e82..6692942a1 100644
--- a/internal/resolver/resolver.go
+++ b/internal/resolver/resolver.go
@@ -23,16 +23,19 @@ import (
 	"strings"
 	"time"
 
-	"github.com/Masterminds/semver/v3"
-	"github.com/pkg/errors"
-
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/chart/loader"
+	"helm.sh/helm/v3/pkg/gates"
 	"helm.sh/helm/v3/pkg/helmpath"
 	"helm.sh/helm/v3/pkg/provenance"
 	"helm.sh/helm/v3/pkg/repo"
+
+	"github.com/Masterminds/semver/v3"
+	"github.com/pkg/errors"
 )
 
+const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
+
 // Resolver resolves dependencies from semantic version ranges to a particular version.
 type Resolver struct {
 	chartpath string
@@ -88,6 +91,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
 			}
 			continue
 		}
+
 		constraint, err := semver.NewConstraint(d.Version)
 		if err != nil {
 			return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name)
@@ -104,21 +108,34 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
 			continue
 		}
 
-		repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
-		if err != nil {
-			return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName)
-		}
+		var vs repo.ChartVersions
+		var version string
+		var ok bool
+		found := true
+		if !strings.HasPrefix(d.Repository, "oci://") {
+			repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
+			if err != nil {
+				return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName)
+			}
 
-		vs, ok := repoIndex.Entries[d.Name]
-		if !ok {
-			return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
+			vs, ok = repoIndex.Entries[d.Name]
+			if !ok {
+				return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
+			}
+			found = false
+		} else {
+			version = d.Version
+			if !FeatureGateOCI.IsEnabled() {
+				return nil, errors.Wrapf(FeatureGateOCI.Error(),
+					"repository %s is an OCI registry", d.Repository)
+			}
 		}
 
 		locked[i] = &chart.Dependency{
 			Name:       d.Name,
 			Repository: d.Repository,
+			Version:    version,
 		}
-		found := false
 		// The version are already sorted and hence the first one to satisfy the constraint is used
 		for _, ver := range vs {
 			v, err := semver.NewVersion(ver.Version)
diff --git a/pkg/action/chart_pull.go b/pkg/action/chart_pull.go
index 97abde7cc..896755201 100644
--- a/pkg/action/chart_pull.go
+++ b/pkg/action/chart_pull.go
@@ -40,5 +40,5 @@ func (a *ChartPull) Run(out io.Writer, ref string) error {
 	if err != nil {
 		return err
 	}
-	return a.cfg.RegistryClient.PullChart(r)
+	return a.cfg.RegistryClient.PullChartToCache(r)
 }
diff --git a/pkg/action/pull.go b/pkg/action/pull.go
index 220ca11b2..258685441 100644
--- a/pkg/action/pull.go
+++ b/pkg/action/pull.go
@@ -43,13 +43,15 @@ type Pull struct {
 	Devel       bool
 	Untar       bool
 	VerifyLater bool
+	OCI         bool
 	UntarDir    string
 	DestDir     string
+	cfg         *Configuration
 }
 
 // NewPull creates a new Pull object with the given configuration.
-func NewPull() *Pull {
-	return &Pull{}
+func NewPull(cfg *Configuration) *Pull {
+	return &Pull{cfg: cfg}
 }
 
 // Run executes 'helm pull' against the given release.
@@ -70,6 +72,16 @@ func (p *Pull) Run(chartRef string) (string, error) {
 		RepositoryCache:  p.Settings.RepositoryCache,
 	}
 
+	if p.OCI {
+		if p.Version == "" {
+			return out.String(), errors.Errorf("--version flag is explicitly required for OCI registries")
+		}
+
+		c.Options = append(c.Options,
+			getter.WithRegistryClient(p.cfg.RegistryClient),
+			getter.WithTagName(p.Version))
+	}
+
 	if p.Verify {
 		c.Verify = downloader.VerifyAlways
 	} else if p.VerifyLater {
@@ -123,6 +135,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
 			_, chartName := filepath.Split(chartRef)
 			udCheck = filepath.Join(udCheck, chartName)
 		}
+
 		if _, err := os.Stat(udCheck); err != nil {
 			if err := os.MkdirAll(udCheck, 0755); err != nil {
 				return out.String(), errors.Wrap(err, "failed to untar (mkdir)")
diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go
index ef26f3348..6c600bebb 100644
--- a/pkg/downloader/chart_downloader.go
+++ b/pkg/downloader/chart_downloader.go
@@ -25,6 +25,7 @@ import (
 
 	"github.com/pkg/errors"
 
+	"helm.sh/helm/v3/internal/experimental/registry"
 	"helm.sh/helm/v3/internal/fileutil"
 	"helm.sh/helm/v3/internal/urlutil"
 	"helm.sh/helm/v3/pkg/getter"
@@ -68,6 +69,7 @@ type ChartDownloader struct {
 	Getters getter.Providers
 	// Options provide parameters to be passed along to the Getter being initialized.
 	Options          []getter.Option
+	RegistryClient   *registry.Client
 	RepositoryConfig string
 	RepositoryCache  string
 }
@@ -100,6 +102,10 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
 	}
 
 	name := filepath.Base(u.Path)
+	if u.Scheme == "oci" {
+		name = fmt.Sprintf("%s-%s.tgz", name, version)
+	}
+
 	destfile := filepath.Join(dest, name)
 	if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil {
 		return destfile, nil, err
diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go
index 145244082..f2945fdb6 100644
--- a/pkg/downloader/manager.go
+++ b/pkg/downloader/manager.go
@@ -26,6 +26,7 @@ import (
 	"os"
 	"path"
 	"path/filepath"
+	"regexp"
 	"strings"
 	"sync"
 
@@ -33,6 +34,7 @@ import (
 	"github.com/pkg/errors"
 	"sigs.k8s.io/yaml"
 
+	"helm.sh/helm/v3/internal/experimental/registry"
 	"helm.sh/helm/v3/internal/resolver"
 	"helm.sh/helm/v3/internal/third_party/dep/fs"
 	"helm.sh/helm/v3/internal/urlutil"
@@ -71,6 +73,7 @@ type Manager struct {
 	SkipUpdate bool
 	// Getter collection for the operation
 	Getters          []getter.Provider
+	RegistryClient   *registry.Client
 	RepositoryConfig string
 	RepositoryCache  string
 }
@@ -332,11 +335,40 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
 			},
 		}
 
-		if _, _, err := dl.DownloadTo(churl, "", destPath); err != nil {
+		untar, version := false, ""
+		if strings.HasPrefix(churl, "oci://") {
+			if !resolver.FeatureGateOCI.IsEnabled() {
+				return errors.Wrapf(resolver.FeatureGateOCI.Error(),
+					"the repository %s is an OCI registry", churl)
+			}
+
+			churl, version, err = parseOCIRef(churl)
+			if err != nil {
+				return errors.Wrapf(err, "could not parse OCI reference")
+			}
+			untar = true
+			dl.Options = append(dl.Options,
+				getter.WithRegistryClient(m.RegistryClient),
+				getter.WithTagName(version))
+		}
+
+		destFile, _, err := dl.DownloadTo(churl, version, destPath)
+		if err != nil {
 			saveError = errors.Wrapf(err, "could not download %s", churl)
 			break
 		}
 
+		if untar {
+			err = chartutil.ExpandFile(destPath, destFile)
+			if err != nil {
+				return errors.Wrapf(err, "could not open %s to untar", destFile)
+			}
+			err = os.RemoveAll(destFile)
+			if err != nil {
+				return errors.Wrapf(err, "chart was downloaded and untarred, but was unable to remove the tarball: %s", destFile)
+			}
+		}
+
 		churls[churl] = struct{}{}
 	}
 
@@ -375,6 +407,18 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
 	return nil
 }
 
+func parseOCIRef(chartRef string) (string, string, error) {
+	refTagRegexp := regexp.MustCompile(`^(oci://[^:]+(:[0-9]{1,5})?[^:]+):(.*)$`)
+	caps := refTagRegexp.FindStringSubmatch(chartRef)
+	if len(caps) != 4 {
+		return "", "", errors.Errorf("improperly formatted oci chart reference: %s", chartRef)
+	}
+	chartRef = caps[1]
+	tag := caps[3]
+
+	return chartRef, tag, nil
+}
+
 // safeDeleteDep deletes any versions of the given dependency in the given directory.
 //
 // It does this by first matching the file name to an expected pattern, then loading
@@ -539,6 +583,11 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
 			continue
 		}
 
+		if strings.HasPrefix(dd.Repository, "oci://") {
+			reposMap[dd.Name] = dd.Repository
+			continue
+		}
+
 		found := false
 
 		for _, repo := range repos {
@@ -648,7 +697,12 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
 //
 // If it finds a URL that is "relative", it will prepend the repoURL.
 func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, err error) {
+	if strings.HasPrefix(repoURL, "oci://") {
+		return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", nil
+	}
+
 	for _, cr := range repos {
+
 		if urlutil.Equal(repoURL, cr.Config.URL) {
 			var entry repo.ChartVersions
 			entry, err = findEntryByName(name, cr)
@@ -671,10 +725,10 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
 	}
 	url, err = repo.FindChartInRepoURL(repoURL, name, version, "", "", "", m.Getters)
 	if err == nil {
-		return
+		return url, username, password, err
 	}
 	err = errors.Errorf("chart %s not found in %s: %s", name, repoURL, err)
-	return
+	return url, username, password, err
 }
 
 // findEntryByName finds an entry in the chart repository whose name matches the given name.
diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go
index 8ee08cb7f..465348456 100644
--- a/pkg/getter/getter.go
+++ b/pkg/getter/getter.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/pkg/errors"
 
+	"helm.sh/helm/v3/internal/experimental/registry"
 	"helm.sh/helm/v3/pkg/cli"
 )
 
@@ -33,10 +34,13 @@ type options struct {
 	certFile              string
 	keyFile               string
 	caFile                string
+	unTar                 bool
 	insecureSkipVerifyTLS bool
 	username              string
 	password              string
 	userAgent             string
+	version               string
+	registryClient        *registry.Client
 	timeout               time.Duration
 }
 
@@ -90,6 +94,24 @@ func WithTimeout(timeout time.Duration) Option {
 	}
 }
 
+func WithTagName(tagname string) Option {
+	return func(opts *options) {
+		opts.version = tagname
+	}
+}
+
+func WithRegistryClient(client *registry.Client) Option {
+	return func(opts *options) {
+		opts.registryClient = client
+	}
+}
+
+func WithUntar() Option {
+	return func(opts *options) {
+		opts.unTar = true
+	}
+}
+
 // Getter is an interface to support GET to the specified URL.
 type Getter interface {
 	// Get file content by url string
@@ -139,11 +161,16 @@ var httpProvider = Provider{
 	New:     NewHTTPGetter,
 }
 
+var ociProvider = Provider{
+	Schemes: []string{"oci"},
+	New:     NewOCIGetter,
+}
+
 // All finds all of the registered getters as a list of Provider instances.
 // Currently, the built-in getters and the discovered plugins with downloader
 // notations are collected.
 func All(settings *cli.EnvSettings) Providers {
-	result := Providers{httpProvider}
+	result := Providers{httpProvider, ociProvider}
 	pluginDownloaders, _ := collectPlugins(settings)
 	result = append(result, pluginDownloaders...)
 	return result
diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go
index 79a3338e9..95d309c16 100644
--- a/pkg/getter/getter_test.go
+++ b/pkg/getter/getter_test.go
@@ -57,7 +57,7 @@ func TestAll(t *testing.T) {
 	env.PluginsDirectory = pluginDir
 
 	all := All(env)
-	if len(all) != 3 {
+	if len(all) != 4 {
 		t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all))
 	}
 
diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go
new file mode 100644
index 000000000..d8fd53862
--- /dev/null
+++ b/pkg/getter/ocigetter.go
@@ -0,0 +1,69 @@
+/*
+Copyright The Helm Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package getter
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+
+	"helm.sh/helm/v3/internal/experimental/registry"
+)
+
+// OCIGetter is the default HTTP(/S) backend handler
+type OCIGetter struct {
+	opts options
+}
+
+//Get performs a Get from repo.Getter and returns the body.
+func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
+	for _, opt := range options {
+		opt(&g.opts)
+	}
+	return g.get(href)
+}
+
+func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
+	client := g.opts.registryClient
+
+	ref := strings.TrimPrefix(href, "oci://")
+	if version := g.opts.version; version != "" {
+		ref = fmt.Sprintf("%s:%s", ref, version)
+	}
+
+	r, err := registry.ParseReference(ref)
+	if err != nil {
+		return nil, err
+	}
+
+	buf, err := client.PullChart(r)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf, nil
+}
+
+// NewOCIGetter constructs a valid http/https client as a Getter
+func NewOCIGetter(options ...Option) (Getter, error) {
+	var client OCIGetter
+
+	for _, opt := range options {
+		opt(&client.opts)
+	}
+
+	return &client, nil
+}
diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go
index 270c8958a..7dc60e948 100644
--- a/pkg/repo/repotest/server.go
+++ b/pkg/repo/repotest/server.go
@@ -16,18 +16,34 @@ limitations under the License.
 package repotest
 
 import (
+	"context"
+	"fmt"
 	"io/ioutil"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
 	"path/filepath"
 	"testing"
+	"time"
 
 	"helm.sh/helm/v3/internal/tlsutil"
+	"helm.sh/helm/v3/pkg/chart"
+	"helm.sh/helm/v3/pkg/chart/loader"
+	"helm.sh/helm/v3/pkg/chartutil"
+	"helm.sh/helm/v3/pkg/repo"
 
 	"sigs.k8s.io/yaml"
 
-	"helm.sh/helm/v3/pkg/repo"
+	auth "github.com/deislabs/oras/pkg/auth/docker"
+	"github.com/docker/distribution/configuration"
+	"github.com/docker/distribution/registry"
+	_ "github.com/docker/distribution/registry/auth/htpasswd"           // used for docker test registry
+	_ "github.com/docker/distribution/registry/storage/driver/inmemory" // used for docker test registry
+
+	ociRegistry "helm.sh/helm/v3/internal/experimental/registry"
+
+	"golang.org/x/crypto/bcrypt"
 )
 
 // NewTempServerWithCleanup creates a server inside of a temp dir.
@@ -43,6 +59,166 @@ func NewTempServerWithCleanup(t *testing.T, glob string) (*Server, error) {
 	return srv, err
 }
 
+type OCIServer struct {
+	*registry.Registry
+	RegistryURL  string
+	Dir          string
+	TestUsername string
+	TestPassword string
+	Client       *ociRegistry.Client
+}
+
+type OCIServerRunConfig struct {
+	DependingChart *chart.Chart
+}
+
+type OCIServerOpt func(config *OCIServerRunConfig)
+
+func WithDependingChart(c *chart.Chart) OCIServerOpt {
+	return func(config *OCIServerRunConfig) {
+		config.DependingChart = c
+	}
+}
+
+func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) {
+	testHtpasswdFileBasename := "authtest.htpasswd"
+	testUsername, testPassword := "username", "password"
+
+	pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
+	if err != nil {
+		t.Fatal("error generating bcrypt password for test htpasswd file")
+	}
+	htpasswdPath := filepath.Join(dir, testHtpasswdFileBasename)
+	err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644)
+	if err != nil {
+		t.Fatalf("error creating test htpasswd file")
+	}
+
+	// Registry config
+	config := &configuration.Configuration{}
+	port, err := getFreePort()
+	if err != nil {
+		t.Fatalf("error finding free port for test registry")
+	}
+
+	config.HTTP.Addr = fmt.Sprintf(":%d", port)
+	config.HTTP.DrainTimeout = time.Duration(10) * time.Second
+	config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
+	config.Auth = configuration.Auth{
+		"htpasswd": configuration.Parameters{
+			"realm": "localhost",
+			"path":  htpasswdPath,
+		},
+	}
+
+	registryURL := fmt.Sprintf("localhost:%d", port)
+
+	r, err := registry.NewRegistry(context.Background(), config)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return &OCIServer{
+		Registry:     r,
+		RegistryURL:  registryURL,
+		TestUsername: testUsername,
+		TestPassword: testPassword,
+		Dir:          dir,
+	}, nil
+}
+
+func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) {
+	cfg := &OCIServerRunConfig{}
+	for _, fn := range opts {
+		fn(cfg)
+	}
+
+	go srv.ListenAndServe()
+
+	credentialsFile := filepath.Join(srv.Dir, "config.json")
+
+	client, err := auth.NewClient(credentialsFile)
+	if err != nil {
+		t.Fatalf("error creating auth client")
+	}
+
+	resolver, err := client.Resolver(context.Background(), http.DefaultClient, false)
+	if err != nil {
+		t.Fatalf("error creating resolver")
+	}
+
+	// init test client
+	registryClient, err := ociRegistry.NewClient(
+		ociRegistry.ClientOptDebug(true),
+		ociRegistry.ClientOptWriter(os.Stdout),
+		ociRegistry.ClientOptAuthorizer(&ociRegistry.Authorizer{
+			Client: client,
+		}),
+		ociRegistry.ClientOptResolver(&ociRegistry.Resolver{
+			Resolver: resolver,
+		}),
+	)
+	if err != nil {
+		t.Fatalf("error creating registry client")
+	}
+
+	err = registryClient.Login(srv.RegistryURL, srv.TestUsername, srv.TestPassword, false)
+	if err != nil {
+		t.Fatalf("error logging into registry with good credentials")
+	}
+
+	ref, err := ociRegistry.ParseReference(fmt.Sprintf("%s/u/ocitestuser/oci-dependent-chart:0.1.0", srv.RegistryURL))
+	if err != nil {
+		t.Fatalf("")
+	}
+
+	err = chartutil.ExpandFile(srv.Dir, filepath.Join(srv.Dir, "oci-dependent-chart-0.1.0.tgz"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// valid chart
+	ch, err := loader.LoadDir(filepath.Join(srv.Dir, "oci-dependent-chart"))
+	if err != nil {
+		t.Fatal("error loading chart")
+	}
+
+	err = os.RemoveAll(filepath.Join(srv.Dir, "oci-dependent-chart"))
+	if err != nil {
+		t.Fatal("error removing chart before push")
+	}
+
+	err = registryClient.SaveChart(ch, ref)
+	if err != nil {
+		t.Fatal("error saving chart")
+	}
+
+	err = registryClient.PushChart(ref)
+	if err != nil {
+		t.Fatal("error pushing chart")
+	}
+
+	if cfg.DependingChart != nil {
+		c := cfg.DependingChart
+		dependingRef, err := ociRegistry.ParseReference(fmt.Sprintf("%s/u/ocitestuser/oci-depending-chart:1.2.3", srv.RegistryURL))
+		if err != nil {
+			t.Fatal("error parsing reference for depending chart reference")
+		}
+
+		err = registryClient.SaveChart(c, dependingRef)
+		if err != nil {
+			t.Fatal("error saving depending chart")
+		}
+
+		err = registryClient.PushChart(dependingRef)
+		if err != nil {
+			t.Fatal("error pushing depending chart")
+		}
+	}
+
+	srv.Client = registryClient
+}
+
 // NewTempServer creates a server inside of a temp dir.
 //
 // If the passed in string is not "", it will be treated as a shell glob, and files
@@ -228,3 +404,17 @@ func setTestingRepository(url, fname string) error {
 	})
 	return r.WriteFile(fname, 0644)
 }
+
+func getFreePort() (int, error) {
+	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
+	if err != nil {
+		return 0, err
+	}
+
+	l, err := net.ListenTCP("tcp", addr)
+	if err != nil {
+		return 0, err
+	}
+	defer l.Close()
+	return l.Addr().(*net.TCPAddr).Port, nil
+}