PostgreSQL Trigger Not Firing on Insert – 3 Fixes

Database Errors Intermediate 👁 12 views 📅 Jun 16, 2026

You inserted rows but your trigger didn't run. Covers the three real reasons this happens and how to fix each, from quickest to deepest.

30-Second Fix: Check If the Trigger Is Enabled

The most common reason a trigger silently stops firing: it got disabled. Maybe you or another dev ran a DROP TRIGGER or ALTER TABLE ... DISABLE TRIGGER during a migration and forgot to re-enable it. Or the trigger was created but never active.

Run this to see the trigger's current state:

SELECT tgname, tgenabled 
FROM pg_trigger 
WHERE tgrelid = 'your_table'::regclass;

The tgenabled column tells you:

  • O – enabled (fires on insert/update/delete depending on definition)
  • D – disabled
  • A – always (fires even in replication, rare)
  • R – fires on replica only

If tgenabled is D, re-enable with:

ALTER TABLE your_table ENABLE TRIGGER your_trigger_name;

Why this works: tgenabled is a single-character flag in pg_trigger. When you disable a trigger, PostgreSQL just flips that flag from O to D. The trigger definition stays intact. Re-enabling flips it back. No need to recreate anything.


5-Minute Fix: Verify Trigger Function Exists and Returns Trigger

If the trigger is enabled but still doesn't fire, the next suspect is the trigger function. It's possible the function was dropped or renamed, or it doesn't return NEW or OLD properly.

Check the function exists:

SELECT proname, prosrc 
FROM pg_proc 
WHERE proname = 'your_function_name';

If it returns zero rows, the function is gone. You'll need to recreate it. If it exists, look at its return type. For a row-level trigger, the function must:

  • Return TRIGGER (not VOID or INTEGER)
  • End with RETURN NEW; (for AFTER INSERT) or RETURN NULL; if you want to skip the insert

Here's a minimal working example:

CREATE OR REPLACE FUNCTION log_insert()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO audit_table(table_name, action, row_data)
  VALUES (TG_TABLE_NAME, 'INSERT', row_to_json(NEW));
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

And the trigger that calls it:

CREATE TRIGGER trg_log_insert
AFTER INSERT ON your_table
FOR EACH ROW
EXECUTE FUNCTION log_insert();

What's actually happening here: the trigger function is invoked by PostgreSQL's executor after the row is inserted. If the function doesn't return NEW, the executor sees a null result and silently skips any further operations (like logging). That's why your audit entry never appears.


15+ Minute Fix: Schema Search Path and Trigger Conditions

If both of the above check out, the problem is more subtle. Two common culprits:

1. Schema Search Path Mismatch

You might have the trigger and table in one schema, but the function references objects (like audit_table) that exist in a different schema. When PostgreSQL runs the trigger, it uses the search path set by the session or user that performed the insert. If that path doesn't include the schema where audit_table lives, the function fails silently.

To test, run this in the same session that does the insert:

SHOW search_path;

Then check if the schema containing audit_table is in that list. If not, fix by either:

  • Schema-qualifying all references inside the function: INSERT INTO my_schema.audit_table(...)
  • Setting a default search path for the trigger function using SET search_path TO my_schema, public; at the top of the function body

The reason the second approach works: SET inside a function overrides the session's search path only for the duration of that function call. So even if the user inserts from a different schema, the trigger sees the correct path.

2. Trigger Condition (WHEN Clause) Blocking Execution

If your trigger has a WHEN clause, it might be evaluating to false for all your inserts. For example:

CREATE TRIGGER trg_log_insert
AFTER INSERT ON your_table
FOR EACH ROW
WHEN (NEW.status = 'active')
EXECUTE FUNCTION log_insert();

If you're inserting rows with status set to 'pending', the WHEN condition fails, and the function never runs. This is by design – PostgreSQL evaluates the condition before calling the function. Check the condition against your insert data:

SELECT pg_get_triggerdef(oid) 
FROM pg_trigger 
WHERE tgname = 'trg_log_insert';

This gives you the full CREATE TRIGGER statement, including the WHEN clause if any. Adjust either the condition or your insert logic to match.


One More Thing: Triggers Are Transactional

If your trigger inserts into audit_table, but you rolled back the main transaction, the trigger's insert also rolls back. You won't see the audit row. To test in isolation, commit after your insert:

BEGIN;
INSERT INTO your_table (...) VALUES (...);
COMMIT;
-- now check audit_table

This trips up a lot of people during debugging. The trigger did fire – you just didn't commit the evidence.

Was this solution helpful?