Mastering Laravel Eloquent JSON serialization is essential for clean APIs. Learn to use hidden attributes and appends to control your model's output today.
Last month, I spent about two days debugging an API endpoint that was leaking password hashes and internal database timestamps. It was a classic case of forgetting that Eloquent models are "too helpful"—when you return a model directly from a route, Laravel automatically converts it into JSON. If you haven't explicitly protected your sensitive data, it’s all going out over the wire.
In this post, we’ll look at Laravel Eloquent serialization and how to take back control of your JSON output.
The most common mistake I see junior devs make is returning a raw User model in a controller. Even if you think your database schema is private, Eloquent will happily serialize every single column in the table.
To stop this, you should use the $hidden property inside your model. Think of this as your "blacklist" for serialization.
PHPnamespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { #6A9955">/** * The attributes that should be hidden for serialization. * * @var array<int, string> */ protected $hidden = ['password', 'remember_token', 'two_factor_secret']; }
Once you add these, they’ll never appear in your JSON output, regardless of how you query the model. If you only want to expose a specific subset of data, you can use the $visible property instead, which acts as a "whitelist." I personally prefer $hidden for most projects because it’s less restrictive, but if you’re building a public-facing API, $visible is a safer bet.
Sometimes, your JSON output needs data that doesn't actually exist as a column in your database table. Maybe you need a full name, or a calculated status field. This is where appends comes in.
Let’s say you want to add a full_name attribute to your User JSON response. First, define the accessor in your model:
PHPpublic function getFullNameAttribute(): string { return "{$this->first_name} {$this->last_name}"; }
By default, Laravel won't include this in your JSON. You have to tell the model to "append" it whenever it's serialized:
PHPprotected $appends = ['full_name'];
Now, every time you return a User object, your JSON will include the full_name key. It's a clean way to keep your logic encapsulated in the model. Just be careful: if your accessor performs complex database queries, you’ll quickly run into N+1 performance issues. I once saw a team add an "is_active" check that triggered a separate query for every single item in a collection of 500 users. It added roughly 200ms to the response time, which is a massive hit for a simple index route.
While hidden and appends are great for quick fixes, they aren't always the right tool for complex APIs. If your JSON needs vary depending on who is requesting the data (for example, an admin user seeing more fields than a standard user), you're better off using API Resources. Mastering Laravel API Resources: A Guide to Clean JSON Responses is a great next step to learn how to decouple your database from your output.
If you are looking to standardize how your application handles these responses, I'd also suggest looking into Mastering Laravel Response Macros for Consistent API Design. It helps ensure your API doesn't just return data, but follows a strict, consistent format across your entire codebase.
Q: Can I override $hidden for a specific request?
A: Yes! You can use the makeVisible() or makeHidden() methods on your model instance at runtime.
PHP$user = User::find(1); return $user->makeVisible('password_reset_token')->toJson();
Q: Does casting affect serialization?
A: Absolutely. If you use Laravel model casting to change a column to a date or json type, Eloquent will respect those formats during the JSON serialization process.
Q: Should I use $appends for everything?
A: No. If you have a lot of custom logic, use an API Resource. It keeps your model clean and makes your code much easier to test.
I still remember the first time I accidentally exposed an is_admin flag in a JSON response because I didn't set up my hidden attributes correctly. It's a humbling lesson. Start by protecting your models with $hidden, use $appends sparingly, and always consider if an API Resource might be a more scalable solution for your specific endpoint.
Mastering Laravel config files and environment variables is essential for clean code. Learn how to manage your settings effectively in this practical guide.