Input Fields

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 status showing enum values
  • A relationship field for customer.id with 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:

tools/create-order.json
{
  "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 $.:

PathDescription
$.referenceTop-level input field
$.customer.idNested relationship field
$.items[*].quantityField within a repeated input

Input field options

OptionTypeDescription
display_namestringLabel shown above the input
display_ordernumberPosition in the form (lower numbers appear first)
visiblebooleanWhether to show the field (default: true)
help_textstringGuidance text shown below the input
placeholderstringPlaceholder text inside empty inputs
lockedbooleanPrevents editing when true
default_valueobjectPre-populated value for the input
visible_conditionstringCEL expression for conditional visibility
section_namestringGroups the input into a named section
lookup_actionobjectLink to a list action for selecting values
get_entry_actionobjectLink 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

TypeDescriptionExample
string_valueText and enum values"pending"
int_valueWhole numbers1
float_valueDecimal numbers0.15
bool_valueBoolean valuestrue
null_valueExplicitly nulltrue

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:

  1. The input is a relationship field (e.g., customer.id)
  2. A list action 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:

tools/create-order.json
{
  "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:

tools/create-order.json
{
  "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 typeInput component
TextText input
NumberNumber input
DecimalDecimal input with precision
BooleanToggle switch
DateDate picker
TimestampDate and time picker
EnumDropdown select
FileFile upload
MarkdownRich 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:

tools/process-return.json
{
  "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

OptionTypeDescription
display_namestringLabel shown above the input
display_ordernumberPosition in the form
help_textstringGuidance text below the input
placeholderstringPlaceholder text inside empty inputs
default_valueobjectPre-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:

tools/create-goods-receipt.json
{
  "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