CREATE_POLICY(7pg17)
π CREATE_POLICY π
okay so this is the CREATE POLICY(7) page from the PostgreSQL 17.11 Documentation, and itβs literally all about defining a new row-level security policy for a table. bestie, this one is long, so hydrate and stay with me π«Άπ
πΈ NAME πΈ
CREATE_POLICY β define a new row-level security policy for a table. periodt. π
β¨ SYNOPSIS β¨
the syntax is exactly this, no cap:
CREATE POLICY name ON table_name
[ AS { PERMISSIVE | RESTRICTIVE } ]
[ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
[ TO { role_name | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ]
[ USING ( using_expression ) ]
[ WITH CHECK ( check_expression ) ]
π DESCRIPTION π
The CREATE POLICY command defines a new row-level security policy for a table. And listen: row-level security must be enabled on the table (using ALTER TABLE ... ENABLE ROW LEVEL SECURITY) in order for created policies to be applied. no cap, that part matters. π
bestie, row-level security must be enabled on the table (using ALTER TABLE ... ENABLE ROW LEVEL SECURITY) in order for created policies to be applied. π«Ά
A policy grants the permission to select, insert, update, or delete rows that match the relevant policy expression. Existing table rows are checked against the expression specified in USING, while new rows that would be created via INSERT or UPDATE are checked against the expression specified in WITH CHECK. When a USING expression returns true for a given row then that row is visible to the user, while if false or null is returned then the row is not visible. Typically, no error occurs when a row is not visible, but see Table 297 for exceptions. When a WITH CHECK expression returns true for a row then that row is inserted or updated, while if false or null is returned then an error occurs. π
For INSERT, UPDATE, and MERGE statements, WITH CHECK expressions are enforced after BEFORE triggers are fired, and before any actual data modifications are made. Thus a BEFORE ROW trigger may modify the data to be inserted, affecting the result of the security policy check. WITH CHECK expressions are enforced before any other constraints. π₯
Policy names are per-table. Therefore, one policy name can be used for many different tables and have a definition for each table which is appropriate to that table. the way thatβs actually so convenient, iykyk β¨
Policies can be applied for specific commands or for specific roles. The default for newly created policies is that they apply for all commands and roles, unless otherwise specified. Multiple policies may apply to a single command; see below for more details. Table 297 summarizes how the different types of policy apply to specific commands. π
For policies that can have both USING and WITH CHECK expressions (ALL and UPDATE), if no WITH CHECK expression is defined, then the USING expression will be used both to determine which rows are visible (normal USING case) and which new rows will be allowed to be added (WITH CHECK case). π
If row-level security is enabled for a table, but no applicable policies exist, a βdefault denyβ policy is assumed, so that no rows will be visible or updatable. and thatβs the dramatic part, fr fr. no rows will be visible or updatable. π
π¦ PARAMETERS π¦
nameβ The name of the policy to be created. This must be distinct from the name of any other policy for the table. bestie, must be distinct β no duplicate-name shenanigans. πtable_nameβ The name (optionally schema-qualified) of the table the policy applies to. πPERMISSIVEβ Specify that the policy is to be created as a permissive policy. All permissive policies which are applicable to a given query will be combined together using the BooleanORoperator. By creating permissive policies, administrators can add to the set of records which can be accessed. Policies are permissive by default. adding access β thatβs the vibe. πRESTRICTIVEβ Specify that the policy is to be created as a restrictive policy. All restrictive policies which are applicable to a given query will be combined together using the BooleanANDoperator. By creating restrictive policies, administrators can reduce the set of records which can be accessed as all restrictive policies must be passed for each record. all restrictive policies must be passed for each record β yes, every single one. π
Note that there needs to be at least one permissive policy to grant access to records before restrictive policies can be usefully used to reduce that access. If only restrictive policies exist, then no records will be accessible. When a mix of permissive and restrictive policies are present, a record is only accessible if at least one of the permissive policies passes, in addition to all the restrictive policies. so likeβ¦ restrictive policies are the βand alsoβ of access checks, but you need at least one permissive policy opening the door first. iykyk π«Ά
commandβ The command to which the policy applies. Valid options areALL,SELECT,INSERT,UPDATE, andDELETE.ALLis the default. See below for specifics regarding how these are applied. πrole_nameβ The role(s) to which the policy is to be applied. The default isPUBLIC, which will apply the policy to all roles.PUBLICby default, bestie. πΈusing_expressionβ Any SQL conditional expression (returning boolean). The conditional expression cannot contain any aggregate or window functions. This expression will be added to queries that refer to the table if row-level security is enabled. Rows for which the expression returns true will be visible. Any rows for which the expression returns false or null will not be visible to the user (in aSELECT), and will not be available for modification (in anUPDATEorDELETE). Typically, such rows are silently suppressed; no error is reported (but see Table 297 for exceptions). true = visible, false/null = not visible, and usually itβs silent about it. πcheck_expressionβ Any SQL conditional expression (returning boolean). The conditional expression cannot contain any aggregate or window functions. This expression will be used inINSERTandUPDATEqueries against the table if row-level security is enabled. Only rows for which the expression evaluates to true will be allowed. An error will be thrown if the expression evaluates to false or null for any of the records inserted or any of the records that result from the update. Note that thecheck_expressionis evaluated against the proposed new contents of the row, not the original contents. proposed new contents, not the original contents β thatβs the key detail, bestie. π₯
π«Ά Per-Command Policies π«Ά
π ALL π
Using ALL for a policy means that it will apply to all commands, regardless of the type of command. If an ALL policy exists and more specific policies exist, then both the ALL policy and the more specific policy (or policies) will be applied. Additionally, ALL policies will be applied to both the selection side of a query and the modification side, using the USING expression for both cases if only a USING expression has been defined. itβs giving βiβm everywhere, iβm everything.β πβ¨
As an example, if an UPDATE is issued, then the ALL policy will be applicable both to what the UPDATE will be able to select as rows to be updated (applying the USING expression), and to the resulting updated rows, to check if they are permitted to be added to the table (applying the WITH CHECK expression, if defined, and the USING expression otherwise). If an INSERT or UPDATE command attempts to add rows to the table that do not pass the ALL policy's WITH CHECK expression (or its USING expression, if it does not have a WITH CHECK expression), the entire command will be aborted. π
π SELECT π
Using SELECT for a policy means that it will apply to SELECT queries and whenever SELECT permissions are required on the relation the policy is defined for. The result is that only those records from the relation that pass the SELECT policy will be returned during a SELECT query, and that queries that require SELECT permissions, such as UPDATE, DELETE, and MERGE, will also only see those records that are allowed by the SELECT policy. A SELECT policy cannot have a WITH CHECK expression, as it only applies in cases where records are being retrieved from the relation, except as described below. so no WITH CHECK here β itβs a retrieval-only icon. π
If a data-modifying query has a RETURNING clause, SELECT permissions are required on the relation, and any newly inserted or updated rows from the relation must satisfy the relation's SELECT policies in order to be available to the RETURNING clause. If a newly inserted or updated row does not satisfy the relation's SELECT policies, an error will be thrown (inserted or updated rows to be returned are never silently ignored). not me expecting the RETURNING rows to just slide through β nope. π
If an INSERT has an ON CONFLICT DO UPDATE clause, or an ON CONFLICT DO NOTHING clause with an arbiter index or constraint specification, then SELECT permissions are required on the relation, and the rows proposed for insertion are checked using the relation's SELECT policies. If a row proposed for insertion does not satisfy the relation's SELECT policies, an error is thrown (the INSERT is never silently avoided). In addition, if the UPDATE path is taken, the row to be updated and the new updated row are checked against the relation's SELECT policies, and an error is thrown if they are not satisfied (an auxiliary UPDATE is never silently avoided). the ON CONFLICT lore is deep, bestie. π«
A MERGE command requires SELECT permissions on both the source and target relations, and so each relation's SELECT policies are applied before they are joined, and the MERGE actions will only see those records that are allowed by those policies. In addition, if an UPDATE action is executed, the target relation's SELECT policies are applied to the updated row, as for a standalone UPDATE, except that an error is thrown if they are not satisfied. MERGE said βcheck both sides before joining, tyvm.β π
π INSERT π
Using INSERT for a policy means that it will apply to INSERT commands and MERGE commands that contain INSERT actions. Rows being inserted that do not pass this policy will result in a policy violation error, and the entire INSERT command will be aborted. An INSERT policy cannot have a USING expression, as it only applies in cases where records are being added to the relation. no USING for INSERT β itβs literally only about adding records. β¨
Note that an INSERT with an ON CONFLICT DO NOTHING/UPDATE clause will check the INSERT policies' WITH CHECK expressions for all rows proposed for insertion, regardless of whether or not they end up being inserted. so yes, even the maybe-rows get checked. π
π§ UPDATE π§
Using UPDATE for a policy means that it will apply to UPDATE, SELECT FOR UPDATE, and SELECT FOR SHARE commands, as well as auxiliary ON CONFLICT DO UPDATE clauses of INSERT commands, and MERGE commands containing UPDATE actions. Since an UPDATE command involves pulling an existing record and replacing it with a new modified record, UPDATE policies accept both a USING expression and a WITH CHECK expression. The USING expression determines which records the UPDATE command will see to operate against, while the WITH CHECK expression defines which modified rows are allowed to be stored back into the relation. itβs giving βold row out, new row in, both get a vibe check.β π¦
Any rows whose updated values do not pass the WITH CHECK expression will cause an error, and the entire command will be aborted. If only a USING clause is specified, then that clause will be used for both USING and WITH CHECK cases. π
Typically an UPDATE also needs to read data from columns in the relation it's updating β think a WHERE clause, a RETURNING clause, or an expression on the right-hand side of the SET clause, iykyk. In that case SELECT rights are also required on the relation being updated, and the appropriate SELECT or ALL policies get applied on top of the UPDATE policies. So basically the user has to be able to see the row(s) being updated through a SELECT or ALL policy, and have permission to update them via an UPDATE or ALL policy. Both. No shortcuts, bestie π
When an INSERT command has an auxiliary ON CONFLICT DO UPDATE clause, and the UPDATE path is taken, here's the β¨ order of operations β¨: the row to be updated is first checked against the USING expressions of any UPDATE policies, and then the new updated row is checked against the WITH CHECK expressions. Note, however β and this is the spicy bit β that unlike a standalone UPDATE command, if the existing row doesn't pass the USING expressions, an error will be thrown (the UPDATE path will never be silently avoided). The exact same thing applies to an UPDATE action of a MERGE command. No quiet dodging, babe π
ποΈ DELETE ποΈ
Using DELETE for a policy means it applies to DELETE commands and to MERGE commands that contain DELETE actions. For a DELETE command, only rows that pass this policy will be seen by the DELETE command. Lowkey there can be rows that are visible through a SELECT policy but are not available for deletion, if they don't pass the USING expression for the DELETE policy. Note, however, that a DELETE action in a MERGE command will see rows that are visible through SELECT policies, and if the DELETE policy doesn't pass for such a row, an error will be thrown. π
In most cases a DELETE command also needs to read data from columns in the relation it's deleting from (again: WHERE clause, RETURNING clause, you know the vibe). In this case, SELECT rights are also required on the relation, and the appropriate SELECT or ALL policies get applied in addition to the DELETE policies. So the user must have access to the row(s) being deleted through a SELECT or ALL policy in addition to being granted permission to delete the row(s) via a DELETE or ALL policy. Two green lights, always π«Ά
A DELETE policy cannot have a WITH CHECK expression, because it only applies in cases where records are being deleted from the relation β so there's literally no new row to check. Nothing to check, nothing to see, moving on π§
π Table 297. Policies Applied by Command Type π
Table 297 is the cheat sheet, bestie β it summarizes how the different types of policy apply to specific commands. Two key words first, because they ate and left no crumbs:
- "check" β the policy expression is checked, and an error is thrown if it returns false or null π
- "filter" β the row is silently ignored if the policy expression returns false or null. Shhh, no drama πΈ
ββββββββββββββββ¬ββββββββββββββββ¬βββββββββββββ¬ββββββββββββββββββββββββββ¬βββββββββββββ
β β SELECT/ALL β INSERT/ALL β UPDATE/ALL policy β DELETE/ALL β
β β policy β policy β β policy β
β Command βββββββββββββββββΌβββββββββββββΌβββββββββββββ¬βββββββββββββΌβββββββββββββ€
β β USING β WITH CHECK β USING β WITH CHECK β USING β
β β expression β expression β expression β expression β expression β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β SELECT / β Filter β β β β β β β β β
β COPY ... TO β existing β β β β β
β β row β β β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β SELECT FOR β Filter β β β Filter β β β β β
β UPDATE/SHARE β existing β β existing β β β
β β row β β row β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β INSERT β Check new β Check new β β β β β β β
β β row [a] β row β β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β UPDATE β Filter β β β Filter β Check new β β β
β β existing β β existing β row β β
β β row [a] & β β row β β β
β β check β β β β β
β β new row [a] β β β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β DELETE β Filter β β β β β β β Filter β
β β existing β β β β existing β
β β row [a] β β β β row β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β INSERT ... β Check new β Check new β β β β β β β
β ON CONFLICT β row [b][c] β row [c] β β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β ON CONFLICT β Check β β β Check β Check new β β β
β DO UPDATE β existing & β β existing β row [d] β β
β β new rows [d] β β row β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β MERGE β Filter source β β β β β β β β β
β β & target rows β β β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β MERGE ... β Check new β Check new β β β β β β β
β THEN INSERT β row [a] β row β β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β MERGE ... β Check new row β β β Check β Check new β β β
β THEN UPDATE β β β existing β row β β
β β β β row β β β
ββββββββββββββββΌββββββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββΌβββββββββββββ€
β MERGE ... β β β β β β β β β Check β
β THEN DELETE β β β β β existing β
β β β β β β row β
ββββββββββββββββ΄ββββββββββββββββ΄βββββββββββββ΄βββββββββββββ΄βββββββββββββ΄βββββββββββββ
And the footnotes, because of course there are footnotes π
[a]β If read access is required to either the existing or new row (for example, aWHEREorRETURNINGclause that refers to columns from the relation).[b]β If an arbiter index or constraint is specified.[c]β Row proposed for insertion is checked regardless of whether or not a conflict occurs.[d]β New row of the auxiliaryUPDATEcommand, which might be different from the new row of the originalINSERTcommand.
Like, the table is giving main character energy and honestly it's the whole blueprint π
π¦ Application of Multiple Policies π¦
okay so this is where it gets spicy β multiple policies stacking up on one command. stay with me bestie π«Ά
When multiple policies of different command types apply to the same command (like, for example, SELECT and UPDATE policies both hitting an UPDATE command), then the user must have both types of permissions β so permission to select rows from the relation and permission to update them. So basically the expressions for one type of policy get combined with the expressions for the other type using the AND operator. No cutting corners, periodt π
When multiple policies of the same command type apply to the same command, then there has to be at least one PERMISSIVE policy granting access to the relation, and all of the RESTRICTIVE policies must pass. So: all the PERMISSIVE policy expressions get combined using OR, all the RESTRICTIVE policy expressions get combined using AND, and then those results get combined using AND. And if there are no PERMISSIVE policies at all, then access is denied. like, full stop, no entry π«
Note that, for the purposes of combining multiple policies, ALL policies are treated as having the same type as whichever other type of policy is being applied. it's giving "chameleon" energy fr fr π¦
So for example, in an UPDATE command requiring both SELECT and UPDATE permissions, if there are multiple applicable policies of each type, they will be combined as follows:
expression from RESTRICTIVE SELECT/ALL policy 1
AND
expression from RESTRICTIVE SELECT/ALL policy 2
AND
...
AND
(
expression from PERMISSIVE SELECT/ALL policy 1
OR
expression from PERMISSIVE SELECT/ALL policy 2
OR
...
)
AND
expression from RESTRICTIVE UPDATE/ALL policy 1
AND
expression from RESTRICTIVE UPDATE/ALL policy 2
AND
...
AND
(
expression from PERMISSIVE UPDATE/ALL policy 1
OR
expression from PERMISSIVE UPDATE/ALL policy 2
OR
...
)
look at that structure. the way that it just ANDs and ORs β iconic πΈ
π NOTES π
You must be the owner of a table to create or change policies for it. no ownership, no vibes π
While policies will be applied for explicit queries against tables in the database, they are not applied when the system is performing internal referential integrity checks or validating constraints. and ngl, that means there are indirect ways to determine that a given value exists. π let me explain:
- Example numero uno: attempting to insert a duplicate value into a column that is a primary key or has a unique constraint. If the insert fails, then the user can infer that the value already exists. (This example assumes that the user is permitted by policy to insert records which they are not allowed to see β yeah, awkward π)
- Example dos: a user is allowed to insert into a table which references another, otherwise hidden table. Existence can be determined by the user inserting values into the referencing table, where success would indicate that the value exists in the referenced table.
These issues can be addressed by carefully crafting policies to prevent users from being able to insert, delete, or update records at all which might possibly indicate a value they are not otherwise able to see β or by using generated values (e.g., surrogate keys) instead of keys with external meanings. pick your fighter π
Generally, the system will enforce filter conditions imposed using security policies prior to qualifications that appear in user queries, in order to prevent inadvertent exposure of the protected data to user-defined functions which might not be trustworthy. but β and this is a big but β functions and operators marked by the system (or the system administrator) as LEAKPROOF may be evaluated before policy expressions, as they are assumed to be trustworthy. iykyk π₯
Since policy expressions are added to the user's query directly, they will be run with the rights of the user running the overall query. Therefore, users who are using a given policy must be able to access any tables or functions referenced in the expression, or they will simply receive a permission denied error when attempting to query the table that has row-level security enabled. like, the table is right there and the door is justβ¦ shut π
This does not change how views work, however. As with normal queries and views, permission checks and policies for the tables which are referenced by a view will use the view owner's rights and any policies which apply to the view owner β except if the view is defined using the security_invoker option (see CREATE VIEW). π
No separate policy exists for MERGE. Instead, the policies defined for SELECT, INSERT, UPDATE, and DELETE are applied while executing MERGE, depending on the actions that are performed. so MERGE is just out here borrowing everyone else's fits π
Additional discussion and practical examples can be found in Section 5.9. go read it, bestie, it eats πβ¨
π COMPATIBILITY π
CREATE POLICY is a PostgreSQL extension. it's a postgres original, no cap β
π SEE ALSO π
ALTER POLICY(ALTER_POLICY(7))DROP POLICY(DROP_POLICY(7))ALTER TABLE(ALTER_TABLE(7))
π«Ά and that's the whole page!! this gorgeous thing came to you straight from PostgreSQL 17.11, dated 2026, and it's CREATE POLICY(7) β go forth and write policies responsibly, mwah πππ§
π¦ where this page comes from
- opensuse-leap 16.0: postgresql17 17.11-160000.1.1 Β· PostgreSQL
- opensuse-leap 16.0: postgresql17 17.11-160000.1.1 Β· PostgreSQL
- opensuse-leap 16.0: postgresql17 17.11-160000.1.1 Β· PostgreSQL
- opensuse-leap 16.0: postgresql17 17.11-160000.1.1 Β· PostgreSQL
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms