Tool Linking

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 typePurposeAppears on
get_entry_actionNavigate from list row to detail viewList tools, mutation tools
create_entry_actionCreate a new record of the same typeList and get tools
entry_activity_actionsActions available on a record (edit, delete, custom)List and get tools
related_actionsAlternative list views or filtersList tools
Response field linkNavigate to related recordsAny 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" }
    }
  ]
}
FieldDescription
keyThe target input path in the destination tool
pathThe source response path in the current tool
valueA 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:

TypeExample
string_value"Processing"
int_value1
float_value0.15
bool_valuetrue

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:

  1. The tool is a list, create, or update action
  2. A get action exists for the same model that takes an id input
tools/list-orders.json
{
  "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:

tools/list-orders.json
{
  "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.

tools/list-orders.json
{
  "id": "list-orders",
  "action_name": "listOrders",
  "create_entry_action": {
    "tool_id": "create-order",
    "title": "New Order",
    "as_dialog": true
  }
}

Configuration options

FieldTypeDescription
tool_idstringThe create action tool ID
titlestringButton label (defaults to "Create [entity]")
as_dialogbooleanOpen in a modal dialog
data_mappingarrayPre-fill values from current context

Pre-filling create forms

Pass values from the current context to pre-fill the create form:

tools/get-customer.json
{
  "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.

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

FieldTypeDescription
tool_idstringTarget action tool ID
titlestringButton label
descriptionstringTooltip or description
as_dialogbooleanOpen in a modal
display_ordernumberPosition in the toolbar
visible_conditionstringCEL expression for conditional display
data_mappingarrayMap response values to target inputs
skip_confirmationbooleanSkip confirmation for mutations
emphasizebooleanHighlight as a primary action
deletedbooleanRemove 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.

tools/list-orders.json
{
  "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.

tools/list-order-lines.json
{
  "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

tools/get-customer.json
{
  "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

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

tools/list-orders.json
{
  "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:

  1. The customer list links to customer details
  2. Customer details show their orders embedded, with an "New Order" button pre-filled with the customer
  3. Order lists link to order details, with tabs for pending orders
  4. Order details show customer links and embedded order lines
  5. Each order line links to its product
  6. Toolbar actions are contextual (delete only shows for pending orders)

Link configuration reference

ToolLink fields

FieldTypeDescription
tool_idstringID of the target tool (required)
titlestringCustom button/link text
descriptionstringTooltip or description text
as_dialogbooleanOpen target tool in a modal dialog
display_ordernumberPosition among other links
visible_conditionstringCEL expression for conditional visibility
data_mappingarrayData to pass to the target tool
skip_confirmationbooleanSkip confirmation for mutation actions
emphasizebooleanHighlight as a primary action
deletedbooleanRemove an auto-generated link

DataMapping fields

FieldTypeDescription
keystringTarget input path in the destination tool
pathobjectSource path from current tool's response
valueobjectLiteral scalar value
objectarrayNested data mappings for complex structures