yiisoft/yii2 · error · InvalidConfigException
"{}" must have a primary key.
Error message
"{}" must have a primary key. What it means
ActiveRecord::findByCondition() powers findOne()/findAll(). When the condition is neither an associative array nor an ExpressionInterface, it is treated as a primary key value and gets wrapped as [pk => value]. If static::primaryKey() returns an empty array, the wrapping cannot happen and InvalidConfigException is thrown stating the class must have a primary key.
Source
Thrown at framework/db/ActiveRecord.php:187
* @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])) {
$pk = $primaryKey[0];
if (!empty($query->join) || !empty($query->joinWith)) {
$pk = static::tableName() . '.' . $pk;
}
// if condition is scalar, search for a single primary key, if it is array, search for multiple primary key values
$condition = [$pk => is_array($condition) ? array_values($condition) : $condition];
} else {
throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.');
}
} elseif (is_array($condition)) {
$aliases = static::filterValidAliases($query);
$condition = static::filterCondition($condition, $aliases);
}
return $query->andWhere($condition);
}
/**
* Returns table aliases which are not the same as the name of the tables.
*
* @param Query $query
* @return array
* @throws InvalidConfigException
* @since 2.0.17
* @internal
*/View on GitHub (pinned to 66f00d18a2)
Solutions
- Define a primary key on the underlying table
- Override primaryKey() in the AR class to return identifying column(s)
- Query by an associative condition instead of a bare scalar: Model::findOne(['slug' => $slug])
- Use find()->andWhere(['column' => $value]) directly, which never assumes a primary key
Example fix
// before $user = UserView::findOne(5); // throws: no primary key // after $user = UserView::find()->where(['email' => $email])->one();
Defensive patterns
Strategy: validation
Validate before calling
// before findOne/findAll with a scalar
if (!is_array($condition) || !ArrayHelper::isAssociative($condition)) {
if (empty($modelClass::primaryKey())) {
throw new InvalidArgumentException("{$modelClass} has no primary key; query by column instead.");
}
}
$model = $modelClass::findOne($condition); Try / catch
try {
$model = Model::findOne($id);
} catch (yii\base\InvalidConfigException $e) {
// model lacks a PK: degrade to a safe column lookup
$model = Model::find()->where(['external_id' => $id])->one();
} Prevention
- Static-analyze models: flag findOne(<scalar>) on classes with empty primaryKey()
- Prefer associative conditions in code that serves keyless tables
- Cover findOne-by-id paths with unit tests per model class
When it happens
Trigger: Calling Model::findOne(1) or Model::findAll([1, 2, 3]) on an AR whose table has no primary key and whose primaryKey() method is not overridden.
Common situations: Database views modeled as AR; tables without a PRIMARY KEY constraint; following tutorial examples that use findOne($id) on a model that was generated for a keyless table; schema cache stale after dropping the key.
Related errors
- "{class}" must have a primary key.
- Primary key of '{$class}' can not be empty.
- {class} does not have a primary key. You should either defin
- There is no sequence associated with table '$tableName'.
- There is no sequence associated with table: $table
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/632d062c49626893.
Report an issue: GitHub.