Homeβ€Ί Blogβ€Ί GCP Architecture Series #20 β€” Boolean, List and Custom Constraints…
GCP Architecture GCP Architecture Series

GCP Architecture Series #20 β€” Boolean, List and Custom Constraints

Post #19 covered how a policy inherits. This one is about what a policy can actually say β€” and the vocabulary has moved under everyone's feet. What the roadmap and most write-ups call boolean and list constraints are now the two rule types of a legacy managed constraint, sitting alongside a newer managed constraint and a custom one you write yourself.

Verified against current vendor documentation on 2 September 2026. Pricing, limits and API behaviour were checked against the official docs on that date. Cloud services change fast — if you are reading this much later, treat the specifics as a starting point and re-check the linked sources.

Business Challenge

Every one of these comes from writing a constraint that looked obviously correct and behaved otherwise.

1
"Is this a predefined constraint or a managed one?"

Both, depending on when the page you are reading was written. What used to be called predefined constraints are now legacy managed constraints, and they have a constraint type of list or boolean. Alongside them sit newer managed constraints, which Google states are designed to replace the equivalent legacy ones with additional flexibility and greater insight from Policy Intelligence tools. Then there are custom constraints, which are managed by your organization instead of by Google.

Correct approach

Read the constraint's own name, not the prose around it. constraints/compute.trustedImageProjects is legacy; iam.managed.disableServiceAccountCreation is managed; anything starting custom. is yours.

2
"I removed the values and expected nothing to be enforced"

An empty list is not an inert one. If no list of values is provided, or the policy is set to the Google-managed default, the default behaviour of the constraint takes effect — and Google is explicit that this either allows all values or denies all values. Which of the two depends on the constraint, so emptying a list can silently mean "deny everything".

Correct approach

Look up the constraint's default before clearing its values, and prefer an explicit list over an empty one. This is the same lesson as #19's "an unset policy is not an open one", one level further down.

3
"The value is right and it still does not match"

List values are not bare strings; they carry a prefix that decides the shape of the comparison. is: applies a comparison against the exact value. under: applies a comparison to the value and all of its child values. in: applies a comparison to all resources that include this value. Writing a folder id without under: matches the folder and nothing inside it.

Correct approach

Decide deliberately whether you mean this resource or this subtree, and write the prefix even where it is optional. The prefix is the semantics, not decoration.

4
"The custom constraint allows what I wrote and blocks everything else"

That is what actionType: ALLOW means. It does not permit the operation when the condition matches and stay quiet otherwise — it permits it only when the condition matches. A constraint written to allow a particular configuration is simultaneously a constraint denying every other configuration of that resource.

Correct approach

Read every custom constraint as a whitelist or a blacklist, never as a rule about one case. If you only want to block one thing, express it as DENY on the thing you are blocking.

Architecture

A constraint is a definition; #19 covered how the policy that configures it reaches a resource. What follows is what the definition itself can express.

Diagram: the three kinds of Google Cloud organization policy constraint β€” legacy managed with boolean and list rules, Google-managed, and custom β€” with the list value prefixes and the custom constraint YAML fields and limits
Three kinds of constraint, the two legacy rule types, the prefixes that decide what a list value matches, and what a custom constraint is made of.

The three kinds, and what each is for

KindWritten byShapeNamed like
Legacy managed Google A constraint type of list or boolean, which determines the values that can be used for checking enforcement. constraints/compute.trustedImageProjects
Managed Google Similar structure to a custom constraint, but managed by Google, and supporting parameters. iam.managed.disableServiceAccountCreation
Custom You A YAML file specifying the resources, methods, conditions and actions subject to the constraint. custom.disableGkeAutoUpgrade

Boolean rules: the whole vocabulary is one word

A legacy managed constraint with a boolean rule is either enforced or not enforced. The policy that configures it says so directly:

rules:
- enforce: true

There is nothing else to express. That is a virtue rather than a limitation — a boolean constraint is unambiguous, cheap to audit, and impossible to get subtly wrong. When a boolean constraint fits the requirement, it is always the right choice.

List rules: values, and the prefixes that give them meaning

List rules allow or disallow a list of values defined in the organization policy, using allowedValues and deniedValues. The values are hierarchy strings, and the prefix decides what the comparison means:

PrefixComparisonUse it when
is: Applies a comparison against the exact value. You mean this one resource and nothing beneath it.
under: Applies a comparison to the value and all of its child values. You mean a folder or project and everything inside it.
in: Applies a comparison to all resources that include this value. You mean membership rather than containment.
An empty list is a decision, not a blank

If no list of values is provided, or the policy is set to the Google-managed default, the default behaviour of the constraint takes effect — which either allows all values or denies all values. So clearing a list does not turn a constraint off. Depending on which constraint it is, it can mean "everything is permitted" or "nothing is", and the two look identical in the policy document. Check the constraint's documented default before removing values, particularly during an incident, when clearing a list feels like the safe rollback and may be the opposite.

Custom constraints: a YAML file and a CEL expression

A custom constraint is defined in a YAML file specifying the resources, methods, conditions and actions subject to it. Google's own example is compact enough to read in full:

name: organizations/1234567890123/customConstraints/custom.disableGkeAutoUpgrade
resourceTypes: container.googleapis.com/NodePool
methodTypes:
  - CREATE
  - UPDATE
condition: "resource.management.autoUpgrade == false"
actionType: ALLOW
displayName: Disable GKE auto upgrade
description: Only allow GKE NodePool resource to be created or updated if AutoUpgrade is not enabled.

