yiisoft/yii2 · error · yii\base\InvalidConfigException

"{class}" must have a primary key.

Error message

"{class}" must have a primary key.

What it means

BaseActiveRecord::findByCondition() implements findOne()/findAll() for all ActiveRecord flavors built on the base class. When the condition is a scalar or a non-associative array (and not an ExpressionInterface), it is interpreted as primary key value(s) and wrapped as [pk => value]; with static::primaryKey() empty, the wrap is impossible and InvalidConfigException is thrown.

Source

Thrown at framework/db/BaseActiveRecord.php:142

     * Finds ActiveRecord instance(s) by the given condition.
     * This method is internally called by [[findOne()]] and [[findAll()]].
     * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter
     * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
     * @throws InvalidConfigException if there is no primary key defined
     * @internal
     */
    protected static function findByCondition($condition)
    {
        $query = static::find();

        if (!ArrayHelper::isAssociative($condition) && !$condition instanceof ExpressionInterface) {
            // query by primary key
            $primaryKey = static::primaryKey();
            if (isset($primaryKey[0])) {
                // if condition is scalar, search for a single primary key, if it is array, search for multiple primary key values
                $condition = [$primaryKey[0] => is_array($condition) ? array_values($condition) : $condition];
            } else {
                throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.');
            }
        }

        return $query->andWhere($condition);
    }

    /**
     * Updates the whole table using the provided attribute values and conditions.
     *
     * For example, to change the status to be 1 for all customers whose status is 2:
     *
     * ```
     * Customer::updateAll(['status' => 1], 'status = 2');
     * ```
     *
     * @param array $attributes attribute values (name-value pairs) to be saved into the table
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
     * Please refer to [[Query::where()]] on how to specify this parameter.

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Define a primary key in the underlying storage and let primaryKey() pick it up
  2. Override primaryKey() to return the identifying column name(s)
  3. Call with an associative condition instead: Model::findOne(['uuid' => $uuid])
  4. Bypass findOne/findAll: Model::find()->andWhere(['uuid' => $uuid])->one()

Example fix

// before
$session = SessionModel::findOne($id); // throws: no PK defined

// after
$session = SessionModel::find()->where(['session_id' => $id])->one();
Defensive patterns

Strategy: validation

Validate before calling

if (!ArrayHelper::isAssociative($condition) && empty($modelClass::primaryKey())) {
    // scalar condition on a PK-less model: switch to explicit column lookup
    $condition = [$fallbackColumn => $condition];
}
$model = $modelClass::findOne($condition);

Try / catch

try {
    $model = SessionModel::findOne($id);
} catch (yii\base\InvalidConfigException $e) {
    if (strpos($e->getMessage(), 'must have a primary key') !== false) {
        $model = SessionModel::find()->where(['session_id' => $id])->one();
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Model::findOne(5) or Model::findAll([1, 2]) on any AR subclass whose backing table/schema declares no primary key and whose primaryKey() is not overridden — including custom AR classes extending BaseActiveRecord directly.

Common situations: Keyless tables or views modeled as AR; custom AR implementations (file/API-backed) that never defined primaryKey(); schema metadata unavailable for the storage; code ported from another AR that assumed a key.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/336eb3971761e704. Report an issue: GitHub.