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

Redactable Models Laravel Package

ashallendesign/redactable-models

Ash Allen's Laravel package makes it easy to redact sensitive Eloquent model data. Mark attributes as redactable and automatically mask or hide values when models are serialized, logged, or returned from APIs, helping prevent leaks of passwords, emails, and other private information.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config and migration:

composer require ashallendesign/redactable-models
php artisan vendor:publish --tag="redactable-models-config"
php artisan vendor:publish --tag="redactable-models-migrations"
php artisan migrate

The first place to look is the published config/redactable-models.php. It is the control panel: you map each redactable model to the attributes that should be redacted and the redactor class responsible for each attribute.

// config/redactable-models.php
'redactors' => [
    App\Models\User::class => [
        'name'  => App\Redactors\NameRedactor::class,
        'email' => App\Redactors\EmailRedactor::class,
        'phone' => App\Redactors\PhoneRedactor::class,
    ],
],

Then add the trait to the model:

use AshAllenDesign\RedactableModels\Traits\Redactable;

class User extends Model
{
    use Redactable;
}

The first practical use case is a GDPR/CCPA erasure request for a customer who has orders. Do not delete the user and orphan the order history. Instead:

$user->redact();

The user row remains, foreign keys remain intact, and the configured PII columns are replaced with redacted values. The package stores the original values in its redactions table so you can inspect what changed and, where supported, restore later. Check that table after your first tinker test:

$user->redact();
$user->refresh()->only(['name', 'email', 'phone']);

Also read the trait source and the built-in redactor classes before writing your own. They show the exact redactor contract, whether partial redaction is supported, and how restoration is handled.

Implementation Patterns

Orchestrate redaction in a job or service, not a controller. Redaction often touches several models and should be retryable and auditable:

class RedactUserData implements ShouldQueue
{
    use SerializesModels;

    public function __construct(public int $userId) {}

    public function handle(): void
    {
        $user = User::withTrashed()->findOrFail($this->userId);

        DB::transaction(function () use ($user) {
            $user->redact();
            $user->addresses()->each->redact();
            $user->profile?->redact();
        });
    }
}

Treat the config as your PII data map. Keep model-to-field mappings in the package config, and keep custom redactors small and deterministic. A redactor should return a safe replacement value, not perform side effects. For example, an email redactor can return redacted+{$user->id}@example.com so the value stays unique per record.

Make redaction idempotent. Jobs retry. A record may already be redacted when the job runs again. Guard against overwriting an already-redacted value with a second redaction, or make redactors safe to run repeatedly. Record the redaction event in your own audit table with actor, timestamp, request ID, and affected fields.

Redact the whole data subject, not just the users row. A customer’s PII usually lives in addresses, profiles, consents, payment_methods, support_tickets, and JSON payloads. Walk relationships deliberately, or build a RedactDataSubject service that owns the full graph. Avoid relying on a single $user->redact() call to be complete.

Use a per-record placeholder strategy where uniqueness matters. If email is unique, redacting every user to [email protected] will fail on the second record. Either generate unique placeholders, set the column to null where the unique index permits multiple nulls, or remove the unique constraint before redacting. The same applies to usernames, handles, and referral codes.

Keep restoration behind authorization. If the package can restore original values, wrap that in a policy and log it as a sensitive action. Restoration is effectively a data re-identification event.

Integrate with downstream systems explicitly. Redaction changes database rows; it does not automatically update Meilisearch/Algolia, Redis caches, CDN responses, analytics exports, or a warehouse. After redaction, clear caches, reindex searchable models, and push a redaction event to downstream consumers.

Test the redaction path with RefreshDatabase. Assert both the redacted column values and the presence of original values in the redactions table. Use Queue::fake() when testing the orchestration job, and include a test for re-running the job twice.

Backfill existing records with a chunked command. For existing data, write an Artisan command using chunkById() and dispatch one job per model or per batch. Do not load the whole users table into memory.

Gotchas and Tips

  • Redaction is not deletion, and it is not a complete GDPR solution. Backups, query logs, failed jobs, email logs, support tools, and third-party processors may still hold PII. Redaction only covers configured database columns. Maintain a separate retention and data-map process.
  • Unique indexes will bite you. Replacing many emails with the same placeholder fails. Use per-record replacements, nullable columns, or adjust indexes. Test against production-like constraints, not just SQLite.
  • Config caching. After changing redactor mappings, run php artisan config:cache in deployment. In tests, if you mutate config at runtime, clear the config cache or use config()->set() before the model is resolved.
  • Model events and observers. Redaction likely calls update()/save(), firing saving, updating, and updated. Observers may re-populate redacted fields, fail validation, or overwrite placeholders. Audit observers on redactable models and consider quiet saves where appropriate.
  • Mass assignment and guarded fields. Confirm the redacted attributes are writable by the package. If a field is guarded, the redaction may silently not persist.
  • Morph maps. The package’s polymorphic redactions table stores model class names. If you rename models or introduce a morphMap, old redaction rows may no longer resolve to the right model. Keep morph aliases stable.
  • Soft deletes. Redacting a soft-deleted user requires withTrashed(). Default queries will not find them. Include soft-deleted records in your data-subject redaction flow if they contain PII.
  • Encryption keys are critical. If original values are encrypted at rest, losing the key makes restoration impossible. Back up APP_KEY and any package-specific key, and document the recovery procedure.
  • Don’t log originals. Laravel’s query log, exception traces, and your own audit logs can capture original values. Disable verbose query logging in production or scrub PII. Never log the redactions table contents.
  • JSON, encrypted casts, and media are not automatic. A redactor for a JSON column must traverse and redact each sensitive key. Files in S3, avatars, signatures, and attachments need separate deletion/redaction logic.
  • Queue retries and stale models. Use SerializesModels carefully and re-fetch inside handle(). A retried job may see an already-redacted model or a model that has since been deleted.
  • Search indexes and caches. Redaction will not update external indexes. Reindex or delete documents after redaction, and invalidate any cached API responses.
  • Validation rules may reject placeholders. If a redacted email is saved through a form request or validator, email validation may fail. Bypass validation for redaction writes or use a valid-format placeholder.
  • Pin the version and check the source. This is a small package with a narrow community. Review the trait and redactor contract for your installed version before relying on undocumented behavior, especially around restoration and partial redaction.
  • The main extension point is the custom redactor. Prefer a small redactor class per field type over modifying the package. Keep redactors pure, testable, and independent of request context.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo