Skip to content

Search and discovery

A catalog is only useful if you can find things in it. Componecat indexes everything and gives you several ways in.

Catalog is where you both explore and search. Open it with an empty search box and you get the whole catalog; type into that box and the same page narrows to what matches. There is no separate search screen to switch to, and no filter that works in one place but not the other.

A toggle at the top right of the results picks how they are drawn:

  • Tree follows the hierarchy — systems containing components containing sub-components — with expand and collapse for navigating deep structures.
  • List ranks results by relevance and shows the matching text, including the documentation snippet that matched.

Browsing starts in the tree; typing a query moves you to the ranked list, because that is when relevance has something to say. Toggle whenever you like — the choice is remembered, and it travels in the link you share.

Narrowing the catalog does not flatten the tree. Matches stay where they belong in the hierarchy, and any parent that did not itself match is still drawn above them, dimmed, so you can see where a result lives. Those context rows are not counted in the result total, but they are still real entities you can open.

Search runs across an entity’s display name, name, description, tags, and key field values — and across documentation sections, so a phrase buried in a runbook still surfaces the entity. Partial and case-insensitive matches are handled, so you don’t have to remember exact spelling.

Narrow the catalog by kind, lifecycle, owner, namespace, source repository, tags, and any searchable field. Filters opens a dialog over the page; each chip carries the number of entities it would leave you with, and a running result count sits at the top so you can see the effect without closing it. Filters combine, so “production components owned by team-payments” is a couple of clicks. Filter state lives in the URL, so a filtered view is a shareable, bookmarkable link.

If your organization defines a lot of fields, the dialog does not draw a row of chips for every one. Pick the ones you want from Custom fields and their chips appear; a field already carrying a selection is shown from the start. Removing a field from that list clears whatever it had selected, so nothing keeps filtering out of sight.

Facets answer “narrow this down”. When you need to ask a real question of the catalog — anything with an or, a not, or a comparison — type it into the same search box:

lifecycle:production AND (language:go OR language:rust) AND NOT tag:deprecated

A query is a mix of bare words, which search names, descriptions, tags, and documentation exactly as they always have, and field clauses, which filter on a specific field. Typing plain words still does plain search — the language only engages once you use a clause or an operator.

Built-in fields: name, displayName, namespace, description, kind, lifecycle, owner, ownerHasMembers, tag, language, repository, branch, hasDescriptor, interface, interfaceVisibility, protocol, hasDocs, hasSpec, scorecard, scorecardScore, scorecardLevel, createdAt, updatedAt, lastSyncedAt, docsUpdatedAt. Several have shorter aliases — ns, desc, team, lang, repo, updated, synced.

interface, interfaceVisibility, and protocol ask about the interfaces an entity exposes, and match when any one of them fits: interface:mcp finds every entity publishing an MCP server, interface:* every entity publishing anything at all, and interface:null the ones publishing nothing. hasDocs:true finds entities carrying documentation, whether a linked page or a section held in the catalog. hasSpec:true finds entities with at least one API interface carrying a spec URL — interface:!api OR hasSpec:true reads as “no API interface, or at least one of its API interfaces is specced” (an any-of test, not a check that every API interface individually carries a spec).

Every entity has an owning team — owner is required, so owner:* matches everything and owner:null matches nothing. ownerHasMembers:true is the useful presence test: it finds entities whose owning team actually has someone on its roster, as opposed to one that’s just an empty placeholder.

lastSyncedAt is when an entity’s componecat.yaml descriptor last synced successfully — an entity that has never synced matches neither side of a comparison operator, only :null, the same way namespace:null matches an absent field below. docsUpdatedAt is when an entity’s documentation — a linked page or a section held in the catalog — was last touched, matching when any one of them falls in the given window, the same any-of convention interface uses.

scorecard:security finds every entity evaluated against the security scorecard, by name or id. scorecardScore: and scorecardLevel: ask about that evaluation’s weighted score (0-100) and attained ladder level, and always need a scorecard: clause in the same AND to say which scorecard they mean — there’s no composite score across scorecards to fall back to:

