# Data: collections and items

A **collection** is a table of structured workspace data; its records are **items**. It is the store your Met can read as a data source and that you operate over the API to save, search and update information. Work server-side with your key:

```ts
const met = new Met(key, { workspaceId });
```

## Collections

```ts
const col = await met.collections.create({ name: 'Preferences' });

await met.collections.list();          // Collection[]
await met.collections.retrieve(col.id);
await met.collections.update(col.id, { name: 'Customer preferences' });
await met.collections.delete(col.id);
```

`create` accepts `name` and an optional `folder_id?` (scopes `collections:read`/`collections:write`). A collection's `id` is **numeric**.

> **Hide a collection from the Met without deleting it:** set it to `expose_to_agent: false` (default `true`). The Met stops seeing it as a data source, but you keep operating it over the API. Re-expose it with `update(col.id, { expose_to_agent: true })`. More context in [Mets and their tools](mets-and-tools.html).

## Items

The first argument to almost everything is the **numeric** `collectionId`:

```ts
const item = await met.items.create(col.id, { name: 'Ana', channel: 'preferred' });

await met.items.list(col.id);                     // one page { data, has_more }
for await (const it of met.items.iterate(col.id)) { /* every page */ }

await met.items.retrieve(item.id);
await met.items.update(col.id, item.id, { channel: 'whatsapp' });
await met.items.patchField(col.id, item.id, 'channel', 'email'); // a single field
await met.items.setStatus(item.id, 'archived');
await met.items.delete(item.id);
```

The item's data lives in `item.data`. If a field is a formula (`"=…"`), its resolved result shows up in `item.computed[field]` — `data` keeps the raw formula; always write to `data`. For an item with no collection (at workspace level) use `met.items.createOrphan(body)`.

## Search

`met.items.search(query)` returns an `Item[]` with the matches across the whole workspace:

```ts
const found = await met.items.search('annual discount');
```

It is **lexical text search**: it matches on the words that appear in the item's fields. **It is not semantic or vector search** — there are no embeddings and no ranking by meaning, so search for the literal terms you expect to find. To narrow by another field, **filter the result in your code**:

```ts
const onlyAnas = (await met.items.search('preference'))
  .filter((it) => it.data?.contact_id === 42);
```

## Example: memory per customer

A preferences collection, one item per contact, and retrieval by search + filter:

```ts
const prefs = await met.collections.create({ name: 'preferences' });

await met.items.create(prefs.id, {
  contact_id: 42,
  note: 'Prefers being written to in the morning, warm tone.',
});

// Later: retrieve what we know about that contact
const memory = (await met.items.search('prefers'))
  .filter((it) => it.data?.contact_id === 42);
```

Because `search` is lexical, store in the item's text the words you will later want to find it by.

## Folders

Once a workspace goes past ten or so collections, the panel groups them into folders. It organizes the view, not the data: moving a collection to another folder changes none of its items and breaks no references.

```ts
const folders = await met.folders.list();
const tree = await met.folders.hierarchy();
```

`list()` returns the folders flat; `hierarchy()` returns them nested, which is what you want in order to paint a tree without rebuilding the parent-child relationship by hand.

```ts
const f = await met.folders.create({ name: 'Operations' });
await met.folders.move(f.id, null);        // null = root
await met.folders.reorder([f.id, other.id]);
```

`move` accepts `null` as the parent to lift a folder to the root. Order is explicit and persistent: `reorder` takes the ids in the order you want, and `reorderCollectionsInFolder` does the same with the collections inside a folder. There is no automatic alphabetical order — if you don't reorder, they stay as they were created.

## Slugs: readable URLs instead of ids

An item and a collection have a numeric id, and can also have a **slug**. It exists so your integration doesn't have to store ids of ours:

```ts
await met.slugs.updateCollectionSlug(collection.id, 'invoices');
await met.slugs.updateItemSlug(item.id, 'inv-001');

const col = await met.slugs.resolveCollectionBySlug('invoices');
const inv = await met.slugs.resolveItemBySlug(col.id, 'inv-001');
```

An item's slug is unique **within its collection**, not across the workspace: that is why `resolveItemBySlug` asks for both. A collection's slug is unique across the workspace.

There are two more ways to get to something, and the difference matters:

```ts
// By readable path: folder/collection
const r = await met.slugs.resolvePath('operations/invoices');

// By the item's short code (the one the panel shows)
const x = await met.slugs.resolveByCode('AB/12');
```

`resolvePath` is for building URLs a human reads and edits. `resolveByCode` is for the reverse: someone reads you the code they see on screen and you have to find it. The code is generated by Meteor and **does not change**; the slug is set by you and can change, so don't use it as a stable identifier in your database — that is what the id, or the code, is for.
