Recent searches

in

Hide or disable an input with an expression

Last modified: October 1st, 2026

On this page

Permissions required

Members of all Default Permission Groups, or Custom Permission Groups with the site:file:write permission, can configure inputs in all locations in the configuration cascade. You can limit permission to specific locations in the configuration using file globs.

You can show, hide, or disable an Input based on the values of other inputs in the same file using the hidden_if and disabled_if Input options. CloudCannon evaluates the expression value of these Input options and updates the Editing Interface immediately when the value changes, allowing you to create a dynamic editing experience for your Team Members. For example, a webpage that holds either a video or an image only needs one set of Inputs in the Editing Interface, and you could hide or disable the other set if a Team Member enters the file path for one.

These instructions assume you know which Input you want to configure and that any other Inputs it relies on are in the same file. For more information, please read our documentation on what inputs are and using the configuration cascade.

To hide or disable an input with an expression:

  1. Open your website files in your local development environment, or log in to CloudCannon and select the Site for which you want to configure your input.
  2. Navigate to the location in the configuration cascade where you want to configure your input.
  3. Identify the _inputs key, or create one at that level of the configuration cascade.
  4. Add an input name key for the input you want to control under the _inputs key.
  5. Set the hidden_if or disabled_if key under your input name key to your expression.

CloudCannon will now apply your expression to all markup files that use your input name key, without needing to save your configuration. This allows you to change your expression and see the result in your inputs live.

When you are happy with your expression, you must save your configuration.

In the following example, type is video, so the video_path input is shown and the image_path input is hidden. published is true, so the slug input is read-only.

Markdown
Copied to clipboard
---
type: video
video_path: /uploads/launch.mp4
image_path:
published: true
slug: launch-week
---

Content goes here.
Copied to clipboard
_inputs:
  type:
    type: select
    options:
      values:
        - video
        - image
  video_path:
    type: url
    hidden_if: type != "video"
  image_path:
    type: url
    hidden_if: type != "image"
  slug:
    type: text
    disabled_if: published
{
  "_inputs": {
    "type": {
      "type": "select",
      "options": {
        "values": [
          "video",
          "image"
        ]
      }
    },
    "video_path": {
      "type": "url",
      "hidden_if": "type != \"video\""
    },
    "image_path": {
      "type": "url",
      "hidden_if": "type != \"image\""
    },
    "slug": {
      "type": "text",
      "disabled_if": "published"
    }
  }
}

The video_path input is hidden whenever type holds anything other than video.

The slug input is visible at all times, but becomes read-only once published is truthy.

In YAML, quote your expression if it starts with a character YAML reserves, such as ! or *, or if it contains a colon. Quoting is always safe, so quote every expression if you are unsure.

The hidden and disabled keys take a boolean or the name of another key, and are unchanged by expressions. hidden: true hides an input outright, and hidden: '!published' hides it whenever the published input is falsy. An input with none of these keys is visible and editable, unless its key starts with an underscore, in which case CloudCannon hides it by default. If you set both hidden and hidden_if, the input is hidden when either is truthy, and the same applies to disabled and disabled_if.

Explain why an input is disabled#

Set the disabled_message key to show a message while an input is read-only. CloudCannon shows the message with the input's comment whenever disabled is true or disabled_if is truthy, and hides it otherwise. The message supports the same limited Markdown as comment.

In the following example, published is true, so the slug input is read-only and shows the message.

Markdown
Copied to clipboard
---
published: true
slug: launch-week
---

Content goes here.
Copied to clipboard
_inputs:
  slug:
    type: text
    disabled_if: published
    disabled_message: Unpublish this post to change its URL.
{
  "_inputs": {
    "slug": {
      "type": "text",
      "disabled_if": "published",
      "disabled_message": "Unpublish this post to change its URL."
    }
  }
}

Reference the value of another input#

A bare name in an expression refers to a sibling input, meaning an input in the same object as the input you are configuring. CloudCannon starts looking for every bare name in that object, which these instructions call the containing object.

Two reserved names let you reach the input you are configuring, or the top of the file.

this — any#

The input you are configuring. In a comparison, this is its value.

$ — any#

The top level of the file, or the nearest enclosing Structure value when the input sits inside a Structure.

These names are only reserved at the start of a path. After a dot they are ordinary keys, so hero.this reads a key named this inside hero.

An expression reads the file a Team Member has open. It cannot read another file, another Site, or anything outside that file, so you cannot hide an input based on a value that lives elsewhere.

Read a nested value with a dot, such as seo.title, and an array item with its index, such as authors[0].

Reading a key that does not exist gives you null rather than an error, so your expression keeps working while a Team Member fills in a file.

In the following example, the caption and alt_text inputs sit inside an image object in your content file, so a bare name would only reach their siblings inside that object. show_captions and seo.locked sit at the top level of the file, so each input reaches them through $. With these values, caption is hidden and alt_text is read-only.

Markdown
Copied to clipboard
---
show_captions: false
seo:
  locked: true
image:
  path: /uploads/hero.jpg
  caption:
  alt_text: A sunrise over the harbor
---

Content goes here.
Copied to clipboard
_inputs:
  caption:
    type: text
    hidden_if: $.show_captions === false
  alt_text:
    type: text
    disabled_if: $.seo.locked
{
  "_inputs": {
    "caption": {
      "type": "text",
      "hidden_if": "$.show_captions === false"
    },
    "alt_text": {
      "type": "text",
      "disabled_if": "$.seo.locked"
    }
  }
}

Climb to an enclosing object

Two functions move between inputs. Both take an input as their first argument rather than a value, so parent(this) is the object containing the input you are configuring, not the parent of its value.

parent(input, levels) — any#

The object or array levels above input. Both arguments are optional: input defaults to this and levels to 1, so parent() is the containing object, parent(2) is the one above that, and parent($) is the one containing the nearest Structure value.

get(input, ...keys) — any#

Follows a path of keys or array indexes starting at input. input is optional and defaults to the containing object, so get("seo", "title") is the same as seo.title, and get(parent(2), "currency") reads from two levels up.

Inputs inside an array

An input inside an array item reads its siblings from that item, exactly as it would anywhere else. The item is its containing object, so parent() gives you the item, parent(2) gives you the array, and parent(3) gives you the object holding the array.

In the following example, each tier hides its own annual_price_suffix input until that tier has an annual price, so the suffix is shown for Pro and hidden for Free. The name input climbs three levels with parent(3) to read currency from the object that holds the array, and is read-only in both tiers because the currency is NZD.

Markdown
Copied to clipboard
---
currency: NZD
pricing_tiers:
  - name: Free
    annual_price:
    annual_price_suffix:
  - name: Pro
    annual_price: 120
    annual_price_suffix: per year
---

Content goes here.
Copied to clipboard
_structures:
  pricing_tiers:
    values:
      - value:
          name: 
          annual_price: 
          annual_price_suffix: 
        _inputs:
          annual_price_suffix:
            type: text
            hidden_if: '!annual_price'
          name:
            type: text
            disabled_if: parent(3).currency == "NZD"
{
  "_structures": {
    "pricing_tiers": {
      "values": [
        {
          "value": {
            "name": null,
            "annual_price": null,
            "annual_price_suffix": null
          },
          "_inputs": {
            "annual_price_suffix": {
              "type": "text",
              "hidden_if": "!annual_price"
            },
            "name": {
              "type": "text",
              "disabled_if": "parent(3).currency == \"NZD\""
            }
          }
        }
      ]
    }
  }
}

Configuration on an array item

Configuration on the array item itself, such as items[*], behaves differently. The containing object of an array item is the array, which has no keys, so a bare name there matches nothing. Use this to read the item's own values, for example this.name rather than name. parent() is the array and parent(2) is the object holding it, one level fewer than for an input inside the item.

$ stops at the nearest enclosing Structure value rather than continuing to the top level of the file. An input inside a Structure value cannot reach the file root with $, so climb with parent($) instead. Note that this makes the Structure less portable as it depends on data outside of itself.

Operators#

You can use a subset of JavaScript operators in your expressions. If you know JavaScript, these behave as you expect, with the conversion rules below.

! && || ?? — Logical#

Negation, and, or, and nullish coalescing. && and || return one of their two sides rather than a boolean.

== != === !== — Equality#

== and != treat a numeric string as a number when the other side is a number, so 2 == "2" is true. === and !== do not, so 2 === "2" is false. No other conversion happens, so 0 == false is false.

< <= > >= — Comparison#

Compare two numbers or two strings. A numeric string is compared as a number when the other side is a number, the same as ==. Any other mix of types gives false.

+ - * / % — Arithmetic#

Arithmetic on numbers. + joins its two sides into a string when either side is a string and the other is a string or a number, so "a" + 1 is "a1". Any other arithmetic on a value that is not a number gives null, so "2" * 3 is null.

