octobercms/october · error · SystemException

Invalid filter operation:

Error message

Invalid filter operation: 

What it means

While translating a filter into SQL, ReportQueryBuilder::applyFilter() matches the filter's operation against the supported set: = , >= , <= , > , < , string_starts_with, string_includes, one_of. Anything else reaches default and throws SystemException('Invalid filter operation: <op>'). Operations are compared as raw strings via the ReportDimensionFilter::OPERATION_* constants, so both unknown values and malformed payloads (wrong key, nested object, array) trigger this.

Source

Thrown at modules/dashboard/classes/ReportQueryBuilder.php:942

            default => throw new SystemException('Invalid filter attribute type')
        };

        $columnName = $this->evalDbObjectName($columnName);
        $operation = $filter->getOperation();
        $value = $filter->getValue();

        match ($operation) {
            ReportDimensionFilter::OPERATION_EQUALS,
            ReportDimensionFilter::OPERATION_MORE_OR_EQUALS,
            ReportDimensionFilter::OPERATION_LESS_OR_EQUALS,
            ReportDimensionFilter::OPERATION_MORE,
            ReportDimensionFilter::OPERATION_LESS => $query->where($columnName, $operation, $value),

            ReportDimensionFilter::OPERATION_STARTS_WITH => $query->where($columnName, 'like', $value . '%'),
            ReportDimensionFilter::OPERATION_STRING_INCLUDES => $query->where($columnName, 'like', '%' . $value . '%'),
            ReportDimensionFilter::OPERATION_ONE_OF => $query->whereIn($columnName, $value),

            default => throw new SystemException('Invalid filter operation: ' . $operation)
        };
    }

    /**
     * applyOrderRule applies ordering
     *
     * @param QueryBuilder $query
     */
    protected function applyOrderRule(QueryBuilder $query): void
    {
        $columnName = match ($this->orderRule->getDataAttributeType()) {
            ReportDataOrderRule::ATTR_TYPE_DIMENSION =>
                ($this->dimension->isDate() && $this->groupInterval !== ReportDataSourceBase::GROUP_INTERVAL_FULL)
                    ? $this->dimension->getDataSetColumName()
                    : ($this->dimension->getLabelColumnName() ?? $this->dimension->getDatabaseColumnName()),

            ReportDataOrderRule::ATTR_TYPE_METRIC =>
                ReportMetric::findMetricByCodeStrict($this->metrics, $this->orderRule->getAttributeName())

View on GitHub (pinned to b608633a7e)

Solutions

  1. Use one of: '=', '>=', '<=', '>', '<', 'string_starts_with', 'string_includes', 'one_of' — ideally via ReportDimensionFilter::OPERATION_* constants.
  2. Map UI operator names to API operations at the boundary (e.g. 'equals' => OPERATION_EQUALS, 'contains' => OPERATION_STRING_INCLUDES).
  3. Validate each filter payload before dispatch: reject/ignore filters whose operation is not in the allowed list.
  4. Trim whitespace and ensure the operation is a scalar string, not a nested structure.

Example fix

// before
$filter->setOperation('contains');

// after
$filter->setOperation(ReportDimensionFilter::OPERATION_STRING_INCLUDES);
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED_OPERATIONS = [
    ReportDimensionFilter::OPERATION_EQUALS,
    ReportDimensionFilter::OPERATION_MORE_OR_EQUALS,
    ReportDimensionFilter::OPERATION_LESS_OR_EQUALS,
    ReportDimensionFilter::OPERATION_MORE,
    ReportDimensionFilter::OPERATION_LESS,
    ReportDimensionFilter::OPERATION_STARTS_WITH,
    ReportDimensionFilter::OPERATION_STRING_INCLUDES,
    ReportDimensionFilter::OPERATION_ONE_OF,
];

$op = $payload['operation'] ?? null;
if (!is_string($op) || !in_array($op, ALLOWED_OPERATIONS, true)) {
    throw new InvalidArgumentException("Unsupported filter operation: " . var_export($op, true));
}
$filter->setOperation($op);

Type guard

function isValidFilterOperation(mixed $op): bool
{
    return is_string($op) && in_array($op, [
        '=', '>=', '<=', '>', '<',
        ReportDimensionFilter::OPERATION_STARTS_WITH,
        ReportDimensionFilter::OPERATION_STRING_INCLUDES,
        ReportDimensionFilter::OPERATION_ONE_OF,
    ], true);
}

Prevention

When it happens

Trigger: A dashboard request filter carrying 'operation': 'like', 'equals', 'in', '>' with surrounding whitespace, or a missing operation defaulting to something invalid; building ReportDimensionFilter programmatically and setting a SQL operator string where the semantic constant belongs ('=' works, 'EQ' does not).

Common situations: Front-end sends its own operator vocabulary that differs from the API constants; payload mangled by JSON serialization (operation becomes an array/object which stringifies oddly); stale cached widget config from before an operation was renamed.

Related errors


AI-assisted analysis of octobercms/october@b608633a7e (2026-08-21). Data as JSON: /api/errors/61b22f7e2c459fc3. Report an issue: GitHub.