Toolbench

Inputs

Every input type, its attributes, and the rules a manifest's inputs are held to.

Each input becomes a labelled control on the form, with its description as the control's accessible description. Its value reaches run(values) under its id.

Types

InputTypeRequiredOptionalUse it for
textstringdefaultmaxLengthOne line: a name, a pattern, an id
textareastringdefaultrows maxLength lang (v6) grammar (v7)Pasted data, a document, source in a notation
numbernumber, clamped to min..maxdefault min maxstep control (v4)A bounded quantity: a rate, a count, a size
selectstring, one of the option valuesdefault optionscontrol (v4) options[].sets (v6)A choice from a short fixed list, as a dropdown or a row of segments, or a preset that fills other inputs
togglebooleandefaultnoneOn or off
date (v9)string, YYYY-MM-DD, HH:MM or YYYY-MM-DDTHH:MM, within min..maxdefaultmode min maxA day, a time of day, or both, in the browser's own picker
color (v9)string, a lowercase #rrggbbdefaultnoneA colour, typed as hex or picked in the system's picker
secret (v9)string, empty until typed, at least minLength charactersminLength maxLengthA password, a token or a key, with no default and kept out of links
multiselect (v9)string[], the chosen option values in the options' order, min to max of themdefault optionsmin max controlSeveral choices from a fixed list, as checkboxes or chips
rows (v9)Row[], one object per row keyed by column id, minRows to maxRows of them, even for one columncolumns maxRows defaultminRowsA list the reader adds to, removes from and reorders: hosts, or paths and their limits

Common attributes

Every input type takes these.

AttributeTypeRequiredDefaultSinceNotes
idstringyesv1The key this input's value has in run(values). Unique within the tool.
labelstringyesv1The visible label, which is also the control's accessible name.
descriptionstringnov1The hint a form shows and a screen reader reads. Not a placeholder: those vanish on focus. Takes light markup: a blank line starts a paragraph, a line starting "- " is a list item, code, strong, and {goodtext} for the tones good, warn, bad, muted and accent. Anything else is text.
detailsstringnov8The rest of the help, behind a native disclosure under the description: every case a format covers, per-vendor rules, the reasons. Takes the same markup as description. It is not part of the control's accessible description, so a screen reader announces only the short hint on focus.
unitstringnov1Shown after the control: "ms", "req/s", "bytes". A number without one is a riddle.
primarybooleannov1Show this input on a compact card, where there is only room for what matters. Mark as many as the question genuinely needs: a card asking "how many servers" is useless with only one of a rate and a duration. Mark none and a card shows the first input. Every input is shown on the full tool regardless.
dir"ltr" | "auto"nov1Force text direction. Bytes, code and identifiers are left-to-right even inside right-to-left prose, and a runtime cannot know which is which, so the tool says.
sharebooleannotrue, and false on a secretv8Whether a host may put this input's value in a link it builds from host.values, and read it back from one. false for a credential or pasted private material: a URL ends up in browser history, server logs, chat previews and referrer headers. A privacy hint for hosts, not access control: the runtime builds no links and does not enforce it, and host.values still writes the input.
rolestringnov8A name for this input, rendered as data-role on its .tb-field-row, so a host stylesheet can style "the credential" or "the advanced ones" without naming each id. The runtime gives it no meaning, as with a result part's role.

text

Type: string. Takes the common attributes, plus:

AttributeTypeRequiredSinceNotes
defaultstringyesv1The value the form opens with.
maxLengthnumbernov1The most characters the control accepts.

Example

JSON
{  "id": "pattern",  "type": "text",  "label": "Pattern",  "description": "A JavaScript regular expression, without the surrounding slashes.",  "primary": true,  "dir": "ltr",  "maxLength": 80,  "default": "(\\w+)\\s+(\\d+)"}

From tools/regex-explainer/tool.json.

textarea

Type: string. Takes the common attributes, plus:

AttributeTypeRequiredDefaultSinceNotes
defaultstringyesv1The value the form opens with.
rowsnumberno3v1Visible lines of text.
maxLengthnumbernov1The most characters the control accepts.
langstringnov6The language of what the reader types, passed to the host's highlight(source, lang) hook, the same one code results use, which paints it behind the textarea. A hint: with no hook, or a hook that cannot paint it, the textarea is the plain one.
grammarTextareaGrammarnov7The tool's own notation for lang, so a textarea is painted where no host highlighter knows the language: a state-machine syntax, a packet filter. Requires lang. A host hook that paints lang still wins.

Grammar attributes

The keys of grammar. Each is optional, and a grammar needs at least one. Patterns are not allowed: a regular expression from a tool could hang the page it is on.

AttributeTypeRequiredSinceNotes
keywordsstring[]nov7Whole words drawn as keywords, case-sensitive: letters, digits and _. At most 64, each 32 characters or fewer.
commentstringnov7What starts a comment that runs to the end of its line, # or //: ASCII punctuation, at most 4 characters.
operatorsstring[]nov7Literal operators, -> or |, the longest match winning: ASCII punctuation. At most 32, each 4 characters or fewer.
stringsstringnov7The characters that open a string, each closed by itself, " or "'. A backslash escapes the next character, and a string ends at the end of its line. At most 4.

Example

JSON
{  "id": "machine",  "type": "textarea",  "label": "Machine",  "description": "One state or transition per line. # starts a comment.",  "primary": true,  "lang": "doors",  "grammar": {    "keywords": ["initial", "state", "on", "final"],    "comment": "#",    "operators": ["->", ":"],    "strings": "\""  },  "rows": 9,  "maxLength": 4000,  "dir": "ltr",  "default": "# A door that locks\ninitial closed\nstate open\nstate locked \"needs a key\"\non push: closed -> open\non pull: open -> closed\non lock: closed -> locked\non unlock: locked -> closed"}

From bench/fixtures/grammar-input/tool.json.

See it live: A textarea with its own grammar

number

Type: number, clamped to min..max. Takes the common attributes, plus:

AttributeTypeRequiredSinceNotes
defaultnumberyesv1The value the form opens with, within min..max.
minnumberyesv1The lowest value. Required: the only guard against a runaway value. A value outside min..max is refused before run, and a host's write is clamped.
maxnumberyesv1The highest value, above min.
stepnumbernov1The increment for the arrow keys and the slider. Any value when omitted.
control"slider"nov4slider asks for a range control beside the number box, for a tool read by sweeping a value rather than by typing one. A hint, not a type: a runtime that does not know it renders the box alone.

Example

JSON
{  "id": "rtt",  "type": "number",  "label": "Round-trip time",  "unit": "ms",  "description": "Drag, use the arrow keys, or type an exact value.",  "primary": true,  "default": 80,  "min": 10,  "max": 500,  "step": 10,  "control": "slider"}

From bench/fixtures/slider/tool.json.

See it live: A slider

select

Type: string, one of the option values. Takes the common attributes, plus:

AttributeTypeRequiredDefaultSinceNotes
defaultstringyesv1The option value the form opens with.
optionsSelectOption[]yesv1Two or more choices, with unique values.
control"segmented"nothe dropdownv4segmented draws the options as a row of segments instead of a dropdown, for two to five short choices a reader should see at once. They are native radio buttons in a fieldset, so the arrow keys, the single tab stop and the announcement are the browser's, and the chosen one is a solid accent fill. When a segment would not fit its label, measured as the form resizes, the row becomes stacked radios. A hint, not a type: run still receives an option value, presets work as they do on a dropdown, and a runtime that does not know it draws the dropdown.

Option attributes

Each entry of options.

AttributeTypeRequiredSinceNotes
valuestringyesv1What run receives when this option is chosen. Unique within the select.
labelstringyesv1What the reader sees.
setsInputValuesnov6A preset: choosing this option writes these values into the other inputs it names, which the reader then edits. Partial, keyed by input id, checked like a sample's input. run still receives the select's own value. The select follows the form afterwards: once the values stop matching this option, it shows the preset they do match, or the first option with no sets (a "Custom"), or stays put if there is none.

Example

JSON
{  "id": "stack",  "type": "select",  "label": "Library",  "description": "Fills the read timeout, retries and backoff. Edit them afterwards.",  "default": "a",  "options": [    {      "value": "a",      "label": "Library A",      "sets": {"read": 30, "retries": 2, "backoff": 200}    },    {      "value": "b",      "label": "Library B",      "sets": {"read": 60, "retries": 3, "backoff": 500}    },    {      "value": "c",      "label": "Library C",      "sets": {"read": 10, "retries": 0, "backoff": 0}    },    {"value": "custom", "label": "Custom"}  ]}

From bench/fixtures/presets/tool.json.

See it live: A select that fills other inputs

toggle

Type: boolean. Takes the common attributes, plus:

AttributeTypeRequiredSinceNotes
defaultbooleanyesv1Whether the form opens with it on.

Example

JSON
{  "id": "fixed",  "type": "toggle",  "label": "Limits added",  "description": "Whether the two free-text fields have been given a length limit.",  "primary": true,  "default": false}

From bench/fixtures/styled-output/tool.json.

date

Type: string, YYYY-MM-DD, HH:MM or YYYY-MM-DDTHH:MM, within min..max. Takes the common attributes, plus:

AttributeTypeRequiredDefaultSinceNotes
mode"date" | "time" | "datetime"no"date"v9What the reader picks, in the browser's own picker: a day, a time of day, or both. run gets YYYY-MM-DD, HH:MM or YYYY-MM-DDTHH:MM, local time, with no zone.
defaultstringyesv9The value the form opens with, written in the mode's format, within min..max.
minstringnov9The earliest value, in the mode's format. A value before it is refused before run, with a message naming the input, so run can trust the range.
maxstringnov9The latest value, in the mode's format, not before min.

Example

JSON
{  "id": "captured",  "type": "date",  "mode": "datetime",  "label": "Captured at",  "unit": "local time",  "description": "When the trace was taken, between **2020-01-01** and **2026-10-25**. Arrives as `2026-10-25T14:30`.",  "default": "2026-10-25T14:30",  "min": "2020-01-01T00:00",  "max": "2026-10-25T23:59"}

From bench/fixtures/native-inputs/tool.json.

See it live: Native controls

color

Type: string, a lowercase #rrggbb. Takes the common attributes, plus:

AttributeTypeRequiredSinceNotes
defaultstringyesv9The colour the form opens with, as run gets every colour: a lowercase #rrggbb, with no alpha and no shorthand. The reader types it in a hex box, the labelled control, or picks it from the system's picker in the swatch beside it.

Example

JSON
{  "id": "accent",  "type": "color",  "label": "Highlight colour",  "description": "Six hex digits, as `#rrggbb`. The swatch opens the system picker.",  "default": "#1f5aa8"}

From bench/fixtures/native-inputs/tool.json.

See it live: Native controls

secret

Type: string, empty until typed, at least minLength characters. Takes the common attributes, plus:

AttributeTypeRequiredSinceNotes
minLengthnumbernov9The fewest characters run accepts. A shorter value is refused before run, naming the input.
maxLengthnumbernov9The most characters the control accepts, not below minLength.

Example

JSON
{  "id": "token",  "type": "secret",  "label": "API token",  "description": "Used for this run only. {warn|Never put in a shared link.}",  "minLength": 16,  "maxLength": 64}

From bench/fixtures/native-inputs/tool.json.

See it live: Native controls

multiselect

Type: string[], the chosen option values in the options' order, min to max of them. Takes the common attributes, plus:

AttributeTypeRequiredDefaultSinceNotes
defaultstring[]yesv9The option values the form opens with chosen, each once, between min and max of them. [] for none.
optionsSelectOption[]yesv9Two or more choices, with unique values. An option here takes no sets: several presets at once have no single meaning.
minnumberno0v9The fewest a reader may choose. 1 is what makes "pick at least one" a rule.
maxnumbernothe number of optionsv9The most a reader may choose. At it, the rest are marked unavailable, with the reason beside them.
control"checkboxes" | "chips"nocheckboxes up to 6 options, chips abovev9How the choices are drawn: checkboxes, a native checkbox per option, or chips, a row of toggle buttons that wraps, for a long list of short values. A hint, like a number's slider.

Example

JSON
{  "id": "regions",  "type": "multiselect",  "label": "Regions",  "description": "Each chosen region asks every record type.",  "default": ["us-east-1", "us-west-2", "eu-west-1"],  "max": 4,  "options": [    {"value": "us-east-1", "label": "us-east-1"},    {"value": "us-east-2", "label": "us-east-2"},    {"value": "us-west-1", "label": "us-west-1"},    {"value": "us-west-2", "label": "us-west-2"},    {"value": "ca-central-1", "label": "ca-central-1"},    {"value": "eu-west-1", "label": "eu-west-1"},    {"value": "eu-west-2", "label": "eu-west-2"},    {"value": "eu-central-1", "label": "eu-central-1"},    {"value": "eu-north-1", "label": "eu-north-1"},    {"value": "ap-south-1", "label": "ap-south-1"},    {"value": "ap-northeast-1", "label": "ap-northeast-1"},    {"value": "ap-southeast-1", "label": "ap-southeast-1"},    {"value": "ap-southeast-2", "label": "ap-southeast-2"},    {"value": "sa-east-1", "label": "sa-east-1"}  ]}

From bench/fixtures/multiselect/tool.json.

See it live: Several choices

rows

Type: Row[], one object per row keyed by column id, minRows to maxRows of them, even for one column. Takes the common attributes, plus:

AttributeTypeRequiredDefaultSinceNotes
columnsRowColumn[]yesv9One to four columns, each a text, number, select or toggle with that type's keys and no default.
maxRowsnumberyesv9The most rows a reader may add, at most 50. Required, as a number's max is: the guard against a runaway list, and a runaway link. At it, Add stays focusable with the reason beside it.
minRowsnumberno0v9The fewest rows Run accepts.
defaultRow[]yesv9The rows the form opens with, each with a value for every column. [] for none.

Column attributes

Each entry of columns: a type of text, number, select or toggle, that type's own keys (maxLength; min, max and step; options), and these. A column takes no default.

AttributeTypeRequiredSinceNotes
idstringyesv9The key of this column's value in each row run receives. Unique within the input.
labelstringyesv9The column's heading, and the start of each cell's accessible name: "Path, row 2 of 3".
unitstringnov9Shown after each cell: "req/s".
dir"ltr" | "auto"nov9Force text direction, as on an input.

Example

JSON
{  "id": "limits",  "type": "rows",  "label": "Limits",  "description": "A path prefix and its limit. The first matching path wins, so order matters.",  "minRows": 1,  "maxRows": 8,  "columns": [    {      "id": "path",      "type": "text",      "label": "Path",      "maxLength": 100,      "dir": "ltr"    },    {      "id": "limit",      "type": "number",      "label": "Limit",      "min": 1,      "max": 10000,      "step": 1,      "unit": "req/s"    }  ],  "default": [{"path": "/api/login", "limit": 5}, {"path": "/api", "limit": 500}]}

From tools/rate-limits/tool.json.

See it live: Rows to add and reorder

Validation rules

validateManifest checks these before anything runs. The message is exactly what an author gets.

RuleMessage
A tool has at least one inputinputs: a tool needs at least one input; a tool with none is a constant
Input ids are uniqueinputs[2].id: "n" is already used by inputs[0]
A number declares min and maxinputs[0].min/max: a number input must declare min and max: they are the only guard against a runaway input
min is below maxinputs[0].min: min (10) must be below max (5)
A number's default is within min..maxinputs[0].default: default 90 is outside min..max (1..10)
control on a number is "slider"inputs[0].control: control must be "slider" when present, got "knob"
control on a select is "segmented"inputs[1].control: control must be "segmented" when present, got "slider"
A date's min, default and max parse in its modeinputs[0].default: must be written YYYY-MM-DD in mode "date", got "25/10/2026"
A date's default is within min..maxinputs[0].default: Day must be on or after 2020-01-01, got 2019-12-31.
mode only on a dateinputs[2].mode: only a date input has a mode; on a text input it would be ignored
A colour's default is a lowercase #rrggbbinputs[0].default: Tint must be a #rrggbb colour, got "#15A".
A secret has no defaultinputs[0].default: a secret has no default: a manifest is published, so every reader would have it
A secret's minLength is not above its maxLengthinputs[0].minLength: 32 is above maxLength 16
No sample or preset sets a secretsamples[0].input.key: is a secret, which a manifest may not set: every reader would have it
A select has two or more optionsinputs[1].options: a select needs at least two options; with one, use a fixed value
Option values are uniqueinputs[1].options: option values must be unique
A select's default is one of its optionsinputs[1].default: must be one of the option values: fast, exact
A multiselect's default is a list of its options, each onceinputs[1].default[1]: "slow" is not one of the options: fast, exact
A multiselect's min is not above its maxinputs[1].min: min (2) is above max (1)
control on a multiselect is "checkboxes" or "chips"inputs[1].control: control must be "checkboxes" or "chips" when present, got "segmented"
Rows declare maxRows, from 1 to 50inputs[2].maxRows: a rows input must declare maxRows: it is the guard against a runaway list and a runaway link
Rows have one to four columnsinputs[2].columns: takes 1 to 4 columns, got 0
A column has no default; the rows input's default gives every row a valueinputs[2].columns[0].default: belongs in the rows input's default, which gives every row a value
Every default row has a value for every columninputs[2].default[0]: has no "port": every row needs a value for every column
lang only on a textareainputs[2].lang: only a textarea is highlighted; on a text input it would be ignored
grammar needs lang beside itinputs[2].grammar: needs a lang beside it: the grammar is the tool's own notation for that language
share is true or falseinputs[2].share: must be true or false
role is a non-empty stringinputs[2].role: must be a non-empty string
details is a non-empty stringinputs[2].details: must be a non-empty string