Values follow JavaScript truthiness, so null, false, 0, and an empty string are all falsy. You can write null, true, false, numbers, quoted strings, and array literals such as [1, 2] directly in an expression, and group with parentheses.

The ternary operator, bitwise operators, and the exponent operator ** are not available. Neither are regular expressions, assignment, or more than one statement in a single expression.

Functions#

Nine functions work on values, alongside parent and get described above. A function that receives a value of the wrong type gives null, except for includes, starts_with, and ends_with, which give false.

length(value) — Number#

The number of characters in a string, items in an array, or keys in an object.

includes(haystack, needle) — Boolean#

Whether a string contains a substring, or an array contains an item.

starts_with(value, search) — Boolean#

Whether a string starts with another string.

ends_with(value, search) — Boolean#

Whether a string ends with another string.

lowercase(value) — String#

A string converted to lowercase.

uppercase(value) — String#

A string converted to uppercase.

trim(value) — String#

A string with leading and trailing whitespace removed.

number(value) — Number#

A numeric string converted to a number. A number passes through unchanged.

string(value) — String#

A number or boolean converted to a string. A string passes through unchanged.

In the following example, the summary input stays hidden until body holds at least 200 characters, the discount_code input is read-only unless the code starts with PROMO in any capitalization, and the permalink input locks itself once it holds a value by reading this. length gives null for an empty input rather than zero, so ?? "" keeps each comparison working before a Team Member has typed anything. With these values, summary is hidden, discount_code is editable, and permalink is read-only.

Markdown
Copied to clipboard
---
body: A short draft.
summary:
code: promo-spring
discount_code:
permalink: /products/spring-jacket
---

Content goes here.
Copied to clipboard
_inputs:
  summary:
    type: textarea
    hidden_if: length(body ?? "") < 200
  discount_code:
    type: text
    disabled_if: '!starts_with(uppercase(code), "PROMO")'
  permalink:
    type: url
    disabled_if: length(this ?? "") > 0
{
  "_inputs": {
    "summary": {
      "type": "textarea",
      "hidden_if": "length(body ?? \"\") < 200"
    },
    "discount_code": {
      "type": "text",
      "disabled_if": "!starts_with(uppercase(code), \"PROMO\")"
    },
    "permalink": {
      "type": "url",
      "disabled_if": "length(this ?? \"\") > 0"
    }
  }
}

Reference a key that is not a valid name#

A bare name works only for key names that are valid identifiers: letters, digits, underscores, and dollar signs, not starting with a digit. In an expression, draft-status parses as draft minus status.

Reach any other key with get. On its own, get("draft-status") reads a sibling of the input you are configuring. Give it an input first to read from elsewhere, such as get($, "draft-status") for a key at the top level of the file. Square brackets with a quoted string work on any value too, so $["draft-status"] is the same as get($, "draft-status").

A quoted key is always a key name, never a reserved name, so get("this") reads a sibling input literally named this.

In the following example, the body input reads draft-status from the top level of the file, and the intro input reads a sibling input with the same name. Both are hidden while draft-status is incomplete.

Markdown
Copied to clipboard
---
draft-status: incomplete
intro:
body:
---

Content goes here.
Copied to clipboard
_inputs:
  body:
    type: markdown
    hidden_if: $["draft-status"] === "incomplete"
  intro:
    type: textarea
    hidden_if: get("draft-status") === "incomplete"
{
  "_inputs": {
    "body": {
      "type": "markdown",
      "hidden_if": "$[\"draft-status\"] === \"incomplete\""
    },
    "intro": {
      "type": "textarea",
      "hidden_if": "get(\"draft-status\") === \"incomplete\""
    }
  }
}

Limits#

An expression cannot change a value. It only controls whether an input is visible or editable.

An expression can be up to 1000 characters long and nest up to 32 levels deep. CloudCannon shows an error on the input itself when an expression exceeds either limit or cannot be parsed, so you can find and correct it.

While an expression is invalid, CloudCannon falls back to the safer option for each key. An input with an invalid hidden_if expression stays visible, so no content disappears, and an input with an invalid disabled_if expression stays read-only, so no value changes by accident.

An expression changes what a Team Member sees, but every key stays in your file. If you want the keys that do not apply to be removed instead, please read our documentation on switching between Structures.

For the full list of configuration keys available for inputs, please read our Inputs reference documentation.

Related Resources

Open in a new tab