diff --git a/template/builder.go b/template/builder.go
index 9944aab4f93631d849539fa74b1424f30a5aaa46..5a2e08cb430dc6ed7ea36f004e6d57125d7066eb 100644
--- a/template/builder.go
+++ b/template/builder.go
@@ -1,12 +1,7 @@
 package template
 
 import (
-	"bytes"
 	"context"
-	html "html/template"
-	"io"
-	"maps"
-	text "text/template"
 
 	"git.perx.ru/perxis/perxis-go/pkg/collections"
 	"git.perx.ru/perxis/perxis-go/pkg/content"
@@ -15,198 +10,138 @@ import (
 )
 
 type Template interface {
-	Execute(w io.Writer, data any) error
+	// Execute выполняет заданный шаблон pattern с предоставленными данными data и возвращает результат в виде строки.
+	Execute(pattern string, data ...any) (string, error)
+
+	// ExecuteList выполняет каждый шаблон из списка patterns с предоставленными данными data и возвращает результаты в виде среза строк.
+	// Каждый шаблон обрабатывается индивидуально.
+	ExecuteList(patterns []string, data ...any) ([]string, error)
+
+	// ExecuteMap выполняет шаблоны из мапы patternMap с предоставленными данными data и возвращает результаты в виде мапы.
+	// Шаблоны применяются к значениям мапы, если они являются строками или срезами строк; в противном случае, значения остаются без изменений.
+	ExecuteMap(patternMap map[string]any, data ...any) (map[string]any, error)
 }
 
 type Builder struct {
+	conf *BuilderConfig
+
 	ctx     context.Context
-	cnt     *content.Content
-	SpaceID string
-	EnvID   string
-	CollID  string
 	data    map[string]any
+	funcMap map[string]any
 
 	// Для кеширования запросов
 	space       *spaces.Space
 	environment *environments.Environment
 	collection  *collections.Collection
-
-	template func(templ string) (Template, error)
 }
 
-func NewBuilder(cnt *content.Content, space, env, coll string) *Builder {
-	b := &Builder{
-		ctx:     context.Background(),
-		cnt:     cnt,
-		SpaceID: space,
-		EnvID:   env,
-		CollID:  coll,
-	}
-	b.template = func(templ string) (Template, error) {
-		t, err := text.New("main").
-			Funcs(b.getFuncs()).
-			Parse(templ)
-		if err != nil {
-			return nil, err
-		}
-		return t, nil
-	}
-	return b
-}
+type BuilderConfig struct {
+	// DefaultHTML если true, по умолчанию используется шаблонизатор для HTML данных,
+	// иначе для текстовых.
+	DefaultHTML bool
 
-func NewHTMLBuilder(cnt *content.Content, space, env, coll string) *Builder {
-	b := &Builder{
-		ctx:     context.Background(),
-		cnt:     cnt,
-		SpaceID: space,
-		EnvID:   env,
-		CollID:  coll,
-	}
-	b.template = func(templ string) (Template, error) {
-		t, err := html.New("main").
-			Funcs(b.getFuncs()).
-			Parse(templ)
-		if err != nil {
-			return nil, err
-		}
-		return t, nil
-	}
-	return b
+	Content       *content.Content
+	SpaceID       string
+	EnvironmentID string
+	CollectionID  string
 }
 
-func (b *Builder) getFuncs() map[string]any {
-	return map[string]any{
-		"lookup": getLookup(b),
-		"system": getSystem(b),
+func NewBuilder(conf *BuilderConfig) *Builder {
+	if conf == nil {
+		conf = &BuilderConfig{}
 	}
-}
 
-func (b *Builder) WithData(data map[string]any) *Builder {
-	bld := *b
-	bld.data = data
-	return &bld
-}
-
-func (b *Builder) WithKV(kv ...any) *Builder {
-	bld := *b
-	if bld.data == nil {
-		//nolint:mnd // Количество аргументов делится на 2, так как они представлены в виде пар ключ-значение.
-		bld.data = make(map[string]any, len(kv)/2)
+	builder := &Builder{
+		conf: conf,
+		ctx:  context.Background(),
+		data: make(map[string]any),
 	}
-	for i := 0; i < len(kv)-1; i += 2 {
-		k, _ := kv[i].(string)
-		v := kv[i+1]
-		if k != "" && v != nil {
-			bld.data[k] = v
-		}
+	builder.funcMap = map[string]any{
+		"lookup": getLookup(builder),
+		"system": getSystem(builder),
 	}
-	return &bld
-}
 
-func (b *Builder) GetData() map[string]any {
-	return b.data
+	return builder
 }
 
-func (b *Builder) WithSpace(space, env string) *Builder {
-	bld := *b
-	bld.SpaceID = space
-	bld.EnvID = env
-	return &bld
+// TextTemplate возвращает новый шаблонизатор для работы с текстовыми данными.
+// При выполнении шаблонов доступны данные и функции из Builder.
+func (b *Builder) TextTemplate() Template {
+	return NewCommonTemplate(false, b.data, b.funcMap)
 }
 
-func (b *Builder) WithContext(ctx context.Context) *Builder {
-	bld := *b
-	bld.ctx = ctx
-	return &bld
+// HTMLTemplate возвращает новый шаблонизатор для работы с HTML данными.
+// При выполнении шаблонов доступны данные и функции из Builder.
+func (b *Builder) HTMLTemplate() Template {
+	return NewCommonTemplate(true, b.data, b.funcMap)
 }
 
 func (b *Builder) Context() context.Context {
 	return b.ctx
 }
 
-func (b *Builder) Template(text string) (Template, error) {
-	return b.template(text)
+func (b *Builder) WithContext(ctx context.Context) *Builder {
+	clone := *b
+	clone.ctx = ctx
+	return &clone
 }
 
-func (b *Builder) Execute(templ string, data ...any) (string, error) {
-	t, err := b.Template(templ)
-	if err != nil {
-		return "", err
-	}
-	buf := new(bytes.Buffer)
-	if err = t.Execute(buf, b.getData(data...)); err != nil {
-		return "", err
-	}
-	return buf.String(), nil
+func (b *Builder) Content() *content.Content {
+	return b.conf.Content
 }
 
-func (b *Builder) ExecuteList(templs []string, data ...any) ([]string, error) {
-	result := make([]string, len(templs))
-	buf := new(bytes.Buffer)
-	d := b.getData(data...)
+func (b *Builder) SpaceID() string {
+	return b.conf.SpaceID
+}
 
-	for i, templ := range templs {
-		t, err := b.Template(templ)
-		if err != nil {
-			return nil, err
-		}
+func (b *Builder) EnvironmentID() string {
+	return b.conf.EnvironmentID
+}
 
-		buf.Reset()
-		err = t.Execute(buf, d)
-		if err != nil {
-			return nil, err
-		}
+func (b *Builder) CollectionID() string {
+	return b.conf.CollectionID
+}
 
-		result[i] = buf.String()
-	}
+// WithData возвращает копию Builder с новыми переданными данными.
+func (b *Builder) WithData(data map[string]any) *Builder {
+	clone := *b
+	clone.data = data
+	return &clone
+}
 
-	return result, nil
-}
-
-func (b *Builder) ExecuteMap(templMap map[string]any, data ...any) (map[string]any, error) {
-	result := make(map[string]any, len(templMap))
-	d := b.getData(data...)
-
-	for k, v := range templMap {
-		switch t := v.(type) {
-		case string:
-			value, err := b.Execute(t, d)
-			if err != nil {
-				return nil, err
-			}
-			result[k] = value
-		case []string:
-			var err error
-			result[k], err = b.ExecuteList(t, d)
-			if err != nil {
-				return nil, err
-			}
-		default:
-			result[k] = v
+func (b *Builder) WithKV(kv ...any) *Builder {
+	clone := *b
+	if clone.data == nil {
+		//nolint:mnd // Количество аргументов делится на 2, так как они представлены в виде пар ключ-значение.
+		clone.data = make(map[string]any, len(kv)/2)
+	}
+	for i := 0; i < len(kv)-1; i += 2 {
+		k, _ := kv[i].(string)
+		v := kv[i+1]
+		if k != "" && v != nil {
+			clone.data[k] = v
 		}
 	}
-
-	return result, nil
+	return &clone
 }
 
-func (b *Builder) getData(data ...any) any {
-	if len(data) == 0 {
-		return b.data
+func (b *Builder) Execute(pattern string, data ...any) (string, error) {
+	if b.conf.DefaultHTML {
+		return b.HTMLTemplate().Execute(pattern, data...)
 	}
+	return b.TextTemplate().Execute(pattern, data...)
+}
 
-	res := maps.Clone(b.data)
-	if res == nil {
-		res = make(map[string]any)
-	}
-	for _, v := range data {
-		if m, ok := v.(map[string]any); ok {
-			maps.Copy(res, m)
-		}
+func (b *Builder) ExecuteList(patterns []string, data ...any) ([]string, error) {
+	if b.conf.DefaultHTML {
+		return b.HTMLTemplate().ExecuteList(patterns, data...)
 	}
+	return b.TextTemplate().ExecuteList(patterns, data...)
+}
 
-	if len(res) == 0 {
-		return data[0]
+func (b *Builder) ExecuteMap(patternMap map[string]any, data ...any) (map[string]any, error) {
+	if b.conf.DefaultHTML {
+		return b.HTMLTemplate().ExecuteMap(patternMap, data...)
 	}
-
-	return res
+	return b.TextTemplate().ExecuteMap(patternMap, data...)
 }
diff --git a/template/builder_test.go b/template/builder_test.go
index 48fdb2cb0826c6d392eaed0cb725f7ee5a6651df..2cd640cd8525671126e84ccfd262a5b19a918364 100644
--- a/template/builder_test.go
+++ b/template/builder_test.go
@@ -1,582 +1,196 @@
 package template
 
 import (
-	"context"
 	"testing"
 
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	collectionsmocks "git.perx.ru/perxis/perxis-go/pkg/collections/mocks"
 	"git.perx.ru/perxis/perxis-go/pkg/content"
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	environmentsmocks "git.perx.ru/perxis/perxis-go/pkg/environments/mocks"
 	"git.perx.ru/perxis/perxis-go/pkg/items"
-	mocksitems "git.perx.ru/perxis/perxis-go/pkg/items/mocks"
+	itemsmocks "git.perx.ru/perxis/perxis-go/pkg/items/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	spacesmocks "git.perx.ru/perxis/perxis-go/pkg/spaces/mocks"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
 )
 
-func TestBuilder_Execute(t *testing.T) {
-	tests := []struct {
-		name        string
-		SpaceID     string
-		EnvID       string
-		CollID      string
-		template    string
-		data        any
-		want        any
-		htmlBuilder bool
-		wantErr     bool
+func TestBuilder(t *testing.T) {
+	t.Run("using default HTML template", func(t *testing.T) {
+		builder := NewBuilder(&BuilderConfig{
+			DefaultHTML: true,
+		}).WithData(map[string]any{
+			"foo": "<b>bar</b>",
+		})
 
-		getCnt func(t *testing.T) *content.Content
+		got, err := builder.Execute("{{ .foo }}")
+		require.NoError(t, err)
+		assert.Equal(t, "&lt;b&gt;bar&lt;/b&gt;", got)
+	})
+}
+
+func TestBuilder_Funcs(t *testing.T) {
+	for _, tc := range []struct {
+		name          string
+		pattern       string
+		setupContent  func(t *testing.T) *content.Content
+		spaceID       string
+		environmentID string
+		collectionID  string
+		want          string
+		assertError   assert.ErrorAssertionFunc
 	}{
 		{
-			name:     "invalid data",
-			template: "hello {{ .a }}",
-			data:     "world",
-			want:     "",
-			wantErr:  true,
-		},
-		{
-			name:     "invalid template",
-			template: "hello {{ . ",
-			data:     "world",
-			want:     "",
-			wantErr:  true,
-		},
-		{
-			name:     "empty template",
-			template: "",
-			data:     "world",
-			want:     "",
-			wantErr:  false,
-		},
-		{
-			name:     "empty data",
-			template: "{{ . }}",
-			data:     "",
-			want:     "",
-			wantErr:  false,
-		},
-		{
-			name:     "success",
-			template: "hello {{ . }}",
-			data:     "world",
-			want:     "hello world",
-			wantErr:  false,
-		},
-		{
-			name:     "success with html builder",
-			template: "<span class=\"comment\">{{ . }}</span>",
-			data:     "<script>alert(localStorage.getItem('secret'))</script>",
-			want: "<span class=\"comment\">" +
-				"&lt;script&gt;alert(localStorage.getItem(&#39;secret&#39;))&lt;/script&gt;" +
-				"</span>",
-			htmlBuilder: true,
-			wantErr:     false,
-		},
-		{
-			name:     "template as plain text",
-			template: "hello",
-			data:     "world",
-			want:     "hello",
-			wantErr:  false,
-		},
-		{
-			name:     "lookup function",
-			SpaceID:  "space",
-			EnvID:    "env",
-			template: "hello, {{ lookup \"secrets.dev.key\" }}",
-			data:     "",
-			want:     "hello, Luk",
-			wantErr:  false,
-			getCnt: func(t *testing.T) *content.Content {
-				itemsSvc := mocksitems.NewItems(t)
-				itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").
+			name:    "basic lookup",
+			pattern: "{{ lookup \"coll_id.item_id.field\" }}",
+			setupContent: func(t *testing.T) *content.Content {
+				itemsService := itemsmocks.NewItems(t)
+				itemsService.On("Get", mock.Anything, "space_id", "env_id", "coll_id", "item_id").
 					Return(&items.Item{
-						ID:           "dev",
-						SpaceID:      "space",
-						EnvID:        "env",
-						CollectionID: "secrets",
 						Data: map[string]any{
-							"id":  "dev",
-							"key": "Luk",
+							"field": "value",
 						},
-					}, nil).Once()
-				return &content.Content{Items: itemsSvc}
-			},
-		},
-		{
-			name:     "lookup function with slice",
-			SpaceID:  "space",
-			EnvID:    "env",
-			template: "numbers {{ lookup \"secrets.dev.slice\" }}",
-			data:     "",
-			want:     "numbers [1 2 3]",
-			wantErr:  false,
-			getCnt: func(t *testing.T) *content.Content {
-				itemsSvc := mocksitems.NewItems(t)
-				itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").
+					}, nil).
+					Once()
+				return &content.Content{
+					Items: itemsService,
+				}
+			},
+			spaceID:       "space_id",
+			environmentID: "env_id",
+			want:          "value",
+			assertError:   assert.NoError,
+		},
+		{
+			name:    "lookup with unknown field",
+			pattern: "{{ lookup \"coll_id.item_id.unknown\" }}",
+			setupContent: func(t *testing.T) *content.Content {
+				itemsService := itemsmocks.NewItems(t)
+				itemsService.On("Get", mock.Anything, "space_id", "env_id", "coll_id", "item_id").
 					Return(&items.Item{
-						ID:           "dev",
-						SpaceID:      "space",
-						EnvID:        "env",
-						CollectionID: "secrets",
 						Data: map[string]any{
-							"id":    "dev",
-							"slice": []int{1, 2, 3},
+							"field": "value",
 						},
-					}, nil).Once()
-				return &content.Content{Items: itemsSvc}
-			},
-		},
-		{
-			name:     "lookup function with unknown field",
-			SpaceID:  "space",
-			EnvID:    "env",
-			template: "numbers {{ lookup \"secrets.dev.slice\" }}",
-			data:     "",
-			want:     "numbers <no value>",
-			wantErr:  false,
-			getCnt: func(t *testing.T) *content.Content {
-				itemsSvc := mocksitems.NewItems(t)
-				itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").
-					Return(&items.Item{
-						ID:           "dev",
-						SpaceID:      "space",
-						EnvID:        "env",
-						CollectionID: "secrets",
-						Data:         map[string]any{},
-					}, nil).Once()
-				return &content.Content{Items: itemsSvc}
-			},
-		},
-		{
-			name:     "lookup function not found item",
-			SpaceID:  "space",
-			EnvID:    "env",
-			template: "hello {{ lookup \"secrets.prod.pass\" }}",
-			data:     "",
-			want:     "",
-			wantErr:  true,
-			getCnt: func(t *testing.T) *content.Content {
-				itemsSvc := mocksitems.NewItems(t)
-				itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "prod").
-					Return(nil, errors.New("not found")).Once()
-				return &content.Content{Items: itemsSvc}
-			},
-		},
-		{
-			name:     "system function",
-			SpaceID:  "space",
-			template: "hello {{ system.SpaceID }}",
-			data:     "",
-			want:     "hello space",
-			wantErr:  false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			var cnt *content.Content
-			if tt.getCnt != nil {
-				cnt = tt.getCnt(t)
-			}
-
-			var b *Builder
-			if tt.htmlBuilder {
-				b = NewHTMLBuilder(cnt, tt.SpaceID, tt.EnvID, tt.CollID)
-			} else {
-				b = NewBuilder(cnt, tt.SpaceID, tt.EnvID, tt.CollID)
-			}
-			got, err := b.Execute(tt.template, tt.data)
-			if tt.wantErr {
-				assert.Error(t, err)
-			} else {
-				assert.NoError(t, err)
-			}
-			assert.Equal(t, tt.want, got)
-		})
-	}
-}
-
-func TestBuilder_ExecuteList(t *testing.T) {
-	tests := []struct {
-		name        string
-		templs      []string
-		data        any
-		want        []string
-		htmlBuilder bool
-		assertError assert.ErrorAssertionFunc
-
-		getCnt func(t *testing.T) *content.Content
-	}{
-		{
-			name: "invalid data",
-			templs: []string{
-				"hello {{ .a }}",
-			},
-			data:        "world",
-			want:        nil,
-			assertError: assert.Error,
-		},
-		{
-			name: "invalid template",
-			templs: []string{
-				"hello {{ . ",
-			},
-			data:        "world",
-			want:        nil,
-			assertError: assert.Error,
-		},
-		{
-			name: "empty template",
-			templs: []string{
-				"",
-			},
-			data: "world",
-			want: []string{
-				"",
-			},
-			assertError: assert.NoError,
-		},
-		{
-			name: "empty data",
-			templs: []string{
-				"{{ . }}",
-			},
-			data: "",
-			want: []string{
-				"",
-			},
-			assertError: assert.NoError,
-		},
-		{
-			name: "success",
-			templs: []string{
-				"hello {{ . }}",
-				"world {{ . }}",
-			},
-			data: "world",
-			want: []string{
-				"hello world",
-				"world world",
-			},
-			assertError: assert.NoError,
-		},
-		{
-			name: "success with html builder",
-			templs: []string{
-				"<span class=\"comment\">{{ . }}</span>",
-			},
-			data: "<script>alert(localStorage.getItem('secret'))</script>",
-			want: []string{
-				"<span class=\"comment\">" +
-					"&lt;script&gt;alert(localStorage.getItem(&#39;secret&#39;))&lt;/script&gt;" +
-					"</span>",
-			},
-			htmlBuilder: true,
-			assertError: assert.NoError,
-		},
-		{
-			name: "template as plain text",
-			templs: []string{
-				"hello",
-			},
-			data: "world",
-			want: []string{
-				"hello",
-			},
+					}, nil).
+					Once()
+				return &content.Content{
+					Items: itemsService,
+				}
+			},
+			spaceID:       "space_id",
+			environmentID: "env_id",
+			want:          "<no value>",
+			assertError:   assert.NoError,
+		},
+		{
+			name:    "invalid lookup",
+			pattern: "{{ lookup \"coll_id.item_id\" }}",
+			setupContent: func(t *testing.T) *content.Content {
+				itemsService := itemsmocks.NewItems(t)
+				return &content.Content{
+					Items: itemsService,
+				}
+			},
+			spaceID:       "space_id",
+			environmentID: "env_id",
+			want:          "",
+			assertError:   assert.Error,
+		},
+		{
+			name:        "system space id",
+			pattern:     "{{ system.SpaceID }}",
+			spaceID:     "space_id",
+			want:        "space_id",
 			assertError: assert.NoError,
 		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			var cnt *content.Content
-			if tt.getCnt != nil {
-				cnt = tt.getCnt(t)
-			}
-
-			var b *Builder
-			if tt.htmlBuilder {
-				b = NewHTMLBuilder(cnt, "space_id", "env_id", "coll_id")
-			} else {
-				b = NewBuilder(cnt, "space_id", "env_id", "coll_id")
-			}
-
-			got, err := b.ExecuteList(tt.templs, tt.data)
-			tt.assertError(t, err)
-			assert.Equal(t, tt.want, got)
-		})
-	}
-}
-
-func TestBuilder_ExecuteMap(t *testing.T) {
-	tests := []struct {
-		name        string
-		SpaceID     string
-		EnvID       string
-		templateMap map[string]any
-		data        any
-		want        map[string]any
-		wantErr     bool
-
-		itemsCall func(itemsSvc *mocksitems.Items)
-	}{
-		{
-			name:        "invalid template",
-			SpaceID:     "space",
-			EnvID:       "env",
-			templateMap: map[string]any{"hello": "{{ . }"},
-			data:        "world",
-			want:        nil,
-			wantErr:     true,
-		},
-		{
-			name:        "empty data",
-			SpaceID:     "space",
-			EnvID:       "env",
-			templateMap: map[string]any{},
-			data:        "",
-			want:        map[string]any{},
-			wantErr:     false,
-		},
-		{
-			name:    "one template",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"hello": "{{ . }}",
-			},
-			data: "world",
-			want: map[string]any{
-				"hello": "world",
-			},
-			wantErr: false,
-		},
-		{
-			name:    "one template with many value templates",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"hello": []string{
-					"{{ .foo }}",
-					"{{ .bar }}",
-				},
-			},
-			data: map[string]any{
-				"foo": "hello",
-				"bar": "world",
-			},
-			want: map[string]any{
-				"hello": []string{
-					"hello",
-					"world",
-				},
-			},
-			wantErr: false,
-		},
-		{
-			name:    "many template",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"hello": "{{ . }}",
-				"go":    "{{ . }}",
-			},
-			data: "world",
-			want: map[string]any{
-				"hello": "world",
-				"go":    "world",
-			},
-			wantErr: false,
-		},
-		{
-			name:    "template in key with one value",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"{{ .key }}": "value",
-			},
-			data: map[string]any{
-				"key": "foo",
-			},
-			want: map[string]any{
-				"{{ .key }}": "value",
-			},
-			wantErr: false,
-		},
 		{
-			name:    "template in key with many values",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"{{ .key }}": []string{
-					"value",
-				},
-			},
-			data: map[string]any{
-				"key": "foo",
-			},
-			want: map[string]any{
-				"{{ .key }}": []string{
-					"value",
-				},
-			},
-			wantErr: false,
-		},
-		{
-			name:    "template as plain text",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"a": "b",
-			},
-			data: "world",
-			want: map[string]any{
-				"a": "b",
-			},
-			wantErr: false,
-		},
-		{
-			name:    "template is not template",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"a": "b",
-				"b": []int{1, 2, 3},
-			},
-			data: "world",
-			want: map[string]any{
-				"a": "b",
-				"b": []int{1, 2, 3},
-			},
-			wantErr: false,
-		},
-		{
-			name:    "lookup function",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"hello": "{{ lookup \"secrets.dev.key\" }}",
-			},
-			want: map[string]any{
-				"hello": "1234",
-			},
-			wantErr: false,
-			itemsCall: func(itemsSvc *mocksitems.Items) {
-				itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
-					ID:           "dev",
-					SpaceID:      "space",
-					EnvID:        "env",
-					CollectionID: "secrets",
-					Data: map[string]any{
-						"id":  "dev",
-						"key": "1234",
-					},
-				}, nil).Once()
-			},
-		},
-		{
-			name:        "lookup function with unknown field",
-			SpaceID:     "space",
-			EnvID:       "env",
-			templateMap: map[string]any{"hello": "{{ lookup \"secrets.dev.incorrect\" }}"},
-			want:        map[string]any{"hello": "<no value>"},
-			wantErr:     false,
-			itemsCall: func(itemsSvc *mocksitems.Items) {
-				itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
-					ID:           "dev",
-					SpaceID:      "space",
-					EnvID:        "env",
-					CollectionID: "secrets",
-					Data: map[string]any{
-						"id":  "dev",
-						"key": "1234",
-					},
-				}, nil).Once()
-			},
-		},
-		{
-			name:    "system function",
-			SpaceID: "space",
-			EnvID:   "env",
-			templateMap: map[string]any{
-				"hello": "{{ system.SpaceID }}",
-			},
-			want: map[string]any{
-				"hello": "space",
-			},
-			wantErr: false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			itemsSvc := &mocksitems.Items{}
-			if tt.itemsCall != nil {
-				tt.itemsCall(itemsSvc)
+			name:    "field from system space",
+			pattern: "{{ system.Space.Name }}",
+			setupContent: func(t *testing.T) *content.Content {
+				spacesService := spacesmocks.NewSpaces(t)
+				spacesService.On("Get", mock.Anything, "space_id").
+					Return(&spaces.Space{
+						Name: "name",
+					}, nil).
+					Once()
+				return &content.Content{
+					Spaces: spacesService,
+				}
+			},
+			spaceID:       "space_id",
+			environmentID: "env_id",
+			want:          "name",
+			assertError:   assert.NoError,
+		},
+		{
+			name:          "system environment id",
+			pattern:       "{{ system.EnvID }}",
+			environmentID: "env_id",
+			want:          "env_id",
+			assertError:   assert.NoError,
+		},
+		{
+			name:    "field from system environment",
+			pattern: "{{ system.Environment.ID }}",
+			setupContent: func(t *testing.T) *content.Content {
+				environmentsService := environmentsmocks.NewEnvironments(t)
+				environmentsService.On("Get", mock.Anything, "space_id", "env_id").
+					Return(&environments.Environment{
+						ID: "env_id",
+					}, nil).
+					Once()
+				return &content.Content{
+					Environments: environmentsService,
+				}
+			},
+			spaceID:       "space_id",
+			environmentID: "env_id",
+			want:          "env_id",
+			assertError:   assert.NoError,
+		},
+		{
+			name:         "system collection id",
+			pattern:      "{{ system.CollectionID }}",
+			collectionID: "coll_id",
+			want:         "coll_id",
+			assertError:  assert.NoError,
+		},
+		{
+			name:    "field from system collection",
+			pattern: "{{ system.Collection.ID }}",
+			setupContent: func(t *testing.T) *content.Content {
+				collectionsService := collectionsmocks.NewCollections(t)
+				collectionsService.On("Get", mock.Anything, "space_id", "env_id", "coll_id").
+					Return(&collections.Collection{
+						ID: "coll_id",
+					}, nil).
+					Once()
+				return &content.Content{
+					Collections: collectionsService,
+				}
+			},
+			spaceID:       "space_id",
+			environmentID: "env_id",
+			collectionID:  "coll_id",
+			want:          "coll_id",
+			assertError:   assert.NoError,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			conf := &BuilderConfig{
+				SpaceID:       tc.spaceID,
+				EnvironmentID: tc.environmentID,
+				CollectionID:  tc.collectionID,
 			}
-			b := NewBuilder(&content.Content{Items: itemsSvc}, tt.SpaceID, tt.EnvID, "")
-
-			got, err := b.ExecuteMap(tt.templateMap, tt.data)
-			if tt.wantErr == true {
-				assert.Error(t, err)
-			} else {
-				assert.NoError(t, err)
+			if tc.setupContent != nil {
+				conf.Content = tc.setupContent(t)
 			}
-			assert.Equal(t, tt.want, got)
-			itemsSvc.AssertExpectations(t)
+			builder := NewBuilder(conf)
+			got, err := builder.TextTemplate().Execute(tc.pattern)
+			tc.assertError(t, err)
+			assert.Equal(t, tc.want, got)
 		})
 	}
 }
-
-func TestBuilder_getData(t *testing.T) {
-	t.Run("empty data", func(t *testing.T) {
-		builder := NewBuilder(nil, "space_id", "env_id", "coll_id")
-		assert.Equal(t,
-			map[string]any(nil),
-			builder.getData(),
-		)
-	})
-	t.Run("with data", func(t *testing.T) {
-		builder := NewBuilder(nil, "space_id", "env_id", "coll_id").
-			WithData(map[string]any{
-				"foo": "bar",
-			})
-		assert.Equal(t,
-			map[string]any{
-				"foo": "bar",
-			},
-			builder.getData(),
-		)
-	})
-	t.Run("merge data", func(t *testing.T) {
-		builder := NewBuilder(nil, "space_id", "env_id", "coll_id")
-		assert.Equal(t,
-			map[string]any{
-				"a": 1,
-				"b": 2,
-			},
-			builder.getData(
-				map[string]any{},
-				map[string]any{"a": 1},
-				map[string]any{"b": 2},
-				[]string{"a", "b"},
-				"string",
-			),
-		)
-	})
-	t.Run("merge with original data", func(t *testing.T) {
-		builder := NewBuilder(nil, "space_id", "env_id", "coll_id").
-			WithData(map[string]any{
-				"foo": "bar",
-				"baz": "qux",
-			})
-		assert.Equal(t,
-			map[string]any{
-				"a":   1,
-				"b":   2,
-				"foo": "bar",
-				"baz": "qux",
-			},
-			builder.getData(
-				map[string]any{"a": 1},
-				map[string]any{"b": 2},
-			),
-		)
-	})
-}
diff --git a/template/funcs.go b/template/funcs.go
index 55eab67682f799ca671e1650ebc6a45546722c20..659b9c9dde6d2c5e3789dd71d7466614035c5f23 100644
--- a/template/funcs.go
+++ b/template/funcs.go
@@ -19,7 +19,7 @@ func getLookup(b *Builder) func(string) (any, error) {
 		collectionID := parsedName[0]
 		itemID := parsedName[1]
 		field := parsedName[2]
-		item, err := b.cnt.Items.Get(b.Context(), b.SpaceID, b.EnvID, collectionID, itemID)
+		item, err := b.Content().Items.Get(b.Context(), b.SpaceID(), b.EnvironmentID(), collectionID, itemID)
 		if err != nil {
 			return nil, errors.Wrapf(err, "failed to get \"%s\"")
 		}
diff --git a/template/funcs_test.go b/template/funcs_test.go
index af4a7717328d8d8de3f3426136dfb1d91f0ba135..931d1fbd4d80f2022f2bbec3085f83c2911c4f32 100644
--- a/template/funcs_test.go
+++ b/template/funcs_test.go
@@ -85,7 +85,12 @@ func Test_getLookup(t *testing.T) {
 			if tc.setup != nil {
 				tc.setup(svc)
 			}
-			b := NewBuilder(&content.Content{Items: svc}, "space_id", "env_id", "coll_id")
+			b := NewBuilder(&BuilderConfig{
+				Content:       &content.Content{Items: svc},
+				SpaceID:       "space_id",
+				EnvironmentID: "env_id",
+				CollectionID:  "coll_id",
+			})
 
 			lookup := getLookup(b)
 			got, err := lookup(tc.input)
@@ -100,11 +105,16 @@ func Test_getSystem(t *testing.T) {
 		spacesSvc := mocksspaces.NewSpaces(t)
 		envsSvc := mocksenvs.NewEnvironments(t)
 		collsSvc := mockscolls.NewCollections(t)
-		b := NewBuilder(&content.Content{
-			Spaces:       spacesSvc,
-			Environments: envsSvc,
-			Collections:  collsSvc,
-		}, "space_id", "env_id", "coll_id")
+		b := NewBuilder(&BuilderConfig{
+			Content: &content.Content{
+				Spaces:       spacesSvc,
+				Environments: envsSvc,
+				Collections:  collsSvc,
+			},
+			SpaceID:       "space_id",
+			EnvironmentID: "env_id",
+			CollectionID:  "coll_id",
+		})
 		system := getSystem(b)
 		assert.Equal(t, "space_id", system().SpaceID())
 	})
@@ -112,11 +122,16 @@ func Test_getSystem(t *testing.T) {
 		spacesSvc := mocksspaces.NewSpaces(t)
 		envsSvc := mocksenvs.NewEnvironments(t)
 		collsSvc := mockscolls.NewCollections(t)
-		b := NewBuilder(&content.Content{
-			Spaces:       spacesSvc,
-			Environments: envsSvc,
-			Collections:  collsSvc,
-		}, "space_id", "env_id", "coll_id")
+		b := NewBuilder(&BuilderConfig{
+			Content: &content.Content{
+				Spaces:       spacesSvc,
+				Environments: envsSvc,
+				Collections:  collsSvc,
+			},
+			SpaceID:       "space_id",
+			EnvironmentID: "env_id",
+			CollectionID:  "coll_id",
+		})
 		system := getSystem(b)
 		assert.Equal(t, "env_id", system().EnvID())
 	})
@@ -124,11 +139,16 @@ func Test_getSystem(t *testing.T) {
 		spacesSvc := mocksspaces.NewSpaces(t)
 		envsSvc := mocksenvs.NewEnvironments(t)
 		collsSvc := mockscolls.NewCollections(t)
-		b := NewBuilder(&content.Content{
-			Spaces:       spacesSvc,
-			Environments: envsSvc,
-			Collections:  collsSvc,
-		}, "space_id", "env_id", "coll_id")
+		b := NewBuilder(&BuilderConfig{
+			Content: &content.Content{
+				Spaces:       spacesSvc,
+				Environments: envsSvc,
+				Collections:  collsSvc,
+			},
+			SpaceID:       "space_id",
+			EnvironmentID: "env_id",
+			CollectionID:  "coll_id",
+		})
 		system := getSystem(b)
 		assert.Equal(t, "coll_id", system().CollectionID())
 	})
