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

# Discovery Metadata

> Document your models for humans and AI discovery

Your model now defines what your data means. This page is about the next stage: **optimizing it for AI retrieval**. Metadata tags document your model for humans and — through the [Credible Analytics Engine](/how-to/analyzing/overview) — make it discoverable to AI agents: when you publish, your tags are indexed and become searchable by people browsing models and by agents answering natural-language questions.

Two tags do the work: `#(doc)` describes what a field means, and `#(index)` makes a dimension's values searchable. A third lever, [discovery curation](#curating-discovery), narrows which models and sources agents see in the first place. This page covers what to tag, index, and curate; for how indexes are built, kept fresh, and paid for, see [Performance & Cost](/how-to/modeling/persistence).

You don't have to write tags by hand: the agent documents as it builds — every field it defines gets its `#(doc)` and `#(index)` tags as part of the [modeling workflow](/how-to/modeling/ai-modeling#build-a-model-with-the-agent) — and you can ask it to document an existing model the same way. Review its work with this page in mind; good documentation directly improves the quality of chat-based analysis downstream.

## Documentation Tags

Use `#(doc)` tags to add human-readable descriptions to fields. Place the tag on the line before the field definition.

```malloy theme={"languages":{"custom":["/languages/motly.tmGrammar.json","/languages/malloy.tmGrammar.json"]}}
source: orders is conn.table('sales.orders') extend {
  dimension:
    #(doc) The unique identifier for each order
    order_id is id

    #(doc) The date when the order was placed
    order_date is created_at::date

    #(doc) Customer segment based on lifetime value: High (>$10k), Medium (>$1k), Low
    customer_segment is case
      when lifetime_value > 10000 then 'High'
      when lifetime_value > 1000 then 'Medium'
      else 'Low'
    end

  measure:
    #(doc) Total revenue from all orders
    total_revenue is sum(amount)

    #(doc) Number of orders that have shipped
    shipped_orders is count() { where: status = 'shipped' }
}
```

The best `#(doc)` descriptions carry meaning the code can't: units, business rules, thresholds, and caveats — like the segment cutoffs above — not restatements of the field name.

## Value Indexing

Use `#(index)` to index all distinct values from a column. This enables AI agents to find fields by searching for data values, not just field names or descriptions.

```malloy theme={"languages":{"custom":["/languages/motly.tmGrammar.json","/languages/malloy.tmGrammar.json"]}}
source: products is conn.table('catalog.products') extend {
  dimension:
    #(index)
    #(doc) The product name
    product_name is name

    #(index)
    #(doc) The product category (e.g., Electronics, Clothing, Home & Garden)
    category is product_category

    #(index)
    #(doc) The brand name
    brand is brand_name
}
```

### Why Value Indexing Matters

Consider a product catalog where the column is named `product_category` but contains values like "Running Shoes" and "Athletic Apparel". When a user asks *"What are our top-selling sports gear products?"*, field names and documentation alone won't surface those products — nothing is named "sports gear".

