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:
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:
// 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:
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 DuplicateAttributeValueExceptionRotating 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 cryptoBy donating, you agree to our donation terms.
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.