scorecard:security AND scorecardScore:>=80
scorecard:security AND scorecardLevel:gold

score: and level: still work as shorter names for the same two fields — unless your organization has its own field definition keyed exactly score or level (a maturity score, a risk score, a support tier level), in which case that field wins the name: score: and level: address your field, reachable the same way any colliding field is (field.score/field.level still works too), and scorecardScore:/scorecardLevel: remain the unambiguous way to reach the scorecard evaluation fields.

If your organization has exactly one scorecard, scorecardScore:/scorecardLevel: work on their own — there’s nothing to disambiguate. With more than one, leaving off scorecard: is a query error rather than a guess, and names every scorecard it could have meant so you can pick one.

Your organization’s field definitions are addressable by their key: tier:1, costCenter:CC-100. If a field key happens to match a built-in name, reach it as field.owner and the built-in as owner.

Start typing a field name in the search box and it will suggest the ones that exist, including your own.

Expression Matches
lifecycle:production Field equals the value
name:~gateway Field contains the value
name:*gateway* Wildcards — * for any run of characters, ? for one
replicas:>=3 Compare — >, >=, <, <= on number and date fields
updatedAt:[2026-01-01 TO 2026-03-01] Inclusive range; * for an open end
updatedAt:<now-90d Relative date — see below
namespace:null Field is absent
repository:* Field is present
owner:!platform-team Field does not equal the value
description:!"" Field is not the empty string — on an always-present field like description, this is how you exclude blanks, since :* only tests presence; on a nullable field (like namespace) it also matches rows where the field is absent, not just non-empty ones

Combine clauses with AND, OR, NOT, and parentheses. A space between clauses means AND, so kind:service lifecycle:production needs no operator. Negation also has the short forms -tag:deprecated and !tag:deprecated. Quote anything containing a space: owner:"Platform Team".

owner: takes a team name or a team id, and kind: takes a kind key.

Date fields — createdAt, updatedAt, and any date field of your own — also accept tokens relative to the moment the query runs, so “not touched in 90 days” can be written once and mean the same thing every day it’s asked:

updatedAt:<now-90d
updatedAt:[now-30d TO now]
field.reviewedAt:<now-180d

now is the moment the search runs. Follow it with a sign and a number and a unit to offset it — now-90d (90 days ago), now+30d (30 days from now), now-24h (24 hours ago), now-6mo (6 months ago), now-1y (1 year ago). Supported units are h (hours), d (days), mo (months), and y (years) — m is rejected, since it could mean either minutes or months.

A relative token resolves to the exact instant it names, not the start or end of a calendar day the way updatedAt:2026-01-01 does — now-90d is 90×24 hours before the moment the query ran, down to the microsecond. Everything in a single query resolves against the same moment, so both ends of updatedAt:[now-30d TO now] line up.

This matters most for anything you save: a saved search or a dynamic collection written with updatedAt:<now-90d keeps meaning “not updated in the last 90 days” every time it’s evaluated, instead of silently rotting into a fixed calendar date the way a literal date would.

Text matching ignores case everywhere, on your organization’s fields as much as on the built-in ones, so tier:GOLD and tier:gold find the same entities. Number, boolean, and date fields compare by value rather than by text, so replicas:>=9 matches 10 and a wildcard in one of them is an error rather than a literal character to look for.

If a query doesn’t parse, the search box says why and points at the character that confused it — nothing is silently ignored. Plain words are never rejected, though: text that happens to contain a colon — auth: token, a pasted link — is searched for as written rather than treated as a broken expression.

The chips write themselves into the search box. Select a kind and kind:service appears there; take the chip off and the clause goes with it. What the box shows is the whole search that ran, so a click-built filter is also a worked example of the language — and when you need an or the chips can’t express, the expression is already there to edit.

