Table Layout

The table layout is the default display for list actions. It presents data in a familiar tabular format with sortable columns, pagination, and row-click navigation to detail views.

Default behaviour

When you create a list action in your schema, Keel automatically generates a table tool. Without any configuration, the table:

  • Displays all response fields as columns in schema order
  • Enables sorting on fields marked with @sortable
  • Provides cursor-based pagination with a default page size of 50
  • Links row clicks to the model's get action (if one exists)
model Order {
  fields {
    reference Text @unique
    status OrderStatus
    customer Customer
    totalAmount Decimal
    createdAt Timestamp
  }
 
  actions {
    get getOrder(id)
    list listOrders(status?, customer.id?) {
      @sortable(reference, createdAt, totalAmount)
    }
  }
}

This schema produces a table tool with sortable columns for reference, createdAt, and totalAmount. Clicking any row navigates to the getOrder tool with the order's ID.

Column configuration

Columns are configured through response field settings in your tool's JSON file. Each field under $.results becomes a column.

tools/list-orders.json
{
  "action_name": "listOrders",
  "response": {
    "$.results.reference": {
      "display_name": "Order Ref",
      "display_order": 1
    },
    "$.results.status": {
      "display_name": "Status",
      "display_order": 2
    },
    "$.results.customer.name": {
      "display_name": "Customer",
      "display_order": 3
    },
    "$.results.totalAmount": {
      "display_name": "Total",
      "display_order": 4
    },
    "$.results.createdAt": {
      "display_name": "Created",
      "display_order": 5
    },
    "$.results.id": {
      "visible": false
    }
  }
}

Column options

OptionTypeDescription
display_namestringColumn header label
display_ordernumberPosition in the table (lower numbers appear first)
visiblebooleanWhether the column is shown (default: true)
visible_conditionstringCEL expression for conditional visibility
help_textstringTooltip shown on column header hover
image_previewbooleanDisplay file fields as inline thumbnails
linkobjectMake the cell a clickable link to another tool

Sorting

Table sorting is controlled at the schema level using the @sortable attribute on your list action:

actions {
  list listOrders() {
    @sortable(reference, createdAt, totalAmount)
  }
}

Only fields declared in @sortable will have clickable column headers. Users can click a column header to sort ascending, click again for descending, and click a third time to clear the sort.

Default sort order

Without user interaction, list actions sort by createdAt descending (newest first). To change the default, use @orderBy:

actions {
  list listOrders() {
    @orderBy(reference: asc)
    @sortable(reference, createdAt, totalAmount)
  }
}

When a user clicks a sortable column, their choice overrides the @orderBy default until they navigate away.

Filtering

Configure quick search to let users filter results by typing:

tools/list-orders.json
{
  "action_name": "listOrders",
  "filter_config": {
    "quick_search_field": "$.where.reference.contains"
  }
}

The quick search field maps to a contains filter on your list action's inputs. As users type, the table filters to show matching records.

For more advanced filtering, define input fields on your list action:

actions {
  list listOrders(status?, customer.id?, reference?) {
    @sortable(reference, createdAt)
  }
}

Each input appears as a filter control above the table.

Pagination

Tables use cursor-based pagination by default. Configure the page size in your tool's JSON file:

tools/list-orders.json
{
  "action_name": "listOrders",
  "pagination": {
    "page_size": {
      "default_value": 25
    }
  }
}

Pagination options

OptionTypeDescription
page_size.default_valuenumberNumber of rows per page (default: 50)

Users can navigate between pages using the pagination controls at the bottom of the table. The controls show the current page position and total record count when available.

Row click navigation

When a user clicks a table row, the table navigates to the model's get action. Keel automatically configures this when:

  1. The list tool exists for a model
  2. A get action exists for the same model that accepts an id input

The row's id is passed to the get action, which loads and displays the full record.

Customising row navigation

Override the default navigation with get_entry_action:

tools/list-orders.json
{
  "action_name": "listOrders",
  "get_entry_action": {
    "tool_id": "get-order",
    "data_mapping": [
      {
        "key": "id",
        "path": { "path": "$.id" }
      }
    ]
  }
}

To disable row click navigation entirely, set get_entry_action to null or mark it as deleted.

Cell links

Make individual cells clickable by adding a link to the response field:

tools/list-orders.json
{
  "action_name": "listOrders",
  "response": {
    "$.results.reference": {
      "display_name": "Order Ref",
      "link": {
        "tool_id": "get-order",
        "data_mapping": [
          {
            "key": "id",
            "path": { "path": "$.id" }
          }
        ]
      }
    },
    "$.results.customer.name": {
      "display_name": "Customer",
      "link": {
        "tool_id": "get-customer",
        "data_mapping": [
          {
            "key": "id",
            "path": { "path": "$.customer.id" }
          }
        ]
      }
    }
  }
}

Cell links are useful for relationship fields, allowing users to navigate directly to related records.

Toolbar actions

Add action buttons to the table toolbar with create_entry_action and entry_activity_actions:

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

The create action button appears in the table toolbar, giving users quick access to add new records.

Complete example

Here's a full configuration for an order management table:

schema.keel
enum OrderStatus {
  Pending
  Processing
  Shipped
  Delivered
  Cancelled
}
 
model Customer {
  fields {
    name Text
    email Text @unique
  }
 
  actions {
    get getCustomer(id)
    list listCustomers()
  }
}
 
model Order {
  fields {
    reference Text @unique
    status OrderStatus @default(OrderStatus.Pending)
    customer Customer
    totalAmount Decimal
    createdAt Timestamp @default(now())
  }
 
  actions {
    get getOrder(id)
    list listOrders(status?, customer.id?) {
      @sortable(reference, createdAt, totalAmount)
      @orderBy(createdAt: desc)
    }
    create createOrder() with (reference, status, customer.id, totalAmount)
    update updateOrder(id) with (status?)
  }
}
tools/list-orders.json
{
  "id": "list-orders",
  "action_name": "listOrders",
  "name": "Orders",
  "help_text": "View and manage customer orders",
 
  "filter_config": {
    "quick_search_field": "$.where.reference.contains"
  },
 
  "pagination": {
    "page_size": {
      "default_value": 25
    }
  },
 
  "response": {
    "$.results.reference": {
      "display_name": "Order Ref",
      "display_order": 1,
      "link": {
        "tool_id": "get-order",
        "data_mapping": [
          { "key": "id", "path": { "path": "$.id" } }
        ]
      }
    },
    "$.results.status": {
      "display_name": "Status",
      "display_order": 2
    },
    "$.results.customer.name": {
      "display_name": "Customer",
      "display_order": 3,
      "link": {
        "tool_id": "get-customer",
        "data_mapping": [
          { "key": "id", "path": { "path": "$.customer.id" } }
        ]
      }
    },
    "$.results.totalAmount": {
      "display_name": "Total",
      "display_order": 4
    },
    "$.results.createdAt": {
      "display_name": "Created",
      "display_order": 5
    },
    "$.results.id": {
      "visible": false
    },
    "$.results.customer.id": {
      "visible": false
    }
  },
 
  "create_entry_action": {
    "tool_id": "create-order",
    "title": "New Order",
    "as_dialog": true
  },
 
  "get_entry_action": {
    "tool_id": "get-order"
  }
}

This configuration creates a table that:

  • Shows orders with sortable columns for reference, total, and date
  • Filters by order status and customer
  • Provides quick search on order reference
  • Displays 25 records per page
  • Links the reference column and customer name to their respective detail views
  • Includes a "New Order" button that opens the create form in a dialog
  • Navigates to order details when clicking any row