Input Fields
Input fields control how users enter data in your tools. Keel generates appropriate input components for each field type automatically, but you can customise labels, help text, default values, ordering, and visibility to create forms that match your workflow.
How input fields work
When you define an action with inputs in your schema, Keel generates input fields for each parameter. The input type is determined by the field's data type:
model Order {
fields {
reference Text
quantity Number
urgent Boolean
placedAt Timestamp
status OrderStatus
customer Customer
}
actions {
create createOrder() with (
reference,
quantity,
urgent?,
placedAt?,
status,
customer.id
)
}
}This creates a form with:
- A text input for
reference - A number input for
quantity - A toggle for
urgent(optional, so includes a null option) - A date/time picker for
placedAt(optional) - A dropdown for
statusshowing enum values - A relationship field for
customer.idwith a lookup button
Configuring input fields
Input field configuration lives in the inputs object of your tool configuration file. Each key is a JSON path to the input field:
{
"id": "create-order",
"action_name": "createOrder",
"inputs": {
"$.reference": {
"display_name": "Order Reference",
"help_text": "A unique identifier for this order",
"placeholder": "ORD-001"
}
}
}JSON path syntax
Input paths use JSON path syntax starting with $.:
| Path | Description |
|---|---|
$.reference | Top-level input field |
$.customer.id | Nested relationship field |
$.items[*].quantity | Field within a repeated input |
Input field options
| Option | Type | Description |
|---|---|---|
display_name | string | Label shown above the input |
display_order | number | Position in the form (lower numbers appear first) |
visible | boolean | Whether to show the field (default: true) |
help_text | string | Guidance text shown below the input |
placeholder | string | Placeholder text inside empty inputs |
locked | boolean | Prevents editing when true |
default_value | object | Pre-populated value for the input |
visible_condition | string | CEL expression for conditional visibility |
section_name | string | Groups the input into a named section |
lookup_action | object | Link to a list action for selecting values |
get_entry_action | object | Link to a get action for previewing selected values |
Display name and help text
Customise how inputs are labelled and explained:
{
"inputs": {
"$.deliveryAddress": {
"display_name": "Delivery Address",
"help_text": "The full address where goods should be delivered"
},
"$.specialInstructions": {
"display_name": "Special Instructions",
"help_text": "Any specific requirements for handling or delivery",
"placeholder": "e.g., Leave at reception, Call on arrival"
}
}
}Help text appears below the input and is useful for explaining field requirements or providing examples. Placeholders appear inside empty inputs as hints.
Field ordering
Control the order fields appear in forms with display_order:
{
"inputs": {
"$.reference": {
"display_order": 1
},
"$.customer.id": {
"display_order": 2
},
"$.deliveryAddress": {
"display_order": 3
},
"$.items[*].product.id": {
"display_order": 4
},
"$.items[*].quantity": {
"display_order": 5
}
}
}Fields without a display_order appear after ordered fields, in the sequence they're defined in your schema.
Field visibility
Hide fields that shouldn't be shown to users:
{
"inputs": {
"$.internalCode": {
"visible": false
}
}
}Hidden fields can still receive values through data mapping when navigating between tools.
Conditional visibility
Show or hide fields based on other input values using CEL expressions:
{
"inputs": {
"$.shippingMethod": {
"display_name": "Shipping Method"
},
"$.carrierAccount": {
"display_name": "Carrier Account",
"visible_condition": "$.shippingMethod == 'courier'"
},
"$.pickupDate": {
"display_name": "Collection Date",
"visible_condition": "$.shippingMethod == 'collection'"
}
}
}The carrierAccount field only appears when shipping method is "courier". The pickupDate field only appears when shipping method is "collection".
CEL expressions in visible_condition have access to all input values using $.fieldName syntax. Common operators include ==, !=, &&, ||, and !.
Default values
Pre-populate inputs with default values:
{
"inputs": {
"$.quantity": {
"default_value": {
"int_value": 1
}
},
"$.status": {
"default_value": {
"string_value": "pending"
}
},
"$.isActive": {
"default_value": {
"bool_value": true
}
},
"$.discount": {
"default_value": {
"float_value": 0.0
}
}
}
}Default value types
| Type | Description | Example |
|---|---|---|
string_value | Text and enum values | "pending" |
int_value | Whole numbers | 1 |
float_value | Decimal numbers | 0.15 |
bool_value | Boolean values | true |
null_value | Explicitly null | true |
Default values are applied when the form loads. Users can change them before submitting.
Locked fields
Prevent users from modifying a field:
{
"inputs": {
"$.createdBy": {
"locked": true
}
}
}Locked fields display their value but cannot be edited. This is useful when a value is passed via data mapping and shouldn't be changed.
Lookups for relationship fields
When an input references another model (like customer.id), Keel can display a lookup button that opens a list action for selecting a record.
Automatic lookups
Keel automatically configures lookups when:
- The input is a relationship field (e.g.,
customer.id) - A
listaction exists on the related model with no required inputs
model Order {
fields {
customer Customer
}
actions {
create createOrder() with (customer.id)
}
}
model Customer {
fields {
name Text
}
actions {
list listCustomers() // Enables automatic lookup
}
}The createOrder form will show a lookup button on the customer.id field that opens listCustomers.
Custom lookups
Override the default lookup action:
{
"inputs": {
"$.customer.id": {
"display_name": "Customer",
"lookup_action": {
"tool_id": "list-active-customers"
}
}
}
}This is useful when you have multiple list actions and want to use a filtered view for selection.
Lookup with data mapping
Pre-filter the lookup results based on other input values:
{
"inputs": {
"$.product.id": {
"lookup_action": {
"tool_id": "list-products",
"data_mapping": [
{
"key": "$.where.category.id.equals",
"path": { "path": "$.category.id" }
}
]
}
}
}
}When the user opens the product lookup, it's pre-filtered to only show products in the selected category.
Get entry action
Configure how selected values are previewed:
{
"inputs": {
"$.supplier.id": {
"lookup_action": {
"tool_id": "list-suppliers"
},
"get_entry_action": {
"tool_id": "get-supplier"
}
}
}
}The get_entry_action loads a preview of the selected record so users can verify they've chosen correctly.
Sections
Group related inputs into sections for better organisation:
{
"sections": [
{
"name": "basics",
"title": "Order Details",
"display_order": 1,
"visible": true
},
{
"name": "delivery",
"title": "Delivery Information",
"display_order": 2,
"visible": true
}
],
"inputs": {
"$.reference": {
"section_name": "basics"
},
"$.customer.id": {
"section_name": "basics"
},
"$.deliveryAddress": {
"section_name": "delivery"
},
"$.deliveryDate": {
"section_name": "delivery"
}
}
}Sections create visual groupings in the form with collapsible headers.
Input types by field type
Keel renders different input components based on your schema field types:
| Field type | Input component |
|---|---|
Text | Text input |
Number | Number input |
Decimal | Decimal input with precision |
Boolean | Toggle switch |
Date | Date picker |
Timestamp | Date and time picker |
Enum | Dropdown select |
File | File upload |
Markdown | Rich text editor |
ID (relationship) | Text input with lookup button |
Optional fields
Optional inputs include a null toggle. When enabled, the field explicitly sends null rather than being omitted.
Repeated fields
Inputs marked as repeated (arrays) show add/remove buttons to manage multiple entries:
actions {
create createOrder() with (
items.product.id,
items.quantity
)
}This creates a repeatable section where users can add multiple order lines.
Flow inputs
Flow tools support a subset of input configuration options:
{
"id": "process-return",
"flow_name": "processReturn",
"inputs": {
"$.orderId": {
"display_name": "Order",
"help_text": "Select the order being returned",
"display_order": 1
},
"$.reason": {
"display_name": "Return Reason",
"placeholder": "Describe why the items are being returned",
"display_order": 2
},
"$.quantity": {
"display_name": "Quantity",
"default_value": {
"int_value": 1
},
"display_order": 3
}
}
}Flow input options
| Option | Type | Description |
|---|---|---|
display_name | string | Label shown above the input |
display_order | number | Position in the form |
help_text | string | Guidance text below the input |
placeholder | string | Placeholder text inside empty inputs |
default_value | object | Pre-populated value |
Flow inputs don't support visible, locked, visible_condition, section_name, lookup_action, or get_entry_action. These options are only available for action tool inputs.
Complete example
Here's a comprehensive example configuring inputs for a goods receipt action:
{
"id": "create-goods-receipt",
"action_name": "createGoodsReceipt",
"name": "Receive Goods",
"help_text": "Record incoming stock from a purchase order",
"sections": [
{
"name": "source",
"title": "Source",
"display_order": 1,
"visible": true
},
{
"name": "items",
"title": "Received Items",
"display_order": 2,
"visible": true
},
{
"name": "notes",
"title": "Notes",
"display_order": 3,
"visible": true
}
],
"inputs": {
"$.purchaseOrder.id": {
"display_name": "Purchase Order",
"help_text": "The PO this receipt is for",
"section_name": "source",
"display_order": 1,
"lookup_action": {
"tool_id": "list-open-purchase-orders"
}
},
"$.supplier.id": {
"display_name": "Supplier",
"section_name": "source",
"display_order": 2,
"locked": true
},
"$.receivedAt": {
"display_name": "Received Date",
"section_name": "source",
"display_order": 3,
"default_value": {
"string_value": "today"
}
},
"$.items[*].stockItem.id": {
"display_name": "Stock Item",
"section_name": "items",
"display_order": 1,
"lookup_action": {
"tool_id": "list-stock-items",
"data_mapping": [
{
"key": "$.where.supplier.id.equals",
"path": { "path": "$.supplier.id" }
}
]
}
},
"$.items[*].quantity": {
"display_name": "Quantity Received",
"section_name": "items",
"display_order": 2,
"default_value": {
"int_value": 1
}
},
"$.items[*].location.id": {
"display_name": "Storage Location",
"section_name": "items",
"display_order": 3,
"lookup_action": {
"tool_id": "list-locations"
}
},
"$.notes": {
"display_name": "Receipt Notes",
"help_text": "Any observations about the delivery condition or discrepancies",
"placeholder": "e.g., 2 items damaged in transit",
"section_name": "notes",
"display_order": 1
},
"$.internalRef": {
"visible": false
}
}
}This configuration:
- Organises inputs into three logical sections
- Provides clear labels and help text for each field
- Uses a custom lookup for purchase orders showing only open ones
- Locks the supplier field (populated via data mapping from the PO)
- Filters the stock item lookup by the selected supplier
- Sets sensible defaults for quantity
- Hides the internal reference field