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

# Defining a table

> Declare your columns and pick a row key — the two decisions everything else follows from.

Go to **Workflows → Custom Data → New table**. You give it a name and declare its columns.

The declaration is not decoration. It is what the platform reads to build your agent's tools, to sort a price as a number rather than as text, and to tell you when an import doesn't match what you said it would contain.

## Naming the table

Lowercase letters, digits and underscores, starting with a letter: `plans`, `store_locations`, `discount_codes`.

The name appears in the generated tool names (`search_plans`), so pick something an agent's prompt can refer to naturally.

## Columns

Each column has a **name**, a **type**, and optionally a description, allowed values, and a reference to another table.

### Types

| Type             | Use for                                | Sorts as   |
| ---------------- | -------------------------------------- | ---------- |
| **Text**         | names, codes, addresses, anything else | text       |
| **Number**       | prices, distances, rates               | number     |
| **Whole number** | counts, quantities, integer IDs        | number     |
| **Yes / no**     | flags like `available`, `used`         | true/false |

Text columns can also carry a **format** of *Date* or *Date & time*, which makes them sort and compare chronologically.

<Warning>
  Type matters more than it looks. A price stored as Text sorts lexicographically — `"9"` comes after `"10"` — so "cheapest first" returns the wrong row. Declare numbers as numbers.
</Warning>

### Descriptions

The description is the single highest-value field you can fill in. It is what the model reads when deciding whether and how to use the column.

Compare:

```
region      (no description)
region      Two-letter region code. EU for Europe, US for North America,
            APAC for Asia-Pacific. Not a country — France is EU.
```

The second one prevents an agent asking for `region = France` and getting nothing.

### Allowed values

If a column only ever holds a handful of values, list them. They become an enumeration on the agent's tool, so the model can't invent a region that doesn't exist. Comma-separated: `EU, US, APAC`.

For a Yes/no column the allowed values must be `true` or `false` — `yes, no` isn't accepted, because it would be stored as something you didn't mean.

### References

If a column holds the key of a row in another Custom Data table, name that table in **References**. It's descriptive only: nothing is enforced, and nothing cascades. It tells your agent's tools that the field points somewhere, which is useful when two tables are refreshed on different schedules and legitimately disagree for a while.

## The row key

Exactly one column is the **row key** — the column whose value identifies a row.

This is the most consequential choice on the page. It decides:

* **What "the same row" means.** When you reload data, a row whose key already exists is an update. A row whose key is new is an insert.
* **What the lookup tool asks for.** `lookup_plans` takes a `plan_id`, not an opaque `row_key`, because the model sees something meaningful.

### Choosing one

A good row key is **stable, unique, and already exists in your source**: a SKU, a plan code, a store number, an email address.

<Warning>
  **Never use a row number or an import order.** If your source re-sorts, every row's identity changes and the next refresh replaces your whole table with itself.
</Warning>

Watch for keys that look unique and aren't. A store list keyed on `name` breaks the moment you have two branches called "Downtown" — and it breaks quietly, by silently merging them into one row. If your data has no natural unique column, combine two in your source before importing (`store_name + city`).

The key must be Text or Whole number. Decimals and Yes/no can't be keys — they have no stable text form.

### It can't be changed later

Once a table has rows, the row key is fixed. Moving it to another column would orphan every stored row: lookups by the new key would find nothing while the rows still answer to the old one, and the next import would write a second copy of every record.

If you need to re-key a populated table, use **Replace all rows** with the new schema — that rewrites identity for everything at once, deliberately.

## Editing the schema later

You can add columns, remove columns, change types and edit descriptions at any time.

* **Adding a column** — existing rows simply don't have it. Backfill by re-importing, or they'll be invisible to any filter on that column.
* **Removing a column** — the data stays on the row; it just stops being visible to agents and to filters. Re-declare it to bring it back.
* **Changing a type** — takes effect on the next read. A value that can't be read as its new type comes back empty rather than breaking the lookup.

<Note>
  A schema edit does **not** update agents that already have generated tools. That's deliberate — nothing silently changes a live agent's abilities. Regenerate the tools from the **Agent tools** tab and paste them in when you're ready.
</Note>

## Undeclared columns

If you import a CSV with columns you didn't declare, the extra columns are **stored but not visible**: agents can't filter or sort on them, and they don't appear in generated tools. The import tells you which ones they were.

That's usually what you want for a mirror of a wide source table — you keep everything, and declare only the parts an agent needs. Declare one later and it becomes usable without re-importing.
