diff --git a/perxis-proto b/perxis-proto
index 95aca241a0cb17f5e1e9f584b1993bf7b933588e..0627c9f829178bc6de2623a0b6d42964c44de496 160000
--- a/perxis-proto
+++ b/perxis-proto
@@ -1 +1 @@
-Subproject commit 95aca241a0cb17f5e1e9f584b1993bf7b933588e
+Subproject commit 0627c9f829178bc6de2623a0b6d42964c44de496
diff --git a/pkg/schema/executor/executor.go b/pkg/schema/executor/executor.go
deleted file mode 100644
index 73db9730709ec22ba48c11c01f590470cfd1e235..0000000000000000000000000000000000000000
--- a/pkg/schema/executor/executor.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package executor
-
-import (
-	"context"
-	"strings"
-
-	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
-)
-
-type Executor interface {
-	// Execute выполняет подстановку значений.
-	Execute(ctx context.Context, input string) (string, error)
-
-	// IsCanceled проверяет, было ли выполнение отменено.
-	IsCanceled() bool
-}
-
-// Executable определяет опции полей, использующие Executor для подстановки значений.
-type Executable interface {
-	Execute(ctx context.Context, exec Executor, field *field.Field) error
-}
-
-// ExecuteAll последовательно выполняет Execute для каждого элемента inputs.
-// Возвращает собранные непустые результаты (игнорируя пустые строки) или первую возникшую ошибку.
-func ExecuteAll(ctx context.Context, exec Executor, inputs []string) ([]string, error) {
-	var result []string
-	for _, input := range inputs {
-		output, err := exec.Execute(ctx, input)
-		if err != nil {
-			return nil, err
-		}
-		if strings.TrimSpace(output) == "" {
-			continue
-		}
-		result = append(result, output)
-	}
-	return result, nil
-}
-
-func Execute(ctx context.Context, w field.Walker, exec Executor) error {
-	_, _, err := w.Walk(ctx,
-		nil,
-		func(ctx context.Context, fld *field.Field, _ any) (field.WalkFuncResult, error) {
-			var (
-				result field.WalkFuncResult
-				err    error
-			)
-			enabled, _ := fld.IsEnabled(ctx)
-			if !enabled {
-				result.Stop = true
-				return result, err
-			}
-
-			for _, op := range fld.Options {
-				executable, ok := op.(Executable)
-				if !ok {
-					continue
-				}
-				err = executable.Execute(ctx, exec, fld)
-				if err != nil {
-					return result, err
-				}
-			}
-
-			return result, err
-		},
-		field.WalkSchema(),
-	)
-
-	return err
-}
-
-//nolint:gochecknoinits // init нужен для регистрации опций
-func init() {
-	field.RegisterOption(CollectionFilter{})
-	field.RegisterOption(ItemFilter{})
-}
diff --git a/pkg/schema/executor/template_executor.go b/pkg/schema/executor/template_executor.go
deleted file mode 100644
index 98878e64987489105530d2edf5b5db8c962f369d..0000000000000000000000000000000000000000
--- a/pkg/schema/executor/template_executor.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package executor
-
-import (
-	"bytes"
-	"context"
-	"sync"
-	"text/template"
-
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
-)
-
-type TemplateExecutor struct {
-	data    map[string]any
-	funcMap template.FuncMap
-}
-
-func NewTemplateExecutor(data map[string]any) *TemplateExecutor {
-	te := &TemplateExecutor{
-		data: data,
-	}
-	te.init()
-	return te
-}
-
-func (exec *TemplateExecutor) init() {
-	if exec.data == nil {
-		exec.data = make(map[string]any)
-	}
-	delete(exec.data, "Error")
-
-	exec.funcMap = template.FuncMap{
-		"error": exec.errorFunc,
-	}
-}
-
-func (exec *TemplateExecutor) Execute(_ context.Context, input string) (string, error) {
-	templ, err := template.New("template_executor").Funcs(exec.funcMap).Parse(input)
-	if err != nil {
-		return "", err
-	}
-
-	buf := getBuffer()
-	defer releaseBuffer(buf)
-
-	err = templ.Execute(buf, exec.data)
-	if err != nil {
-		return "", err
-	}
-
-	output := buf.String()
-	if output == "<no value>" {
-		return "", nil
-	}
-
-	return output, nil
-}
-
-// IsCanceled возвращает true, если в процессе выполнения шаблона была вызвана
-// шаблонная функция error.
-func (exec *TemplateExecutor) IsCanceled() bool {
-	_, ok := exec.data["Error"]
-	return ok
-}
-
-func (exec *TemplateExecutor) errorFunc(text string) (string, error) {
-	exec.data["Error"] = text
-	return "", errors.New(text)
-}
-
-//nolint:gochecknoglobals // Доступ к пулу необходим на глобальном уровне.
-var bufferPool = &sync.Pool{
-	New: func() any {
-		return new(bytes.Buffer)
-	},
-}
-
-func getBuffer() *bytes.Buffer {
-	buf, _ := bufferPool.Get().(*bytes.Buffer)
-	return buf
-}
-
-func releaseBuffer(buf *bytes.Buffer) {
-	const maxBufCap = 1024
-	if buf.Cap() > maxBufCap {
-		return
-	}
-	buf.Reset()
-	bufferPool.Put(buf)
-}
diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go
index 7929b5ebd2fae325f4c295bbff67e42ebd18ed38..8f9c3543a529209c98a617388e2a75016490d097 100644
--- a/pkg/schema/schema.go
+++ b/pkg/schema/schema.go
@@ -6,9 +6,9 @@ import (
 
 	"git.perx.ru/perxis/perxis-go/pkg/errors"
 	"git.perx.ru/perxis/perxis-go/pkg/expr"
-	"git.perx.ru/perxis/perxis-go/pkg/schema/executor"
 	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
 	"git.perx.ru/perxis/perxis-go/pkg/schema/modify"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/template"
 	"git.perx.ru/perxis/perxis-go/pkg/schema/validate"
 )
 
