dgraph-io/dgraph · error

Node with %q cant have filter, please place the filter on th

Error message

Node with %q cant have filter, please place the filter on the upper level

What it means

A node carrying an aggregator or password-verifier function must not also carry an @filter directive. An embedded filter under an aggregate is ambiguous — e.g. `min(...) @filter(...)` inside another filter — so treeCopy rejects it and asks that the filter be placed at the parent/upper level instead.

Source

Thrown at query/query.go:629

		if gchild.MathExp != nil {
			mathExp := &mathTree{}
			if err := mathCopy(mathExp, gchild.MathExp); err != nil {
				return err
			}
			dst.MathExp = mathExp
		}

		if gchild.Func != nil &&
			(gchild.Func.IsAggregator() || gchild.Func.IsPasswordVerifier()) {
			if len(gchild.Children) != 0 {
				return errors.Errorf("Node with %q cant have child attr", gchild.Func.Name)
			}
			// embedded filter will cause ambiguous output like following,
			// director.film @filter(gt(initial_release_date, "2016")) {
			//    min(initial_release_date @filter(gt(initial_release_date, "1986"))
			// }
			if gchild.Filter != nil {
				return errors.Errorf(
					"Node with %q cant have filter, please place the filter on the upper level",
					gchild.Func.Name)
			}
			if gchild.Func.Attr == "uid" {
				return errors.Errorf(`Argument cannot be "uid"`)
			}
			dst.createSrcFunction(gchild.Func)
		}

		if gchild.Filter != nil {
			dstf := &SubGraph{}
			if err := filterCopy(dstf, gchild.Filter); err != nil {
				return err
			}
			dst.Filters = append(dst.Filters, dstf)
		}

		if gchild.FacetsFilter != nil {

View on GitHub (pinned to 759e242be6)

Solutions

  1. Move the @filter to the parent level so filtering happens on the set before aggregation
  2. Filter the scalar predicate inside the parent's filter: e.g. `friend @filter(gt(score, 3)) { min(score) }`
  3. If filtering aggregated results is needed, store the aggregate in a variable and apply it in a func/filter at the root

Example fix

// before
{
  me(func: uid(0x1)) {
    friend {
      min(initial_release_date) @filter(gt(initial_release_date, "1986"))
    }
  }
}
// after
{
  me(func: uid(0x1)) {
    friend @filter(gt(initial_release_date, "1986")) {
      min(initial_release_date)
    }
  }
}
Defensive patterns

Strategy: validation

Validate before calling

function assertNoFilterOnAggregate(node) {
  if (/^(min|max|sum|avg|count)\s*\(/.test(node.field) && node.filter) {
    throw new Error('Move the @filter from the aggregated node to the parent level');
  }
}

Type guard

const isAggregateWithFilter = (n) => Boolean(n.func && n.func.isAggregator && n.func.isAggregator() && n.filter);

Try / catch

try {
  await txn.query(q);
} catch (e) {
  if (e.message.includes('cant have filter')) {
    throw new Error('Relocate @filter to the parent level of the aggregate');
  }
  throw e;
}

Prevention

When it happens

Trigger: Query like `director @filter(...)` where the child is `min(initial_release_date) @filter(gt(initial_release_date, "1986"))` — i.e. a filter attached to the aggregated node itself.

Common situations: Attempting to filter values before aggregation in a single expression; copy-paste of filters across levels; older queries retargeted after adding an aggregate.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/2ee380aff4248f32. Report an issue: GitHub.