With value indexing, the [Credible Analytics Engine](/how-to/analyzing/overview#how-it-works) searches the actual values: it matches the question to fields whose values fit, then helps the agent construct a query with the correct filter — finding "Running Shoes" and "Athletic Apparel" without exact string matching.

### When to Use Value Indexing

Use `#(index)` on:

* **Names and titles** - Product names, customer names, program titles
* **Categorical values** - Status codes, types, categories
* **Lookup values** - Region names, department names, brand names

Skip value indexing for:

* Numeric fields (amounts, counts, IDs)
* Timestamps and dates

## Example: Well-Documented Model

```malloy theme={"languages":{"custom":["/languages/motly.tmGrammar.json","/languages/malloy.tmGrammar.json"]}}
source: orders is conn.table('sales.orders') extend {
  primary_key: order_id

  join_one: customers is conn.table('sales.customers') on customer_id
  join_one: products is conn.table('catalog.products') on product_id

  dimension:
    #(doc) Date the order was placed
    order_date is created_at::date

    #(doc) Order status: pending, processing, shipped, delivered, cancelled
    #(index)
    status is order_status

    #(doc) Product category from the catalog
    #(index)
    category is products.category

    #(doc) Customer's geographic region
    #(index)
    region is customers.region

  measure:
    #(doc) Total number of orders
    order_count is count()

    #(doc) Total revenue in USD
    total_revenue is sum(amount)

    #(doc) Average revenue per order in USD
    avg_order_value is total_revenue / order_count

    #(doc) Percentage of orders that were cancelled
    cancellation_rate is count() { where: status = 'cancelled' } / order_count * 100

  view:
    #(doc) Monthly revenue trend with order counts
    monthly_revenue is {
      group_by: order_date.month
      aggregate: total_revenue, order_count
    }
}
```

## Curating Discovery

Tags improve how well agents understand what they find; discovery curation narrows **what they find in the first place**. By default, every model is listed and every source is directly queryable. Curation happens at two levels: `export` in the model itself controls which sources a file exposes, and manifest fields control which files agents land on.

### `export { … }` — in the model

[`export { … }`](https://docs.malloydata.dev/documentation/language/imports) filters which *sources within a file* are exposed. A file with no `export` exposes all its top-level sources; add one to keep imported or scaffolding sources out of discovery.

```malloy theme={"languages":{"custom":["/languages/motly.tmGrammar.json","/languages/malloy.tmGrammar.json"]}}
// Intermediate building block — hidden from discovery
source: raw_order_lines is conn.table('sales.order_lines') extend {
  measure: line_count is count()
}

// The curated interface built on it — this is what agents should find
source: orders is raw_order_lines extend {
  measure: order_revenue is sum(amount)
}

export { orders }
```

With the `export`, agents discover `orders` but not `raw_order_lines`; without it, both would be listed.

### `explores` and `queryableSources` — in the manifest

Add optional fields to the package manifest (`publisher.json`, described in [Publishing](/how-to/modeling/publishing#the-package-manifest)) to point agents at the model *files* that matter:

```json theme={"languages":{"custom":["/languages/motly.tmGrammar.json","/languages/malloy.tmGrammar.json"]}}
{
    "name": "ecommerce",
    "version": "0.0.1",
    "description": "ecommerce demo data",
    "explores": ["orders.malloy", "customer_health.malloy"],
    "queryableSources": "declared"
}
```

**`explores`** — `.malloy` file paths (relative to the package root) whose models agents should discover and land on.

* **Omit** — discovery unchanged; every model and source stays listed.
* **Set** — listings narrow to these files, plus each file's `export { … }` closure.

**`queryableSources`** — whether non-curated sources can still be queried by name. Only applies when `explores` is set.

| Value                  | Listing          | Query by name                  |
| ---------------------- | ---------------- | ------------------------------ |
| `"declared"` (default) | Curated set only | Non-curated sources return 404 |
| `"all"`                | Curated set only | All sources still queryable    |

<Note>
  This curates **discovery**, not access — *what* is listed and queryable by name, not *who* may query. To restrict access by user or group, use [`#(authorize)` and secure givens](/how-to/modeling/fine-grained-acls), covered on the next page.

  **Hiding an `#(authorize)`-gated source.** An access gate blocks *querying*, not *discovery*, so a gated source still appears in listings to callers who can't query it. To hide it while keeping it queryable for authorized callers, leave it out of the curated `explores`/`export` set **and** set `queryableSources: "all"`. Under the default `"declared"`, the non-curated source returns 404 by name — which blocks authorized callers too.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Access Control" icon="filter" color="#5C7A93" href="/how-to/modeling/fine-grained-acls">
    Secure your model — row, column, and source-level access rules
  </Card>

  <Card title="Analytics Engine Overview" icon="brain-circuit" color="#94793A" href="/how-to/analyzing/overview">
    Learn how metadata powers AI discovery
  </Card>
</CardGroup>
