Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mongodb Laravel Package

mongodb/laravel-mongodb

Laravel MongoDB adds MongoDB support to Laravel's Eloquent models and query builder while preserving the familiar Laravel API. Maintained by MongoDB, it works with Laravel 10.x and lets you use existing Eloquent methods with MongoDB.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Check the compatibility matrix before you install. The package is versioned in lockstep with Laravel support and does not span all versions:

  • v5.x — Laravel 12+, PHP 8.2+ (v5.9.0 explicitly dropped Laravel 10/11 and PHP 8.1)
  • v4.x — Laravel 10/11 era

Pin the major version in composer.json so a composer update doesn't silently jump a Laravel boundary.

2. Install the PHP extension first, then the package.

pecl install mongodb          # ext-mongodb — required, not optional
php -m | grep mongodb
composer require mongodb/laravel-mongodb

The package pulls in mongodb/mongodb (the PHP library) itself; the extension is never installed by Composer.

3. Add a connection. There is no publishable config file you must run — you add the block to config/database.php yourself, and set DB_CONNECTION=mongodb (or use it as a secondary connection alongside MySQL).

'mongodb' => [
    'driver'   => 'mongodb',
    'dsn'      => env('DB_URI', 'mongodb://127.0.0.1:27017'),
    'database' => env('DB_DATABASE', 'laravel'),
    'options'  => [], // passed straight to MongoDB\Driver\Manager
],

4. Where to look first. In this order: (a) the connection block above, (b) the official docs at mongodb.com/docs/drivers/php/laravel-mongodb/, (c) the MongoDB\Laravel\Eloquent\Model class and its Builder — that's where every non-SQL behaviour lives. The README is intentionally thin; the docs site is the real manual.

5. First use case. Don't migrate your billing schema. Pick one append-heavy or read-heavy model — an Activity, AuditLog, Event, or a product catalog with wildly varying attributes — point it at the mongodb connection, and keep writing normal Eloquent. That single model proves the workflow with zero risk to the rest of the app:

namespace App\Models;

use MongoDB\Laravel\Eloquent\Model;

class Activity extends Model
{
    protected $connection = 'mongodb';
    protected $guarded = [];   // no schema => fillable/guarded is your only guard
}
Activity::create(['user_id' => $user->id, 'type' => 'login', 'meta' => ['ip' => $ip]]);
Activity::where('meta.ip', $ip)->latest()->take(20)->get();

Implementation Patterns

Model layer. Extend MongoDB\Laravel\Eloquent\Model (or use the DocumentModel trait on a third-party model). The _id field is aliased to id, so $model->id, route-model binding, and find() all behave like a string primary key. Set $guarded = [] or $fillable deliberately — MongoDB won't reject an unexpected field for you.

Dot notation is the join replacement. Nested documents and arrays of subdocuments are queried with paths, and this is the single biggest day-to-day shift:

Product::where('specs.weight', '<', 500)->get();
Product::where('tags', 'sale')->get();                  // array contains
Product::where('variants.sku', 'ABC-1')->get();          // any element matches
Product::whereNotNull('deleted_at')->count();            // -> $exists

Use the aggregation builder for anything reporting-shaped. SQL-style join() is not part of the supported surface; you denormalize/embed, or aggregate. The package ships a fluent pipeline builder on top of mongodb/mongodb:

Order::aggregate()
    ->match(['status' => 'paid'])
    ->group(['_id' => '$customer_id', 'total' => ['$sum' => '$amount']])
    ->sort(['total' => -1])
    ->limit(10)
    ->get();

When the builder can't express it (e.g. $lookup with a pipeline, $facet, $graphLookup), drop to the driver: Model::raw() returns the underlying MongoDB\Collection, and raw(fn ($c) => ...) scopes it. That escape hatch is what keeps the package usable — treat it as a first-class feature, not a hack.

Writes: prefer atomic operators over read-modify-save. Concurrency is much cheaper in MongoDB than in SQL, so lean on upsert, increment, and array operators rather than loading a model:

Activity::where('date', $day)->upsert(['date' => $day], ['$inc' => ['logins' => 1]]);
Post::whereKey($id)->push('tags', 'featured');
Post::whereKey($id)->pull('tags', 'draft');
Post::whereKey($id)->increment('views');       // $inc, no race

Batch inserts with Model::insert($rows) (single round trip) for importers and log pipelines — this is where MongoDB earns its keep.

Migrations become index management. There are no columns to alter. A migration creates the collection and its indexes; that's the whole lifecycle:

Schema::connection('mongodb')->create('activities', function ($collection) {
    $collection->index('user_id');
    $collection->index('meta.ip');
    $collection->unique('event_key');
    // TTL / sparse options vary by version — check the docs for the current helper names
});

Design indexes alongside the queries (including compound and multikey indexes for array fields). A missing index is the number-one cause of "MongoDB is slow."

Treat the rest of the Laravel stack as switchable per-driver:

// config/cache.php   -> 'default' => 'mongodb'  (also provides the mongodb lock driver)
// config/session.php -> 'driver'  => 'mongodb'
// config/queue.php   -> 'driver'  => 'mongodb'  (failed jobs + batching supported)
// config/filesystems -> 'driver'  => 'gridfs'   (GridFS disk for large uploads)
// config/scout.php   -> 'driver'  => 'mongodb'  (Atlas Search)

