The Visual Website Builder
You Actually Own. React + Next.js, low-code, self-hosted. Your data, your infrastructure, every feature included.
© ChaiBuilder. All rights reserved.

Every block can be hidden - permanently, or based on the data the page is rendering with. A "Sold out" badge that only appears when stock is zero, a featured banner that only appears on featured posts, a download section that only appears when the record has a file attached.
There are two levels to this, and they use the same setting.
| What it is | When to use it | |
|---|---|---|
| Toggle | A plain on/off switch | Park a block you are not ready to publish |
| Condition | A data path, optionally piped through a boolean pipe | Show the block only for records that qualify |
The Visibility switch sits near the top of the block settings panel. Switch it off and the block stays in the page - editable, still in the outline - but does not render.
The same toggle is on every row of the Outline panel as an eye icon, which is the faster way to hide something several levels deep, and in the block's right-click menu.
This is the right tool for work in progress. It is not a draft mechanism for content - if a whole section is not ready, hiding it is fine, but remember it stays hidden after publish until someone switches it back on.
Click the small gear beside the Visibility switch. The popover reads "Conditional Visibility - Choose a boolean data path or add boolean pipes. The block is visible only when the result is true."
A condition is written the same way as a data binding: a data path, optionally piped through pipes. The difference is that it has to end up as a real boolean.
blog.featured
global.showAnnouncementBar
blog.status | equals 'published'
blog.price | gt 0
blog.tags | empty | not
blog.downloadUrl | truthy
Above the input, a row of chips lists the top-level keys available on this page with their
types, so you do not have to guess whether it is blog or post, and typing a path opens an
autocomplete that drills into it. Below the input, the same formatter editor used by binding
badges appears: Add formatter, argument controls, reordering, and a live Preview of
the result. The field validates as you type, so an unknown pipe, a bad argument, or a
condition that does not produce a boolean is flagged before you can save.
Once saved, the Visibility row shows the condition inline as a small code chip with the note "visible when true", and the on/off switch is disabled - a block cannot be both conditionally shown and manually toggled. The trash icon in the popover removes the condition and returns the block to always visible.
Conditions read the same data as bindings: page type data, global.*, and $index inside a
repeater.
This is the part worth reading twice.
The block renders only when the condition resolves to the boolean true. Everything else
hides it: false, a missing field, a string, a number, a broken pipe, an expression that is
not valid at all.
| Condition result | Block |
|---|---|
true |
shown |
false |
hidden |
"published" (a string, even a non-empty one) |
hidden |
0 or 1 |
hidden |
| missing field | hidden |
| invalid condition | hidden |
So {{blog.featured}} works only when featured is genuinely a boolean field. Point a
condition at a text or number field and the block disappears, whatever the value. That is
what the boolean pipes are for: they turn any value into a real true or false.
| Pipe | Argument | True when |
|---|---|---|
equals |
a literal | The value is exactly the argument. No type coercion, so '5' does not equal 5 |
notEquals |
a literal | The value is anything other than the argument |
gt / gte / lt / lte |
a number | The numeric comparison holds. Numeric strings are converted; anything non-numeric is false |
not |
- | The value is falsy |
truthy |
- | The value is truthy |
empty |
- | The value is null, undefined, "", or an empty array |
Value pipes can run first, as long as a boolean pipe finishes the chain:
blog.title | trim | empty | not
blog.tags | join ',' | truthy
There is no && or ||. A condition is one path and one chain of pipes, so two
independent rules cannot be combined in the panel. When you need "featured and
published", expose a single computed boolean from the data provider (isPromoted) and bind
to that. It is cheaper too: the provider computes it once per request.
The rest of the rules are the binding rules from
Paths and pipes: no JavaScript, no
brackets (blog.tags.0, not blog.tags[0]), literal pipe arguments only, and a 500
character maximum.
A block that fails its condition is not rendered at all. It is absent from the HTML, not
present-and-hidden with display: none.
That has consequences worth knowing:
Conditions are not personalization. They are evaluated when the page is rendered against that page's data, and the result is cached and served to every visitor identically. A condition cannot depend on who is viewing, what is in their cart, or what time it is for them. If you need per-visitor behaviour, that belongs in a custom block that runs on the client. See Custom blocks and Caching and revalidation.
Conditionally hidden blocks behave differently in the builder on purpose - you need to be able to edit a block you cannot currently see.
Flip it on before publishing to check that the sections you expect are actually there.
To find every conditional block on a page, use the filter in the Outline panel - it can narrow the tree to blocks that are hidden or carry a condition. That is the fastest way to audit a page someone else built, or to work out why a section is missing.
Inside a repeater, a condition can
reference the current item with $index:
$index.featured
$index.stock | gt 0
$index.badge | equals 'new'
The condition is evaluated per item, so a badge inside a card template appears only on the cards that qualify.
A condition set on a partial's reference applies to everything the partial contains. Hiding a partial on one page hides that whole block group on that page, without changing the partial itself or any other page using it.
Earlier versions accepted a small subset of JavaScript here, so conditions like
blog.featured === true or blog.tags.length > 0 were normal. That engine has been removed
and those conditions are now invalid, which means the blocks carrying them are hidden, not
shown. Rewrite them:
| Old condition | Now |
|---|---|
blog.featured === true |
blog.featured (if the field is a boolean) |
blog.status === 'published' |
blog.status | equals 'published' |
blog.status !== 'draft' |
blog.status | notEquals 'draft' |
blog.price > 0 |
blog.price | gt 0 |
blog.downloadUrl !== undefined |
blog.downloadUrl | truthy |
blog.tags.length > 0 |
blog.tags | empty | not |
!blog.featured |
blog.featured | not |
a === true && b === true |
one computed boolean from the data provider |
Because the failure direction flipped - old conditions used to fail open, new ones fail closed - a page that upgraded without a pass over its conditions loses sections rather than showing extra ones. The Outline filter lists every conditional block on a page, which is the quickest way to work through them.
A missing field now hides the block. This is the reverse of the old behaviour, where
anything that was not exactly false showed it. A typo in a path silently removes a section
instead of leaving it visible.
A non-boolean value hides the block. blog.status on its own hides the block even when
the status is published, because a string is not true. Finish the chain with a boolean
pipe.
Draft data can mislead you. The builder loads data in draft mode. A condition like
blog.status | equals 'published' behaves differently in the canvas than on the live site.
Hiding is not access control. A hidden block is absent from the page, which is good, but visibility conditions run on page data - they are not a permissions system and should not be used to gate content that specific people should not see. Use roles and permissions for that.
The manual toggle and a condition are mutually exclusive. Setting a condition disables the switch. Remove the condition with the trash icon before you can toggle by hand again.
An empty condition clears it. Saving a blank input returns the block to always visible rather than hiding it.
