> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracecat.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tables

Tables are the built-in structured data store behind `core.table.*`.
Use them when your workflows need durable, queryable records such as asset inventories, user allowlists, enrichment results, or investigation evidence.

## What tables are good for

* Persisting enrichment data across workflow runs
* Looking up records by a known field such as hostname, email, or indicator
* Searching and exporting structured data for analysts
* Attaching case context to reusable datasets

## Common workflow pattern

Most table workflows follow the same lifecycle:

1. Create the table once with a schema that fits your data.
2. Insert or upsert rows as new events arrive.
3. Look up, search, or export rows later from another workflow step.

```yaml theme={null}
- ref: ensure_inventory_table
  action: core.table.create_table
  args:
    name: asset_inventory
    columns:
      - name: hostname
        type: TEXT
      - name: owner
        type: TEXT
      - name: last_seen
        type: TIMESTAMPTZ
    raise_on_duplicate: false
- ref: upsert_asset
  action: core.table.insert_row
  args:
    table: asset_inventory
    upsert: true
    row_data:
      hostname: ${{ TRIGGER.hostname }}
      owner: ${{ TRIGGER.owner }}
      last_seen: ${{ FN.now() }}
- ref: find_asset
  action: core.table.lookup
  args:
    table: asset_inventory
    column: hostname
    value: ${{ TRIGGER.hostname }}
```

## Column schema

`core.table.create_table` takes `columns` as a JSON array of column objects.
This is the same schema you use for tables that you later link to cases.

* `name`: Required string. Use letters, numbers, and underscores, and start with a letter or underscore.
* `type`: Required uppercase string. Use `TEXT`, `INTEGER`, `NUMERIC`, `BOOLEAN`, `DATE`, `TIMESTAMPTZ`, `JSONB`, `SELECT`, or `MULTI_SELECT`.
* `nullable`: Optional boolean. Defaults to `true`.
* `default`: Optional value. It must match the column type.
* `options`: Optional array of strings. Required for `SELECT` and `MULTI_SELECT`, and invalid for other types.

The documented `type` values match the custom tables picker.
Case custom fields use the same storage type family: `TEXT`, `INTEGER`, `NUMERIC`, `BOOLEAN`, `DATE`, `TIMESTAMPTZ`, `JSONB`, `SELECT`, and `MULTI_SELECT`.
In the case field picker, raw `JSONB` is currently surfaced through the `URL` kind, and `Long text` is layered on top of `TEXT`.

## Notes

* Use `lookup` or `is_in` when you already know the column and value you need.
* Use `search_rows` when you need broader text search or paginated results.
* Use `download` when you want to export rows as JSON, NDJSON, CSV, or Markdown.

## FAQ

<AccordionGroup>
  <Accordion title="How do I insert more than 1000 rows into a table?">
    `core.table.insert_rows` is best for batch inserts, but you should split large imports into smaller batches first.
    Create batches upstream, then run one `insert_rows` action per batch.

    ```yaml theme={null}
    - ref: insert_batch
      action: core.table.insert_rows
      for_each: ${{ for var.batch in TRIGGER.row_batches }}
      args:
        table: asset_inventory
        rows_data: ${{ var.batch }}
    ```

    Each `var.batch` should be a list of rows that stays within your chosen batch size.
    This keeps imports predictable and makes retry behavior easier to reason about.
  </Accordion>

  <Accordion title="What column types should I use for tables and case-linked fields?">
    Use the documented uppercase type names: `TEXT`, `INTEGER`, `NUMERIC`, `BOOLEAN`, `DATE`, `TIMESTAMPTZ`, `JSONB`, `SELECT`, and `MULTI_SELECT`.

    * Use `SELECT` and `MULTI_SELECT` only when you also provide `options`.
    * Use `TEXT` or `JSONB` for flexible payloads.
    * Case-linked custom fields follow the same storage type family as tables.

    ```yaml theme={null}
    - ref: create_table
      action: core.table.create_table
      args:
        name: asset_inventory
        columns:
          - name: hostname
            type: TEXT
          - name: first_seen_at
            type: TIMESTAMPTZ
          - name: owner_team
            type: SELECT
            options:
              - secops
              - platform
              - it
    ```
  </Accordion>
