Skip to main content
A form is a YAML file that defines the input a user fills in when they request a workflow. Bentra renders it as a web form, validates submissions against it (both in the browser and on the server), and passes the submitted values to the workflow’s steps.

About YAML syntax for forms

  • Forms live at .bento/form/form.yml in the workflow’s repository or file set. Only request-type workflows have a form.
  • The file is parsed with yaml.safe_load and validated against a JSON Schema. The schema is the single source of truth for the editor’s IntelliSense, server validation, and AI generation.
  • Every object in the schema disallows unknown keys. A typo such as lable: is reported as an error with its line number, rather than being silently ignored.
  • A form must be valid before a workflow version can be cut. In the editor you can save an invalid draft only after acknowledging the errors.
Removed featuresoptions_loader, validations.validator, and auto_populate with type: inline or type: file have been removed. Bentra never runs user-supplied scripts in a form. Using any of these produces a dedicated error message.

Example: a complete form


name

Required. The title displayed at the top of the form.

description

Optional. An explanation shown under the title. If omitted, it defaults to an empty string. Supports Markdown.

Markdown in descriptions

The form description and each field’s attributes.description are rendered as Markdown:
  • Paragraphs: separate them with a blank line.
  • Lists: -, * or 1. items. A list can start on the line right after a paragraph, as on GitHub.
  • Links: [text](https://…), and bare https:// URLs are linked automatically. Links open in a new tab.
  • **bold**, *italic*, `inline code` and fenced code blocks.
  • Headings (#, ##, …) render as small subheadings under the form title.
Raw HTML, images and tables are removed.

body

Required. An ordered list of fields. Fields render in the order they are listed.

body[*].type

Required. The kind of field to render.

body[*].id

Required. A unique identifier for the field. The id is used in several places:
  • Conditions and dependencies. It is referenced in visibility.condition, dynamic.depends_on, and templates such as {field_id}.
  • Workflow inputs. Workflow steps receive the value as an upper-cased environment variable (service_name becomes $SERVICE_NAME). See Workflow syntax → Inputs.
Use snake_case ids. Since they become environment variable names, keep them to letters, digits, and underscores.

body[*].section

Optional. Groups fields under a header. A section header is shown whenever the section value changes between one field and the next, so list the fields of each section consecutively.

body[*].attributes

Optional. Controls how the field looks and behaves. Every key is optional.

body[*].attributes.label

The label displayed for the field. If omitted, a generic label for the field type is used (for example “Text Input” or “Choose an option”).

body[*].attributes.description

Help text displayed under the field’s label. Supports Markdown.

body[*].attributes.placeholder

Placeholder text for input and textarea fields. For input_type: email it defaults to you@example.com.

body[*].attributes.input_type

input fields only. Sets the HTML input type. If the value is missing or unrecognized, a text input is rendered.

body[*].attributes.default

The initial value of the field. The type can be string, number, boolean, list, object, or null, and must suit the field type:
  • tags: an object of key/value pairs, e.g. { team: platform }
  • dropdown with multiple: true: a list of option values

body[*].attributes.readonly

Boolean. Renders the field as read-only.

body[*].attributes.steppers_only

Boolean. Applies to input_type: number fields only. When true, typing is blocked and the value can only be changed with the spinner arrows.

body[*].attributes.rows

Integer. The number of visible rows in a textarea. Defaults to 4.

body[*].attributes.multiple

Boolean. dropdown only. When true, the field is a multi-select and it submits a list of values.

body[*].attributes.size

Integer. dropdown only. The number of option rows visible in the panel before it scrolls.

body[*].attributes.searchable

Boolean. dropdown only. Adds a search box that filters the options. Defaults to false.

body[*].attributes.options

The choices for dropdown, checkboxes, and radio fields. Each option is an object with these keys:
On the server, a submitted value for dropdown, radio, or checkboxes must be one of the listed option values. For cascading dropdowns, the value must be one of the options listed under the parent field’s submitted value in options_map.

body[*].attributes.options_from

dropdown, checkboxes and radio only. Loads the choices from a dataset that a job publishes, instead of listing them in options.
  • The form’s workflow needs a read grant on the job (the job’s Data sharing settings). Without one, the version can’t be published.
  • A job that hasn’t published the dataset yet is only a warning; the field has no choices until it does.
  • options_from can’t be combined with options or dynamic.options_map on the same field.
  • With a "{field_id}" filter, the browser re-filters whenever that field changes, the same way options_map cascades. The server checks that the submitted value is in a matching row of the current dataset version.

body[*].attributes.key_placeholder

tags only. Placeholder text for the key input. Defaults to key.

body[*].attributes.value_placeholder

tags only. Placeholder text for the value input. Defaults to value.

body[*].attributes.max_pairs

tags only. An integer or null. The maximum number of key/value pairs the user can add in the UI.
Accepted but no effectThe schema accepts cols and render (text or markdown), but no template uses them yet.

body[*].validations

Optional. Rules the value must satisfy. Every key except required accepts null, which means “no limit”. The server skips all validation for a field that is hidden.

body[*].validations.required

Boolean. The field must have a value. Empty strings, empty lists, and empty objects count as missing.

body[*].validations.min_length

Integer. The minimum length of the text.

body[*].validations.max_length

Integer. The maximum length of the text.

body[*].validations.pattern

A regex. The value must contain a match, so anchor the pattern with ^…$ if the whole value must match.

body[*].validations.min

Number. The minimum value for input_type: number.

body[*].validations.max

Number. The maximum value for input_type: number.

body[*].validations.step

Number. The allowed increment for input_type: number.

body[*].validations.min_date

A date string (YYYY-MM-DD). The earliest date allowed for input_type: date.

body[*].validations.max_date

A date string (YYYY-MM-DD). The latest date allowed for input_type: date.
min_date and max_date are passed straight into the HTML min and max attributes. Relative values such as today or today+30 don’t work here. Use a literal YYYY-MM-DD date.

body[*].validations.min_selections

Integer. The minimum number of checked boxes (checkboxes) or key/value pairs (tags).

body[*].validations.max_selections

Integer. The maximum number of checked boxes (checkboxes) or key/value pairs (tags).

body[*].validations.key_pattern

tags only. A regex that every key must match.

body[*].validations.value_pattern

tags only. A regex that every value must match.

Where each validation is enforced

API submissions bypass the browser, so only the checks marked in the Server column apply to them.

body[*].visibility

Optional. Shows the field only when a condition is true.

body[*].visibility.condition

A condition expression evaluated against the current values of the other fields. It is re-evaluated whenever any field changes. When the condition is false:
  • the field is hidden and disabled in the browser
  • the server skips all of the field’s validations, including required
A condition that doesn’t parse is a form error when the form is saved.
A condition that fails to parse is treated as true, so the field is shown. The warning is logged rather than raised.
visibility.depends_on is accepted for backward compatibility but has no effect, because conditions already re-evaluate on every change.

body[*].dynamic

Optional. Computes values or options from other fields. All dynamic behavior runs in the browser.

body[*].dynamic.auto_populate

Fills the field automatically. It accepts either a string or a built-in function object. String forms
Built-in function object
The schema also accepts a values key next to params, but it is ignored. Put the list under params.values.

body[*].dynamic.computed

Boolean. Default false.
  • true: the field is read-only and is overwritten every time it is recalculated.
  • false: the auto-populated value is only a suggestion. It fills the field when the field is empty, or when the current value was itself auto-filled. It never overwrites a value the user typed.

body[*].dynamic.depends_on

A list of field ids. A change to any of them recalculates auto_populate. Changes from text inputs are debounced by 500 ms. The value is also calculated once when the page loads.

body[*].dynamic.options_map

dropdown only. Replaces the field’s options based on the value of a parent field. This is how you build cascading dropdowns. It is a map from each parent value to a list of options.
The lookup is a flat options_map[parent_value] on a single parent. Nested maps and multi-parent lookups are not supported.

body[*].dynamic.depends_on_field

The id of the parent field for options_map. If omitted, the first entry of dynamic.depends_on is used.
depends_on_fields and lookup_path are accepted by the schema but not implemented.

Condition syntax

Conditions are a small, fixed expression language. They cannot run arbitrary code. The browser and the server evaluate them identically. Precedence, from loosest to tightest: ||, then &&, then !, then == and !=. Value rules
  • An empty value ("", [], {}) is treated as null. For example, region != null means “the user picked a region”.
  • A checkboxes field evaluates as the list of checked option values.
  • includes tests membership for lists and substring for strings. It is false for null.
  • == and != compare values as strings, except that booleans compare directly.
<, >, <=, and >= are not supported. A condition that uses them fails to parse, which means the field is always shown.

Submitted values

This is what each field type submits, as seen in bento_inputs.json and the environment variables your workflow receives. See Workflow syntax → Inputs and environment variables.