Search palette
Cmd+K search across records and pages.
Press Cmd+K (Ctrl+K on Windows and Linux) on any dashboard page, or click the search box in the header, to open the palette. Type to find records across every collection you can read, or to jump to a page from the sidebar. Arrow keys move the selection and Enter opens it. Escape steps back to a blank search, then closes the palette; opening it again picks up where you left it.
A record shows under its collection by its label, so give each collection one:
import { defineCollection, field } from 'ohnejs';
export default defineCollection({
api: { read: true },
dashboard: { recordLabel: 'name' },
fields: {
name: field('text'),
tooltip: field('text', { nullable: true }),
},
});Typing ashbringer now lists the item named Ashbringer, and any item whose tooltip mentions it. Picking a record opens its editor, or the page its collection's recordPath names.
What is searched
Each word you type must match one of a record's fields, ignoring case and accents, so cafe finds Café. Each field type decides how a word matches it:
- Text fields match anywhere inside, so
bringfinds Ashbringer. - A select matches by its choice labels as you see them, so
progressfinds the status "In progress". - A date matches the start of its
YYYY-MM-DDform:2026,2026-03or2026-03-14. - Number fields are skipped unless they ask to be searched.
Each field can switch search on or off, and so can a whole collection. See Search for both switches.
Paste a record's UUID to find that record. A UUID inside an object, repeater, or block item finds the record that holds it. A UUID finds its record even when the switches keep that collection or field out of word search. It also lists the records that use it, see Where a record is used.
- A collection takes part in word search when you may read it and it has a label: its
recordLabel, or else its first text field. A singleton, or a collection without a label, takes part only for a pastedUUID. - The search reads as the collection's own list read, so its access rules and read middleware apply. A field outside your read scope is never searched.
- Fields inside a record's own objects, repeaters, and blocks are searched too. A linked record counts only by its label, see Related records.
- A translatable collection is searched in your content language, the one you set on your account.
- Each word is looked for in the label fields first, then in the other fields in the order you declare them, as far as the query limits allow. With the default limits, a two-word search checks about the first 50 text fields. Declare the fields people search for first, and set
search: falseon the ones nobody does. - A collection that refuses the read is skipped, never an error.
- Records whose label fields hold every word come first.
- Records are searched once your query holds a word. Until then it only filters the pages.
- Each collection shows its five newest matches. "Load more" under it shows the next five.
- Quotes keep a phrase together:
"new york"is one word. - Punctuation around a word, a lone ASCII letter, and a repeated word are ignored, and only the first ten words count. A sign before a number stays, so
-300finds -300. - A query starting with
/is a command for a layer's own rows, like the assistant's skills, and searches nothing.
Related records
A record is also found through a record it links to. Say each person links a portrait:
import { defineCollection, field } from 'ohnejs';
export default defineCollection({
api: { read: true },
dashboard: { recordLabel: 'name' },
fields: {
name: field('text'),
portrait: field('record', { collection: 'Uploads' }),
},
});Typing ceo lists the people whose portrait is named ceo-portrait.webp, under "People via Uploads". Related groups come after the records that match on their own and the pages. Each row shows the linked record that matched at its end. Hover it to see the field that links it: "Portrait" here, or a path like "Body > Hero > Image" for a link inside blocks.
- Each word matches the record's own fields or the label of a record it links to. So the words can split across the link:
benno ceofinds Benno, whose portrait matchesceo. - A list relation can match each word through a different record: one image for
offsite, another forkeynote. - Links inside objects, repeaters, and blocks count too, like a block's image in a page's body.
- Only the linked record's label fields match, and only those with search on.
- A record that matches on its own shows once, never again in a related group.
- Words split across a link cost more of the query limits. With the default limits and two words, the record's own word must sit in its label or its first 25 or so text fields. Words that all match on one side still find the record.
- A relation with
search: falseis never followed, and neither is the inverse side of arecordsrelation. A collection in search shows how to keep a whole collection from being followed. - Each related group shows its five newest records, and "Load more" shows the next five. A common word can link through many collections, so only the first eight related groups show, and a line under the results says so. Add a word to narrow it down.
Where a record is used
Paste a record's UUID to find the record, then every record that links to it. They show as related records: a page using an upload as its cover shows under "Pages via Uploads".
A link counts even through a relation with search: false, or into a collection kept out of word search. Keep in mind what the list can and cannot tell you:
- It lists only the records you can open. Records hidden from you are not listed.
- A link stored only in another language shows after you switch your content language.
- It is not a delete guard. A record with no usage here may still be linked from a record you cannot open.
Searching from your code
The palette asks POST /search, and so can your own pages:
const response = await api('POST /search', {
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ q: 'ashbringer', limit: 10 }),
});
const { results } = await response.json();
// [{ collection: 'Items', UUID: '...', label: 'Ashbringer' }]A related result also carries via: the collection it was found through, and each linked record that matched, with the path of the relation field that links it:
// { collection: 'People', UUID: '...', label: 'Benno Quade', via: {
// collection: 'Uploads',
// targets: [{ UUID: '...', label: 'ceo-portrait.webp', path: 'portrait' }],
// } }limitcaps the records per collection and per related group, up to 50. Omitted, each answers five.collectionsearches that one alone, andoffsetskips its first matches, to page through it. An unknowncollectionanswers no results, and paging stops past anoffsetof 1000.viabesidecollectionpages that collection's records found throughviainstead. An unknownvia, or one the collection never links to, answers no results.excludelists collections to leave out.truncated: trueon the answer means some related groups were left out.labelis''when a record's label fields are empty.- A record that matches on its own through a late text field can come back again as related. The palette shows it once.
- The route needs a signed-in user; a guest gets
401.
With the assistant installed, the last row asks it your question instead, and so does Tab, whatever row is selected.