getgrav/grav · error · InvalidArgumentException

Unsupported media filter operator

Error message

Unsupported media filter operator "%s".

What it means

AbstractMedia::filterBy() (line 203) validates $operator against the class constant META_OPERATORS = ['==','!=','>','>=','<','<=','in','contains'] using a strict in_array check, and throws InvalidArgumentException for anything else. Even near-misses like '=', 'IN', or an operator with stray whitespace are rejected. Per the docblock, 'contains' tests list membership and 'in' tests intersection for list fields.

Solutions

  1. Use one of the eight supported operators exactly as written: == != > >= < <= in contains.
  2. Whitelist incoming operators against AbstractMedia::META_OPERATORS and normalize common aliases (= → ==) before calling filterBy().
  3. For substring matching use 'contains'; for list membership use 'in' — there is no 'like' operator.

Example fix

// before
$videos = $media->filterBy('meta.type', 'video', '=');

// after
$op = $request->get('op', '==');
$op = in_array($op, \Grav\Common\Page\Medium\AbstractMedia::META_OPERATORS, true) ? $op : '==';
$videos = $media->filterBy('meta.type', 'video', $op);
Defensive patterns

Strategy: type-guard

Validate before calling

use Grav\Common\Page\Medium\AbstractMedia;
$op = $request->get('op', '==');
if (!\in_array($op, AbstractMedia::META_OPERATORS, true)) {
    $op = '=='; // or reject with 422
}
$media->filterBy($field, $value, $op);

Type guard

use Grav\Common\Page\Medium\AbstractMedia;
function isSupportedMetaOperator(string $op): bool
{
    return \in_array($op, AbstractMedia::META_OPERATORS, true);
}

Try / catch

try {
    $result = $media->filterBy($field, $value, $op);
} catch (\InvalidArgumentException $e) {
    // 422: unsupported operator — list the allowed set in the response
}

Prevention

When it happens

Trigger: Calling $media->filterBy($field, $value, '=') (single equals), 'like', '=~', 'not in', or a wrongly-cased/spaced operator; forwarding a user-supplied operator query parameter straight from a URL into filterBy().

Common situations: Porting SQL or Twig query syntax to media filtering; API endpoints exposing the operator to clients; confusing this API with page collection filtering that accepts a different operator vocabulary.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/c9031a802d1f65bb. Report an issue: GitHub.

Appendix: source

Thrown at system/src/Grav/Common/Page/Medium/AbstractMedia.php:203

     *
     * Returns a new collection (same type) containing only the media whose
     * `$field` satisfies `$operator` against `$value`, so calls chain:
     * `page.media.filterBy('rating', 3, '>=').sortBy('rating', 'desc')`.
     *
     * Operators: `== != > >= < <= in contains`. Comparison is
     * loose-comparison-safe — numeric only when both operands are numeric,
     * otherwise a strict string compare. For list fields (e.g. `tags`),
     * `contains` tests membership and `in` tests intersection with `$value`.
     *
     * @param string $field    Metadata key (dot notation supported).
     * @param mixed  $value    Value to compare against.
     * @param string $operator One of {@see META_OPERATORS}.
     * @return static
     */
    public function filterBy($field, $value, $operator = '==')
    {
        if (!in_array($operator, self::META_OPERATORS, true)) {
            throw new \InvalidArgumentException(sprintf('Unsupported media filter operator "%s".', $operator));
        }

        $items = array_filter(
            $this->all(),
            static fn($medium) => self::compareMeta($medium->get($field), $value, $operator)
        );

        return $this->createFrom($items);
    }

    /**
     * Filter the collection by several equality criteria at once (ANDed).
     *
     * Each `field => value` pair must match. A scalar value is compared with
     * `==`; an array value is treated as an `in` set (the field must equal one
     * of the listed values). For anything beyond equality/membership, chain
     * {@see filterBy()} calls instead.
     *

View on GitHub (pinned to 6040efed04)