Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Encrypted Field Type Laravel Package

anomaly/encrypted-field_type

Adds an encrypted field type to Anomaly/Streams, letting you store sensitive values in encrypted form. It integrates as a field type so encrypted data can be managed through your existing Streams forms and modules, with encryption handled by the package.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a PyroCMS/Streams Platform field type addon, not a standalone Laravel package — it only does anything once the anomaly/streams platform is installed in a Laravel app.

Minimal steps:

  1. composer require anomaly/encrypted-field_type (Laravel 5.5+/Pyro 3.x auto-discovers Anomaly\EncryptedFieldType\EncryptedFieldTypeServiceProvider). On older setups add the provider to config/app.php.
  2. Confirm APP_KEY is set and treated as production infrastructure. The field type delegates to Laravel's Crypt facade; if the key changes, every stored value becomes undecryptable.
  3. Optionally publish config/resources: php artisan vendor:publish --tag=encrypted_field_type.

Where to look first in the repo:

  • src/EncryptedFieldType.php — the type definition (slug encrypted, column type, validation).
  • src/EncryptedFieldTypePresenter.php — what $entry->field actually returns in views (plaintext vs. ciphertext).
  • resources/config/config.php — any cipher/key overrides.
  • src/EncryptedFieldTypeServiceProvider.php — bindings and route/asset registration.

First use case: a per-tenant API token or TOTP secret on an existing stream. In the control panel: Streams → Fields → Create Field, type Encrypted, assign it to a stream; or in a migration/seed via the Streams API:

$this->fields->add([
    'slug'   => 'api_secret',
    'type'   => 'anomaly.field_type.encrypted',
    'config' => ['input_type' => 'text'],
]);

Then $entry->api_secret = 'plaintext'; $entry->save(); and read it back with $entry->api_secret — encryption/decryption is transparent at the assignment boundary.

Implementation Patterns

Assign to streams declaratively. Prefer defining the field in a stream migration/installer rather than clicking it in the CP, so staging and production agree. Keep the plaintext out of the migration — it should only define the column.

Read/write through the entry.

$entry->api_secret = $request->input('api_secret'); // encrypted on set
$entry->save();

$token = $entry->api_secret;              // decrypted accessor
$token = $entry->api_secret->value();     // presenter form, if you need the raw string

Always check the presenter when wiring Blade views — some field types return a decorated object, and calling string functions on it fails.

Typical day-to-day targets: third-party API keys, webhook signing secrets, OAuth refresh tokens, SMTP passwords stored in a settings stream, per-tenant credentials in a multi-tenant Pyro install.

Form integration is boring on purpose. The field type renders a standard text/textarea input and runs the platform's usual validation. Use input_type => 'textarea' for long secrets (PEM keys, JWT signing material) so the ciphertext column doesn't get truncated.

Rotate keys with a migration command, not by hand.

foreach (TenantModel::cursor() as $tenant) {
    $plain = $tenant->api_secret;      // decrypt with current key
    $tenant->api_secret = $plain;      // re-encrypt, new key
    $tenant->save();
}

Run it in batches, log the entry IDs you've processed, and keep the old key available until the batch verifies clean.

Extension point: subclass EncryptedFieldType and override the encrypt/decrypt helpers to route through KMS/Vault or to swap in encryptString/decryptString. Register your subclass in your own addon's service provider rather than forking.

Pair with a deterministic lookup column whenever you need to find rows by the encrypted value (see below). Store hash_hmac('sha256', $plain, config('app.key')) in a normal indexed column and query on that.

Gotchas and Tips

  • Key rotation is a data-loss event. Laravel's Crypt derives from APP_KEY. Use APP_PREVIOUS_KEYS (Laravel 11+) or override the encrypter binding, then re-encrypt rows. Back up the key alongside every database dump — a dump without its key is worthless here.
  • Ciphertext is non-deterministic. Encrypting the same string twice yields different bytes (random IV), so where('api_secret', $value), ORDER BY, LIKE, and distinct-value counts are all impossible. This is the biggest architectural constraint; plan the hash-lookup column up front.
  • Use TEXT, not VARCHAR(255). Laravel's payload is base64 of a JSON envelope (iv, value, mac/tag) plus the original length; a 100-character secret easily exceeds 255 bytes. Truncation produces DecryptException at read time, which is ugly to diagnose.
  • Search and filtering won't work. Streams' search/filter UI and any where-based filtering on the field are effectively dead. Hide or exclude the field from searchable config in the stream definition.
  • Seeds and fixtures leak plaintext. Never put real secrets in database/seeds, stream installer data, or the repo. Use .env/vault-backed values and a command to populate.
  • Handle DecryptException. Wrap reads in a try/catch in presenters and return null or a masked placeholder; a single bad row otherwise blows up an entire listing page.
  • Debugging: php artisan tinker → Crypt::decryptString(DB::table('...')->value('api_secret')) confirms storage; dump the raw column to verify it's actually ciphertext and not silently plaintext via a cached field type.
  • Testing: pin a fixed APP_KEY in phpunit.xml. Tests that encrypt then compare bytes will fail; assert on decrypted values only.
  • Config quirks: cipher and key fall back to config('app.cipher')/config('app.key'); mixing AES-256-CBC and AES-256-GCM across environments breaks portability of dumps between environments even with the same key.
  • Maintenance risk is real — 0 stars, 0 dependents, no visible activity. Pin an exact version in composer.json, and if it disappears, the fallback is a five-line custom field type using Crypt::encryptString() on a TEXT column. Budget an afternoon for that migration rather than assuming upstream fixes arrive.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums