Laravel Eloquent - Encrypting/Decrypt Data on call

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Securing Your Data: The Laravel Way to Encrypt and Decrypt Eloquent Attributes Transparently

As developers, data security is paramount. When dealing with sensitive information—names, emails, phone numbers—encrypting this data at rest in your database is a crucial first step. However, the challenge lies not just in saving the encrypted blob, but ensuring that this data is automatically decrypted every time it is retrieved and used by your application logic.

You propose an excellent workflow: encrypt on save, decrypt on load, ideally transparently across all Eloquent operations, including relationships. While manually overriding methods like save() or creating custom global functions can technically achieve the goal, they often lead to brittle code that bypasses Laravel's elegant architecture. The "Laravel Way" involves using built-in features like Model Observers and Mutators to achieve this transparency elegantly.

Let’s explore how we can implement full-stack, automatic encryption/decryption for Eloquent models effectively.

Why Direct Method Overrides Fall Short

Your initial idea of modifying the save() method to encrypt attributes is a viable start. However, this only handles the writing process. The moment you call $user = User::find(1);, the data retrieved from the database will still be encrypted strings. To get usable data, you would need to manually hook into every getter method (getName(), getEmail(), etc.) on every model, which quickly becomes repetitive and error-prone as your application grows.

A better approach is to leverage Laravel's event system or attribute casting mechanisms to intercept the data flow before it hits the model attributes (for saving) and after it leaves the model (for retrieval).

The Idiomatic Laravel Solution: Mutators and Accessors

The most idiomatic way to handle data transformation within Eloquent, especially for fields that should always be encrypted, is by using Mutators (for setting values before saving) and Accessors (for retrieving decrypted values).

For sensitive fields like names or emails, instead of storing the raw model attributes directly, we can control how those attributes are stored in the database.

Step 1: Implementing the Encryption Logic

We will use a trait or an Observer to manage this transformation globally across models. For demonstration, let's focus on making the data flow predictable. Since you are dealing with complex transformations (like decrypting entire arrays), we can encapsulate that logic within the model itself or a dedicated service.

If we opt for the approach where the database stores encrypted strings:

use Illuminate\Support\Facades\Crypt;
use Illuminate\Database\Eloquent\Model;

class EncryptableModel extends Model
{
    // This trait handles the encryption/decryption logic for specific attributes
    protected static function encryptAttribute($value)
    {
        if (is_string($value)) {
            return Crypt::encryptString($value);
        }
        // Handle other types if necessary. For complex objects, JSON encoding is often used first.
        return Crypt::encrypt($value);
    }

    /**
     * Mutator to encrypt data before saving.
     */
    protected function setAttribute(string $key, $value)
    {
        // Only apply encryption if the value is present and not already encrypted (safety check)
        if ($key === 'name' || $key === 'email') { 
            $this->attributes[$key] = $this->encryptAttribute($value);
        } else {
            $this->attributes[$key] = $value;
        }
    }

    /**
     * Accessor to decrypt data when retrieving.
     */
    public function getNameAttribute($value)
    {
        if (is_string($value)) {
            return Crypt::decryptString($value);
        }
        return $value; // Return as is if not encrypted
    }

    // ... other accessors for email, phone, etc.
}

By defining these methods within the model, any interaction with $user->name or retrieving the data via Eloquent automatically triggers the encryption/decryption cycle, making the process transparent to the rest of your application code. This pattern keeps the security logic tightly coupled with the data structure, which is a best practice when building robust systems like those discussed in Laravel documentation.

Handling Relationships and Collections

You specifically mentioned extending this to relationships, such as User::with('someOtherTable')->find($id). When Eloquent loads relationships, it fetches related models. If those related models also use the encryption pattern described above (i.e., they are also EncryptableModel instances), the decryption happens automatically upon loading the relationship data as well.

For nested or complex data structures, like your proposed decrypt($array) function, passing this logic through Model Events (like the retrieved event) ensures that whenever an object is instantiated from the database, it is immediately "unwrapped" and decrypted before any business logic accesses its properties. This avoids the need for a separate global helper function, keeping your application code clean and adhering to SOLID principles.

Conclusion

While creating custom methods like decrypt() offers direct control, relying on Laravel's built-in Eloquent features—Mutators, Accessors, and Model Events—provides a far more maintainable, scalable, and "Laravel Way" solution. By embedding the encryption/decryption logic directly into your models, you ensure that data security is enforced automatically across all read and write operations, providing robust protection for your sensitive information as you build powerful applications on the Laravel framework.