diff --git a/src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt b/src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt
index 465a8ecf..70eb2976 100644
--- a/src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt
@@ -47,3 +47,13 @@ NRedisStack.Search.SearchResult.Warnings.get -> string![]!
NRedisStack.Search.AggregationResult.Warnings.get -> string![]!
NRedisStack.Search.HybridSearchResult.Warnings.get -> string![]!
NRedisStack.Search.AggregationRequest.AddScores() -> NRedisStack.Search.AggregationRequest!
+NRedisStack.Search.Aggregation.CollectReducer
+override NRedisStack.Search.Aggregation.CollectReducer.Name.get -> string!
+NRedisStack.Search.Aggregation.CollectReducer.Fields(params string![]! fields) -> NRedisStack.Search.Aggregation.CollectReducer!
+NRedisStack.Search.Aggregation.CollectReducer.FieldsAll() -> NRedisStack.Search.Aggregation.CollectReducer!
+NRedisStack.Search.Aggregation.CollectReducer.SortBy(params NRedisStack.Search.Aggregation.SortedField![]! fields) -> NRedisStack.Search.Aggregation.CollectReducer!
+NRedisStack.Search.Aggregation.CollectReducer.SortByAsc(string! field) -> NRedisStack.Search.Aggregation.CollectReducer!
+NRedisStack.Search.Aggregation.CollectReducer.SortByDesc(string! field) -> NRedisStack.Search.Aggregation.CollectReducer!
+NRedisStack.Search.Aggregation.CollectReducer.Limit(int count) -> NRedisStack.Search.Aggregation.CollectReducer!
+NRedisStack.Search.Aggregation.CollectReducer.Limit(int offset, int count) -> NRedisStack.Search.Aggregation.CollectReducer!
+static NRedisStack.Search.Aggregation.Reducers.Collect() -> NRedisStack.Search.Aggregation.CollectReducer!
\ No newline at end of file
diff --git a/src/NRedisStack/Search/CollectReducer.cs b/src/NRedisStack/Search/CollectReducer.cs
new file mode 100644
index 00000000..878697cc
--- /dev/null
+++ b/src/NRedisStack/Search/CollectReducer.cs
@@ -0,0 +1,167 @@
+using NRedisStack.Search.Literals;
+
+namespace NRedisStack.Search.Aggregation;
+
+///
+/// Reducer for REDUCE COLLECT, which gathers per-document projections within a GROUPBY group and returns them
+/// as an array of per-entry maps under the reducer alias, optionally sorted and bounded.
+///
+/// The grammar implemented by this reducer is:
+///
+/// REDUCE COLLECT <narg>
+/// FIELDS ( * | <num_fields> <@field> [<@field> ...] )
+/// [SORTBY <narg> <@field> [ASC|DESC] [<@field> [ASC|DESC] ...]]
+/// [LIMIT <offset> <count>]
+/// [AS <alias>]
+///
+///
+///
+/// Field and sort-key names are referenced with an @ prefix on the wire (this builder adds it automatically when it is
+/// missing). The output map keys are the bare names. FIELDS * projects whatever the pipeline has materialized at the
+/// COLLECT stage; it does not implicitly fetch the full document.
+///
+///
+/// Configure the reducer fully before attaching it to a GROUPBY:
+/// serializes the reducer immediately, so builder calls made after that point cannot reach the wire and throw
+/// .
+///
+///
+/// Experimental. Both the underlying Redis Search feature and this API may change. COLLECT is gated behind
+/// search-enable-unstable-features; enable it on the server (for example via
+/// CONFIG SET search-enable-unstable-features yes) before issuing aggregations that use this reducer, otherwise the
+/// server replies with an error.
+///
+///
+///
+public sealed class CollectReducer : Reducer
+{
+ private bool _allFields = false;
+ private readonly List _fields = new List();
+ private readonly List _sortFields = new List();
+ private int? _limitOffset;
+ private int? _limitCount;
+ private bool _serialized;
+
+ internal CollectReducer() : base(null) { }
+
+ public override string Name => "COLLECT";
+
+ ///
+ /// Project the named fields for every document in the group. Names may be supplied with or without a leading @;
+ /// the builder normalizes each to a single @<name> on the wire. Use @__key or ordinary document field
+ /// names. Mutually exclusive with ; may be called multiple times to append further fields.
+ ///
+ public CollectReducer Fields(params string[] fields)
+ {
+ EnsureMutable();
+ if (_allFields)
+ throw new InvalidOperationException("REDUCE COLLECT cannot mix FIELDS * with explicit field names");
+ _fields.AddRange(fields);
+ return this;
+ }
+
+ ///
+ /// Project every field present in the pipeline at the COLLECT stage (FIELDS *). Per the COLLECT
+ /// specification, * does not trigger an implicit load — fields must already be in the pipeline (typically via
+ /// LOAD * or because they are grouping keys / reducer aliases). Mutually exclusive with .
+ ///
+ public CollectReducer FieldsAll()
+ {
+ EnsureMutable();
+ if (_fields.Count > 0)
+ throw new InvalidOperationException("REDUCE COLLECT cannot mix FIELDS * with explicit field names");
+ _allFields = true;
+ return this;
+ }
+
+ ///
+ /// In-group sort by one or more fields. May be called multiple times to append further sort keys.
+ ///
+ public CollectReducer SortBy(params SortedField[] fields)
+ {
+ EnsureMutable();
+ _sortFields.AddRange(fields);
+ return this;
+ }
+
+ /// Convenience for SortBy(SortedField.Asc(field)).
+ public CollectReducer SortByAsc(string field) => SortBy(SortedField.Asc(field));
+
+ /// Convenience for SortBy(SortedField.Desc(field)).
+ public CollectReducer SortByDesc(string field) => SortBy(SortedField.Desc(field));
+
+ /// Bound the output per group to the first entries (offset 0).
+ public CollectReducer Limit(int count) => Limit(0, count);
+
+ /// Bound the output per group to entries starting at .
+ public CollectReducer Limit(int offset, int count)
+ {
+ EnsureMutable();
+ if (offset < 0 || count < 0)
+ throw new ArgumentException("LIMIT offset and count must be non-negative");
+ _limitOffset = offset;
+ _limitCount = count;
+ return this;
+ }
+
+ protected override int GetOwnArgsCount()
+ {
+ if (!_allFields && _fields.Count == 0)
+ throw new InvalidOperationException(
+ "REDUCE COLLECT requires either Fields(...) or FieldsAll() to be configured");
+
+ int count = _allFields ? 2 : 2 + _fields.Count;
+ if (_sortFields.Count > 0)
+ count += 2 + _sortFields.Count * 2; // SORTBY then a @field/ASC|DESC pair per key
+ if (_limitOffset != null)
+ count += 3; // LIMIT
+ return count;
+ }
+
+ protected override void AddOwnArgs(List