laravel/framework · error · InvalidArgumentException

Index name contains invalid characters.

Error message

Index name contains invalid characters.

What it means

MySqlGrammar::compileIndexHint() validates each comma-separated index name against the regex /^[a-zA-Z0-9_$]+$/ and throws InvalidArgumentException if any segment fails. MySQL index hints (USE/FORCE/IGNORE INDEX) require bare identifiers, so Laravel rejects names containing spaces, hyphens, dots, or quoting characters before emitting SQL.

Source

Thrown at src/Illuminate/Database/Query/Grammars/MySqlGrammar.php:153

    /**
     * Compile the index hints for the query.
     *
     * @param  \Illuminate\Database\Query\Builder  $query
     * @param  \Illuminate\Database\Query\IndexHint  $indexHint
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    protected function compileIndexHint(Builder $query, $indexHint)
    {
        $index = $indexHint->index;

        $indexes = array_map('trim', explode(',', $index));

        foreach ($indexes as $i) {
            if (! preg_match('/^[a-zA-Z0-9_$]+$/', $i)) {
                throw new InvalidArgumentException('Index name contains invalid characters.');
            }
        }

        return match ($indexHint->type) {
            'hint' => "use index ({$index})",
            'force' => "force index ({$index})",
            default => "ignore index ({$index})",
        };
    }

    /**
     * Compile a group limit clause.
     *
     * @param  \Illuminate\Database\Query\Builder  $query
     * @return string
     */
    protected function compileGroupLimit(Builder $query)
    {

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Pass the exact bare index name as it exists in MySQL (letters, digits, underscore, dollar only).
  2. Sanitize the index name before passing it: strip or replace invalid characters.
  3. If the index truly has special characters, reference it via a raw query snippet instead of the index hint API.

Example fix

// before
$query->from('users')->forceIndex('idx-last-name');

// after (use the real bare index name)
$query->from('users')->forceIndex('idx_last_name');
Defensive patterns

Strategy: validation

Validate before calling

$index = 'idx_last_name';
if (! preg_match('/^[a-zA-Z0-9_$]+$/', $index)) {
    throw new \InvalidArgumentException("Invalid MySQL index name: {$index}");
}
$query->from('users')->forceIndex($index);

Type guard

function isValidMysqlIndexName(string $name): bool
{
    foreach (explode(',', $name) as $segment) {
        if (! preg_match('/^[a-zA-Z0-9_$]+$/', trim($segment))) {
            return false;
        }
    }
    return true;
}

Prevention

When it happens

Trigger: Calling useIndex($name), forceIndex($name), or ignoreIndex($name) on a MySQL/MariaDB query where $name contains characters outside [a-zA-Z0-9_$] (e.g. 'idx-name', 'my index', 'schema.idx'). Multi-index strings like 'a,b' are split and each segment validated.

Common situations: Generating index hint names dynamically from user input or config that may include hyphens/spaces. Mismatch between an index's actual database name and the string passed to the hint (e.g. quoting it).

Related errors


AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06). Data as JSON: /data/errors/528d2a2a5caaf943.json. Report an issue: GitHub.