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

Triggers Laravel Package

caneara/triggers

caneara/triggers extends Laravel’s schema builder with database trigger support, so you can create, modify, and drop triggers directly in migrations using a clean, fluent API. Useful for enforcing data integrity and automating database-side logic.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with composer require mattkingshott/triggers. Laravel auto-discovery handles the service provider; there is no config file to publish in the v1.0.x line. Start with the README, then read src/ and the tests — the package is small, and the generated trigger SQL is the most important thing to understand before using it in production migrations.

The first practical use case is an audit/history table. Define the audit table in a normal migration, then create the trigger in a follow-up migration:

// database/migrations/xxxx_create_user_audits_table.php
Schema::create('user_audits', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->string('action');
    $table->timestamps();
});

// database/migrations/xxxx_create_user_audit_trigger.php
public function up(): void
{
    Schema::createTrigger('users_audit_insert', function ($trigger) {
        $trigger->on('users');
        $trigger->afterInsert();
        $trigger->statement(
            "INSERT INTO user_audits (user_id, action) VALUES (NEW.id, 'created')"
        );
    });
}

public function down(): void
{
    Schema::dropTrigger('users_audit_insert');
}

Confirm the exact builder method names against the README for your installed version; the important part is that trigger DDL lives in a migration with a matching down().

Implementation Patterns

Keep triggers in migrations, next to the tables they touch. Create the table first, then the trigger in a separate migration. This preserves ordering for migrate and migrate:rollback, and makes trigger definitions reviewable in PRs.

Use triggers for things Eloquent cannot guarantee. Triggers are a good fit for:

  • Audit rows for direct SQL, bulk updates, and writes from other services.
  • Maintaining denormalized counters or "last modified" columns atomically.
  • Enforcing invariants that must apply even when writes bypass your application.

Do not use them as a replacement for model events when the logic is application-specific, needs queued jobs, or needs to know the authenticated user.

Branch on the driver when the trigger body is not portable. Trigger syntax varies materially: MySQL and PostgreSQL use NEW/OLD; SQL Server uses inserted/deleted and does not use FOR EACH ROW; SQLite has its own restrictions. For multi-database projects, keep the trigger body in a small per-driver method:

$driver = DB::connection()->getDriverName();

$statement = match ($driver) {
    'mysql', 'mariadb', 'pgsql' => "INSERT INTO audits (...) VALUES (NEW.id, 'created')",
    'sqlsrv' => "INSERT INTO audits (...) SELECT id, 'created' FROM inserted",
    default => throw new RuntimeException("Unsupported driver: {$driver}"),
};

Use deterministic trigger names. Include the table and event in the name (users_audit_insert, orders_total_after_update) so dropTrigger and manual debugging are unambiguous. This also avoids collisions when multiple migrations touch the same table.

Pair triggers with tests that write directly. A feature test that uses Eloquent is useful, but a test using DB::table('users')->insert(...) proves the trigger actually fires. Use RefreshDatabase so migrations run and triggers exist for the test database.

Consolidate raw SQL workarounds after upgrading. If you previously used DB::unprepared() only because v1.0 emitted backticks that broke PostgreSQL/SQLite/SQL Server, v1.0.1 lets you move that DDL back into the package's migration API.

Gotchas and Tips

v1.0.1 is the cross-database fix. v1.0 wrapped identifiers in backticks, which MySQL accepts but PostgreSQL, SQLite, and SQL Server reject. Upgrade to v1.0.1 if you target anything other than MySQL. This is a patch-level fix, not a breaking API change for normal usage.

Removing backticks changes identifier handling on MySQL too. Backticks were also the escape hatch for reserved words and mixed-case identifiers. After v1.0.1, a table or column named order, user, group, etc. may fail or behave differently depending on the driver. Avoid reserved words in trigger-referenced identifiers, or quote them explicitly in a raw statement.

PostgreSQL folds unquoted identifiers to lowercase. If your schema uses camelCase or mixed-case column names, unquoted references in the trigger body will not resolve. Either quote identifiers yourself or standardize on snake_case.

Drop triggers before dropping tables. A down() that drops the table before the trigger can fail on some engines or leave orphaned trigger metadata. Order migrations and rollbacks accordingly.

migrate:fresh is not a substitute for tested rollbacks. Triggers are usually dropped with their tables on MySQL and PostgreSQL, but explicit dropTrigger calls in down() are safer and make the migration reversible on all supported drivers.

MySQL DDL causes implicit commits. If your test suite relies on DatabaseTransactions around migrations, trigger creation may break the transaction boundary. Prefer RefreshDatabase for tests that depend on triggers.

Triggers bypass Eloquent events and observers. Model events, casts, and mutators do not run for changes made inside a trigger. Likewise, bulk DB::table()->update() calls fire triggers but not Eloquent events. Keep trigger logic small and independent of application state.

Debugging trigger creation failures is easier with raw SQL. Log or dump the generated statement when a migration fails. Inspect existing triggers with SHOW TRIGGERS (MySQL), SELECT * FROM information_schema.triggers (MySQL/PostgreSQL variants), or SELECT * FROM pg_trigger (PostgreSQL).

Multi-statement trigger bodies need care. BEGIN ... END bodies and driver-specific delimiter handling are the usual source of migration failures. Keep bodies to a single statement where possible; if not, verify the package's statement handling against your driver.

Extension point: the generated SQL is the contract. The package is small enough that if you need different quoting, identifier casing, or driver-specific DDL, the cleanest path is to wrap trigger creation in your own migration helper or use DB::unprepared() for the exceptional cases, rather than fighting the builder.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core