@@ -144,11 +164,16 @@ func Test_getSystem(t *testing.T) {
 				Once()
 			envsSvc := mocksenvs.NewEnvironments(t)
 			collsSvc := mockscolls.NewCollections(t)
-			b := NewBuilder(&content.Content{
-				Spaces:       spacesSvc,
-				Environments: envsSvc,
-				Collections:  collsSvc,
-			}, "space_id", "env_id", "coll_id")
+			b := NewBuilder(&BuilderConfig{
+				Content: &content.Content{
+					Spaces:       spacesSvc,
+					Environments: envsSvc,
+					Collections:  collsSvc,
+				},
+				SpaceID:       "space_id",
+				EnvironmentID: "env_id",
+				CollectionID:  "coll_id",
+			})
 			system := getSystem(b)
 			got, err := system().Space()
 			require.NoError(t, err)
@@ -165,11 +190,16 @@ func Test_getSystem(t *testing.T) {
 				Once()
 			envsSvc := mocksenvs.NewEnvironments(t)
 			collsSvc := mockscolls.NewCollections(t)
-			b := NewBuilder(&content.Content{
-				Spaces:       spacesSvc,
-				Environments: envsSvc,
-				Collections:  collsSvc,
-			}, "space_id", "env_id", "coll_id")
+			b := NewBuilder(&BuilderConfig{
+				Content: &content.Content{
+					Spaces:       spacesSvc,
+					Environments: envsSvc,
+					Collections:  collsSvc,
+				},
+				SpaceID:       "space_id",
+				EnvironmentID: "env_id",
+				CollectionID:  "coll_id",
+			})
 			system := getSystem(b)
 			got, err := system().Space()
 			require.Error(t, err)
@@ -187,11 +217,16 @@ func Test_getSystem(t *testing.T) {
 				}, nil).
 				Once()
 			collsSvc := mockscolls.NewCollections(t)
-			b := NewBuilder(&content.Content{
-				Spaces:       spacesSvc,
-				Environments: envsSvc,
-				Collections:  collsSvc,
-			}, "space_id", "env_id", "coll_id")
+			b := NewBuilder(&BuilderConfig{
+				Content: &content.Content{
+					Spaces:       spacesSvc,
+					Environments: envsSvc,
+					Collections:  collsSvc,
+				},
+				SpaceID:       "space_id",
+				EnvironmentID: "env_id",
+				CollectionID:  "coll_id",
+			})
 			system := getSystem(b)
 			got, err := system().Environment()
 			require.NoError(t, err)
@@ -207,11 +242,16 @@ func Test_getSystem(t *testing.T) {
 				Return(nil, errors.New("some error")).
 				Once()
 			collsSvc := mockscolls.NewCollections(t)
-			b := NewBuilder(&content.Content{
-				Spaces:       spacesSvc,
-				Environments: envsSvc,
-				Collections:  collsSvc,
-			}, "space_id", "env_id", "coll_id")
+			b := NewBuilder(&BuilderConfig{
+				Content: &content.Content{
+					Spaces:       spacesSvc,
+					Environments: envsSvc,
+					Collections:  collsSvc,
+				},
+				SpaceID:       "space_id",
+				EnvironmentID: "env_id",
+				CollectionID:  "coll_id",
+			})
 			system := getSystem(b)
 			got, err := system().Environment()
 			require.Error(t, err)
@@ -231,11 +271,16 @@ func Test_getSystem(t *testing.T) {
 					Name:    "Collection",
 				}, nil).
 				Once()
-			b := NewBuilder(&content.Content{
-				Spaces:       spacesSvc,
-				Environments: envsSvc,
-				Collections:  collsSvc,
-			}, "space_id", "env_id", "coll_id")
+			b := NewBuilder(&BuilderConfig{
+				Content: &content.Content{
+					Spaces:       spacesSvc,
+					Environments: envsSvc,
+					Collections:  collsSvc,
+				},
+				SpaceID:       "space_id",
+				EnvironmentID: "env_id",
+				CollectionID:  "coll_id",
+			})
 			system := getSystem(b)
 			got, err := system().Collection()
 			require.NoError(t, err)
@@ -253,11 +298,16 @@ func Test_getSystem(t *testing.T) {
 			collsSvc.On("Get", mock.Anything, "space_id", "env_id", "coll_id").
 				Return(nil, errors.New("some error")).
 				Once()
-			b := NewBuilder(&content.Content{
-				Spaces:       spacesSvc,
-				Environments: envsSvc,
-				Collections:  collsSvc,
-			}, "space_id", "env_id", "coll_id")
+			b := NewBuilder(&BuilderConfig{
+				Content: &content.Content{
+					Spaces:       spacesSvc,
+					Environments: envsSvc,
+					Collections:  collsSvc,
+				},
+				SpaceID:       "space_id",
+				EnvironmentID: "env_id",
+				CollectionID:  "coll_id",
+			})
 			system := getSystem(b)
 			got, err := system().Collection()
 			require.Error(t, err)
diff --git a/template/system.go b/template/system.go
index c7dda43f08c852f590cd7cfa16ec00709423c7a3..38ad1a693f758259e516ea749476f382d7eb1f22 100644
--- a/template/system.go
+++ b/template/system.go
@@ -10,44 +10,45 @@ type System struct {
 	builder *Builder
 }
 
-func (s *System) SpaceID() string {
-	return s.builder.SpaceID
-}
-
-func (s *System) EnvID() string {
-	return s.builder.EnvID
-}
-
-func (s *System) CollectionID() string {
-	return s.builder.CollID
-}
-
 func (s *System) Space() (*spaces.Space, error) {
 	if s.builder.space != nil {
 		return s.builder.space, nil
 	}
 
-	space, err := s.builder.cnt.Spaces.Get(s.builder.ctx, s.builder.SpaceID)
+	space, err := s.builder.Content().Spaces.Get(s.builder.Context(), s.builder.SpaceID())
 	s.builder.space = space
 	return s.builder.space, err
 }
 
+func (s *System) SpaceID() string {
+	return s.builder.SpaceID()
+}
+
 func (s *System) Environment() (*environments.Environment, error) {
 	if s.builder.environment != nil {
 		return s.builder.environment, nil
 	}
 
-	env, err := s.builder.cnt.Environments.Get(s.builder.ctx, s.builder.SpaceID, s.builder.EnvID)
+	env, err := s.builder.Content().Environments.Get(s.builder.ctx, s.builder.SpaceID(), s.builder.EnvironmentID())
 	s.builder.environment = env
 	return s.builder.environment, err
 }
 
+func (s *System) EnvID() string {
+	return s.builder.EnvironmentID()
+}
+
 func (s *System) Collection() (*collections.Collection, error) {
 	if s.builder.collection != nil {
 		return s.builder.collection, nil
 	}
 
-	coll, err := s.builder.cnt.Collections.Get(s.builder.ctx, s.builder.SpaceID, s.builder.EnvID, s.builder.CollID)
+	coll, err := s.builder.Content().Collections.Get(s.builder.ctx, s.builder.SpaceID(),
+		s.builder.EnvironmentID(), s.builder.CollectionID())
 	s.builder.collection = coll
 	return s.builder.collection, err
 }
+
+func (s *System) CollectionID() string {
+	return s.builder.CollectionID()
+}
diff --git a/template/template.go b/template/template.go
new file mode 100644
index 0000000000000000000000000000000000000000..74e6c7d6dc89acdadc4f46e52aff091fa78ff839
--- /dev/null
+++ b/template/template.go
@@ -0,0 +1,114 @@
+package template
+
+import (
+	"bytes"
+	html "html/template"
+	"io"
+	"maps"
+	text "text/template"
+)
+
+func NewCommonTemplate(html bool, data map[string]any, funcMap map[string]any) *CommonTemplate {
+	return &CommonTemplate{
+		html:    html,
+		data:    data,
+		funcMap: funcMap,
+	}
+}
+
+type CommonTemplate struct {
+	html    bool
+	data    map[string]any
+	funcMap map[string]any
+}
+
+func (t *CommonTemplate) Execute(pattern string, data ...any) (string, error) {
+	return t.execute(pattern, t.getData(data...))
+}
+
+func (t *CommonTemplate) ExecuteList(patterns []string, data ...any) ([]string, error) {
+	return t.executeList(patterns, t.getData(data...))
+}
+
+func (t *CommonTemplate) ExecuteMap(patternMap map[string]any, data ...any) (map[string]any, error) {
+	result := make(map[string]any, len(patternMap))
+	d := t.getData(data...)
+
+	for key, value := range patternMap {
+		var err error
+		switch v := value.(type) {
+		case string:
+			result[key], err = t.execute(v, d)
+			if err != nil {
+				return nil, err
+			}
+		case []string:
+			result[key], err = t.executeList(v, d)
+			if err != nil {
+				return nil, err
+			}
+		default:
+			result[key] = value
+		}
+	}
+
+	return result, nil
+}
+
+func (t *CommonTemplate) execute(pattern string, data any) (string, error) {
+	var (
+		exec interface {
+			Execute(w io.Writer, data any) error
+		}
+		err error
+	)
+	if t.html {
+		exec, err = html.New("main").Funcs(t.funcMap).Parse(pattern)
+	} else {
+		exec, err = text.New("main").Funcs(t.funcMap).Parse(pattern)
+	}
+	if err != nil {
+		return "", err
+	}
+
+	var buf bytes.Buffer
+	err = exec.Execute(&buf, data)
+	if err != nil {
+		return "", err
+	}
+
+	return buf.String(), nil
+}
+
+func (t *CommonTemplate) executeList(patterns []string, data any) ([]string, error) {
+	result := make([]string, len(patterns))
+
+	var err error
+	for i, pattern := range patterns {
+		result[i], err = t.execute(pattern, data)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return result, nil
+}
+
+func (t *CommonTemplate) getData(data ...any) any {
+	if len(data) == 0 {
+		return t.data
+	}
+
+	m, ok := data[0].(map[string]any)
+	if !ok {
+		return data[0]
+	}
+
+	result := maps.Clone(t.data)
+	if result == nil {
+		result = make(map[string]any)
+	}
+	maps.Copy(result, m)
+
+	return result
+}
diff --git a/template/template_test.go b/template/template_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e54720369b20996b750a76c4205a6067ba21f258
--- /dev/null
+++ b/template/template_test.go
@@ -0,0 +1,452 @@
+package template
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCommonTemplate_Execute(t *testing.T) {
+	for _, tc := range []struct {
+		name           string
+		pattern        string
+		data           map[string]any
+		additionalData []any
+		funcMap        map[string]any
+		wantText       string
+		wantHTML       string
+		assertError    assert.ErrorAssertionFunc
+	}{
+		{
+			name:    "basic",
+			pattern: "{{ .foo }}",
+			data: map[string]any{
+				"foo": "bar",
+			},
+			wantText:    "bar",
+			wantHTML:    "bar",
+			assertError: assert.NoError,
+		},
+		{
+			name:    "additional data",
+			pattern: "{{ . }}",
+			additionalData: []any{
+				"bar",
+			},
+			wantText:    "bar",
+			wantHTML:    "bar",
+			assertError: assert.NoError,
+		},
+		{
+			name:    "map in additional data",
+			pattern: "{{ .foo }}",
+			additionalData: []any{
+				map[string]any{
+					"foo": "bar",
+				},
+			},
+			wantText:    "bar",
+			wantHTML:    "bar",
+			assertError: assert.NoError,
+		},
+		{
+			name:    "html in additional data",
+			pattern: "{{ . }}",
+			additionalData: []any{
+				"<a href=\"https://example.com/\">Link</a>",
+			},
+			wantText:    "<a href=\"https://example.com/\">Link</a>",
+			wantHTML:    "&lt;a href=&#34;https://example.com/&#34;&gt;Link&lt;/a&gt;",
+			assertError: assert.NoError,
+		},
+		{
+			name:        "empty pattern",
+			pattern:     "",
+			wantText:    "",
+			wantHTML:    "",
+			assertError: assert.NoError,
+		},
+		{
+			name:        "plain text",
+			pattern:     "text",
+			wantText:    "text",
+			wantHTML:    "text",
+			assertError: assert.NoError,
+		},
+		{
+			name:    "func map",
+			pattern: "{{ foo }}",
+			funcMap: map[string]any{
+				"foo": func() string {
+					return "bar"
+				},
+			},
+			wantText:    "bar",
+			wantHTML:    "bar",
+			assertError: assert.NoError,
+		},
+		{
+			name:    "data and additional data",
+			pattern: "{{ .foo }},{{ .bar }}",
+			data: map[string]any{
+				"foo": "foo",
+			},
+			additionalData: []any{
+				map[string]any{
+					"bar": "bar",
+				},
+			},
+			wantText:    "foo,bar",
+			wantHTML:    "foo,bar",
+			assertError: assert.NoError,
+		},
+		{
+			name:        "invalid pattern",
+			pattern:     "{{ .",
+			wantText:    "",
+			wantHTML:    "",
+			assertError: assert.Error,
+		},
+		{
+			name:        "no value",
+			pattern:     "{{ .foo }}",
+			wantText:    "<no value>",
+			wantHTML:    "",
+			assertError: assert.NoError,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, enc := range []struct {
+				name     string
+				template *CommonTemplate
+				want     string
+			}{
+				{
+					name:     "text",
+					template: NewCommonTemplate(false, tc.data, tc.funcMap),
+					want:     tc.wantText,
+				},
+				{
+					name:     "html",
+					template: NewCommonTemplate(true, tc.data, tc.funcMap),
+					want:     tc.wantHTML,
+				},
+			} {
+				t.Run(enc.name, func(t *testing.T) {
+					got, err := enc.template.Execute(tc.pattern, tc.additionalData...)
+					tc.assertError(t, err)
+					assert.Equal(t, enc.want, got)
+				})
+			}
+		})
+	}
+}
+
+func TestCommonTemplate_ExecuteList(t *testing.T) {
+	for _, tc := range []struct {
+		name           string
+		patterns       []string
+		data           map[string]any
+		additionalData []any
+		funcMap        map[string]any
+		wantText       []string
+		wantHTML       []string
+		assertError    assert.ErrorAssertionFunc
+	}{
+		{
+			name:     "basic",
+			patterns: []string{"{{ .foo }}", "{{ .bar }}"},
+			data: map[string]any{
+				"foo": "hello",
+				"bar": "world",
+			},
+			wantText:    []string{"hello", "world"},
+			wantHTML:    []string{"hello", "world"},
+			assertError: assert.NoError,
+		},
+		{
+			name:     "additional data",
+			patterns: []string{"{{ . }}"},
+			additionalData: []any{
+				"test",
+			},
+			wantText:    []string{"test"},
+			wantHTML:    []string{"test"},
+			assertError: assert.NoError,
+		},
+		{
+			name:     "map in additional data",
+			patterns: []string{"{{ .foo }}", "{{ .bar }}"},
+			additionalData: []any{
+				map[string]any{
+					"foo": "value1",
+					"bar": "value2",
+				},
+			},
+			wantText:    []string{"value1", "value2"},
+			wantHTML:    []string{"value1", "value2"},
+			assertError: assert.NoError,
+		},
+		{
+			name:     "html escaping",
+			patterns: []string{"{{ . }}"},
+			additionalData: []any{
+				"<script>alert('secret')</script>",
+			},
+			wantText:    []string{"<script>alert('secret')</script>"},
+			wantHTML:    []string{"&lt;script&gt;alert(&#39;secret&#39;)&lt;/script&gt;"},
+			assertError: assert.NoError,
+		},
+		{
+			name:        "empty list",
+			patterns:    []string{},
+			wantText:    []string{},
+			wantHTML:    []string{},
+			assertError: assert.NoError,
+		},
+		{
+			name:        "single plain text",
+			patterns:    []string{"just text"},
+			wantText:    []string{"just text"},
+			wantHTML:    []string{"just text"},
+			assertError: assert.NoError,
+		},
+		{
+			name:     "func map",
+			patterns: []string{"{{ greet }}", "{{ 42 | add }}"},
+			funcMap: map[string]any{
+				"greet": func() string { return "hello" },
+				"add":   func(i int) int { return i + 1 },
+			},
+			wantText:    []string{"hello", "43"},
+			wantHTML:    []string{"hello", "43"},
+			assertError: assert.NoError,
+		},
+		{
+			name:     "data merging",
+			patterns: []string{"{{ .foo }}", "{{ .bar }}"},
+			data: map[string]any{
+				"foo": "base",
+			},
+			additionalData: []any{
+				map[string]any{
+					"bar": "extra",
+				},
+			},
+			wantText:    []string{"base", "extra"},
+			wantHTML:    []string{"base", "extra"},
+			assertError: assert.NoError,
+		},
+		{
+			name:        "invalid pattern",
+			patterns:    []string{"valid", "{{ .invalid "},
+			wantText:    []string(nil),
+			wantHTML:    []string(nil),
+			assertError: assert.Error,
+		},
+		{
+			name:        "unknown value",
+			patterns:    []string{"{{ .unknown }}"},
+			wantText:    []string{"<no value>"},
+			wantHTML:    []string{""},
+			assertError: assert.NoError,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, enc := range []struct {
+				name     string
+				template *CommonTemplate
+				want     []string
+			}{
+				{
+					name:     "text",
+					template: NewCommonTemplate(false, tc.data, tc.funcMap),
+					want:     tc.wantText,
+				},
+				{
+					name:     "html",
+					template: NewCommonTemplate(true, tc.data, tc.funcMap),
+					want:     tc.wantHTML,
+				},
+			} {
+				t.Run(enc.name, func(t *testing.T) {
+					got, err := enc.template.ExecuteList(tc.patterns, tc.additionalData...)
+					tc.assertError(t, err)
+					assert.Equal(t, enc.want, got)
+				})
+			}
+		})
+	}
+}
+
+func TestCommonTemplate_ExecuteMap(t *testing.T) {
+	for _, tc := range []struct {
+		name           string
+		patternMap     map[string]any
+		data           map[string]any
+		additionalData []any
+		funcMap        map[string]any
+		wantText       map[string]any
+		wantHTML       map[string]any
+		assertError    assert.ErrorAssertionFunc
+	}{
+		{
+			name: "mixed types",
+			patternMap: map[string]any{
+				"key1": "{{ .foo }}",
+				"key2": []string{"{{ .bar }}", "static"},
+				"key3": 42,
+			},
+			data: map[string]any{
+				"foo": "value1",
+				"bar": "value2",
+			},
+			wantText: map[string]any{
+				"key1": "value1",
+				"key2": []string{"value2", "static"},
+				"key3": 42,
+			},
+			wantHTML: map[string]any{
+				"key1": "value1",
+				"key2": []string{"value2", "static"},
+				"key3": 42,
+			},
+			assertError: assert.NoError,
+		},
+		{
+			name: "html escaping",
+			patternMap: map[string]any{
+				"html": "{{ .text }}",
+			},
+			data: map[string]any{
+				"text": "<b>content</b>",
+			},
+			wantText: map[string]any{
+				"html": "<b>content</b>",
+			},
+			wantHTML: map[string]any{
+				"html": "&lt;b&gt;content&lt;/b&gt;",
+			},
+			assertError: assert.NoError,
+		},
+		{
+			name: "func map",
+			patternMap: map[string]any{
+				"func":  "{{ add 1 2 }}",
+				"slice": []string{"{{ greet }}"},
+			},
+			funcMap: map[string]any{
+				"add":   func(a, b int) int { return a + b },
+				"greet": func() string { return "hello" },
+			},
+			wantText: map[string]any{
+				"func":  "3",
+				"slice": []string{"hello"},
+			},
+			wantHTML: map[string]any{
+				"func":  "3",
+				"slice": []string{"hello"},
+			},
+			assertError: assert.NoError,
+		},
+		{
+			name: "data merging",
+			patternMap: map[string]any{
+				"merged": "{{ .base }}, {{ .extra }}",
+			},
+			data: map[string]any{
+				"base": "foo",
+			},
+			additionalData: []any{
+				map[string]any{
+					"extra": "bar",
+				},
+			},
+			wantText: map[string]any{
+				"merged": "foo, bar",
+			},
+			wantHTML: map[string]any{
+				"merged": "foo, bar",
+			},
+			assertError: assert.NoError,
+		},
+		{
+			name: "invalid pattern",
+			patternMap: map[string]any{
+				"ok":    "valid",
+				"error": "{{ .invalid",
+			},
+			wantText:    map[string]any(nil),
+			wantHTML:    map[string]any(nil),
+			assertError: assert.Error,
+		},
+		{
+			name: "missing value",
+			patternMap: map[string]any{
+				"missing": "{{ .unknown }}",
+			},
+			wantText: map[string]any{
+				"missing": "<no value>",
+			},
+			wantHTML: map[string]any{
+				"missing": "",
+			},
+			assertError: assert.NoError,
+		},
+		{
+			name: "nested slices",
+			patternMap: map[string]any{
+				"slice": []string{
+					"{{ .item1 }}",
+					"{{ .item2 }}",
+					"plain text",
+				},
+			},
+			additionalData: []any{
+				map[string]any{
+					"item1": "value1",
+					"item2": "value2",
+				},
+			},
+			wantText: map[string]any{
+				"slice": []string{"value1", "value2", "plain text"},
+			},
+			wantHTML: map[string]any{
+				"slice": []string{"value1", "value2", "plain text"},
+			},
+			assertError: assert.NoError,
+		},
+		{
+			name:        "empty map",
+			patternMap:  map[string]any{},
+			wantText:    map[string]any{},
+			wantHTML:    map[string]any{},
+			assertError: assert.NoError,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, enc := range []struct {
+				name     string
+				template *CommonTemplate
+				want     map[string]any
+			}{
+				{
+					name:     "text",
+					template: NewCommonTemplate(false, tc.data, tc.funcMap),
+					want:     tc.wantText,
+				},
+				{
+					name:     "html",
+					template: NewCommonTemplate(true, tc.data, tc.funcMap),
+					want:     tc.wantHTML,
+				},
+			} {
+				t.Run(enc.name, func(t *testing.T) {
+					got, err := enc.template.ExecuteMap(tc.patternMap, tc.additionalData...)
+					tc.assertError(t, err)
+					assert.Equal(t, enc.want, got)
+				})
+			}
+		})
+	}
+}