---
title: Global Search
slug: global-search
kind: pattern
summary: One search box that looks across every kind of record in the product and returns them grouped by type, for the person who knows what they want but not where it lives.
problem: >-
  Someone knows a name — a customer, an invoice number, a job — and has to guess
  which section owns it before they can look for it. Guessing wrong costs a
  navigation round trip, and for anything that appears in two places it is not
  even clear which section is the right one.
family: [find]
data_shape: [collection, app]
principles: [friction, surface-dont-bury]
interaction: [search]
density: medium
complexity: medium
status: stable
visibility: public
use_when:
  - The product holds several kinds of record and people refer to them by name.
  - Someone often knows the exact thing they want before they start looking.
  - Records are cross-referenced, so "which section owns this" has no obvious answer.
avoid_when:
  - There is one kind of record. Then it is a filter on that list, not global search.
  - The collection is small and enumerable. Browsing is faster than typing.
  - People search by attributes rather than identity — that is faceted filtering.
alternatives:
  - slug: command-palette
    when: The audience is expert and daily, and they want to run actions as well as find records.
  - slug: filter-bar
    when: People know attributes, not names — "all overdue invoices for Dana", not "INV-1041".
ask_leo: |
  Add one global search box to this application.

  - Put it in the persistent header so it is in the same place on every screen,
    and give it a keyboard shortcut.
  - Search every kind of record a person would name out loud, and return results
    GROUPED BY TYPE with a heading and a count for each group.
  - Show enough of each result to tell two similar records apart — for a
    customer, the city or the owner; for an invoice, the amount and the state.
  - Highlight the matched text in each result so it is obvious why it is there.
  - Search identifiers as well as names: an invoice number, a reference, an
    email address. People paste those.
  - Rank exact identifier matches first, then names starting with the term, then
    everything else. Show the strongest few per group with a "see all" link into
    that section's filtered list.
  - Never leave the box empty-handed: when nothing matches, say what was searched
    and offer the nearest filtered list.
related:
  - title: Cmd+K Global Search
    url: /cookbook/global-search-command-palette
    summary: The implementation of this and the command palette in Rails and Stimulus.
  - title: Command Palette
    url: /patterns/command-palette
    summary: The expert-facing sibling that adds actions to the same box.
---

## Anatomy

```
┌───────────────────────────────────────────────┐
│ 🔍 riverside                                  │
├───────────────────────────────────────────────┤
│ CUSTOMERS (1)                                 │
│   Riverside Fit-Out Ltd        Leeds · Dana   │
│ INVOICES (2)                                  │
│   INV-1041  $8,400             overdue        │
│   INV-1039  $2,000             paid           │
│ JOBS (1)                                      │
│   Phase 2 — Framing            in progress    │
│   ─────────────────────────── see all jobs →  │
└───────────────────────────────────────────────┘
```

- **Grouped by type, with counts.** An undifferentiated list makes people read
  every row to work out what kind of thing it is. The heading does that work.
- **Disambiguating detail on every row.** Two customers called Marlow are one
  row each and identical without it.
- **A door into the full list.** Global search shows the strongest few; when the
  answer is not there, "see all" hands over to the section that can filter
  properly.

## Why it works

It removes a decision the person should never have had to make: *which part of
the app owns this?* That decision is an artefact of how the data is modelled,
and asking a user to make it is asking them to hold your schema in their head.

It is also the honest fallback for imperfect navigation. Ranking a nav bar is a
compromise; search is the escape valve for everything the compromise pushed
down.

## Where the quality actually comes from

Almost all of it is in **ranking and matching**, not layout:

- **Identifiers must match exactly and rank first.** People paste invoice
  numbers and emails. A fuzzy match that buries the exact hit is worse than no
  search.
- **Prefix beats substring.** "Mar" should surface Marlow before Kestrel Marine.
- **Recency is a legitimate tie-breaker**, because what someone touched this
  morning is usually what they mean.
- **Search what people say, not what you store.** If the business says "job" and
  the table is `work_orders`, both must match.

## Getting it wrong

- **One flat list**, so the type of each result is a guess.
- **Names only**, so pasting an invoice number returns nothing — the single most
  common real search.
- **A results page with no route onward** when the match is not in the top five.
- **Searching only the current section** while looking global, which teaches
  people the box is unreliable and they stop using it.

## Exemplars

**Stripe's search** takes an amount, an email, a card fingerprint or an object
id and routes each to the right kind of record. It demonstrates that global
search is mostly a matching problem: the interface is trivial, the ranking is
the product.

**Slack** shows the grouping principle under pressure — messages, files,
channels and people are different enough that one ranked list would be unusable.

The extractable rule: **global search is a promise that the user does not need
to know your data model.** Every result type you leave out is a place that
promise breaks, and it only has to break once.
