Global Search
Avo has a powerful global search feature powered by Hotwire. It searches through all the resources that have the search attribute with a valid configuration.
The global search leverages the resource search configuration. Please refer to the resource search section for more information on how to configure the search for a resource.
You open the global search by clicking the trigger on the navbar or by using the Cmd + K keyboard shortcut (Ctrl + K on Windows). The search includes enhanced keyboard navigation:
- Ctrl + K or Cmd + K - Open global search
- Up and Down arrow keys - Navigate through search results
- Enter - Visit the selected record
- Esc - Close the search modal
The global search shows a limited number of quick results in the dropdown, with an option to view all matching results on a dedicated page without limits.
Global configuration
Use the global_search configuration to enable/disable the feature and control related options.
# config/initializers/avo.rb
Avo.configure do |config|
config.global_search = {
enabled: true,
navigation_section: true,
search_on_type: true,
}
endSet enabled: false to hide the global search.
All configuration options can be set using a lambda. Within this block, you gain access to all attributes of Avo::ExecutionContext.
# config/initializers/avo.rb
Avo.configure do |config|
config.global_search = {
enabled: -> { current_user.is_admin? },
navigation_section: -> { current_user.is_admin? },
}
end-> search_on_type
By default, Avo runs the search as you type in the global search input. Set search_on_type: false to disable this behavior — the dropdown still opens on focus or Cmd + K, but typing no longer triggers a search. The user must press Enter to run the search and navigate to the dedicated results page.
# config/initializers/avo.rb
Avo.configure do |config|
config.global_search = {
enabled: true,
search_on_type: false,
}
end-> item
Configures how each result row renders in the palette. It's a hash with the following keys:
| Option | Description | Default | Possible Values |
|---|---|---|---|
title | The title of the result | Resource title | Any string |
description | The description of the result | nil | Any string |
image_url | The URL of the image to display in the result | nil | Any valid URL |
image_format | The format of the image to display in the result | :circle | :square, :rounded, :circle |
path | The path to navigate to when clicking the result | Record show page | Any valid path |
# app/avo/resources/post.rb
class Avo::Resources::Post < Avo::BaseResource
self.search = {
query: -> { query.ransack(name_cont: q, body_cont: q, m: "or").result(distinct: false) },
item: -> do
{
title: "[#{record.id}] #{record.name}",
description: record.truncated_body,
image_url: main_app.url_for(record.cover_photo),
image_format: :rounded,
path: avo.resources_post_path(record, custom: "search")
}
end
}
end-> hide_on_global
You might have a resource that you'd like to be able to perform a search on when on its Index page but not have it present in the global search. You can hide it using hide_on_global: true.
class Avo::Resources::TeamMembership < Avo::BaseResource
self.search = {
query: -> { query.ransack(id_eq: q, m: "or").result(distinct: false) },
item: -> do
{
description: record.level,
}
end,
hide_on_global: true
}
endLimiting results
By default, Avo applies config.search_results_count (default: 8) to search queries that return an ActiveRecord relation without a user-applied .limit().
Set the global default in your initializer:
# config/initializers/avo.rb
Avo.configure do |config|
config.search_results_count = 16
endOverride per query by adding .limit() in the query: proc. Your limit always wins:
class Avo::Resources::User < Avo::BaseResource
self.search = {
query: -> {
query.ransack(name_cont: q).result(distinct: false).limit(5)
}
}
endDynamic limits work too. The query: block has access to all Avo::ExecutionContext locals:
class Avo::Resources::User < Avo::BaseResource
self.search = {
query: -> {
query.ransack(name_cont: q).result(distinct: false).limit(current_user.admin? ? 30 : 10)
}
}
endThis applies to resource-index search, global search, and searchable association pickers.
Custom array search providers
If your query: proc returns an Array of hashes, Avo does not auto-cap the results. Use .first(N) or .take(N) in your proc if you need a limit.
-> display_count
By default, Avo displays the search results count for each resource in the global search. Example: "Users (8 of 21)". You can avoid counting the number of results by configuring the display_count option
This is useful if you have a custom search provider that doesn't return the number of results or if you want to avoid counting the number of results on large datasets.
# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
self.search = {
display_count: false
query: -> {
# ...
},
}
endYou can also assign a lambda to dynamically set the value. Inside that block you have access to all attributes of the Avo::ExecutionContext.
# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
self.search = {
display_count: -> { user.admin? }
}
endsearch_type
The same self.search[:query] proc runs from four surfaces. To branch per surface, use the search_type local.
| Value | Surface |
|---|---|
:resource | resource-index search bar (and the standalone /search endpoint) |
:global | navbar ⌘K palette |
:association | searchable association picker on edit forms and the attach modal |
:kanban | kanban board "add a card" picker |
class Avo::Resources::User < Avo::BaseResource
self.search = {
query: -> {
case search_type
when :global # navbar ⌘K — widest, includes email
query.ransack(first_name_cont: q, last_name_cont: q, email_cont: q, m: "or").result(distinct: false)
when :resource # index search bar — name only
query.ransack(first_name_cont: q, last_name_cont: q, m: "or").result(distinct: false)
when :association # picker — tightest
query.ransack(first_name_cont: q).result(distinct: false)
when :kanban # kanban "add a card" picker
query.ransack(first_name_cont: q, last_name_cont: q, m: "or").result(distinct: false)
end
}
}
endIf you don't need surface-specific behavior, ignore the local and write a single query that runs on every surface.
Custom search provider
You can use custom search providers like Elasticsearch. In such cases, or when you want to have full control over the search results, the query block should return an array of hashes. Each hash should follow the structure below:
{
_id: 1,
_label: "The label",
_url: "The URL",
_description: "Some description about the record", # only with Avo Pro and above
_avatar: "URL to an image that represents the record", # only with Avo Pro and above
_avatar_type: :rounded # or :circle or :square; only with Avo Pro and above
}Example:
class Avo::Resources::Project < Avo::BaseResource
self.search = {
query: -> do
[
{ _id: 1, _label: "Record One", _url: "https://example.com/1" },
{ _id: 2, _label: "Record Two", _url: "https://example.com/2" },
{ _id: 3, _label: "Record Three", _url: "https://example.com/3" }
]
end
}
endWARNING
Results count will not be available with custom search providers.