diff --git a/pkg/contexts/ocm/utils/localize/README.md b/pkg/contexts/ocm/utils/localize/README.md
index 5d20e7549e..8d1c30b344 100644
--- a/pkg/contexts/ocm/utils/localize/README.md
+++ b/pkg/contexts/ocm/utils/localize/README.md
@@ -47,4 +47,8 @@ resource and further helper parts, like json scheme validation for config files.
 Such a specification object can be applied by the function `Instantiate` 
 together with configuration values to
 a component version. As substitution result it returns a virtual filesystem
-with the snapshot according to the resolved substitutions.
\ No newline at end of file
+with the snapshot according to the resolved substitutions.
+
+Additionally, there is a set of more basic types and methods, which can be used
+to describe end execute localizations for single data objects (see `ImageMappings`,
+`LocalizeMappings` and `SubstituteMappings`).
\ No newline at end of file
diff --git a/pkg/contexts/ocm/utils/localize/format.go b/pkg/contexts/ocm/utils/localize/format.go
index 6b735b6bf3..fd1fd50e14 100644
--- a/pkg/contexts/ocm/utils/localize/format.go
+++ b/pkg/contexts/ocm/utils/localize/format.go
@@ -7,8 +7,12 @@ package localize
 import (
 	"encoding/json"
 	"fmt"
+	"strings"
 
+	"github.com/open-component-model/ocm/pkg/contexts/ocm"
 	v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1"
+	"github.com/open-component-model/ocm/pkg/contexts/ocm/utils"
+	"github.com/open-component-model/ocm/pkg/errors"
 	"github.com/open-component-model/ocm/pkg/runtime"
 )
 
