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 | An expression evaluated against page data | 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 - Enter a javascript expression. Block will be visible when expression evaluates to true."
Type an expression against the page's data:
blog.featured
blog.tags.length > 0
global.showAnnouncementBar
blog.status === 'published'
Below the input is a row of chips listing the top-level keys available on this page, with
their types, so you do not have to guess whether it is blog or post. The input validates
as you type - an expression that cannot run is flagged before you save.
Once saved, the Visibility row shows the expression 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 come from the same data as data binding:
page type data, global.*, and $index inside a repeater. Anything you can bind, you can
condition on.
This is the part worth reading twice.
The expression is evaluated and its result is converted to text. The block is hidden only
when that text is exactly false. Everything else - including an empty value - shows the
block.
| Expression result | Block |
|---|---|
false |
hidden |
true |
shown |
0 |
shown |
"" (empty) |
shown |
| missing field | shown |
The last row is the trap. {{blog.featured}} on a record where featured was never set
resolves to nothing, and nothing is not false, so the block appears. A condition that
looks like it is working on the one post you tested can be wrong on every other post.
Write conditions that always produce a boolean. Compare, rather than relying on the value being falsy:
blog.featured === true instead of blog.featured
blog.tags.length > 0 instead of blog.tags
blog.downloadUrl !== undefined instead of blog.downloadUrl
Comparison operators (===, !==, >, <, >=, <=), !, and &&/|| are all
available, so combined conditions work:
blog.featured === true && blog.status === 'published'
The expression rules are the ones described in
Data binding: no assignment, no brackets
(blog.tags.0.name, not blog.tags[0]), 500 characters maximum, and a fixed set of allowed
globals.
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 === true
$index.price > 0
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.
A missing field shows the block. Covered above, and it is the single most common surprise. Prefer explicit comparisons.
Draft data can mislead you. The builder loads data in draft mode. A condition like
blog.status === '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 expression clears the condition. Saving a blank input returns the block to always visible rather than hiding it.
