Veil is a Laravel package that helps you export database data with anonymized sensitive columns.
It's useful when you need to:
- Share production-like data with developers or contractors
- Create safe accurate data for local or staging environments
- Debug real-world issues without exposing personal data
- Comply with privacy and data protection requirements
Veil lets you define anonymization rules per table and column, ensuring sensitive values are replaced consistently during export. The exported SQL file contains only INSERT statements, making it easy to import into an existing database that already has the schema defined via Laravel migrations.
"This package was created and maintained by the team behind SignDeck — a lightweight e-signature platform for collecting documents and signatures."
| Veil | Laravel | PHP |
|---|---|---|
| 2.x | 11.x, 12.x, 13.x | 8.3+ |
| 1.x | 11.x | 8.3+ |
Laravel 13 has been verified with PHPUnit 12 on PHP 8.3, 8.4, and 8.5, including installation and export in a fresh Laravel 13 application. Laravel 11 remains API-compatible but currently has upstream security advisories that block fresh resolution with current Composer. See local development and compatibility testing for the recorded compatibility checks, security limitation, and local PHPUnit commands.
Veil v2 is a complete rewrite of the export engine. The public API (your VeilTable classes, config, and artisan commands) is unchanged — no code changes are needed in your application.
Under the hood, v2 replaces the mysqldump → parse → rewrite pipeline with a direct query-builder approach:
- Faster scoped exports —
query()scopes are now applied at the database level. In v1,mysqldumpdumped all rows and filtering happened after the fact in PHP. - Lower memory usage — Data is read in chunks and streamed to the output file, instead of loading the entire SQL dump into memory.
- Fewer dependencies —
spatie/laravel-db-snapshotsandphpmyadmin/sql-parserhave been removed. Veil now only depends on Laravel's own Illuminate components. - No
mysqldumpbinary required — The export runs entirely in PHP via Laravel's query builder.
You can install the package via Composer:
composer require signdeck/veilPublish the configuration file:
php artisan vendor:publish --tag=veil-configThis will create a config/veil.php file where you can configure your export settings.
Make sure you also have a filesystem disk configured for storing exports. By default, Veil uses the local disk.
Generate a new Veil table class using the artisan command:
php artisan veil:make-table usersThis creates app/Veil/VeilUsersTable.php:
<?php
namespace App\Veil;
use SignDeck\Veil\Veil;
use SignDeck\Veil\Contracts\VeilTable;
class VeilUsersTable implements VeilTable
{
public function table(): string
{
return 'users';
}
public function columns(): array
{
return [
'id' => Veil::unchanged(), // Keep original value
'email' => 'user@example.com', // Replace with this value
];
}
}In the columns() method, specify which columns to include in the export:
public function columns(): array
{
return [
'id' => Veil::unchanged(), // Keep original value
'name' => 'John Doe', // Replace all names with "John Doe"
'email' => 'redacted@example.com', // Replace all emails
'phone' => '000-000-0000', // Replace all phone numbers
// 'password' - not listed, so it won't be exported
];
}Important: Only columns defined in columns() will be included in the export. Any columns not listed will be excluded from the exported SQL.
You can use closures or callables to generate unique values per row. The callable receives:
$original— the original value of the column$row— an array of all column values in the current row
public function columns(): array
{
return [
'id' => Veil::unchanged(),
// Generate unique fake email for each row
'email' => fn ($original) => fake()->unique()->safeEmail(),
// Transform the original value
'name' => fn ($original) => strtoupper($original),
// Access other columns via $row parameter
'email' => fn ($original, $row) => "user{$row['id']}@example.com",
// Combine multiple column values
'display_name' => fn ($original, $row) => "{$row['name']} (ID: {$row['id']})",
];
}This is useful when you need unique anonymized values per row or want to reference other columns in the transformation.
Important: If you want to use Faker or other generators to create different values per row, you must wrap them in a closure:
// ❌ Wrong - executes once, same value for all rows
'first_name' => app(Generator::class)->firstName(),
// ✅ Correct - executes per row, different value for each row
'first_name' => fn () => app(Generator::class)->firstName(),
// or
'first_name' => fn () => fake()->firstName(),When exporting multiple related tables, always use Veil::unchanged() for primary keys and foreign keys to maintain referential integrity.
// ✅ Correct - IDs are preserved, relationships remain intact
class VeilUsersTable implements VeilTable
{
public function columns(): array
{
return [
'id' => Veil::unchanged(), // Primary key - keep unchanged
'name' => 'John Doe',
'email' => 'user@example.com',
];
}
}
class VeilPostsTable implements VeilTable
{
public function columns(): array
{
return [
'id' => Veil::unchanged(), // Primary key - keep unchanged
'user_id' => Veil::unchanged(), // Foreign key - keep unchanged
'title' => 'Anonymized Title',
];
}
}// ❌ Wrong - This will break foreign key relationships
class VeilUsersTable implements VeilTable
{
public function columns(): array
{
return [
'id' => fn () => fake()->randomNumber(), // Don't anonymize IDs!
'name' => 'John Doe',
];
}
}Why? If you change a user's id from 1 to 999, all their posts with user_id = 1 will become orphaned because the foreign key no longer matches.
Rule of thumb: Only anonymize data columns (names, emails, addresses), never identifier columns (IDs, UUIDs, foreign keys).
You must define a query() method to filter which rows are exported. Return null to export all rows:
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Facades\DB;
use SignDeck\Veil\Veil;
use SignDeck\Veil\Contracts\VeilTable;
class VeilUsersTable implements VeilTable
{
public function table(): string
{
return 'users';
}
public function columns(): array
{
return [
'id' => Veil::unchanged(),
'email' => 'redacted@example.com',
];
}
/**
* Only export users created in the last year.
* Return null to export all rows.
*/
public function query(): Builder|QueryBuilder|null
{
return DB::table('users')
->where('created_at', '>', now()->subYear());
// Or return null to export all rows:
// return null;
}
}The query should return a Laravel query builder instance that filters the rows you want to export. Return null to export all rows.
In v2, the query scope is applied directly at the database level — only matching rows are read from the database. This makes scoped exports significantly faster on large tables compared to v1, which dumped all rows and filtered afterward.
Add your Veil table classes to config/veil.php:
'tables' => [
\App\Veil\VeilUsersTable::class,
\App\Veil\VeilOrdersTable::class,
// Add more tables as needed
],Execute the export command:
php artisan veil:exportThis will create a timestamped SQL file (e.g., veil_2025-01-15_10-30-00.sql) on your configured disk with all specified tables and anonymized column values.
You can also specify a custom name for the export:
php artisan veil:export --name=staging-exportThis will create staging-export.sql instead of the timestamped filename.
Veil exports data only. It does not export:
- CREATE TABLE statements
- DROP TABLE statements
- ALTER TABLE statements
- Database schema definitions
Why? Laravel manages database schema through migrations, so Veil focuses solely on exporting and anonymizing data. This approach:
- Keeps exported files smaller and focused on data
- Aligns with Laravel's migration-based schema management
- Makes it easy to import data into existing databases that already have the schema
To use the exported data:
- Ensure your target database has the schema (run migrations)
- Import the Veil export file to populate the data
Veil fires events before and after the export process, allowing you to hook into the export lifecycle.
SignDeck\Veil\Events\ExportStarted- Fired before the export beginsSignDeck\Veil\Events\ExportCompleted- Fired after the export completes
You can listen to these events in your EventServiceProvider:
use SignDeck\Veil\Events\ExportStarted;
use SignDeck\Veil\Events\ExportCompleted;
protected $listen = [
ExportStarted::class => [
// Your listeners here
],
ExportCompleted::class => [
// Your listeners here
],
];ExportStarted event contains:
$snapshotName- The custom name provided (ornullif using default)$tableNames- Array of table names being exported
ExportCompleted event contains:
$fileName- The filename of the created export$snapshotName- The custom name provided (ornullif using default)$tableNames- Array of table names that were exported
use SignDeck\Veil\Events\ExportCompleted;
use Illuminate\Support\Facades\Log;
class LogExportCompleted
{
public function handle(ExportCompleted $event): void
{
Log::info('Database export completed', [
'file' => $event->fileName,
'tables' => $event->tableNames,
]);
}
}You can preview what would be exported without actually creating the file:
php artisan veil:export --dry-runThis will show:
- Which tables will be exported
- Which columns will be included
- Estimated row counts
- What filename would be created
No files are created in dry-run mode, making it safe to test your configuration.
No changes to your application code are required. Your VeilTable classes, config file, and artisan commands work exactly the same.
The only steps needed:
-
Update the package:
composer require signdeck/veil:^2.0
-
If you previously required
spatie/laravel-db-snapshotssolely for Veil, you can remove it:composer remove spatie/laravel-db-snapshots
-
The
mysqldumpbinary is no longer required on your server.
See CONTRIBUTING.md for installation, PHPUnit, framework-specific dependency selection, and installing a local checkout through a Composer path repository.
If you discover any security related issues, please send the author an email instead of using the issue tracker.
Please see the license file for more information.