@@ -196,6 +196,42 @@ func (s *Schema) ToValue(ctx context.Context, data map[string]interface{}) (res
 	return data, err
 }
 
+func (s *Schema) ResolveFieldsTemplates(ctx context.Context, exec template.Executor) (*Schema, error) {
+	resolved := s.Clone(false)
+
+	_, _, err := resolved.Walk(ctx,
+		nil,
+		func(ctx context.Context, fld *field.Field, _ any) (field.WalkFuncResult, error) {
+			var result field.WalkFuncResult
+
+			enabled, _ := fld.IsEnabled(ctx)
+			if !enabled {
+				result.Stop = true
+				return result, nil
+			}
+
+			for _, op := range fld.Options {
+				executable, ok := op.(template.Executable)
+				if !ok {
+					continue
+				}
+				err := executable.Execute(exec, fld)
+				if err != nil {
+					return result, err
+				}
+			}
+
+			return result, nil
+		},
+		field.WalkSchema(),
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	return resolved, nil
+}
+
 type parentFieldCtxKey struct{}
 
 func (s *Schema) Introspect(ctx context.Context, data map[string]interface{}) (map[string]interface{}, *Schema, error) {
@@ -267,20 +303,6 @@ func (s *Schema) Introspect(ctx context.Context, data map[string]interface{}) (m
 	return val, mutatedSchema, nil
 }
 
-func (s *Schema) Execute(ctx context.Context, exec executor.Executor) (*Schema, error) {
-	if err := s.Load(ctx); err != nil {
-		return nil, err
-	}
-
-	clone := s.Clone(false)
-	err := executor.Execute(ctx, clone, exec)
-	if err != nil {
-		return nil, err
-	}
-
-	return clone, nil
-}
-
 // GetEnum возвращает список опций перечисления для поля
 func (s *Schema) GetEnum(fieldPath string) []validate.EnumOpt {
 	f := s.Field.GetField(fieldPath)
diff --git a/pkg/schema/schema_test.go b/pkg/schema/schema_test.go
index 6779ca60802fd6624f06a4aeb74fa1d901ec95d0..dbedf48f9667c348c0898b73a3d308312aede6c6 100644
--- a/pkg/schema/schema_test.go
+++ b/pkg/schema/schema_test.go
@@ -1,13 +1,10 @@
 package schema
 
 import (
-	"context"
 	"testing"
 
-	"git.perx.ru/perxis/perxis-go/pkg/schema/executor"
 	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 )
 
 func TestSchema_Clone(t *testing.T) {
@@ -19,47 +16,3 @@ func TestSchema_Clone(t *testing.T) {
 	fld = f.Clone(false)
 	assert.NotNil(t, fld.State)
 }
-
-func TestSchema_Execute(t *testing.T) {
-	sch := New("layout", field.String().AddOptions(
-		executor.ItemFilter{
-			Query: []string{
-				`icontains(group, "{{ .Collection.ID }}")`,
-				`{{ if .Item.Data.name -}} name == "{{ .Item.Data.name }}" {{- else }} {{ error "Пожалуйста, укажите значение для поля \"name\"" }} {{ end }}`,
-				`age >= {{ .Item.Data.age }}`,
-				`{{ if ge .Item.Data.age 18 -}} adult == true {{- end }}`,
-			},
-			QueryError: "{{ .Error }}",
-		},
-	))
-
-	executed, err := sch.Execute(context.Background(), executor.NewTemplateExecutor(map[string]any{
-		"Item": map[string]any{
-			"Data": map[string]any{
-				"age":  18,
-				"name": "John",
-			},
-		},
-		"Collection": map[string]any{
-			"ID": "coll_id",
-		},
-	}))
-	require.NoError(t, err)
-	executed = executed.ClearState()
-
-	want := New("layout", field.String().AddOptions(
-		executor.ItemFilter{
-			Query: []string{
-				`icontains(group, "coll_id")`,
-				`name == "John"`,
-				`age >= 18`,
-				`adult == true`,
-			},
-		},
-	))
-	err = want.Load(context.Background())
-	want = want.ClearState()
-
-	require.NoError(t, err)
-	assert.Equal(t, want, executed)
-}
diff --git a/pkg/schema/executor/collection_filter.go b/pkg/schema/template/collection_filter.go
similarity index 77%
rename from pkg/schema/executor/collection_filter.go
rename to pkg/schema/template/collection_filter.go
index 283a92c52dadf692e1efdad98774a02336c34bf7..0ab7178f66e8d67d2bf9a21d78b5cce129214561 100644
--- a/pkg/schema/executor/collection_filter.go
+++ b/pkg/schema/template/collection_filter.go
@@ -1,8 +1,6 @@
-package executor
+package template
 
 import (
-	"context"
-
 	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
 )
 
@@ -28,20 +26,19 @@ func (opt CollectionFilter) GetName() string {
 	return "collection_filter"
 }
 
-func (opt CollectionFilter) Execute(ctx context.Context, exec Executor, fld *field.Field) error {
+func (opt CollectionFilter) Execute(exec Executor, fld *field.Field) error {
 	var err error
-
-	opt.ID, err = ExecuteAll(ctx, exec, opt.ID)
+	opt.ID, err = exec.ExecuteList(opt.ID)
 	if err != nil && !exec.IsCanceled() {
 		return err
 	}
 
-	opt.Name, err = ExecuteAll(ctx, exec, opt.Name)
+	opt.Name, err = exec.ExecuteList(opt.Name)
 	if err != nil && !exec.IsCanceled() {
 		return err
 	}
 
-	opt.Tag, err = ExecuteAll(ctx, exec, opt.Tag)
+	opt.Tag, err = exec.ExecuteList(opt.Tag)
 	if err != nil && !exec.IsCanceled() {
 		return err
 	}
diff --git a/pkg/schema/executor/collection_filter_test.go b/pkg/schema/template/collection_filter_test.go
similarity index 66%
rename from pkg/schema/executor/collection_filter_test.go
rename to pkg/schema/template/collection_filter_test.go
index 470dd5132aa120e6dea48c6857c24df9431a64a6..4af61b2295d205e1076ebc1237a79dd2fc0afee6 100644
--- a/pkg/schema/executor/collection_filter_test.go
+++ b/pkg/schema/template/collection_filter_test.go
@@ -1,7 +1,6 @@
-package executor
+package template
 
 import (
-	"context"
 	"testing"
 
 	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
@@ -12,13 +11,17 @@ func TestCollectionFilterExecute(t *testing.T) {
 	tests := []struct {
 		name      string
 		input     CollectionFilter
-		want      CollectionFilter
+		want      *CollectionFilter
 		assertErr assert.ErrorAssertionFunc
 	}{
 		{
-			name:      "empty",
-			input:     CollectionFilter{},
-			want:      CollectionFilter{},
+			name:  "empty",
+			input: CollectionFilter{},
+			want: &CollectionFilter{
+				ID:   []string{},
+				Name: []string{},
+				Tag:  []string{},
+			},
 			assertErr: assert.NoError,
 		},
 		{
@@ -28,10 +31,12 @@ func TestCollectionFilterExecute(t *testing.T) {
 					"{{ .data.id }}",
 				},
 			},
-			want: CollectionFilter{
+			want: &CollectionFilter{
 				ID: []string{
 					"adebcfg",
 				},
+				Name: []string{},
+				Tag:  []string{},
 			},
 			assertErr: assert.NoError,
 		},
@@ -42,10 +47,12 @@ func TestCollectionFilterExecute(t *testing.T) {
 					"{{ .data.name }}",
 				},
 			},
-			want: CollectionFilter{
+			want: &CollectionFilter{
 				Name: []string{
 					"John",
 				},
+				ID:  []string{},
+				Tag: []string{},
 			},
 			assertErr: assert.NoError,
 		},
@@ -56,10 +63,12 @@ func TestCollectionFilterExecute(t *testing.T) {
 					"{{ .data.layout }}",
 				},
 			},
-			want: CollectionFilter{
+			want: &CollectionFilter{
 				Tag: []string{
 					"post",
 				},
+				ID:   []string{},
+				Name: []string{},
 			},
 			assertErr: assert.NoError,
 		},
@@ -70,10 +79,12 @@ func TestCollectionFilterExecute(t *testing.T) {
 					"tag",
 				},
 			},
-			want: CollectionFilter{
+			want: &CollectionFilter{
 				Tag: []string{
 					"tag",
 				},
+				ID:   []string{},
+				Name: []string{},
 			},
 			assertErr: assert.NoError,
 		},
@@ -84,14 +95,17 @@ func TestCollectionFilterExecute(t *testing.T) {
 					`{{ error "some error" }}`,
 				},
 			},
-			want:      CollectionFilter{},
+			want: &CollectionFilter{
+				ID:   []string{},
+				Name: []string{},
+			},
 			assertErr: assert.NoError,
 		},
 	}
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			exec := NewTemplateExecutor(map[string]any{
+			exec := newTestExecutor(map[string]any{
 				"data": map[string]any{
 					"id":     "adebcfg",
 					"name":   "John",
@@ -99,11 +113,14 @@ func TestCollectionFilterExecute(t *testing.T) {
 				},
 			})
 
-			fld := field.String().AddOptions(tc.input)
-			err := Execute(context.Background(), fld, exec)
+			fld := field.String()
+			err := tc.input.Execute(exec, fld)
 			tc.assertErr(t, err)
 
-			want := field.String().AddOptions(tc.want)
+			want := field.String()
+			if tc.want != nil {
+				want = want.AddOptions(*tc.want)
+			}
 			assert.Equal(t, want, fld)
 		})
 	}
diff --git a/pkg/schema/executor/item_filter.go b/pkg/schema/template/item_filter.go
similarity index 78%
rename from pkg/schema/executor/item_filter.go
rename to pkg/schema/template/item_filter.go
index bde2499b6fd99f6ca911bf702ff29eb7d09dc953..713ca1c2c4a5005103d541db1dfb27a923e04020 100644
--- a/pkg/schema/executor/item_filter.go
+++ b/pkg/schema/template/item_filter.go
@@ -1,8 +1,6 @@
-package executor
+package template
 
 import (
-	"context"
-
 	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
 )
 
@@ -26,15 +24,14 @@ func (opt ItemFilter) GetName() string {
 	return "item_filter"
 }
 
-func (opt ItemFilter) Execute(ctx context.Context, exec Executor, fld *field.Field) error {
+func (opt ItemFilter) Execute(exec Executor, fld *field.Field) error {
 	var err error
-
-	opt.Query, err = ExecuteAll(ctx, exec, opt.Query)
+	opt.Query, err = exec.ExecuteList(opt.Query)
 	if err != nil && !exec.IsCanceled() {
 		return err
 	}
 
-	opt.QueryError, err = exec.Execute(ctx, opt.QueryError)
+	opt.QueryError, err = exec.Execute(opt.QueryError)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/schema/executor/item_filter_test.go b/pkg/schema/template/item_filter_test.go
similarity index 80%
rename from pkg/schema/executor/item_filter_test.go
rename to pkg/schema/template/item_filter_test.go
index f4f631598f196492660f60334c4ba826c8312b1c..701b4fa900a9a18907cca01b2897734d1932fad4 100644
--- a/pkg/schema/executor/item_filter_test.go
+++ b/pkg/schema/template/item_filter_test.go
@@ -1,7 +1,6 @@
-package executor
+package template
 
 import (
-	"context"
 	"testing"
 
 	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
@@ -12,13 +11,15 @@ func TestItemFilter_Execute(t *testing.T) {
 	tests := []struct {
 		name      string
 		input     ItemFilter
-		want      ItemFilter
+		want      *ItemFilter
 		assertErr assert.ErrorAssertionFunc
 	}{
 		{
-			name:      "empty",
-			input:     ItemFilter{},
-			want:      ItemFilter{},
+			name:  "empty",
+			input: ItemFilter{},
+			want: &ItemFilter{
+				Query: []string{},
+			},
 			assertErr: assert.NoError,
 		},
 		{
@@ -31,7 +32,7 @@ func TestItemFilter_Execute(t *testing.T) {
 					``,
 				},
 			},
-			want: ItemFilter{
+			want: &ItemFilter{
 				Query: []string{
 					`adebcfg`,
 					`first_name == "John"`,
@@ -47,7 +48,7 @@ func TestItemFilter_Execute(t *testing.T) {
 					`{{ if .data.last_name -}} last_name == "{{ .data.last_name }}" {{- else -}} first_name == "{{ .data.first_name }}" {{- end }}`,
 				},
 			},
-			want: ItemFilter{
+			want: &ItemFilter{
 				Query: []string{
 					`first_name == "John"`,
 					`last_name == "Smith"`,
@@ -64,7 +65,7 @@ func TestItemFilter_Execute(t *testing.T) {
 				},
 				QueryError: `{{ .Error }}`,
 			},
-			want: ItemFilter{
+			want: &ItemFilter{
 				QueryError: `Пожалуйста, укажите значение для поля "login"`,
 			},
 			assertErr: assert.NoError,
@@ -76,18 +77,13 @@ func TestItemFilter_Execute(t *testing.T) {
 					`{{ if `,
 				},
 			},
-			want: ItemFilter{
-				Query: []string{
-					`{{ if `,
-				},
-			},
 			assertErr: assert.Error,
 		},
 	}
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			exec := NewTemplateExecutor(map[string]any{
+			exec := newTestExecutor(map[string]any{
 				"data": map[string]any{
 					"id":         "adebcfg",
 					"first_name": "John",
@@ -95,11 +91,14 @@ func TestItemFilter_Execute(t *testing.T) {
 				},
 			})
 
-			fld := field.String().AddOptions(tc.input)
-			err := Execute(context.Background(), fld, exec)
+			fld := field.String()
+			err := tc.input.Execute(exec, fld)
 			tc.assertErr(t, err)
 
-			want := field.String().AddOptions(tc.want)
+			want := field.String()
+			if tc.want != nil {
+				want = want.AddOptions(*tc.want)
+			}
 			assert.Equal(t, want, fld)
 		})
 	}
diff --git a/pkg/schema/template/template.go b/pkg/schema/template/template.go
new file mode 100644
index 0000000000000000000000000000000000000000..bfd9c277f4dea6d913187158e84d75b854b0996e
--- /dev/null
+++ b/pkg/schema/template/template.go
@@ -0,0 +1,21 @@
+package template
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
+)
+
+type Executor interface {
+	Execute(input string, data ...any) (string, error)
+	ExecuteList(inputs []string, data ...any) ([]string, error)
+	IsCanceled() bool
+}
+
+type Executable interface {
+	Execute(exec Executor, fld *field.Field) error
+}
+
+//nolint:gochecknoinits // init нужен для регистрации опций
+func init() {
+	field.RegisterOption(CollectionFilter{})
+	field.RegisterOption(ItemFilter{})
+}
diff --git a/pkg/schema/template/template_test.go b/pkg/schema/template/template_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b753fab2d29fc3a8db3eb9ac96fb1de39e1e638e
--- /dev/null
+++ b/pkg/schema/template/template_test.go
@@ -0,0 +1,84 @@
+package template
+
+import (
+	"bytes"
+	"text/template"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+type testExecutor struct {
+	data map[string]any
+}
+
+func newTestExecutor(data map[string]any) *testExecutor {
+	return &testExecutor{
+		data: data,
+	}
+}
+
+func (exec *testExecutor) Execute(input string, _ ...any) (string, error) {
+	templ, err := exec.template().Parse(input)
+	if err != nil {
+		return "", err
+	}
+
+	var buf bytes.Buffer
+	err = templ.Execute(&buf, exec.data)
+	if err != nil {
+		return "", err
+	}
+
+	output := buf.String()
+	if output == "<no value>" {
+		return "", nil
+	}
+
+	return output, nil
+}
+
+func (exec *testExecutor) ExecuteList(inputs []string, _ ...any) ([]string, error) {
+	templ := exec.template()
+
+	result := make([]string, 0, len(inputs))
+
+	var buf bytes.Buffer
+	for _, input := range inputs {
+		t, err := templ.Parse(input)
+		if err != nil {
+			return nil, err
+		}
+
+		buf.Reset()
+		err = t.Execute(&buf, exec.data)
+		if err != nil {
+			return nil, err
+		}
+
+		output := buf.String()
+		if output == "" || output == "<no value>" {
+			continue
+		}
+
+		result = append(result, output)
+	}
+
+	return result, nil
+}
+
+func (exec *testExecutor) IsCanceled() bool {
+	_, ok := exec.data["Error"]
+	return ok
+}
+
+func (exec *testExecutor) template() *template.Template {
+	return template.New("test_executor").Funcs(template.FuncMap{
+		"error": func(text string) (string, error) {
+			if exec.data == nil {
+				exec.data = make(map[string]any)
+			}
+			exec.data["Error"] = text
+			return "", errors.New(text)
+		},
+	})
+}
diff --git a/template/builder.go b/template/builder.go
index 64530be62337de21dc5b93956df261ce5211a302..34faf1b05a94469b0de1061e189f40423eb8c8e3 100644
--- a/template/builder.go
+++ b/template/builder.go
@@ -3,6 +3,7 @@ package template
 import (
 	"bytes"
 	"context"
+	"sync"
 	"text/template"
 
 	"git.perx.ru/perxis/perxis-go/pkg/collections"
@@ -11,6 +12,16 @@ import (
 	"git.perx.ru/perxis/perxis-go/pkg/spaces"
 )
 
+type Option func(*Builder)
+
+// WithStripEmptyValue заменяет "<no value>" на пустую строку при одиночной обработке,
+// при множественной обработке удаляет строки с "<no value>" вместе с пустыми строками из результата.
+func WithStripEmptyValue() Option {
+	return func(builder *Builder) {
+		builder.StripEmptyValue = true
+	}
+}
+
 type Builder struct {
 	ctx     context.Context
 	cnt     *content.Content
@@ -19,26 +30,36 @@ type Builder struct {
 	CollID  string
 	data    map[string]interface{}
 
+	// Опции билдера
+	StripEmptyValue bool
+
 	// Для кеширования запросов
 	space       *spaces.Space
 	environment *environments.Environment
 	collection  *collections.Collection
 }
 
-func NewBuilder(cnt *content.Content, space, env, col string) *Builder {
-	return &Builder{
+func NewBuilder(cnt *content.Content, space, env, col string, opts ...Option) *Builder {
+	b := &Builder{
 		ctx:     context.Background(),
 		cnt:     cnt,
 		SpaceID: space,
 		EnvID:   env,
 		CollID:  col,
 	}
+
+	for _, opt := range opts {
+		opt(b)
+	}
+
+	return b
 }
 
 func (b *Builder) getFuncs() template.FuncMap {
 	return template.FuncMap{
 		"lookup": getLookup(b),
 		"system": getSystem(b),
+		"error":  getErrorFunc(b),
 	}
 }
 
@@ -89,23 +110,34 @@ func (b *Builder) Template() *template.Template {
 }
 
 func (b *Builder) Execute(str string, data ...any) (string, error) {
-	t := b.Template()
-	buf := new(bytes.Buffer)
-	t, err := t.Parse(str)
+	t, err := b.Template().Parse(str)
 	if err != nil {
 		return "", err
 	}
+
+	buf := getBuffer()
+	defer releaseBuffer(buf)
+
 	if err = t.Execute(buf, b.getData(data...)); err != nil {
 		return "", err
 	}
-	return buf.String(), nil
+
+	output := buf.String()
+	if b.StripEmptyValue && output == "<no value>" {
+		output = ""
+	}
+
+	return output, nil
 }
 
 func (b *Builder) ExecuteList(str []string, data ...any) ([]string, error) {
 	t := b.Template()
-	result := make([]string, len(str))
-	buffer := new(bytes.Buffer)
-	for i, tmpl := range str {
+	result := make([]string, 0, len(str))
+
+	buf := getBuffer()
+	defer releaseBuffer(buf)
+
+	for _, tmpl := range str {
 		if tmpl == "" {
 			continue
 		}
@@ -113,12 +145,20 @@ func (b *Builder) ExecuteList(str []string, data ...any) ([]string, error) {
 		if err != nil {
 			return []string{}, err
 		}
-		if err = t.Execute(buffer, b.getData(data...)); err != nil {
+
+		buf.Reset()
+		if err = t.Execute(buf, b.getData(data...)); err != nil {
 			return []string{}, err
 		}
-		result[i] = buffer.String()
-		buffer.Reset()
+
+		output := buf.String()
+		if b.StripEmptyValue && (output == "" || output == "<no value>") {
+			continue
+		}
+
+		result = append(result, output)
 	}
+
 	return result, nil
 }
 
@@ -150,6 +190,13 @@ func (b *Builder) ExecuteMap(str map[string]interface{}, data ...any) (map[strin
 	return result, nil
 }
 
+// IsCanceled возвращает true, если в процессе выполнения шаблона была вызвана
+// шаблонная функция error.
+func (b *Builder) IsCanceled() bool {
+	_, ok := b.data["Error"]
+	return ok
+}
+
 func (b *Builder) getData(data ...any) any {
 	if len(data) == 0 {
 		return b.data
@@ -177,3 +224,20 @@ func mergeMaps(in ...map[string]interface{}) map[string]interface{} {
 	}
 	return out
 }
+
+//nolint:gochecknoglobals // Доступ к пулу необходим на глобальном уровне.
+var bufferPool = &sync.Pool{
+	New: func() any {
+		return new(bytes.Buffer)
+	},
+}
+
+func getBuffer() *bytes.Buffer {
+	buf, _ := bufferPool.Get().(*bytes.Buffer)
+	return buf
+}
+
+func releaseBuffer(buf *bytes.Buffer) {
+	buf.Reset()
+	bufferPool.Put(buf)
+}
diff --git a/template/funcs.go b/template/funcs.go
index 0c320ad139e964f002b691ce097b24e70e6cfaf3..b1cf830e305788ee511029d262c8822e7e62ce93 100644
--- a/template/funcs.go
+++ b/template/funcs.go
@@ -41,3 +41,15 @@ func getSystem(b *Builder) any {
 		return &System{builder: b}
 	}
 }
+
+// getErrorFunc возвращает функцию, которая устанавливает сообщение об ошибке с заданным текстом.
+// Текст ошибки можно использовать в других шаблонах с помощью конструкции {{ .Error }}.
+func getErrorFunc(b *Builder) func(string) (string, error) {
+	return func(text string) (string, error) {
+		if b.data == nil {
+			b.data = make(map[string]any)
+		}
+		b.data["Error"] = text
+		return "", errors.New(text)
+	}
+}