Unlock Model Return from Laravel `insert` in 5 Easy Steps
Tired of Laravel's `insert()` only returning a boolean? Learn how to efficiently retrieve full Eloquent models after a single or bulk insert in 5 easy steps.
Daniel Ivanov
Daniel is a senior PHP developer specializing in Laravel performance and database optimization.
The Frustration: Why `insert()` Only Returns `true`
You’ve meticulously prepared an array of data, ready to be inserted into your database. You call the seemingly obvious method, User::insert($data)
, expecting a collection of newly created User models in return. Instead, you get a simple, unceremonious boolean: true
.
This is a classic Laravel developer stumbling block. Why does a method named `insert` not return the very things it inserted? The answer lies in its origin. The insert()
method is part of Laravel's Query Builder, not the Eloquent ORM. Its primary goals are speed and efficiency.
It works by generating a single, highly optimized SQL `INSERT` statement for all your data. It doesn't interact with Eloquent models, which means it bypasses:
- Model instantiation (creating individual `User` objects)
- Eloquent events (like `creating`, `created`, `saving`)
- Model observers and listeners
- Mutators and accessors
This direct-to-database approach is incredibly fast for inserting thousands of rows, but it leaves you in the dark about the auto-incremented IDs and other database-generated defaults. This forces you to write another query to fetch the data you just created, which feels redundant and inefficient. But fear not, there's a better way. This guide will show you how to "unlock" those models in just five steps.
Unlock Model Return from Laravel `insert` in 5 Easy Steps
The secret to getting models back isn't a hidden method called insertAndGetModels()
. It's about understanding the different tools Laravel provides and choosing the right one for your specific scenario. Let's break it down.
Step 1: Assess Your Need - Single vs. Bulk Insert
Before writing a line of code, ask yourself the most important question: "Am I inserting one record or many?"
- Single Insert: You have one set of attributes for one new database row. For example, registering a new user.
- Bulk Insert: You have an array of arrays, representing multiple new rows to be inserted in one go. For example, importing users from a CSV file.
The optimal solution is completely different for each case. This single decision point will guide you through the rest of the steps.
Step 2: For Single Records, Always Use `create()`
If you are inserting a single record, the answer is simple and definitive: use the Eloquent create()
method. This is the canonical, intended way to create and persist a single model.
It not only inserts the data but also returns a fully hydrated, feature-rich Eloquent model instance, complete with the auto-incremented `id`.
// Don't forget to set the `$fillable` property on your User model!
$userData = [
'name' => 'Jane Doe',
'email' => 'jane.doe@example.com',
'password' => bcrypt('password'),
];
// The create() method returns the complete User model
$user = User::create($userData);
echo $user->id; // Outputs the new auto-incremented ID
echo $user->name; // Outputs 'Jane Doe'
Using create()
ensures all your model's boot methods, events, and observers are fired correctly. For single-record creation, this is the only method you should be using.
Step 3: For Small Bulk Inserts, A Simple Loop Can Suffice
What if you have a small, manageable number of records to insert—say, less than 50? While not the most performant, a simple loop that calls create()
for each record is often the most readable and maintainable solution.
$usersData = [
['name' => 'Alice', 'email' => 'alice@example.com', ...],
['name' => 'Bob', 'email' => 'bob@example.com', ...],
['name' => 'Charlie', 'email' => 'charlie@example.com', ...],
];
// Use `collect()` and `map()` for a clean, functional approach
$createdUsers = collect($usersData)->map(function ($data) {
return User::create($data);
});
// $createdUsers is now a Collection of User models!
Warning: This approach creates a separate `INSERT` query for each item in the array (an "N+1 query problem" for inserts). It's fine for a dozen records, but it will be very slow for hundreds or thousands. If performance is a concern, move to Step 4.
Step 4: The Pro Pattern for High-Performance Bulk Inserts
Here is the most robust and performant pattern for bulk inserting records and retrieving their corresponding models. It's a two-step process that combines the speed of insert()
with a clever retrieval query.
The logic is simple: find out the highest ID in the table before you insert, perform the fast bulk insert, and then retrieve all records with an ID greater than what you recorded.
use Illuminate\Support\Facades\DB;
use App\Models\User;
// 1. Get the maximum ID currently in the table.
// The `?? 0` handles cases where the table is empty.
$lastIdBeforeInsert = User::max('id') ?? 0;
// 2. Prepare your data. Make sure to add timestamps manually!
$usersData = [
['name' => 'User One', 'email' => 'one@test.com', 'created_at' => now(), 'updated_at' => now()],
['name' => 'User Two', 'email' => 'two@test.com', 'created_at' => now(), 'updated_at' => now()],
// ... 1000s more users
];
// 3. Perform the highly efficient bulk insert.
User::insert($usersData);
// 4. Retrieve all the newly created models in a single, efficient query.
$newUsers = User::where('id', '>', $lastIdBeforeInsert)->get();
// $newUsers is now a Collection containing all your new User models!
This pattern gives you the best of both worlds: the raw speed of a single `INSERT` statement and the full Eloquent models you need for subsequent operations, all while being safe from database race conditions.
Step 5: Know When to Use Modern Tools like `upsert()`
Laravel 8 introduced the upsert()
method, designed for "update or insert" operations. It's a powerful tool for synchronizing data, but it's crucial to understand what it returns.
upsert()
returns an integer representing the number of affected rows (rows inserted + rows updated). It does not return the models.
// Upsert will insert new users or update existing ones based on the 'email' unique key.
$affectedRows = User::upsert(
[
['name' => 'Updated Name', 'email' => 'one@test.com'],
['name' => 'New User Three', 'email' => 'three@test.com'],
],
['email'], // The column(s) to check for uniqueness
['name'] // The column(s) to update if a match is found
);
echo $affectedRows; // Outputs 2 (1 updated, 1 inserted)
Use upsert()
when your primary goal is to ensure data exists and is up-to-date, and you don't immediately need the model instances. For retrieving models after a bulk insert, stick with the pattern in Step 4.
Comparison of Laravel Insert Methods
To help you choose the right tool at a glance, here’s a comparison table summarizing the key methods for creating data in Laravel.
Method | Return Value | Performance | Fires Eloquent Events? | Best For |
---|---|---|---|---|
Model::create($data) |
Eloquent Model | Good (for one) | Yes | Creating a single, event-aware record. |
Model::insert($data) |
Boolean | Excellent | No | Fire-and-forget bulk inserts where models aren't needed immediately. |
DB::table()->insertGetId($data) |
Integer (ID) | Excellent | No | Inserting a single record and only needing its ID back. |
Pro Pattern (Step 4) | Eloquent Collection | Excellent | No (on insert) | High-performance bulk inserts where you need the models back. |
Common Pitfalls & Best Practices
As you work with these methods, keep these critical considerations in mind to avoid bugs and ensure data integrity.
Pitfall: The Silence of Eloquent Events
Remember that Query Builder methods like insert()
and upsert()
are silent. They do not trigger any of the Eloquent model events (`creating`, `created`, `saving`, `saved`, `deleting`, etc.). This means if you have logic in an observer or a model's `boot()` method that relies on these events (e.g., generating a UUID, logging an activity), it will not run. Always use create()
if this event-driven logic is essential.
Pitfall: Manually Handling Timestamps
Eloquent's create()
method automatically handles the created_at
and updated_at
timestamps. The insert()
method does not. When using insert()
, you must manually add these values to your data array for each record, as shown in the Step 4 example.
$data = [
'name' => 'A new user',
'email' => 'new@example.com',
// You MUST add these manually!
'created_at' => now(),
'updated_at' => now(),
];
User::insert($data);
Best Practice: Always Use Database Transactions
When performing any bulk operation, you should wrap it in a database transaction. This ensures that if any part of the operation fails, the entire set of changes is rolled back, preventing a partially-inserted, corrupted data state.
DB::transaction(function () use ($usersData) {
// The logic from Step 4 goes here
$lastIdBeforeInsert = User::max('id') ?? 0;
User::insert($usersData);
$newUsers = User::where('id', '>', $lastIdBeforeInsert)->get();
// ...do something with $newUsers
});
// If an exception occurs inside the closure, all queries are automatically rolled back.