Tool Linking
Tool linking connects your tools together, enabling navigation between related records and passing data from one tool to another. When you click a customer name in an order list, or tap "Edit" on a record detail view, you're using tool links.
Keel generates most links automatically based on your schema relationships. You can also configure custom links to create workflows tailored to your operations.
How tool linking works
When Keel generates tools from your schema, it analyses relationships between models and creates links that let users navigate naturally through your data. These links carry data mappings that pre-populate fields in the target tool.
For example, when viewing an order, clicking the customer name opens the customer detail view with the correct customer already loaded. When you click "Edit", the update form opens with all current values pre-filled. This happens through data mapping, the mechanism that passes values from one tool's response to another tool's inputs.
Types of tool links
Keel supports several types of tool links, each serving a different purpose:
| Link type | Purpose | Appears on |
|---|---|---|
get_entry_action | Navigate from list row to detail view | List tools, mutation tools |
create_entry_action | Create a new record of the same type | List and get tools |
entry_activity_actions | Actions available on a record (edit, delete, custom) | List and get tools |
related_actions | Alternative list views or filters | List tools |
Response field link | Navigate to related records | Any response field |
Data mapping
Data mapping is the mechanism that passes values between tools. Each mapping specifies where to get a value (the source) and where to put it (the target).
Mapping structure
A data mapping has three possible configurations:
{
"data_mapping": [
{
"key": "$.where.id.equals",
"path": { "path": "$.id" }
}
]
}| Field | Description |
|---|---|
key | The target input path in the destination tool |
path | The source response path in the current tool |
value | A literal scalar value (alternative to path) |
Path-based mapping
Map a value from the current tool's response to the target tool's input:
{
"key": "$.where.customer.id.equals",
"path": { "path": "$.customerId" }
}This takes the customerId from the current response and passes it to the target tool's where.customer.id.equals input.
Value-based mapping
Pass a literal value instead of mapping from the response:
{
"key": "$.status",
"value": { "string_value": "Processing" }
}Value types match the default value types:
| Type | Example |
|---|---|
string_value | "Processing" |
int_value | 1 |
float_value | 0.15 |
bool_value | true |
Nested mapping
For complex inputs, you can nest mappings:
{
"data_mapping": [
{
"key": "$.where",
"object": [
{
"key": "$.order.id.equals",
"path": { "path": "$.id" }
},
{
"key": "$.status.equals",
"value": { "string_value": "Pending" }
}
]
}
]
}Get entry action
The get_entry_action defines which tool opens when a user clicks a row in a list, or after a create/update completes. Keel automatically generates this link when:
- The tool is a list, create, or update action
- A
getaction exists for the same model that takes anidinput
{
"id": "list-orders",
"action_name": "listOrders",
"get_entry_action": {
"tool_id": "get-order",
"data_mapping": [
{
"key": "$.id",
"path": { "path": "$.results[*].id" }
}
]
}
}Customising the get entry action
Override the default to use a different get action or add custom data mapping:
{
"get_entry_action": {
"tool_id": "get-order-with-lines",
"data_mapping": [
{
"key": "$.id",
"path": { "path": "$.results[*].id" }
}
]
}
}Removing an auto-generated link
Set deleted to true to remove an auto-generated link:
{
"get_entry_action": {
"tool_id": "get-order",
"deleted": true
}
}Create entry action
The create_entry_action adds a "Create new" button to list and get tools. Keel generates this automatically when a create action exists for the model.
{
"id": "list-orders",
"action_name": "listOrders",
"create_entry_action": {
"tool_id": "create-order",
"title": "New Order",
"as_dialog": true
}
}Configuration options
| Field | Type | Description |
|---|---|---|
tool_id | string | The create action tool ID |
title | string | Button label (defaults to "Create [entity]") |
as_dialog | boolean | Open in a modal dialog |
data_mapping | array | Pre-fill values from current context |
Pre-filling create forms
Pass values from the current context to pre-fill the create form:
{
"create_entry_action": {
"tool_id": "create-order",
"title": "New Order for Customer",
"data_mapping": [
{
"key": "$.customer.id",
"path": { "path": "$.id" }
}
]
}
}This creates a "New Order for Customer" button on the customer detail view that opens the create order form with the customer already selected.
Entry activity actions
Entry activity actions are the toolbar buttons that appear when viewing a record: edit, delete, and any custom actions. Keel generates these automatically for list and get tools.
{
"id": "get-order",
"action_name": "getOrder",
"entry_activity_actions": [
{
"tool_id": "update-order",
"display_order": 1
},
{
"tool_id": "ship-order",
"title": "Ship Order",
"as_dialog": true,
"display_order": 2,
"emphasize": true
},
{
"tool_id": "delete-order",
"visible_condition": "$.status == 'Draft'",
"display_order": 3
}
]
}Auto-generated entry actions
Keel automatically creates entry activity actions for:
- Update actions that take the model's ID as input
- Delete actions that take the model's ID as input
- Custom actions (read, write) that operate on the same model
- Flows associated with the model
Actions are sorted alphabetically, with delete actions appearing last. Write actions and flows open as dialogs by default.
Configuration options
| Field | Type | Description |
|---|---|---|
tool_id | string | Target action tool ID |
title | string | Button label |
description | string | Tooltip or description |
as_dialog | boolean | Open in a modal |
display_order | number | Position in the toolbar |
visible_condition | string | CEL expression for conditional display |
data_mapping | array | Map response values to target inputs |
skip_confirmation | boolean | Skip confirmation for mutations |
emphasize | boolean | Highlight as a primary action |
deleted | boolean | Remove an auto-generated action |
Conditional visibility
Show or hide actions based on record data:
{
"entry_activity_actions": [
{
"tool_id": "approve-order",
"visible_condition": "$.status == 'PendingApproval'"
},
{
"tool_id": "ship-order",
"visible_condition": "$.status == 'Approved' && $.shippingAddress != null"
},
{
"tool_id": "cancel-order",
"visible_condition": "$.status != 'Shipped' && $.status != 'Delivered'"
}
]
}Emphasised actions
Use emphasize to highlight the primary action:
{
"entry_activity_actions": [
{
"tool_id": "process-order",
"title": "Process",
"emphasize": true,
"visible_condition": "$.status == 'Pending'"
}
]
}Skip confirmation
For safe, reversible actions, skip the confirmation dialog:
{
"entry_activity_actions": [
{
"tool_id": "mark-as-read",
"skip_confirmation": true
}
]
}Only use skip_confirmation for actions that are safe to execute immediately. Destructive or irreversible actions should always show a confirmation.
Related actions
Related actions provide tabs or dropdown options to switch between different list views of the same model. Keel generates these automatically when you have multiple list actions for a model.
{
"id": "list-orders",
"action_name": "listOrders",
"related_actions": [
{
"tool_id": "list-pending-orders",
"title": "Pending",
"display_order": 1
},
{
"tool_id": "list-processing-orders",
"title": "Processing",
"display_order": 2
},
{
"tool_id": "list-completed-orders",
"title": "Completed",
"display_order": 3
}
]
}This creates tabs above the order list, letting users quickly switch between filtered views.
Auto-generated related actions
Keel generates related actions for:
- List actions: Links to other list actions on the same model
- Delete actions: Links to list actions for navigation after deletion
Response field links
Individual response fields can link to related records. Keel generates these automatically for relationship fields.
{
"response": {
"$.results[*].productId": {
"link": {
"tool_id": "get-product",
"data_mapping": [
{
"key": "$.id",
"path": { "path": "$.productId" }
}
]
}
},
"$.results[*].orderId": {
"link": {
"tool_id": "get-order",
"data_mapping": [
{
"key": "$.id",
"path": { "path": "$.orderId" }
}
]
}
}
}
}Auto-generated field links
Keel automatically creates field links for:
- Foreign key fields: Link to the related model's get action
- Has-many fields: Link to a list action filtered by the parent ID
For a foreign key like customerId, clicking the field navigates to get-customer with the ID pre-filled.
For a has-many field like orderLines, clicking shows list-order-lines filtered to the current order.
ERP example: Order management
Here's a complete example showing tool linking for an order management system with customers, orders, and products.
Schema
model Customer {
fields {
name Text
email Text
orders Order[]
}
actions {
get getCustomer(id)
list listCustomers(name?)
create createCustomer() with (name, email)
update updateCustomer(id) with (name?, email?)
}
}
model Order {
fields {
reference Text @unique
status OrderStatus @default(OrderStatus.Pending)
customer Customer
lines OrderLine[]
total Decimal
}
actions {
get getOrder(id)
list listOrders(status?, customer.id?)
list listPendingOrders() {
@where(order.status == OrderStatus.Pending)
}
create createOrder() with (reference, customer.id)
update updateOrder(id) with (status?)
delete deleteOrder(id)
}
}
model OrderLine {
fields {
order Order
product Product
quantity Number
unitPrice Decimal
}
actions {
list listOrderLines(order.id)
create createOrderLine() with (order.id, product.id, quantity, unitPrice)
}
}
model Product {
fields {
sku Text @unique
name Text
price Decimal
}
actions {
get getProduct(id)
list listProducts(name?)
}
}
enum OrderStatus {
Pending
Processing
Shipped
Delivered
Cancelled
}Customer tool configuration
{
"id": "get-customer",
"action_name": "getCustomer",
"title": "{{$.name}}",
"entry_activity_actions": [
{
"tool_id": "update-customer",
"display_order": 1
},
{
"tool_id": "create-order",
"title": "New Order",
"as_dialog": true,
"display_order": 2,
"emphasize": true,
"data_mapping": [
{
"key": "$.customer.id",
"path": { "path": "$.id" }
}
]
}
],
"embedded_tools": [
{
"id": "customer-orders",
"title": "Orders",
"display_order": 1,
"visible": true,
"tools": [
{
"action_link": {
"tool_id": "list-orders",
"data_mapping": [
{
"key": "$.where.customer.id.equals",
"path": { "path": "$.id" }
}
]
},
"response_overrides": {
"$.results[*].customerId": false
}
}
]
}
]
}Order tool configuration
{
"id": "get-order",
"action_name": "getOrder",
"title": "Order #{{$.reference}}",
"entry_activity_actions": [
{
"tool_id": "update-order",
"display_order": 1
},
{
"tool_id": "create-order-line",
"title": "Add Line",
"as_dialog": true,
"display_order": 2,
"data_mapping": [
{
"key": "$.order.id",
"path": { "path": "$.id" }
}
]
},
{
"tool_id": "delete-order",
"visible_condition": "$.status == 'Pending'",
"display_order": 3
}
],
"response": {
"$.customerId": {
"display_name": "Customer",
"link": {
"tool_id": "get-customer",
"data_mapping": [
{
"key": "$.id",
"path": { "path": "$.customerId" }
}
]
}
}
},
"embedded_tools": [
{
"id": "order-lines",
"title": "Order Lines",
"display_order": 1,
"visible": true,
"tools": [
{
"action_link": {
"tool_id": "list-order-lines",
"data_mapping": [
{
"key": "$.where.order.id.equals",
"path": { "path": "$.id" }
}
]
},
"response_overrides": {
"$.results[*].orderId": false
}
}
]
}
]
}Order list configuration
{
"id": "list-orders",
"action_name": "listOrders",
"get_entry_action": {
"tool_id": "get-order",
"data_mapping": [
{
"key": "$.id",
"path": { "path": "$.results[*].id" }
}
]
},
"create_entry_action": {
"tool_id": "create-order",
"title": "New Order"
},
"related_actions": [
{
"tool_id": "list-pending-orders",
"title": "Pending",
"display_order": 1
}
],
"response": {
"$.results[*].customerId": {
"display_name": "Customer",
"link": {
"tool_id": "get-customer",
"data_mapping": [
{
"key": "$.id",
"path": { "path": "$.customerId" }
}
]
}
}
}
}This configuration creates a connected workflow where:
- The customer list links to customer details
- Customer details show their orders embedded, with an "New Order" button pre-filled with the customer
- Order lists link to order details, with tabs for pending orders
- Order details show customer links and embedded order lines
- Each order line links to its product
- Toolbar actions are contextual (delete only shows for pending orders)
Link configuration reference
ToolLink fields
| Field | Type | Description |
|---|---|---|
tool_id | string | ID of the target tool (required) |
title | string | Custom button/link text |
description | string | Tooltip or description text |
as_dialog | boolean | Open target tool in a modal dialog |
display_order | number | Position among other links |
visible_condition | string | CEL expression for conditional visibility |
data_mapping | array | Data to pass to the target tool |
skip_confirmation | boolean | Skip confirmation for mutation actions |
emphasize | boolean | Highlight as a primary action |
deleted | boolean | Remove an auto-generated link |
DataMapping fields
| Field | Type | Description |
|---|---|---|
key | string | Target input path in the destination tool |
path | object | Source path from current tool's response |
value | object | Literal scalar value |
object | array | Nested data mappings for complex structures |