</AccordionGroup>

## `core.table.create_table`

Create a new lookup table with optional columns.

### Inputs

<ParamField path="name" type="string" required>
  The name of the table to create.
</ParamField>

<ParamField path="columns" type="array[object] | null">
  List of column definitions. Each item is a JSON object with required `name` and uppercase `type`, plus optional `nullable`, `default`, and `options` fields. Use `options` only with `SELECT` or `MULTI_SELECT`.

  Default: `null`.
</ParamField>

<ParamField path="raise_on_duplicate" type="boolean">
  If true, raise an error if the table already exists.

  Default: `true`.
</ParamField>

### Examples

**Create and inspect a table**

```yaml theme={null}
- ref: create_table
  action: core.table.create_table
  args:
    name: asset_inventory
    columns:
      - name: hostname
        type: TEXT
      - name: owner
        type: TEXT
    raise_on_duplicate: false
- ref: list_tables
  action: core.table.list_tables
- ref: table_metadata
  action: core.table.get_table_metadata
  args:
    name: asset_inventory
```

## `core.table.list_tables`

Get a list of all available tables in the workspace.

### Inputs

This action does not take input fields.

### Examples

**Create and inspect a table**

```yaml theme={null}
- ref: create_table
  action: core.table.create_table
  args:
    name: asset_inventory
    columns:
      - name: hostname
        type: TEXT
      - name: owner
        type: TEXT
    raise_on_duplicate: false
- ref: list_tables
  action: core.table.list_tables
- ref: table_metadata
  action: core.table.get_table_metadata
  args:
    name: asset_inventory
```

## `core.table.get_table_metadata`

Get a table's metadata by name. This includes the columns and whether they are indexed.

### Inputs

<ParamField path="name" type="string" required>
  The name of the table to get.
</ParamField>

### Examples

**Create and inspect a table**

```yaml theme={null}
- ref: create_table
  action: core.table.create_table
  args:
    name: asset_inventory
    columns:
      - name: hostname
        type: TEXT
      - name: owner
        type: TEXT
    raise_on_duplicate: false
- ref: list_tables
  action: core.table.list_tables
- ref: table_metadata
  action: core.table.get_table_metadata
  args:
    name: asset_inventory
```

## `core.table.lookup`

Get a single row from a table corresponding to the given column and value.

### Inputs

<ParamField path="column" type="string" required>
  The column to lookup the value in.
</ParamField>

<ParamField path="table" type="string" required>
  The table to lookup the value in.
</ParamField>

<ParamField path="value" type="any" required>
  The value to lookup.
</ParamField>

### Examples

**Look up rows**

```yaml theme={null}
- ref: lookup_row
  action: core.table.lookup
  args:
    table: asset_inventory
    column: hostname
    value: ${{ TRIGGER.hostname }}
- ref: row_exists
  action: core.table.is_in
  args:
    table: asset_inventory
    column: hostname
    value: ${{ TRIGGER.hostname }}
- ref: lookup_many_rows
  action: core.table.lookup_many
  args:
    table: asset_inventory
    column: owner
    value: secops
    limit: 25
```

## `core.table.is_in`

Check if a value exists in a table column.

### Inputs

<ParamField path="column" type="string" required>
  The column to check in.
</ParamField>

<ParamField path="table" type="string" required>
  The table to check.
</ParamField>

<ParamField path="value" type="any" required>
  The value to check for.
</ParamField>

### Examples

**Look up rows**

```yaml theme={null}
- ref: lookup_row
  action: core.table.lookup
  args:
    table: asset_inventory
    column: hostname
    value: ${{ TRIGGER.hostname }}
- ref: row_exists
  action: core.table.is_in
  args:
    table: asset_inventory
    column: hostname
    value: ${{ TRIGGER.hostname }}
- ref: lookup_many_rows
  action: core.table.lookup_many
  args:
    table: asset_inventory
    column: owner
    value: secops
    limit: 25
```

## `core.table.lookup_many`

Get multiple rows from a table corresponding to the given column and values.

### Inputs

<ParamField path="column" type="string" required>
  The column to lookup the value in.
</ParamField>

<ParamField path="table" type="string" required>
  The table to lookup the value in.
</ParamField>

<ParamField path="value" type="any" required>
  The value to lookup.
</ParamField>

<ParamField path="limit" type="integer">
  The maximum number of rows to return.

  Default: `100`.
</ParamField>

### Examples

**Look up rows**

```yaml theme={null}
- ref: lookup_row
  action: core.table.lookup
  args:
    table: asset_inventory
    column: hostname
    value: ${{ TRIGGER.hostname }}
- ref: row_exists
  action: core.table.is_in
  args:
    table: asset_inventory
    column: hostname
    value: ${{ TRIGGER.hostname }}
- ref: lookup_many_rows
  action: core.table.lookup_many
  args:
    table: asset_inventory
    column: owner
    value: secops
    limit: 25
```

## `core.table.search_rows`

Search for rows in a table with optional filtering.

### Inputs

<ParamField path="table" type="string" required>
  The table to search in.
</ParamField>

<ParamField path="cursor" type="string | null">
  Cursor for pagination.

  Default: `null`.
</ParamField>

<ParamField path="end_time" type="string | null">
  Filter rows created before this time.

  Default: `null`.
</ParamField>

<ParamField path="limit" type="integer">
  The maximum number of rows to return.

  Default: `100`.
</ParamField>

<ParamField path="paginate" type="boolean">
  If true, return cursor pagination metadata along with items.

  Default: `false`.
</ParamField>

<ParamField path="reverse" type="boolean">
  Reverse pagination direction.

  Default: `false`.
</ParamField>

<ParamField path="search_term" type="string | null">
  Text to search for across all text and JSONB columns.

  Default: `null`.
</ParamField>

<ParamField path="start_time" type="string | null">
  Filter rows created after this time.

  Default: `null`.
</ParamField>

<ParamField path="updated_after" type="string | null">
  Filter rows updated after this time.

  Default: `null`.
</ParamField>

<ParamField path="updated_before" type="string | null">
  Filter rows updated before this time.

  Default: `null`.
</ParamField>

### Examples

**Search table rows**

```yaml theme={null}
- ref: search_rows
  action: core.table.search_rows
  args:
    table: asset_inventory
    search_term: database
    limit: 50
    paginate: true
```

## `core.table.insert_row`

Insert a row into a table.

### Inputs

<ParamField path="row_data" type="object" required>
  The data to insert into the row.
</ParamField>

<ParamField path="table" type="string" required>
  The table to insert the row into.
</ParamField>

<ParamField path="upsert" type="boolean">
  If true, update the row if it already exists (based on primary key).

  Default: `false`.
</ParamField>

### Examples

**Insert, update, and delete rows**

```yaml theme={null}
- ref: insert_row
  action: core.table.insert_row
  args:
    table: asset_inventory
    row_data:
      hostname: app-01
      owner: secops
- ref: insert_rows
  action: core.table.insert_rows
  args:
    table: asset_inventory
    rows_data:
      - hostname: app-02
        owner: secops
      - hostname: app-03
        owner: platform
- ref: update_row
  action: core.table.update_row
  args:
    table: asset_inventory
    row_id: ${{ TRIGGER.row_id }}
    row_data:
      owner: incident-response
- ref: delete_row
  action: core.table.delete_row
  args:
    table: asset_inventory
    row_id: ${{ TRIGGER.row_id }}
```