It reads the other way too. Type kind:service into the box and Service ticks in the kind picker, because a clause you wrote by hand belongs on the control that owns it as much as one you clicked.

That only happens when what you typed is exactly what the chips would have written. The page redraws the box from the chips, so anything it rewrote — a different clause order, quotes it didn’t need — would move the text and your cursor with it while you were still typing. So recognition is deliberately narrow, and everything else simply stays as text and still runs: an or, a not, a comparison, a wildcard, and a value your organization doesn’t have, which is far more likely a typo than a filter. When the box holds something it can’t put on a chip, the chips hand over and the query does the work. The source repository is the one facet with no clause of its own, so it stays a chip either way.

The expression you type in the UI is the same one the API takes. Pass it as q to GET /api/rest/v1/catalog/search, as query to the GraphQL searchCatalog field, or as query to the MCP search_catalog tool — see the API reference.

A search you run often doesn’t have to be rebuilt every time. Saved — beside the Filters button — keeps the current search under a name and puts it one click away.

What it stores is the whole search, not just the text: the query, the facet selections, whether you were in the tree or the list, and the sort order. Opening one puts the page back exactly as you left it, chips and all.

Saved searches are private. Yours are visible only to you, and two people can each keep a “My services” without colliding. Names have to be unique within your own list, so there is never a pair you can’t tell apart.

From the menu you can also rename a saved search, delete it, or — once you’ve changed the search it opened — update it to match what’s on screen. The update offer only appears when the page has actually drifted from what was saved. Deleting one removes the shortcut and nothing else; the entities it matched are untouched.

A query that doesn’t parse is refused at save time, for the same reason a collection’s is: a shortcut that quietly matched nothing would be worse than one that won’t save.

Where a saved search is a personal shortcut, a collection is a named, shareable set of catalog entities that everyone in the organization can see. A static one holds the entities you picked by hand, in the order you put them in. A dynamic one holds a query instead, and its members are whatever that query matches right now — an entity that starts matching joins on its own, and one that stops matching leaves.

Both are built with the search you already know. A static collection’s Add items box takes the same query language, the same Filters dialog, and the same Saved searches as the catalog — so you can narrow to the twelve services you want and add them one by one, instead of remembering each name.

The query is written in the language above, in a box with the same field suggestions and the same syntax help, alongside a live preview of what the collection would contain. You do not have to write it from memory: Filters opens the same dialog the catalog uses, and every chip you pick writes itself into the query, exactly as it does on the catalog page. Saved starts you from one of your saved searches, so a view you already worked out can become a collection everyone can see.

The one filter missing here is the source repository. A collection stores a query and nothing else, and that filter has no clause in the language, so it is left out rather than offered and then quietly dropped.

Anything you can search for, you can keep:

kind:service AND lifecycle:production AND NOT tag:deprecated
interface:mcp AND hasDocs:true
owner:"Platform Team" AND updatedAt:>2026-01-01

A collection’s page shows its query, and Open in catalog hands the query back to the catalog search box so you can widen it, narrow it, and save the result as another collection.

Where the search box treats a query it can’t make sense of as ordinary words to search for, a saved query is refused until it parses — a collection that quietly matched nothing would be worse than one that won’t save.

Press Cmd/Ctrl + K anywhere for a global command palette: type to jump straight to any entity. Your recently viewed entities are one keystroke away, so hopping between the things you’re working on is fast.

Results are grouped by what they are. Alongside Catalog entries and app pages, a Documentation group matches this documentation itself, so a question about how Componecat works — “descriptor”, “team sync”, “impact analysis” — finds the page that answers it without leaving what you were doing. Catalog matches always rank first, so searching for a term that names one of your entities still puts the entity at the top. Selecting a documentation result opens the page here on the documentation site in a new tab, at the exact section that matched.

The palette shows the best few matches, not all of them. See all results at the bottom of the Catalog group opens the catalog page with the same text, where the filters and the query language are waiting.