- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
By default, shadcn search fetches your entire registry.json and filters
items locally. This works well for most registries and requires nothing more
than a static file.
For large registries with thousands of items, you can implement search on your registry server instead. The CLI forwards the search parameters to your registry and your server returns only the matching items.
Dynamic search is opt-in and fully backwards compatible. Static registries keep working without any changes.
How It Works#
When you run shadcn search, the CLI appends the search parameters to the
catalog request:
pnpm dlx shadcn@latest search @acme --query button --limit 50
GET https://acme.com/r/registry.json?q=button&limit=50&offset=0What happens next depends on your registry:
- Static registries ignore the query parameters and return the full
registry.json. The CLI filters the items locally. This is the default behavior and requires no changes. - Dynamic registries filter the items server-side and return the matching
items along with a
paginationobject. When the CLI seespaginationin the response, it trusts the results as pre-filtered and skips local filtering.
The presence of pagination in the response is what tells the CLI your
registry handles search server-side. There is no configuration or capability
negotiation required.
Query Parameters#
The CLI sends the following query parameters with every search request:
| Parameter | Description |
|---|---|
q | The search query string. |
type | Comma-separated item types, e.g. registry:ui,registry:block. |
limit | Maximum number of items to return. |
offset | Number of items to skip. |
All parameters are optional. A request without q or type should return
all items, paginated.
Response Format#
Return your regular registry.json shape with an additional pagination
object:
{
"name": "acme",
"homepage": "https://acme.com",
"items": [
{
"name": "button",
"type": "registry:ui",
"description": "A button component."
},
{
"name": "icon-button",
"type": "registry:ui",
"description": "A button component with an icon."
}
],
"pagination": {
"total": 12,
"offset": 0,
"limit": 2,
"hasMore": true
}
}Search results only need name, type and description for each item. You
do not need to include files, dependencies or other item properties. The
CLI fetches the full item definition when the user runs shadcn add.
pagination#
| Property | Type | Description |
|---|---|---|
total | number | Total number of items matching the query. |
offset | number | Number of items skipped. |
limit | number | Maximum number of items in this response. |
hasMore | boolean | Whether more items are available beyond this page. |
Server Implementation#
Here's an example using a Next.js route handler:
import { NextRequest, NextResponse } from "next/server"
export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl
const query = searchParams.get("q")
const types = searchParams.get("type")?.split(",")
const limit = Number(searchParams.get("limit") ?? 100)
const offset = Number(searchParams.get("offset") ?? 0)
// Filter items using your database or search index.
const { items, total } = await searchItems({ query, types, limit, offset })
return NextResponse.json({
name: "acme",
homepage: "https://acme.com",
items,
pagination: {
total,
offset,
limit,
hasMore: offset + limit < total,
},
})
}You can back searchItems with anything: a database query, a full-text
search index or an external search service.
Multiple Registries#
When searching a single registry, the CLI forwards all parameters and uses
your pagination response as-is.
When searching multiple registries at once, e.g. shadcn search @acme @lib,
a global offset cannot be split across registries. The CLI forwards q and
type to each registry along with a limit large enough to fill the
requested page (offset + limit), then merges and paginates the combined
results locally. Server-side filtering still applies, so each registry only
returns matching items.
If your registry caps the number of items per response below the requested
limit, the CLI treats it as exhausted for deeper pages. Honor the requested
limit where possible so all your matches stay reachable through paging.
Authentication#
Dynamic search works with all authentication patterns. The CLI sends the configured headers and params with the search request, so you can scope search results to the authenticated user:
export async function GET(request: NextRequest) {
const token = request.headers.get("authorization")?.replace("Bearer ", "")
const team = await getTeamFromToken(token)
// Only search items this team can access.
const { items, total } = await searchItems({
query: request.nextUrl.searchParams.get("q"),
team,
})
// ...
}Backwards Compatibility#
- Static registries require no changes. Query parameters on a static file are ignored by the file server and the CLI falls back to local filtering.
- Older CLI versions fetch the catalog without query parameters and
ignore the
paginationfield. Your registry should return a sensible default response for requests without parameters, e.g. the first page of items. - Ranking is up to your server. When your registry returns pre-filtered results, the CLI preserves your order instead of re-ranking locally.
Testing#
Test your dynamic registry with curl:
curl "https://acme.com/r/registry.json?q=button&limit=10"Then verify with the CLI:
pnpm dlx shadcn@latest search @acme --query button
To confirm server-side search is active, check that the response includes the
pagination object and only the matching items.