These are genuinely useful day-to-day: the mongodb cache + lock drivers give you atomic locks without Redis, the queue driver removes a broker from small deployments, and the GridFS disk lets you stream files > 16 MB without S3.

Pagination. Use paginate() where users need page numbers, but for large or hot collections prefer simplePaginate() or cursorPaginate() — paginate() runs a count() plus a skip/limit, and both degrade as the collection grows.

Backfills and exports. chunkById() and ->cursor() work; avoid ->skip($n) at high offsets in scripts for the same reason.

Schema evolution. Documents drift. Use the HasSchemaVersion trait for the schema-versioning pattern, and AsBsonDocument / AsBsonArray casts (added in 5.9) or auto-embedding when you want typed BSON instead of loose arrays.

Transactions. DB::connection('mongodb')->transaction(fn () => ...) works, but only on a replica set or sharded cluster — see Gotchas.

Gotchas and Tips

It's a rename, not a new package. mongodb/laravel-mongodb is the successor to jenssegers/mongodb, with namespaces moved (Jenssegers\Mongodb\* → MongoDB\Laravel\*). Old tutorials and Stack Overflow answers will give you the wrong FQCNs; the package name fallback still resolves in some places, so a stale import can appear to work until it doesn't.

Standalone MongoDB has no transactions. If a RefreshDatabase test or a DB::transaction() call fails with "Transaction numbers are only allowed on a replica set member or mongos," that's your local mongod running standalone. Run a single-node replica set locally (--replSet rs0 + rs.initiate()), or use Atlas. This trips up CI pipelines constantly.

_id is immutable. You cannot change a document's primary key via save(); MongoDB rejects it. Plan for stable IDs, and don't design a workflow that reassigns them.

String vs ObjectId is a real source of bugs. The _id alias accepts a hex string in find()/where(), but a raw aggregation $match on _id with a string will silently match nothing. Same for foreign-key-ish fields: either store references as strings consistently, or apply the bundled ObjectId cast. When a query "returns nothing but the document is clearly there," suspect a type mismatch before anything else.

There is no schema, so validation is entirely yours. Use Form Requests / Validator / typed DTOs on write. Casts ('meta' => 'array', AsBsonDocument, date casts) are your type safety net. Never trust $guarded = [] plus unvalidated request()->all().

like is a regex. where('name', 'like', '%foo%') compiles to a non-anchored regex — no index use, full scan. Use anchored prefixes ('foo%'), or $text indexes via raw queries, or Atlas Search through the Scout driver for real full-text relevance.

Dates are BSON dates. They compare and sort correctly, but they serialise to extended JSON in raw output and differ from SQL string timestamps in BI tools and exports. Be explicit in $casts. A 5.8.2 fix addressed getCastType mutating the model-wide $dateFormat — if you're on an older patch and see weird date formatting leaking between attributes, that's the culprit.

Aggregation results are not models. ->get() on the aggregation builder hands back raw documents (stdClass/arrays) — no casts, no mutators, no model events, no relations. Don't assume Eloquent semantics. Model::raw() likewise bypasses the ORM entirely, including observers and timestamps.

No joins, no ALTER TABLE, no foreign keys. Deletes don't cascade; enforce referential integrity in application code or via scheduled cleanup jobs. Uniqueness lives in indexes, and a unique-index violation surfaces as a BulkWriteException, not a QueryException — handle it explicitly if you rely on createOrFirst-style races.

Testing. RefreshDatabase behaves differently depending on topology: with a replica set it can wrap in transactions; on standalone it must drop/recreate collections, which is slow and destructive. For CI, either provision a replica set or clean collections manually in setUp(). Factories, seeders, and observers all work normally.

Debugging toolkit. DB::connection('mongodb')->enableQueryLog() plus getQueryLog() shows the compiled command. The query builder can hand you the raw Mongo query document (e.g. toMongo()), which is far more useful than a SQL string would be. For slow queries, explain() via Model::raw() and the database profiler (db.setProfilingLevel(1, { slowms: 50 })) tell you whether an index was used. Enable query logging only in dev — the log grows fast.

Config quirks. The connection array must have 'driver' => 'mongodb', and the options array is passed verbatim to the driver Manager — TLS, appName, replicaSet, readPreference, directConnection all live there, not as top-level keys. Prefer dsn over the host/port/user/pass sprawl; it's easier to keep consistent between local, CI, and production. Collection-name prefixes (v4.4+) are available if you share a database between environments.

Extension points worth knowing. Custom casts for your own value objects; the DocumentModel trait to retrofit an existing model class; overridable _id/id aliasing at the Grammar level (5.9) if you have a legacy collection keyed differently; HasSchemaVersion for document migration. Also relevant: the package ships Laravel Boost tooling/agent skills, so AI-assisted development in this repo has guidance — useful, but verify generated Mongo APIs against the docs rather than trusting them blind.

When not to use it. Heavy multi-entity relational reporting, cross-collection transactional integrity across many documents, or anything where a DBA expects to write ad-hoc joins — keep those on the SQL connection. The package's strength is that it lets you move one model over without rewriting the app; its pitfall is assuming all models should follow.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin