Inputs
Generated from packages/sdk/src/types.ts
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
| Input | Type | Required | Optional | Use it for |
|---|---|---|---|---|
text | string | default | maxLength | One line: a name, a pattern, an id |
textarea | string | default | rows maxLength lang (v6) grammar (v7) | Pasted data, a document, source in a notation |
number | number, clamped to min..max | default min max | step control (v4) | A bounded quantity: a rate, a count, a size |
select | string, one of the option values | default options | control (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 |
toggle | boolean | default | none | On or off |
date (v9) | string, YYYY-MM-DD, HH:MM or YYYY-MM-DDTHH:MM, within min..max | default | mode min max | A day, a time of day, or both, in the browser's own picker |
color (v9) | string, a lowercase #rrggbb | default | none | A colour, typed as hex or picked in the system's picker |
secret (v9) | string, empty until typed, at least minLength characters | minLength maxLength | A 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 them | default options | min max control | Several 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 column | columns maxRows default | minRows | A list the reader adds to, removes from and reorders: hosts, or paths and their limits |
Common attributes
Every input type takes these.
| Attribute | Type | Required | Default | Since | Notes | |
|---|---|---|---|---|---|---|
id | string | yes | v1 | The key this input's value has in run(values). Unique within the tool. | ||
label | string | yes | v1 | The visible label, which is also the control's accessible name. | ||
description | string | no | v1 | The 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 {good | text} for the tones good, warn, bad, muted and accent. Anything else is text. | |
details | string | no | v8 | The 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. | ||
unit | string | no | v1 | Shown after the control: "ms", "req/s", "bytes". A number without one is a riddle. | ||
primary | boolean | no | v1 | Show 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" | no | v1 | Force 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. | ||
share | boolean | no | true, and false on a secret | v8 | Whether 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. | |
role | string | no | v8 | A 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:
| Attribute | Type | Required | Since | Notes |
|---|---|---|---|---|
default | string | yes | v1 | The value the form opens with. |
maxLength | number | no | v1 | The most characters the control accepts. |
Example
{ "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:
| Attribute | Type | Required | Default | Since | Notes |
|---|---|---|---|---|---|
default | string | yes | v1 | The value the form opens with. | |
rows | number | no | 3 | v1 | Visible lines of text. |
maxLength | number | no | v1 | The most characters the control accepts. | |
lang | string | no | v6 | The 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. | |
grammar | TextareaGrammar | no | v7 | The 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.
| Attribute | Type | Required | Since | Notes |
|---|---|---|---|---|
keywords | string[] | no | v7 | Whole words drawn as keywords, case-sensitive: letters, digits and _. At most 64, each 32 characters or fewer. |
comment | string | no | v7 | What starts a comment that runs to the end of its line, # or //: ASCII punctuation, at most 4 characters. |
operators | string[] | no | v7 | Literal operators, -> or |, the longest match winning: ASCII punctuation. At most 32, each 4 characters or fewer. |
strings | string | no | v7 | The 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
{ "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:
| Attribute | Type | Required | Since | Notes |
|---|---|---|---|---|
default | number | yes | v1 | The value the form opens with, within min..max. |
min | number | yes | v1 | The 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. |
max | number | yes | v1 | The highest value, above min. |
step | number | no | v1 | The increment for the arrow keys and the slider. Any value when omitted. |
control | "slider" | no | v4 | slider 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
{ "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.
select
Type: string, one of the option values. Takes the common attributes, plus:
| Attribute | Type | Required | Default | Since | Notes |
|---|---|---|---|---|---|
default | string | yes | v1 | The option value the form opens with. | |
options | SelectOption[] | yes | v1 | Two or more choices, with unique values. | |
control | "segmented" | no | the dropdown | v4 | segmented 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.
| Attribute | Type | Required | Since | Notes |
|---|---|---|---|---|
value | string | yes | v1 | What run receives when this option is chosen. Unique within the select. |
label | string | yes | v1 | What the reader sees. |
sets | InputValues | no | v6 | A 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
{ "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:
| Attribute | Type | Required | Since | Notes |
|---|---|---|---|---|
default | boolean | yes | v1 | Whether the form opens with it on. |
Example
{ "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:
| Attribute | Type | Required | Default | Since | Notes |
|---|---|---|---|---|---|
mode | "date" | "time" | "datetime" | no | "date" | v9 | What 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. |
default | string | yes | v9 | The value the form opens with, written in the mode's format, within min..max. | |
min | string | no | v9 | The 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. | |
max | string | no | v9 | The latest value, in the mode's format, not before min. |
Example
{ "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.
color
Type: string, a lowercase #rrggbb. Takes the common attributes, plus:
| Attribute | Type | Required | Since | Notes |
|---|---|---|---|---|
default | string | yes | v9 | The 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
{ "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.
secret
Type: string, empty until typed, at least minLength characters. Takes the common attributes, plus:
| Attribute | Type | Required | Since | Notes |
|---|---|---|---|---|
minLength | number | no | v9 | The fewest characters run accepts. A shorter value is refused before run, naming the input. |
maxLength | number | no | v9 | The most characters the control accepts, not below minLength. |
Example
{ "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.
multiselect
Type: string[], the chosen option values in the options' order, min to max of them. Takes the common attributes, plus:
| Attribute | Type | Required | Default | Since | Notes |
|---|---|---|---|---|---|
default | string[] | yes | v9 | The option values the form opens with chosen, each once, between min and max of them. [] for none. | |
options | SelectOption[] | yes | v9 | Two or more choices, with unique values. An option here takes no sets: several presets at once have no single meaning. | |
min | number | no | 0 | v9 | The fewest a reader may choose. 1 is what makes "pick at least one" a rule. |
max | number | no | the number of options | v9 | The most a reader may choose. At it, the rest are marked unavailable, with the reason beside them. |
control | "checkboxes" | "chips" | no | checkboxes up to 6 options, chips above | v9 | How 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
{ "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.
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:
| Attribute | Type | Required | Default | Since | Notes |
|---|---|---|---|---|---|
columns | RowColumn[] | yes | v9 | One to four columns, each a text, number, select or toggle with that type's keys and no default. | |
maxRows | number | yes | v9 | The 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. | |
minRows | number | no | 0 | v9 | The fewest rows Run accepts. |
default | Row[] | yes | v9 | The 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.
| Attribute | Type | Required | Since | Notes |
|---|---|---|---|---|
id | string | yes | v9 | The key of this column's value in each row run receives. Unique within the input. |
label | string | yes | v9 | The column's heading, and the start of each cell's accessible name: "Path, row 2 of 3". |
unit | string | no | v9 | Shown after each cell: "req/s". |
dir | "ltr" | "auto" | no | v9 | Force text direction, as on an input. |
Example
{ "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.
| Rule | Message |
|---|---|
| A tool has at least one input | inputs: a tool needs at least one input; a tool with none is a constant |
| Input ids are unique | inputs[2].id: "n" is already used by inputs[0] |
A number declares min and max | inputs[0].min/max: a number input must declare min and max: they are the only guard against a runaway input |
min is below max | inputs[0].min: min (10) must be below max (5) |
A number's default is within min..max | inputs[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 mode | inputs[0].default: must be written YYYY-MM-DD in mode "date", got "25/10/2026" |
A date's default is within min..max | inputs[0].default: Day must be on or after 2020-01-01, got 2019-12-31. |
mode only on a date | inputs[2].mode: only a date input has a mode; on a text input it would be ignored |
A colour's default is a lowercase #rrggbb | inputs[0].default: Tint must be a #rrggbb colour, got "#15A". |
| A secret has no default | inputs[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 maxLength | inputs[0].minLength: 32 is above maxLength 16 |
| No sample or preset sets a secret | samples[0].input.key: is a secret, which a manifest may not set: every reader would have it |
| A select has two or more options | inputs[1].options: a select needs at least two options; with one, use a fixed value |
| Option values are unique | inputs[1].options: option values must be unique |
| A select's default is one of its options | inputs[1].default: must be one of the option values: fast, exact |
| A multiselect's default is a list of its options, each once | inputs[1].default[1]: "slow" is not one of the options: fast, exact |
A multiselect's min is not above its max | inputs[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 50 | inputs[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 columns | inputs[2].columns: takes 1 to 4 columns, got 0 |
A column has no default; the rows input's default gives every row a value | inputs[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 column | inputs[2].default[0]: has no "port": every row needs a value for every column |
lang only on a textarea | inputs[2].lang: only a textarea is highlighted; on a text input it would be ignored |
grammar needs lang beside it | inputs[2].grammar: needs a lang beside it: the grammar is the tool's own notation for that language |
share is true or false | inputs[2].share: must be true or false |
role is a non-empty string | inputs[2].role: must be a non-empty string |
details is a non-empty string | inputs[2].details: must be a non-empty string |