Each field has a job and a limit worth knowing before you hit it:

  • resourceTypes — the fully qualified name of the Google Cloud resource containing the object and field you want to restrict. Each service defines the set of custom constraint fields available on its resources, so the field you want has to be one that service exposes.
  • methodTypes — the REST methods enforced on. CREATE is universal; UPDATE depends on the resource, and a constraint that only fires on CREATE will not stop somebody editing a compliant resource into a non-compliant one.
  • condition — a CEL expression, up to 1000 characters, combined with && and ||. Organization Policy Service uses Common Expression Language to evaluate these.
  • actionTypeALLOW or DENY, and the source of the inversion below.
  • displayName up to 200 characters, description up to 2000. The description is where the reason lives, and it is the only part a colleague reads at three in the morning.
Naming and the ceiling you will actually hit

A custom constraint name can only contain letters and numbers — no hyphens, no underscores — up to 70 characters, not counting the custom. prefix. So custom.disableGkeAutoUpgrade is legal and custom.disable-gke-auto-upgrade is not, which is a different character set again from the labels, tags and resource names in #17 and #18. And most resource types support up to 20 custom constraints; attempting to create more fails. Twenty per resource type sounds generous until a platform team writes one per requirement rather than one per rule, at which point a single popular resource type — a GKE node pool, a Compute instance — runs out.

Why This Architecture Holds Up

actionType reads backwards

This is the single thing most worth internalising about custom constraints. ALLOW does not mean "permit this when the condition matches". It means permit the operation only if the condition evaluates to true — and therefore deny it in every other case.

Google's own example makes the trap visible once you look for it. The constraint is called disableGkeAutoUpgrade, the condition is resource.management.autoUpgrade == false, and the action is ALLOW. Read casually, it looks like a rule that allows auto-upgrade to be disabled. It is the opposite: node pools may be created or updated only with auto-upgrade off, and every node pool with it on is refused. The name says what it enforces; the YAML says how, and the two only agree once you know the inversion.

A rule of thumb that survives the inversion

Write the condition to describe the state you consider correct, then use ALLOW. Or write it to describe the state you consider wrong, then use DENY. Mixing the two — a condition describing the bad state with ALLOW — produces a constraint that permits only the thing you were trying to prevent, and it will pass review because every individual line reads correctly.

Which kind to reach for

If the requirement isUseBecause
A capability that should be off everywhere A boolean rule on the existing constraint One word to write, nothing to get subtly wrong, and Google maintains it.
An approved set of regions, images or domains A list rule, with explicit prefixes The set is the requirement, and under: expresses a subtree without enumerating it.
The same shape as an existing legacy constraint The managed equivalent where one exists Managed constraints are designed to replace legacy ones with more flexibility and better Policy Intelligence insight.
A rule about a field no constraint covers A custom constraint It is the only kind that can reach an arbitrary field on a supported resource.
A rule about who, not what Not a constraint at all That is IAM. Organization Policy does not know who is asking, which is the point of it.

Custom constraints are code, and should be treated as such

A custom constraint is a CEL expression evaluated on every create — and sometimes every update — of a resource type across an organization. That is a piece of production logic with a blast radius larger than most services, written in a language nobody uses daily, in a YAML file, against a resource schema that varies per service.

Three habits follow. Keep the constraints in version control alongside everything else, because 20 per resource type is a budget that needs review rather than a limit to discover. Write the description as though the reader has no context, because the 2000 characters are free and the constraint will outlive whoever wrote it. And never let one reach production without watching it run first — which is exactly what the next post is about.

Key Architecture Decisions

DecisionChoose thisBecause
Identifying a constraint's kind Read its name, not the surrounding prose constraints/, .managed. and custom. are unambiguous; the documentation's wording has moved.
When a boolean constraint fits Use it, and do not reach further It is either enforced or not enforced — nothing to misconfigure.
Writing list values Always state the prefix is:, under: and in: are three different comparisons, and the default is not obvious.
Rolling back a list constraint Set an explicit permissive list, do not empty it An empty list falls through to the constraint's default, which may deny all values.
Choosing between legacy and managed The managed equivalent, where one exists They are designed to replace the legacy ones, with more flexibility and better tooling insight.
actionType Condition describes correct state → ALLOW; wrong state → DENY Mixing them produces a constraint permitting only what you meant to forbid.
methodTypes Include UPDATE wherever the resource supports it A CREATE-only constraint does not stop a compliant resource being edited into a non-compliant one.
Naming a custom constraint Letters and numbers only, under 70 characters Hyphens and underscores are rejected, unlike every other identifier in this series.
Budgeting custom constraints One per rule, not one per requirement Most resource types support up to 20, and creation fails past that.
The description field Write the reason, at length 2000 characters, and it is the only context a future reader gets.
Storing custom constraints Version control, reviewed like code It is a CEL expression on every create across the organization.

Closing Thought

There is a progression buried in these three kinds, and it is the usual one. A boolean constraint expresses a decision somebody already made for you and named. A list constraint expresses a decision you made, from a vocabulary Google supplied. A custom constraint expresses a decision nobody has made before, in a language that will evaluate it on every resource your organization creates.

The power increases and so does the number of ways to be quietly wrong — an empty list meaning "deny all", a missing prefix matching a folder but not its contents, an actionType that inverts a condition reading perfectly well in English. None of those produce an error. They produce a policy that is enforced exactly as written and not at all as intended, which is the most expensive kind of correct. Reach for the simplest constraint that expresses the requirement, and when you cannot, assume the custom one is wrong until you have watched it run.

Next in this series

#21 is the answer to that last sentence: dry-run mode — how to enforce a policy that only reports, what it logs, and why every constraint in this post deserves a period of being watched before it is allowed to deny anything.

Comments

How was your experience?
Your feedback helps improve this site.
PoorExcellent