When you attempt to insert or update a row through a view defined with WITH CHECK OPTION, and the resulting row would not satisfy the view's WHERE clause, PostgreSQL raises:
ERROR: new row violates check option for view "active_users"
DETAIL: Failing row contains (42, inactive, ...).
SQLSTATE: 44000
The condition name is with_check_option_violation. PostgreSQL is enforcing the guarantee that every row written through the view remains visible through that same view.
What This Error Means
SQLSTATE 44000 belongs to PostgreSQL's error class 44 — "WITH CHECK OPTION Violation". It is a standalone class with a single member condition, meaning the error is always precisely with_check_option_violation with no sub-conditions.
WITH CHECK OPTION is a constraint attached to an updatable view. When a view has this option, PostgreSQL verifies, after performing the underlying INSERT or UPDATE, that the modified row satisfies the view's WHERE clause. If the row would become invisible through the view after the write — either by falling outside the WHERE filter or by violating a cascaded check in a parent view — the statement is rolled back and this error is raised.
There are two variants of the option:
WITH LOCAL CHECK OPTION: only the defining view's ownWHEREclause is checked, not those of views it is built upon.WITH CASCADED CHECK OPTION(the default when you writeWITH CHECK OPTION): the row must satisfy theWHEREclauses of the view and all its underlying views recursively.
After this error is raised, the current statement is aborted. If you are inside a transaction, the transaction enters an error state and must be rolled back with ROLLBACK before any further work can proceed.
Common Causes
Writing a row that violates the view's own filter. If the view is defined as
SELECT * FROM users WHERE status = 'active'and you insert a row withstatus = 'inactive'through it, the row passes the underlying table constraint but fails the view's check.Cascaded check violation in a parent view. A view is built on top of another view that also carries
WITH CHECK OPTION. A write that satisfies the child view's filter but not the parent's will trigger the error.Trigger or default value changes a filtered column. A
BEFORE INSERTtrigger or a column default silently modifies the value of the column used in the view'sWHEREclause, causing the final row to fail the check.Misunderstanding LOCAL vs. CASCADED semantics. A developer adds
WITH LOCAL CHECK OPTIONexpecting it to cascade to underlying views, or addsWITH CASCADED CHECK OPTIONwithout realising a base view also has a conflictingWHEREclause.Application-level bypassing of business logic. Application code inserts directly through a restricted view (e.g., a tenant-scoped view) and attempts to write a row belonging to a different tenant.
How to Fix with_check_option_violation
Inspect the failing view definition. Identify every
WHEREclause in the view chain.-- Find the view definition SELECT definition FROM pg_views WHERE viewname = 'active_users'; -- Or use \d+ in psql \d+ active_usersEnsure the written row satisfies all required filters. Adjust the data before writing it through the view so that the row remains visible through the view's
WHEREclause.-- This will fail if the view filters on status = 'active' INSERT INTO active_users (id, status) VALUES (42, 'inactive'); -- Fix: write the row with a value that satisfies the view's WHERE clause INSERT INTO active_users (id, status) VALUES (42, 'active');Write directly to the underlying table when the check is intentionally bypassed. If the row legitimately does not satisfy the view's filter (e.g., you are inserting a deactivated user from an admin context), bypass the view and write directly to the base table.
-- Write to the base table, bypassing the view's check option INSERT INTO users (id, status) VALUES (42, 'inactive');Switch from CASCADED to LOCAL if cascading is unintended. If you only want to enforce the immediate view's filter and not those of underlying views, recreate the view with
WITH LOCAL CHECK OPTION.CREATE OR REPLACE VIEW active_users AS SELECT * FROM users WHERE status = 'active' WITH LOCAL CHECK OPTION;Audit triggers that modify filtered columns. If a
BEFORE INSERTorBEFORE UPDATEtrigger changes the value of a column used in the view's filter, make sure it respects the intended semantics or adjust the view definition accordingly.-- Check for triggers on the underlying table SELECT trigger_name, event_manipulation, action_statement FROM information_schema.triggers WHERE event_object_table = 'users';Drop the check option if it is no longer appropriate. If the view was given
WITH CHECK OPTIONhistorically but the constraint is no longer desired, recreate it without the option.CREATE OR REPLACE VIEW active_users AS SELECT * FROM users WHERE status = 'active'; -- No WITH CHECK OPTION — writes are now unrestricted
Additional Information
WITH CHECK OPTIONon views was supported since early PostgreSQL releases. Full support forWITH LOCAL CHECK OPTIONandWITH CASCADED CHECK OPTIONsyntax conforming to the SQL standard was present by PostgreSQL 9.3, which also introduced writable views as a formal feature.- Related SQLSTATE codes in adjacent classes: SQLSTATE
42501(insufficient_privilege) can occur if the session also lacks permission to write to the view's base table; SQLSTATE23000-series codes (integrity constraint violations) are raised by table constraints, not view check options. - Most PostgreSQL drivers surface this as a database exception with
sqlstate == "44000". In psycopg2/psycopg3 it is raised aspsycopg2.errors.WithCheckOptionViolation. In JDBC it appears as aPSQLExceptionwithSQLState 44000. - There is no performance overhead for views without
WITH CHECK OPTION. The overhead for views that do carry it is a single re-evaluation of the view'sWHEREclause after each modified row — negligible in almost all workloads.
Frequently Asked Questions
What is the difference between WITH LOCAL CHECK OPTION and WITH CASCADED CHECK OPTION?
LOCAL checks only the WHERE clause of the view you are inserting through, ignoring any underlying views. CASCADED (the default when you write just WITH CHECK OPTION) checks the WHERE clauses of the view and every view it references, all the way down to the base table.
Can I get this error even when inserting into the base table directly?
No. The check is triggered only when writing through a view that carries WITH CHECK OPTION. Direct writes to the underlying table skip the check entirely.
Why does PostgreSQL allow this constraint on views at all?
WITH CHECK OPTION enforces view-level data integrity: it guarantees that every row written through a view remains visible through that view. This is critical for tenant-isolation patterns and row-level security workarounds where a view is the only intended write path for a given application role.
Does this error abort the entire transaction?
The failing statement is always rolled back. If you are inside an explicit transaction block (BEGIN ... COMMIT), the transaction enters an aborted state and you must issue ROLLBACK before you can continue. If you are running in autocommit mode (single statement), only that statement is affected.