mongodb/laravel-mongodb · error · InvalidArgumentException

First argument of must be a field path as "string". Got

Error message

First argument of %s must be a field path as "string". Got "%s"

What it means

The first argument to where() must be a field path string (or an array/callable). Floats, booleans and null are rejected immediately, because Mongo field paths are strings and there is no sane interpretation of where(1.5, ...).

Solutions

  1. Pass the field path as a string: ->where('score', '>', 10).
  2. Cast/coerce the column: ->where((string) $column, $op, $value) after verifying it is a valid path.
  3. Verify upstream data so the column variable cannot be null/bool/float (add assertions).
  4. Use the array form ->where([$column => $value]) for dynamic columns, ensuring keys are strings.

Example fix

// before
$col = 1; // came from array keys
$query->where($col, '>', 5);
// after
$col = 'position';
$query->where($col, '>', 5);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!is_string($column) || $column === '') {
    throw new InvalidArgumentException('Field path must be a non-empty string');
}

Type guard

function isFieldPath(mixed $v): bool {
    return is_string($v) && $v !== '';
}

Prevention

When it happens

Trigger: ->where(1.5, '>', 10); ->where(true); ->where(null, 'exists'); variables that were meant to hold a column name but contain a number/bool/null (e.g. from misordered list destructuring or JSON keys cast to ints).

Common situations: Dynamic column names sourced from config/input that came out non-string; typos passing a value where a column was expected.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of mongodb/laravel-mongodb@0634653039 (2026-09-15). Data as JSON: /api/errors/7449463d26efeddc. Report an issue: GitHub.

Appendix: source

Thrown at src/Query/Builder.php:1314

            if (is_string($operator) && str_starts_with($operator, '$')) {
                $operator = substr($operator, 1);
            }

            if ($operator === self::UNSAFE_FIELD_QUERY) {
                $operator = '=';
            } elseif ($operator === '=' && self::firstOperatorKey($params[2]) !== null) {
                $this->throwIfIdLikeOperatorValue($params[0], $params[2]);

                $params[2] = ['$eq' => $params[2]];
            }
        }

        if (func_num_args() === 1 && ! is_array($column) && ! is_callable($column)) {
            throw new ArgumentCountError(sprintf('Too few arguments to function %s(%s), 1 passed and at least 2 expected when the 1st is not an array or a callable', __METHOD__, var_export($column, true)));
        }

        if (is_float($column) || is_bool($column) || $column === null) {
            throw new InvalidArgumentException(sprintf('First argument of %s must be a field path as "string". Got "%s"', __METHOD__, get_debug_type($column)));
        }

        return parent::where(...$params);
    }

    /** Array-of-wheres calls are marked so their generated "=" keeps building an operator document. */
    #[Override]
    protected function addArrayOfWheres($column, $boolean, $method = 'where')
    {
        return $this->whereNested(function ($query) use ($column, $method, $boolean) {
            foreach ($column as $key => $value) {
                if (is_numeric($key) && is_array($value)) {
                    $query->{$method}(...array_values($value), boolean: $boolean);
                } else {
                    $query->{$method}($key, self::UNSAFE_FIELD_QUERY, $value, $boolean);
                }
            }
        }, $boolean);

View on GitHub (pinned to 0634653039)