Each row carries its own sealcraft_key column (an auto-populated UUID) and gets its own DEK. Best for vault-style rows where each record is its own security boundary.
Configure
use Crumbls\Sealcraft\Casts\Encrypted;
use Crumbls\Sealcraft\Concerns\HasEncryptedAttributes;
class VaultEntry extends Model
{
use HasEncryptedAttributes;
protected string $sealcraftStrategy = 'per_row';
protected $casts = ['secret' => Encrypted::class];
}
Migration
Add a row-key column to the table:
$table->string('sealcraft_key', 191)->nullable()->index();
Empty row-keys are a hard error
If a saved row has NULL or empty in its row-key column, sealcraftContext() throws InvalidContextException. Silently minting a fresh UUID would orphan a new DEK on every read and guarantee decryption failure, since the original ciphertext was bound to a different (also throwaway) context.
Backfill before turning encryption on
When adopting per-row on an existing table, populate row-keys on legacy rows first:
php artisan sealcraft:backfill-row-keys "App\\Models\\VaultEntry"
The command is idempotent, supports --chunk and --dry-run, and bypasses model events so it is safe to run on tables that already contain ciphertext.
New rows are handled automatically
A creating hook on the trait ensures every newly INSERTed per-row model carries a row-key, even if no encrypted attribute is touched during fill.
Performance
One KEK unwrap per distinct row you read. The DEK cache keeps steady-state request overhead low, but bulk reads of unique rows are more expensive than per-group.
Contributors
Thank you to everyone who has contributed to this package. Every pull request, bug report, and idea makes a difference.