Laravel MongoDB
Implementation skill for mongodb/laravel-mongodb. Exists to prevent the common mistakes: auto-increment IDs, toSql(), SQL JOIN, distinct()->get() expecting scalar arrays, and belongsTo() over native ObjectId FKs.
Core Workflow
- Identify layer: model, query builder, relationship, schema/index, queue/cache/session, search, or transaction.
- Confirm model extends
MongoDB\Laravel\Eloquent\Model (or uses DocumentModel trait).
- Map every FK and
_id: ObjectId in DB, string in Eloquent — cast accordingly.
- Replace unsupported helpers (
toSql, groupByRaw, whereFulltext, union, inRandomOrder, whereColumn) with MongoDB alternatives.
- Validate:
php artisan migrate --database=mongodb, inspect queries with ->dump().
Related MongoDB tooling
If the user needs MongoDB-related skills that aren't otherwise available through e.g. Boost or the skills directory, such as:
- Inspecting Atlas system resources
- Managing an Atlas cluster
- Analysing MongoDB schema anti-patterns
- Migrating an SQL schema to MongoDB
- Setting up the official MongoDB MCP server
Suggest installing the MongoDB agent skills plugin if not already installed. Instructions for installing it are in the MongoDB agent skills plugin README.
Reference Guide
| Reference file |
Load when |
references/eloquent-models.md |
The user is defining a new model, changing its base class, or mapping _id / ObjectId fields |
references/query-builder.md |
The user is writing a query and hits a missing Eloquent helper (toSql, distinct, grouping, random ordering, raw SQL builders) or needs relation aggregates |
references/relationships.md |
The user is defining a relationship between models, especially embedded, hybrid, or cross-database (MongoDB ↔ SQL) relations |
references/connection.md |
The user is configuring config/database.php, setting up multiple connections, or accessing the underlying MongoDB client |
references/schema.md |
The user is writing a migration or creating an index (regular, unique, TTL, geospatial, Atlas Search, Vector Search) |
references/queues.md |
The user is configuring the MongoDB queue driver or dispatching jobs onto it |
references/transactions.md |
The user needs to write multiple documents atomically or is asking about beginTransaction, replica set requirements, or transactional testing traits |
references/cache-sessions.md |
The user is configuring MongoDB as a cache store or session driver |
references/search-engine.md |
The user is implementing full-text search on a MongoDB collection or is deciding whether to use Laravel Scout |
references/vector-search.md |
The user is implementing semantic search, storing embeddings, using autoEmbed, or combining full-text and vector search |
references/installation.md |
The user is setting up ext-mongodb, installing the package, or configuring the connection for the first time |
references/support.md |
The user has hit a suspected bug and needs to route the issue to the correct MongoDB repository |
Constraints
MUST DO
- Extend
MongoDB\Laravel\Eloquent\Model (or apply DocumentModel trait to base classes you cannot change).
- Cast
_id to string in every API resource: 'id' => (string) $this->_id.
- Cast FK fields to
string via $casts on the child model when FK values may come from outside model attributes (imports, raw ObjectIds) — prevents BSON type mismatches on direct where('author_id', $id) queries.
- Eager-load with
::with() — MongoDB does no server-side joins for Eloquent relations.
- Use aggregation pipeline for grouping, counting per group,
$lookup, and $sample.
- Relation aggregates (
withCount(), withExists(), withSum(), withAvg(), withMin(), withMax()) are supported. Use a $lookup pipeline when the aggregated value must be filtered, sorted or paginated on.
- Create indexes in migrations:
Schema::connection('mongodb')->create('posts', fn (Blueprint $c) => $c->index('user_id')).
- Use
DB::connection('mongodb')->transaction(...) only on replica set / sharded cluster.
MUST NOT DO
orderBy() on a withCount() / withAggregate() alias — the value is computed after the documents are read, so it throws. Use $lookup + $size aggregation, or sort the resulting collection.
toSql() / toRawSql() — no SQL. Use ->dump() / ->dd().
distinct('field')->get() expecting scalars — returns a Collection. Use ->distinct()->pluck('field').
groupByRaw(), orderByRaw(), havingRaw(), whereFulltext(), union(), whereColumn() — use aggregation.
inRandomOrder() — use Model::raw(fn($c) => $c->aggregate([['$sample' => ['size' => N]]])).
- Auto-increment IDs — primary keys are ObjectIds.
protected $collection — removed. Use protected $table instead.
$keyType = 'string' on a SQL model in a cross-database relationship — only needed on MongoDB models. The HybridRelations trait handles the comparison on the SQL side.
- Unencrypted PII — use Laravel encrypted casts or Queryable Encryption.
Code Templates
1. Eloquent model
<?php
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
final class Post extends Model
{
protected $connection = 'mongodb';
protected $table = 'posts'; // $table not $collection
protected $fillable = ['title', 'body', 'author_id', 'published_at'];
protected $casts = [
'author_id' => 'string', // FK as string for Eloquent relationship matching
'published_at' => 'datetime',
];
}
2. Relationship with ObjectId/string casting
<?php
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\BelongsTo;
use MongoDB\Laravel\Relations\EmbedsMany;
final class Post extends Model
{
protected $casts = ['author_id' => 'string']; // cast FK to string for relation matching
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'author_id');
}
public function comments(): EmbedsMany
{
return $this->embedsMany(Comment::class);
}
}
final class User extends Model
{
protected $keyType = 'string'; // expose primary key as string so Post.author_id matches
}
3. Queue job
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
final class IndexPostJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public string $postId) {}
public function handle(): void {}
}
IndexPostJob::dispatch((string) $post->_id)->onConnection('mongodb');
4. Feature test (Pest)
<?php
use App\Models\Post;
it('creates a post with an ObjectId primary key', function (): void {
$post = Post::create(['title' => 'Hello Mongo', 'body' => 'first', 'tags' => ['mongo', 'laravel']]);
expect($post->id)->toBeString()
->and(Post::query()->where('_id', $post->id)->exists())->toBeTrue();
});
Validation Checkpoints
| Stage |
Command |
Expected Result |
| Indexes / migration |
php artisan migrate --database=mongodb |
Migrations run; indexes created |
| Query inspection |
Model::query()->where(...)->dump() |
Prints MongoDB filter array (no SQL) |
1---2name: laravel-mongodb3description: Implementation specialist for the mongodb/laravel-mongodb package. Triggers on "Laravel MongoDB", "mongodb/laravel-mongodb", "Eloquent MongoDB", "MongoDB model", "_id", "ObjectId in Laravel", "MongoDB queue/cache/session driver", "Atlas Search Laravel", "Laravel Scout MongoDB", "embedsMany", "embedsOne", "withCount MongoDB", "distinct MongoDB", "distinct array MongoDB", "get unique values MongoDB", "Laravel aggregation pipeline", "cross-database relationship MongoDB". Corrects LLM mistakes when MySQL/Eloquent patterns are applied to MongoDB. Do NOT use this skill for the raw PHP MongoDB driver or mongodb/mongodb library without Laravel, for MongoDB schema design and data-modelling decisions (embed vs reference, unbounded arrays), for MongoDB Atlas infrastructure or cluster/index management via the UI or Admin API, for query performance tuning and index selection, or for non-Laravel PHP frameworks (Symfony, CodeIgniter, plain PHP).4license: Apache-2.05---67# Laravel MongoDB89Implementation skill for `mongodb/laravel-mongodb`. Exists to prevent the common mistakes: auto-increment IDs, `toSql()`, SQL `JOIN`, `distinct()->get()` expecting scalar arrays, and `belongsTo()` over native ObjectId FKs.1011## Core Workflow12131. Identify layer: model, query builder, relationship, schema/index, queue/cache/session, search, or transaction.142. Confirm model extends `MongoDB\Laravel\Eloquent\Model` (or uses `DocumentModel` trait).153. Map every FK and `_id`: ObjectId in DB, **string** in Eloquent — cast accordingly.164. Replace unsupported helpers (`toSql`, `groupByRaw`, `whereFulltext`, `union`, `inRandomOrder`, `whereColumn`) with MongoDB alternatives.175. Validate: `php artisan migrate --database=mongodb`, inspect queries with `->dump()`.1819## Related MongoDB tooling2021If the user needs MongoDB-related skills that aren't otherwise available through e.g. Boost or the skills directory, such as:2223- Inspecting Atlas system resources24- Managing an Atlas cluster25- Analysing MongoDB schema anti-patterns26- Migrating an SQL schema to MongoDB27- Setting up the official MongoDB MCP server2829Suggest installing the MongoDB agent skills plugin if not already installed. Instructions for installing it are in the [MongoDB agent skills plugin README](https://github.com/mongodb/agent-skills/blob/main/README.md).3031## Reference Guide3233| Reference file | Load when |34|---|---|35| `references/eloquent-models.md` | The user is defining a new model, changing its base class, or mapping `_id` / ObjectId fields |36| `references/query-builder.md` | The user is writing a query and hits a missing Eloquent helper (`toSql`, `distinct`, grouping, random ordering, raw SQL builders) or needs relation aggregates |37| `references/relationships.md` | The user is defining a relationship between models, especially embedded, hybrid, or cross-database (MongoDB ↔ SQL) relations |38| `references/connection.md` | The user is configuring `config/database.php`, setting up multiple connections, or accessing the underlying MongoDB client |39| `references/schema.md` | The user is writing a migration or creating an index (regular, unique, TTL, geospatial, Atlas Search, Vector Search) |40| `references/queues.md` | The user is configuring the MongoDB queue driver or dispatching jobs onto it |41| `references/transactions.md` | The user needs to write multiple documents atomically or is asking about `beginTransaction`, replica set requirements, or transactional testing traits |42| `references/cache-sessions.md` | The user is configuring MongoDB as a cache store or session driver |43| `references/search-engine.md` | The user is implementing full-text search on a MongoDB collection or is deciding whether to use Laravel Scout |44| `references/vector-search.md` | The user is implementing semantic search, storing embeddings, using `autoEmbed`, or combining full-text and vector search |45| `references/installation.md` | The user is setting up `ext-mongodb`, installing the package, or configuring the connection for the first time |46| `references/support.md` | The user has hit a suspected bug and needs to route the issue to the correct MongoDB repository |4748## Constraints4950### MUST DO5152- Extend `MongoDB\Laravel\Eloquent\Model` (or apply `DocumentModel` trait to base classes you cannot change).53- Cast `_id` to string in every API resource: `'id' => (string) $this->_id`.54- Cast FK fields to `string` via `$casts` on the child model when FK values may come from outside model attributes (imports, raw ObjectIds) — prevents BSON type mismatches on direct `where('author_id', $id)` queries.55- Eager-load with `::with()` — MongoDB does no server-side joins for Eloquent relations.56- Use aggregation pipeline for grouping, counting per group, `$lookup`, and `$sample`.57- Relation aggregates (`withCount()`, `withExists()`, `withSum()`, `withAvg()`, `withMin()`, `withMax()`) are supported. Use a `$lookup` pipeline when the aggregated value must be filtered, sorted or paginated on.58- Create indexes in migrations: `Schema::connection('mongodb')->create('posts', fn (Blueprint $c) => $c->index('user_id'))`.59- Use `DB::connection('mongodb')->transaction(...)` only on replica set / sharded cluster.6061### MUST NOT DO6263- `orderBy()` on a `withCount()` / `withAggregate()` alias — the value is computed after the documents are read, so it throws. Use `$lookup` + `$size` aggregation, or sort the resulting collection.64- `toSql()` / `toRawSql()` — no SQL. Use `->dump()` / `->dd()`.65- `distinct('field')->get()` expecting scalars — returns a Collection. Use `->distinct()->pluck('field')`.66- `groupByRaw()`, `orderByRaw()`, `havingRaw()`, `whereFulltext()`, `union()`, `whereColumn()` — use aggregation.67- `inRandomOrder()` — use `Model::raw(fn($c) => $c->aggregate([['$sample' => ['size' => N]]]))`.68- Auto-increment IDs — primary keys are ObjectIds.69- `protected $collection` — removed. Use `protected $table` instead.70- `$keyType = 'string'` on a SQL model in a cross-database relationship — only needed on MongoDB models. The `HybridRelations` trait handles the comparison on the SQL side.71- Unencrypted PII — use Laravel encrypted casts or Queryable Encryption.7273## Code Templates7475### 1. Eloquent model7677```php78<?php7980namespace App\Models;8182use MongoDB\Laravel\Eloquent\Model;8384final class Post extends Model85{86 protected $connection = 'mongodb';87 protected $table = 'posts'; // $table not $collection8889 protected $fillable = ['title', 'body', 'author_id', 'published_at'];9091 protected $casts = [92 'author_id' => 'string', // FK as string for Eloquent relationship matching93 'published_at' => 'datetime',94 ];95}96```9798### 2. Relationship with ObjectId/string casting99100```php101<?php102103namespace App\Models;104105use MongoDB\Laravel\Eloquent\Model;106use MongoDB\Laravel\Relations\BelongsTo;107use MongoDB\Laravel\Relations\EmbedsMany;108109final class Post extends Model110{111 protected $casts = ['author_id' => 'string']; // cast FK to string for relation matching112113 public function author(): BelongsTo114 {115 return $this->belongsTo(User::class, 'author_id');116 }117118 public function comments(): EmbedsMany119 {120 return $this->embedsMany(Comment::class);121 }122}123124final class User extends Model125{126 protected $keyType = 'string'; // expose primary key as string so Post.author_id matches127}128```129130### 3. Queue job131132```php133<?php134135namespace App\Jobs;136137use Illuminate\Bus\Queueable;138use Illuminate\Contracts\Queue\ShouldQueue;139use Illuminate\Foundation\Bus\Dispatchable;140use Illuminate\Queue\InteractsWithQueue;141use Illuminate\Queue\SerializesModels;142143final class IndexPostJob implements ShouldQueue144{145 use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;146147 public function __construct(public string $postId) {}148149 public function handle(): void {}150}151152IndexPostJob::dispatch((string) $post->_id)->onConnection('mongodb');153```154155### 4. Feature test (Pest)156157```php158<?php159160use App\Models\Post;161162it('creates a post with an ObjectId primary key', function (): void {163 $post = Post::create(['title' => 'Hello Mongo', 'body' => 'first', 'tags' => ['mongo', 'laravel']]);164165 expect($post->id)->toBeString()166 ->and(Post::query()->where('_id', $post->id)->exists())->toBeTrue();167});168```169170## Validation Checkpoints171172| Stage | Command | Expected Result |173|---|---|---|174| Indexes / migration | `php artisan migrate --database=mongodb` | Migrations run; indexes created |175| Query inspection | `Model::query()->where(...)->dump()` | Prints MongoDB filter array (no SQL) |