Mastering $hidden and $visible in Laravel Eloquent

Securing API Responses: Laravel's $hidden vs $visible

Mastering $hidden and $visible in Laravel Eloquent


When developing REST APIs or full-stack applications with Laravel, returning Eloquent models directly from your controllers is common practice. However, database tables often contain sensitive or internal information—such as password hashes, two-factor authentication secrets, remember tokens, payment gateway customer IDs, or internal status flags—that should never be exposed in API responses or serialized JSON payloads.

Accidentally leaking sensitive database fields can lead to major security vulnerabilities. To solve this, Laravel Eloquent provides built-in model properties: $hidden and $visible, along with runtime methods like makeHidden() and makeVisible().

In this tutorial, we will learn how to protect sensitive attributes using $hidden and $visible, how to override them at runtime for specific endpoints, and best practices to keep your API payloads secure.

What is Attribute Serialization in Eloquent?

When you return an Eloquent model or collection directly from a route or controller method, Laravel automatically converts it to an array or JSON using the toArray() or toJson() methods under the hood.

By default, every column retrieved from the database is included in that JSON output. The $hidden and $visible properties act as internal filters during this serialization step.

Using the $hidden Property (Blacklist Approach)

The $hidden property acts as a blacklist. Any database column or appended attribute listed in the $hidden array will be stripped out when the model is converted to an array or JSON.

You have likely already seen this in Laravel's default User model:

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    protected $fillable = [
        'name',
        'email',
        'password',
        'stripe_id',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_secret',
        'stripe_id',
    ];
}


Now, when you fetch a user and return it in a controller:

public function show(User $user)
{
    return response()->json($user);
}


The JSON response will only output the unhidden attributes:

{
    "id": 1,
    "name": "Kishan Kumar",
    "email": "kishan@example.com",
    "created_at": "2026-08-25T07:45:00.000000Z",
    "updated_at": "2026-08-25T07:45:00.000000Z"
}


Even though password and stripe_id exist in the database and are loaded in memory on the model instance, they are completely excluded from the serialized output.

Using the $visible Property (Whitelist Approach)

The $visible property acts as a whitelist. When you define $visible on a model, only the attributes specified in that array will be included when converted to JSON or array form. Everything else is hidden by default.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Transaction extends Model
{
    /**
     * The attributes that should be visible in serialization.
     *
     * @var array<int, string>
     */
    protected $visible = [
        'reference_id',
        'amount',
        'status',
        'created_at',
    ];
}


When to use $visible: A whitelist approach is often safer for high-security domain models (like financial ledgers, audit trails, or identity verifications) because if a developer adds a new column to the migration table later, it will not be exposed unintentionally.

Managing Visibility Dynamically at Runtime

Static model properties apply globally across your entire project. But what if you have an Admin panel route where you need to view a hidden field, or a public profile endpoint where you want to hide an extra attribute?

Laravel provides runtime methods to temporarily adjust attribute visibility for a specific model or collection.

1. makeVisible()

The makeVisible() method reveals attributes that are normally hidden in $hidden:

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\User;

class AdminUserController extends Controller
{
    public function show(User $user)
    {
        // Unhide the stripe_id and two_factor_confirmed_at for the admin view
        return response()->json(
            $user->makeVisible(['stripe_id', 'two_factor_confirmed_at'])
        );
    }
}


2. makeHidden()

The makeHidden() method temporarily hides attributes that are normally visible:

namespace App\Http\Controllers;

use App\Models\User;

class PublicProfileController extends Controller
{
    public function show(User $user)
    {
        // Hide email and updated_at on public user profile
        return response()->json(
            $user->makeHidden(['email', 'updated_at'])
        );
    }
}


3. Using with Collections

Both makeVisible() and makeHidden() work seamlessly on Eloquent collections as well:

$users = User::where('is_active', true)->get();

// Make stripe_id visible across all models in this collection
$users->makeVisible(['stripe_id']);

return response()->json($users);


$hidden vs $visible Comparison

Feature $hidden (Blacklist) $visible (Whitelist)
Strategy Show all columns except those listed. Hide all columns except those listed.
New Migration Columns Visible by default unless explicitly added to $hidden. Hidden by default until explicitly added to $visible.
Best For General models with only 1 or 2 sensitive fields (e.g. password). High-security models where data leaks must be strictly prevented.

Important Things to Remember

  • Do Not Use Both Simultaneously: Choose either $hidden or $visible on a single model. Using both on the same class can lead to conflicting and unexpected behavior.
  • Access in PHP Code Still Works: Adding an attribute to $hidden only prevents it from appearing in JSON/array serialization. You can still access $user->password or $user->stripe_id directly in your PHP backend code.
  • Hiding Appended Attributes: If you have an accessor attached via $appends (e.g. full_name), you can also add it to $hidden or hide it dynamically using makeHidden('full_name').
  • API Resources (Alternative): For complex or versioned APIs, using Laravel API Resources (JsonResource) alongside model visibility settings is the recommended architectural pattern to control output schema cleanly.

Conclusion

Protecting sensitive data in your APIs should not be an afterthought. By properly utilizing Laravel's $hidden and $visible properties—and leveraging makeVisible() and makeHidden() for contextual responses—you can safeguard internal credentials and build secure, professional API endpoints.

Thank you for reading this article 😊

For any query, do not hesitate to comment 💬



Previous Post Next Post

Contact Form