mongodb/laravel-mongodb · error · InvalidArgumentException
Cannot have both "id" and "_id" fields.
Error message
Cannot have both "id" and "_id" fields.
What it means
When preparing an attribute array for a MongoDB query, the library renames the Eloquent-style "id" field to MongoDB's "_id". If both "id" and "_id" are present in the same array with different values, the rename would silently lose one value, so an InvalidArgumentException is thrown.
Solutions
- Remove one of the two keys from the payload array before passing it to the query
- Normalize your data to a single convention (Eloquent 'id' or MongoDB '_id') before building the payload
- If both keys must exist and be equal, keep only '_id' (the library would map 'id' to it anyway)
- Audit array_merge/array_replace call sites that combine model attributes with raw documents
Example fix
// before $data = ['id' => '64b0...', '_id' => 'different']; $collection->insertOne($data); // after unset($data['id']); // keep only '_id' $collection->insertOne($data);
Defensive patterns
Strategy: validation
Validate before calling
if (array_key_exists('id', $data) && array_key_exists('_id', $data) && $data['id'] !== $data['_id']) {
throw new InvalidArgumentException('Payload must not define both "id" and "_id".');
} Type guard
function hasIdConflict(array $values): bool {
return array_key_exists('id', $values) && array_key_exists('_id', $values) && $values['id'] !== $values['_id'];
} Try / catch
try {
$model->create($data);
} catch (\InvalidArgumentException $e) {
if (str_contains($e->getMessage(), '"id" and "_id"')) {
unset($data['id']);
$model->create($data);
} else { throw $e; }
} Prevention
- Never mix Eloquent 'id' attributes with raw '_id' documents in one payload
- Normalize payloads through model hydration before querying
- Unset 'id' after mapping it to '_id' when converting formats
- Add a unit test asserting payload keys for insert/update helpers
When it happens
Trigger: Calling any query/write method (e.g. insert, create, update, where-style payload paths) via prepareFieldsForQuery with an array containing keys 'id' and '_id' whose values differ, at the document root (or in an embedded document when renameEmbeddedIdField is enabled).
Common situations: Merging Eloquent model attributes (which use 'id') with raw MongoDB documents (which use '_id'); building payloads by array_merge where a raw BSON doc and a hydrated model both contribute; deserializing JSON that includes both id keys.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- Cannot have both " " and " " fields.
- This method is not supported by MongoDB
- This method is not supported by MongoDB. Use vectorSearch()…
- This method is not supported by MongoDB. Use vectorSearch()…
- This method is not supported by MongoDB. Use vectorSearch()…
AI-assisted analysis of mongodb/laravel-mongodb@0634653039 (2026-09-15).
Data as JSON: /api/errors/60d99e61ee5afccb.
Report an issue: GitHub.
Appendix: source
Thrown at src/Query/Grammar.php:50
{
/**
* Prepare fields for the MongoDB query by aliasing "id" to "_id" and handling arrow notation.
* Users can override this method to customize field aliasing behavior.
*
* @param array<string, mixed> $values The values to prepare
* @param bool $root Whether this is the root level (affects embedded id field handling)
* @psalm-param T $values
*
* @return array<string, mixed> The prepared values
* @psalm-return T
*
* @template T of array
*/
public function prepareFieldsForQuery(array $values, bool $root = true): array
{
if (array_key_exists('id', $values) && ($root || $this->connection->getRenameEmbeddedIdField())) {
if (array_key_exists('_id', $values) && $values['id'] !== $values['_id']) {
throw new InvalidArgumentException('Cannot have both "id" and "_id" fields.');
}
$values['_id'] = $values['id'];
unset($values['id']);
}
foreach ($values as $key => $value) {
if (! is_string($key)) {
continue;
}
// "->" arrow notation for subfields is an alias for "." dot notation
if (str_contains($key, '->')) {
$newKey = str_replace('->', '.', $key);
if (array_key_exists($newKey, $values) && $value !== $values[$newKey]) {
throw new InvalidArgumentException(sprintf('Cannot have both "%s" and "%s" fields.', $key, $newKey));
}
View on GitHub (pinned to 0634653039)