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

# Public search

> Search published records straight against the search engine with a public token, from a browser or a backend, or add InstantSearch.

Public search finds published records straight in the search engine, with a **search token** instead of an API key: results come back as fast as you type, and the searches don't count toward your API requests. Use it from your site's browser code or from your own backend. Search runs on [Meilisearch](https://www.meilisearch.com), so its clients and [InstantSearch](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/) work as they are.

Your servers can also search with an API key, through [the API](https://docs.nohead.io/guides/api-search.md): that returns full records, with `expand`.

You need three things: the search URL, a token, and the index name of each collection you search.

## Include collections in public search

Tokens find only collections included in public search. In the web app, open a collection's **Search** tab and turn on **Include in public search**, or turn it on when you create the collection. It is off by default, and it applies to every token of the project, so a collection you include later is searchable with the tokens you already have. Through the API, it is the collection's `public_search`.

A token finds only the **published** records of included collections. The search engine enforces that: other collections, other projects and unpublished records stay out of reach, whatever a request asks for. Including or removing a collection reaches every token within seconds.

> [!WARNING]
>
> A token is public: in a site, it goes in your page source. Every field of the published records it finds can be read with it, so keep private content out of public search.

## Index names

Each collection has its own index. Its name shows on the collection's **Search** tab, under **Public search index name**, while the collection is included in public search. Through the API, it is the `index` of the collection's search index:

```bash theme={null}
# An API key with schema:read
curl "https://api.nohead.io/v1/collections/blog-posts/search-index" \
  -H "Authorization: Bearer $NOHEAD_API_KEY"
```

Index names are opaque and never change, so you can keep them in your code or configuration.

## Create a token

In the web app, open your project's **Search tokens** page, choose **New search token** and name it after the site or app that will use it. Copy the token from the list. Make one per site or app, so deleting one stops only that site's searches. A project can have 25 tokens.

## Search

Send searches to `https://search.nohead.io`, the search URL for every token (a token's `search_url` in the API). It lets searches through and nothing else, and limits how many requests one address can make:

```javascript theme={null}
const response = await fetch("https://search.nohead.io/multi-search", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    queries: [{ indexUid: index, q: "lentil", limit: 10 }],
  }),
})
const { results } = await response.json()
for (const hit of results[0].hits) console.log(hit.data.title)
```

Hits look like records:

* `id` (`rec_…`), and `published_at`, `created_at` and `updated_at` as Unix seconds; `published_at` can be filtered and sorted on, such as `filter: "published_at > 1767225600"` for posts published since 2026
* field values under `data`, by field key: dates as Unix seconds, rich text as plain text, JSON as a string, assets and relations as IDs

Several collections at once take one query each, or a federated search across them (`federation: {}` in the request).

## Filter and sort

Fields take part in searches by their [search settings](https://docs.nohead.io/guides/content-modelling.md#search-options), as in every search: words match `searchable` fields, and `filterable` and `sortable` fields can be used in filters and sorts. Name them under `data`:

```javascript theme={null}
{ indexUid: index, q: "soup", filter: 'data.cuisine = "french"', sort: ["published_at:desc"] }
```

Dates are Unix seconds here too: `'data.starts_on >= 1790812800'` finds records from 1 October 2026 (00:00 UTC) on.

## Facets

Facets count the hits per value of a `filterable` field, for filter lists such as "French (12)". Ask for them with `facets`:

```javascript theme={null}
{ indexUid: index, q: "soup", facets: ["data.cuisine", "data.servings"] }
```

Each result then has a `facetDistribution`, counted over the hits that match the query and filters, and a `facetStats` with the lowest and highest value of number and date fields, for range sliders:

```json theme={null}
{
  "facetDistribution": { "data.cuisine": { "french": 12, "italian": 8 }, "data.servings": { "2": 9, "4": 11 } },
  "facetStats": { "data.servings": { "min": 2, "max": 4 } }
}
```

* **Fields that facet well** have a few values shared by many records: enums, booleans, and short text such as a brand or a city. Fields with several values count each value once per record, so tags work as facets.
* **Relations and assets count by ID** (`rec_…`, `ast_…`). To show names, look them up, or keep an enum or text field next to the relation.
* **Up to 100 values** are returned per facet, and only for published records, like hits.
* **In a federated search**, name the facets per index in the federation instead, `federation: { facetsByIndex: { [index]: ["data.cuisine"] } }`, and read them from the response's `facetsByIndex`. A query's own `facets` is refused there.

To find values within a facet as someone types (InstantSearch's `refinementList` with `searchable: true`), search them:

```javascript theme={null}
const response = await fetch(`https://search.nohead.io/indexes/${index}/facet-search`, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
  body: JSON.stringify({ facetName: "data.cuisine", facetQuery: "ita" }),
})
const { facetHits } = await response.json() // [{ value: "italian", count: 8 }]
```

## InstantSearch

```javascript theme={null}
import instantsearch from "instantsearch.js"
import { searchBox, hits } from "instantsearch.js/es/widgets"
import { instantMeiliSearch } from "@meilisearch/instant-meilisearch"

const { searchClient } = instantMeiliSearch("https://search.nohead.io", token)

instantsearch({ indexName: index, searchClient })
  .addWidgets([
    searchBox({ container: "#search" }),
    hits({ container: "#hits", templates: { item: (hit, { html }) => html`${hit.data.title}` } }),
  ])
  .start()
```

## Rename or delete a token

Rename a token or delete it from its menu on the **Search tokens** page. A renamed token stays the same. A deleted one fails at once, and your other tokens keep working. To stop searches of one collection, remove it from public search instead. While a project or organization is scheduled for deletion its tokens stop working, and they work again if it is restored.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.