@@ -25,6 +29,9 @@ import (
 // ImageMapping describes a dedicated substitution of parts
 // of container image names based on a relative OCM resource reference.
 type ImageMapping struct {
+	// The optional but unique(!) name of the mapping to support referencing mapping entries
+	Name string `json:"name,omitempty"`
+
 	// The resource reference used to resolve the substitution
 	v1.ResourceReference `json:",inline"`
 
@@ -38,6 +45,73 @@ type ImageMapping struct {
 	Image string `json:"image,omitempty"`
 }
 
+type ImageMappings []ImageMapping
+
+func (m *ImageMapping) Evaluate(idx int, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (ValueMappings, error) {
+	name := "image mapping"
+	if m.Name != "" {
+		name = fmt.Sprintf("%s %q", name, m.Name)
+	} else { //nolint: gocritic // yes
+		if idx >= 0 {
+			name = fmt.Sprintf("%s %d", name, idx+1)
+		}
+	}
+	acc, rcv, err := utils.ResolveResourceReference(cv, m.ResourceReference, resolver)
+	if err != nil {
+		return nil, errors.ErrNotFoundWrap(err, "mapping", fmt.Sprintf("%s (%s)", name, &m.ResourceReference))
+	}
+	rcv.Close()
+	ref, err := utils.GetOCIArtifactRef(cv.GetContext(), acc)
+	if err != nil {
+		return nil, errors.Wrapf(err, "mapping %s: cannot resolve resource %s to an OCI Reference", name, &m.ResourceReference)
+	}
+	ix := strings.Index(ref, ":")
+	if ix < 0 {
+		ix = strings.Index(ref, "@")
+		if ix < 0 {
+			return nil, errors.Wrapf(err, "mapping %s: image tag or digest missing (%s)", name, ref)
+		}
+	}
+	repo := ref[:ix]
+	tag := ref[ix+1:]
+
+	cnt := 0
+	if m.Repository != "" {
+		cnt++
+	}
+	if m.Tag != "" {
+		cnt++
+	}
+	if m.Image != "" {
+		cnt++
+	}
+	if cnt == 0 {
+		return nil, fmt.Errorf("no substitution target given for %s", name)
+	}
+
+	var result ValueMappings
+	var r *ValueMapping
+	if m.Repository != "" {
+		if r, err = NewValueMapping(substitutionName(name, "repository", cnt), m.Repository, repo); err != nil {
+			return nil, errors.Wrapf(err, "setting repository for %s", substitutionName(name, "repository", cnt))
+		}
+		result = append(result, *r)
+	}
+	if m.Tag != "" {
+		if r, err = NewValueMapping(substitutionName(name, "tag", cnt), m.Tag, tag); err != nil {
+			return nil, errors.Wrapf(err, "setting tag for %s", substitutionName(name, "tag", cnt))
+		}
+		result = append(result, *r)
+	}
+	if m.Image != "" {
+		if r, err = NewValueMapping(substitutionName(name, "image", cnt), m.Image, ref); err != nil {
+			return nil, errors.Wrapf(err, "setting image for %s", substitutionName(name, "image", cnt))
+		}
+		result = append(result, *r)
+	}
+	return result, nil
+}
+
 // Localization is a request to substitute an image location.
 // The specification describes substitution targets given by the file path and
 // the YAML/JSON value paths of the elements in this file.
@@ -45,8 +119,6 @@ type ImageMapping struct {
 // from the access specification of the given resource provided by the actual
 // component version.
 type Localization struct {
-	// The optional but unique(!) name of the mapping to support referencing mapping entries
-	Name string `json:"name,omitempty"`
 	// The path of the file for the substitution
 	FilePath string `json:"file"`
 	// The image mapping request
@@ -63,6 +135,45 @@ type Localization struct {
 // value.
 type Configuration Substitution
 
+type ValueMapping struct {
+	// The optional but unique(!) name of the mapping to support referencing mapping entries
+	Name string `json:"name,omitempty"`
+	// The target path for the value substitution
+	ValuePath string `json:"path"`
+	// The value to set
+	Value json.RawMessage `json:"value"`
+}
+
+func NewValueMapping(name, path string, value interface{}) (*ValueMapping, error) {
+	var (
+		v   []byte
+		err error
+	)
+
+	if value != nil {
+		v, err = runtime.DefaultJSONEncoding.Marshal(value)
+		if err != nil {
+			return nil, fmt.Errorf("cannot marshal substitution value: %w", err)
+		}
+	}
+	return &ValueMapping{
+		Name:      name,
+		ValuePath: path,
+		Value:     v,
+	}, nil
+}
+
+type ValueMappings []ValueMapping
+
+func (s *ValueMappings) Add(name, path string, value interface{}) error {
+	m, err := NewValueMapping(name, path, value)
+	if err != nil {
+		return err
+	}
+	*s = append(*s, *m)
+	return nil
+}
+
 // Here comes the structure used for resolved execution requests.
 // They can be applied to a filesystem content without further external information.
 // It basically has the same structure as the configuration request, but
@@ -73,14 +184,10 @@ type Configuration Substitution
 // element given by the value path in the given file path by the given
 // direct value.
 type Substitution struct {
-	// The optional but unique(!) name of the mapping to support referencing mapping entries
-	Name string `json:"name,omitempty"`
 	// The path of the file for the substitution
 	FilePath string `json:"file"`
-	// The target path for the value substitution
-	ValuePath string `json:"path"`
-	// The value to set
-	Value json.RawMessage `json:"value"`
+	// The field mapping toapply to given file path
+	ValueMapping `json:",inline"`
 }
 
 func (s *Substitution) GetValue() (interface{}, error) {
@@ -91,24 +198,19 @@ func (s *Substitution) GetValue() (interface{}, error) {
 
 type Substitutions []Substitution
 
-func (s *Substitutions) Add(name, file, path string, value interface{}) error {
-	var (
-		v   []byte
-		err error
-	)
-
-	if value != nil {
-		v, err = runtime.DefaultJSONEncoding.Marshal(value)
-		if err != nil {
-			return fmt.Errorf("cannot marshal substitution value: %w", err)
-		}
-	}
+func (s *Substitutions) AddValueMapping(m *ValueMapping, file string) {
 	*s = append(*s, Substitution{
-		Name:      name,
-		FilePath:  file,
-		ValuePath: path,
-		Value:     v,
+		FilePath:     file,
+		ValueMapping: *m,
 	})
+}
+
+func (s *Substitutions) Add(name, file, path string, value interface{}) error {
+	m, err := NewValueMapping(name, path, value)
+	if err != nil {
+		return err
+	}
+	s.AddValueMapping(m, file)
 	return nil
 }
 
diff --git a/pkg/contexts/ocm/utils/localize/localize.go b/pkg/contexts/ocm/utils/localize/localize.go
index 6542dd48dd..df68df9620 100644
--- a/pkg/contexts/ocm/utils/localize/localize.go
+++ b/pkg/contexts/ocm/utils/localize/localize.go
@@ -5,78 +5,44 @@
 package localize
 
 import (
-	"fmt"
-	"strings"
-
 	"github.com/open-component-model/ocm/pkg/contexts/ocm"
-	"github.com/open-component-model/ocm/pkg/contexts/ocm/utils"
-	"github.com/open-component-model/ocm/pkg/errors"
 )
 
+// Localize maps a list of filesystem related localization requests to an
+// appropriate set of substitution requests.
 func Localize(mappings []Localization, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (Substitutions, error) {
 	var result Substitutions
-	ctx := cv.GetContext()
 
 	for i, v := range mappings {
-		name := "image mapping"
-		if v.Name != "" {
-			name = fmt.Sprintf("%s %q", name, v.Name)
-		}
-		acc, rcv, err := utils.ResolveResourceReference(cv, v.ResourceReference, resolver)
-		if err != nil {
-			return nil, errors.ErrNotFoundWrap(err, "mapping", fmt.Sprintf("%d (%s)", i+1, &v.ResourceReference))
-		}
-		rcv.Close()
-		ref, err := utils.GetOCIArtifactRef(ctx, acc)
+		m, err := v.Evaluate(i, cv, resolver)
 		if err != nil {
-			return nil, errors.Wrapf(err, "mapping %d: cannot resolve resource %s to an OCI Reference", i+1, v)
+			return nil, err
 		}
-		ix := strings.Index(ref, ":")
-		if ix < 0 {
-			ix = strings.Index(ref, "@")
-			if ix < 0 {
-				return nil, errors.Wrapf(err, "mapping %d: image tag or digest missing (%s)", i+1, ref)
-			}
+		for _, r := range m {
+			result.AddValueMapping(&r, v.FilePath)
 		}
-		repo := ref[:ix]
-		tag := ref[ix+1:]
+	}
+	return result, nil
+}
 
-		cnt := 0
-		if v.Repository != "" {
-			cnt++
-		}
-		if v.Tag != "" {
-			cnt++
-		}
-		if v.Image != "" {
-			cnt++
-		}
-		if cnt == 0 {
-			return nil, fmt.Errorf("no substitution target given for %s", name)
-		}
+// LocalizeMappings maps a set of pure image mappings into
+// an appropriate set of value mapping request for a single data object.
+func LocalizeMappings(mappings ImageMappings, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (ValueMappings, error) {
+	var result ValueMappings
 
-		if v.Repository != "" {
-			if err := result.Add(substitutionName(v.Name, "repository", cnt), v.FilePath, v.Repository, repo); err != nil {
-				return nil, errors.Wrapf(err, "setting repository for %s", substitutionName(v.Name, "repository", cnt))
-			}
-		}
-		if v.Tag != "" {
-			if err := result.Add(substitutionName(v.Name, "tag", cnt), v.FilePath, v.Tag, tag); err != nil {
-				return nil, errors.Wrapf(err, "setting tag for %s", substitutionName(v.Name, "tag", cnt))
-			}
-		}
-		if v.Image != "" {
-			if err := result.Add(substitutionName(v.Name, "image", cnt), v.FilePath, v.Image, ref); err != nil {
-				return nil, errors.Wrapf(err, "setting image for %s", substitutionName(v.Name, "image", cnt))
-			}
+	for i, v := range mappings {
+		m, err := v.Evaluate(i, cv, resolver)
+		if err != nil {
+			return nil, err
 		}
+		result = append(result, m...)
 	}
 	return result, nil
 }
 
 func substitutionName(name, sub string, cnt int) string {
 	if name == "" {
-		return ""
+		return sub
 	}
 	if cnt <= 1 {
 		return name
diff --git a/pkg/contexts/ocm/utils/localize/localize_test.go b/pkg/contexts/ocm/utils/localize/localize_test.go
index 182bcf04ef..c93c01b660 100644
--- a/pkg/contexts/ocm/utils/localize/localize_test.go
+++ b/pkg/contexts/ocm/utils/localize/localize_test.go
@@ -74,7 +74,7 @@ var _ = Describe("image value mapping", func() {
 		subst, err := localize.Localize(mappings, cv, nil)
 		Expect(err).To(Succeed())
 		Expect(subst).To(Equal(Substitutions(`
-- name: test1
+- name: image mapping "test1"
   file: file1
   path: a.b.img
   value: ghcr.io/mandelsoft/test:v1
@@ -95,15 +95,15 @@ var _ = Describe("image value mapping", func() {
 		subst, err := localize.Localize(mappings, cv, nil)
 		Expect(err).To(Succeed())
 		Expect(subst).To(Equal(Substitutions(`
-- name: test1-repository
+- name: image mapping "test1"-repository
   file: file1
   path: a.b.rep
   value: ghcr.io/mandelsoft/test
-- name: test1-tag
+- name: image mapping "test1"-tag
   file: file1
   path: a.b.tag
   value: v1
-- name: test1-image
+- name: image mapping "test1"-image
   file: file1
   path: a.b.img
   value: ghcr.io/mandelsoft/test:v1
diff --git a/pkg/contexts/ocm/utils/localize/subst.go b/pkg/contexts/ocm/utils/localize/subst.go
index d6259034a0..b0c9a6f951 100644
--- a/pkg/contexts/ocm/utils/localize/subst.go
+++ b/pkg/contexts/ocm/utils/localize/subst.go
@@ -49,6 +49,29 @@ func Substitute(subs Substitutions, fs vfs.FileSystem) error {
 	return nil
 }
 
+// SubstituteMappings substitutes value mappings for a dedicated substitution target.
+func SubstituteMappings(subs ValueMappings, target subst.SubstitutionTarget) error {
+	for i, s := range subs {
+		if err := target.SubstituteByData(s.ValuePath, s.Value); err != nil {
+			return errors.Wrapf(err, "entry %d: cannot substitute value", i+1)
+		}
+	}
+	return nil
+}
+
+// SubstituteMappingsForData substitutes value mappings for some data.
+func SubstituteMappingsForData(subs ValueMappings, data []byte) ([]byte, error) {
+	target, err := subst.Parse(data)
+	if err != nil {
+		return nil, err
+	}
+	err = SubstituteMappings(subs, target)
+	if err != nil {
+		return nil, err
+	}
+	return target.Content()
+}
+
 func Set(content *ast.File, path string, value *ast.File) error {
 	p, err := yaml.PathString("$." + path)
 	if err != nil {
diff --git a/pkg/contexts/ocm/utils/localize/target_test.go b/pkg/contexts/ocm/utils/localize/target_test.go
new file mode 100644
index 0000000000..5fe0c3811d
--- /dev/null
+++ b/pkg/contexts/ocm/utils/localize/target_test.go
@@ -0,0 +1,155 @@
+// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors.
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package localize_test
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	. "github.com/open-component-model/ocm/pkg/testutils"
+
+	"github.com/mandelsoft/vfs/pkg/vfs"
+
+	"github.com/open-component-model/ocm/pkg/common/accessio"
+	"github.com/open-component-model/ocm/pkg/common/accessobj"
+	"github.com/open-component-model/ocm/pkg/contexts/ocm"
+	"github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact"
+	v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1"
+	"github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf"
+	"github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize"
+	"github.com/open-component-model/ocm/pkg/env/builder"
+)
+
+var _ = Describe("value substitution in single target", func() {
+
+	Context("localize", func() {
+		const (
+			ARCHIVE   = "archive.ctf"
+			COMPONENT = "github.com/comp"
+			VERSION   = "1.0.0"
+			IMAGE     = "image"
+		)
+
+		var (
+			repo ocm.Repository
+			cv   ocm.ComponentVersionAccess
+			env  *builder.Builder
+		)
+
+		BeforeEach(func() {
+			env = builder.NewBuilder(nil)
+			env.OCMCommonTransport(ARCHIVE, accessio.FormatDirectory, func() {
+				env.Component(COMPONENT, func() {
+					env.Version(VERSION, func() {
+						env.Provider("mandelsoft")
+						env.Resource(IMAGE, "", "Spiff", v1.LocalRelation, func() {
+							env.Access(ociartifact.New("ghcr.io/mandelsoft/test:v1"))
+						})
+					})
+				})
+			})
+
+			var err error
+			repo, err = ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, ARCHIVE, 0, env)
+			Expect(err).To(Succeed())
+
+			cv, err = repo.LookupComponentVersion(COMPONENT, VERSION)
+			Expect(err).To(Succeed())
+		})
+
+		AfterEach(func() {
+			Expect(cv.Close()).To(Succeed())
+			Expect(repo.Close()).To(Succeed())
+			vfs.Cleanup(env)
+		})
+
+		It("uses image ref data from component version", func() {
+			mappings := ImageMappings(`
+- name: test1
+  image: a.b.img
+  resource:
+    name: image
+`)
+			subst, err := localize.LocalizeMappings(mappings, cv, nil)
+			Expect(err).To(Succeed())
+			Expect(subst).To(Equal(ValueMappings(`
+- name: image mapping "test1"
+  path: a.b.img
+  value: ghcr.io/mandelsoft/test:v1
+`)))
+		})
+
+		It("uses multiple resolved image ref data from component version", func() {
+
+			mappings := ImageMappings(`
+- name: test1
+  repository: a.b.rep
+  tag: a.b.tag  
+  image: a.b.img
+  resource:
+    name: image
+`)
+			subst, err := localize.LocalizeMappings(mappings, cv, nil)
+			Expect(err).To(Succeed())
+			Expect(subst).To(Equal(ValueMappings(`
+- name: image mapping "test1"-repository
+  path: a.b.rep
+  value: ghcr.io/mandelsoft/test
+- name: image mapping "test1"-tag
+  path: a.b.tag
+  value: v1
+- name: image mapping "test1"-image
+  path: a.b.img
+  value: ghcr.io/mandelsoft/test:v1
+`)))
+		})
+	})
+
+	Context("substitute", func() {
+		var data = []byte(`
+manifest:
+  value1: orig1
+  value2: orig2
+`)
+
+		It("handles simple values substitution", func() {
+			subs := ValueMappings(`
+- name: test1
+  path: manifest.value1
+  value: config1
+- name: test2
+  path: manifest.value2
+  value: config2
+`)
+			result, err := localize.SubstituteMappingsForData(subs, data)
+			Expect(err).To(Succeed())
+
+			Expect(string(result)).To(StringEqualTrimmedWithContext(`
+manifest:
+  value1: config1
+  value2: config2
+`))
+		})
+
+		It("handles json substitution", func() {
+			subs := ValueMappings(`
+- name: test1
+  path: manifest.value1
+  value:
+    some:
+      value: 1
+`)
+			result, err := localize.SubstituteMappingsForData(subs, data)
+			Expect(err).To(Succeed())
+
+			Expect(string(result)).To(StringEqualTrimmedWithContext(`
+manifest:
+  value1:
+      some:
+        value: 1
+  value2: orig2
+`))
+		})
+	})
+})
diff --git a/pkg/contexts/ocm/utils/localize/utils_test.go b/pkg/contexts/ocm/utils/localize/utils_test.go
index c81e6cc1d1..269d3c79bc 100644
--- a/pkg/contexts/ocm/utils/localize/utils_test.go
+++ b/pkg/contexts/ocm/utils/localize/utils_test.go
@@ -34,6 +34,18 @@ func Substitutions(data string) localize.Substitutions {
 	return v
 }
 
+func ImageMappings(data string) localize.ImageMappings {
+	var v localize.ImageMappings
+	Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed())
+	return v
+}
+
+func ValueMappings(data string) localize.ValueMappings {
+	var v localize.ValueMappings
+	Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed())
+	return v
+}
+
 func InstRules(data string) *localize.InstantiationRules {
 	var v localize.InstantiationRules
 	Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed())