---
title: "Encrypted values — Attributes for Laravel | Roundly"
description: "Mark a definition encrypted to store its value as Crypt ciphertext at rest and decrypt it transparently on read — for every type."
url: https://roundly-consulting.com/open-source/docs/attributes-for-laravel/encryption
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Attributes for Laravel](https://roundly-consulting.com/open-source/docs/attributes-for-laravel.md)

# Encrypted values

Mark a definition encrypted: true and its value is transparently Crypt-encrypted at rest and decrypted on read. It works for every type; null values are never run through Crypt:

```php
Attributes::define(new AttributeDefinitionData('token', AttributeType::String_, encrypted: true));

Attributes::for($product)->set('token', 'secret');
Attributes::for($product)->get('token'); // 'secret' (DB column holds ciphertext)
```

The same flag works in config definitions and in per-model schemas:

```php
// config/attributes.php — or a model's $attributeDefinitions
'definitions' => [
    'api_token'  => ['type' => 'string', 'encrypted' => true],
    'birth_date' => ['type' => 'datetime', 'encrypted' => true],
],
```

## How it works

- The value is converted to its storage string first, then encrypted with Laravel’s Crypt — so the type round-trips intact.
- Each row carries an is\_encrypted flag, and reads decrypt according to the row, not the current definition. Enabling encryption later affects only values written afterwards; older rows keep reading correctly.
- Values are encrypted with your application key, so APP\_KEY must be set wherever attributes are read or written.
- unique still works: an encrypted unique value is indexed by a keyed blind index derived from APP\_KEY, never by its plaintext.
- Revisions store the ciphertext, so the audit log never leaks secrets.
- php artisan about reports definitions by count only, so encrypted attribute names never appear in diagnostics.

## Limitations

Ciphertext is non-deterministic, so encrypted values cannot be matched, ranged or ordered by the query scopes. Presence scopes such as whereHasAttribute() still work, and so does unique:

```php
use RoundlyConsulting\Attributes\DataTransferObjects\AttributeDefinitionData;
use RoundlyConsulting\Attributes\Enums\AttributeType;
use RoundlyConsulting\Attributes\Enums\UniqueScope;
use RoundlyConsulting\Attributes\Facades\Attributes;

Attributes::define(new AttributeDefinitionData(
    'national_id',
    AttributeType::String_,
    unique: UniqueScope::Global_,
    encrypted: true,
));

Attributes::for($a)->set('national_id', 'AB123');
Attributes::for($b)->set('national_id', 'AB123'); // throws DuplicateAttributeValueException
```

Rotating APP\_KEY changes the blind-index key — re-save encrypted unique values afterwards.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/attributes-for-laravel.md)
