On this page
Guides / Data: collections and items

Data: collections and items

Store structured data in collections, create it, search it by text and keep control of it.

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:

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

Collections

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.

Items

The first argument to almost everything is the numeric collectionId:

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).

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

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:

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:

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.

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.

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:

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:

// 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.