## `core.table.insert_rows`

Insert multiple rows into a table.

### Inputs

<ParamField path="rows_data" type="array[object]" required>
  The list of data to insert into the table.
</ParamField>

<ParamField path="table" type="string" required>
  The table to insert the rows into.
</ParamField>

<ParamField path="upsert" type="boolean">
  If true, update the rows if they already exist (based on primary key).

  Default: `false`.
</ParamField>

### Examples

**Insert, update, and delete rows**

```yaml theme={null}
- ref: insert_row
  action: core.table.insert_row
  args:
    table: asset_inventory
    row_data:
      hostname: app-01
      owner: secops
- ref: insert_rows
  action: core.table.insert_rows
  args:
    table: asset_inventory
    rows_data:
      - hostname: app-02
        owner: secops
      - hostname: app-03
        owner: platform
- ref: update_row
  action: core.table.update_row
  args:
    table: asset_inventory
    row_id: ${{ TRIGGER.row_id }}
    row_data:
      owner: incident-response
- ref: delete_row
  action: core.table.delete_row
  args:
    table: asset_inventory
    row_id: ${{ TRIGGER.row_id }}
```

## `core.table.update_row`

Update a row in a table.

### Inputs

<ParamField path="row_data" type="object" required>
  The new data for the row.
</ParamField>

<ParamField path="row_id" type="string" required>
  The ID of the row to update.
</ParamField>

<ParamField path="table" type="string" required>
  The table to update the row in.
</ParamField>

### Examples

**Insert, update, and delete rows**

```yaml theme={null}
- ref: insert_row
  action: core.table.insert_row
  args:
    table: asset_inventory
    row_data:
      hostname: app-01
      owner: secops
- ref: insert_rows
  action: core.table.insert_rows
  args:
    table: asset_inventory
    rows_data:
      - hostname: app-02
        owner: secops
      - hostname: app-03
        owner: platform
- ref: update_row
  action: core.table.update_row
  args:
    table: asset_inventory
    row_id: ${{ TRIGGER.row_id }}
    row_data:
      owner: incident-response
- ref: delete_row
  action: core.table.delete_row
  args:
    table: asset_inventory
    row_id: ${{ TRIGGER.row_id }}
```

## `core.table.delete_row`

Delete a row from a table.

### Inputs

<ParamField path="row_id" type="string" required>
  The ID of the row to delete.
</ParamField>

<ParamField path="table" type="string" required>
  The table to delete the row from.
</ParamField>

### Examples

**Insert, update, and delete rows**

```yaml theme={null}
- ref: insert_row
  action: core.table.insert_row
  args:
    table: asset_inventory
    row_data:
      hostname: app-01
      owner: secops
- ref: insert_rows
  action: core.table.insert_rows
  args:
    table: asset_inventory
    rows_data:
      - hostname: app-02
        owner: secops
      - hostname: app-03
        owner: platform
- ref: update_row
  action: core.table.update_row
  args:
    table: asset_inventory
    row_id: ${{ TRIGGER.row_id }}
    row_data:
      owner: incident-response
- ref: delete_row
  action: core.table.delete_row
  args:
    table: asset_inventory
    row_id: ${{ TRIGGER.row_id }}
```

## `core.table.download`

Download a table's data by name as list of dicts, JSON string, NDJSON string, CSV or Markdown.

### Inputs

<ParamField path="name" type="string" required>
  The name of the table to download.
</ParamField>

<ParamField path="format" type="string | null">
  The format to download the table data in.

  Default: `null`.
</ParamField>

<ParamField path="limit" type="integer">
  The maximum number of rows to download.

  Default: `1000`.
</ParamField>

### Examples

**Export table data**

```yaml theme={null}
- ref: export_table
  action: core.table.download
  args:
    name: asset_inventory
    format: csv
    limit: 1000
```
