Search page
The search page displays a list of records matching user queries. It uses react-searchkit to provide faceted search, sorting, and pagination with customizable result item components.
Request and Application Flow
Request Flow
Application Flow
The search application is organized into server-side and client-side layers:
Server-side, the UI Resource View handles the search page request and provides search_app_config containing the search endpoint, facets, sort options, and other configuration. The record_search.html template renders a <div> with data-invenio-search-config attribute containing this JSON configuration and includes the webpack bundle.
Client-side, the Webpack Entry Point reads the config, uses parseSearchAppConfigs() and createSearchAppsInit() to mount the react-searchkit app, and registers your component overrides (like ResultsListItem). The react-searchkit library renders SearchAppLayout which contains facets (SearchAppFacets) and results area (SearchAppResults). The results area renders ResultsList which loops through search results and renders each with your customized ResultsListItem component.
Key Files
| File | Location | Purpose |
|---|---|---|
| Search template | oarepo_ui.record_search | oarepo-ui |
| Results list item | {{model_name}}/semantic-ui/js/{{model_name}}/search/ResultsListItem.jsx | Model-specific (nrp-model-copier ) |
| Search index | {{model_name}}/semantic-ui/js/{{model_name}}/search/index.js | Model-specific |
| Search components | oarepo_ui/search | oarepo-ui |
- webpack.py
Configuration
UI Resource
The search routes are defined in your model’s UI resource config:
from oarepo_ui.resources.records.config import RecordsUIResourceConfig
from oarepo_ui.theme.config import UIComponent, UIComponentImportMode
class MymodelUIResourceConfig(RecordsUIResourceConfig):
blueprint_name = "mymodel"
url_prefix = "/mymodel"
routes = {
"search": "",
# ... other routes
}
# Critical: Required for global search UI to work
search_component = UIComponent(
"DatasetsResultsListItem",
"@js/mymodel/search/ResultsListItem",
UIComponentImportMode.DEFAULT,
)The search_component declares the React component used to render search result items for your record model. This is critical for the global search UI to work - the global search displays results from multiple record models and needs to know which component to use for each.
The search_component automatically registers your result item component with the override system using the overridable component key. For more on server-side component overrides, see Component Overrides.
Search App Configuration
The search app configuration is dynamically generated by RecordsUIResourceConfig.search_app_config() method using oarepo-model’s presets.
For detailed information on how to customize search behavior through oarepo-model presets and customizations (facets, mappings, analyzers), see Search configuration.
Template Context
The search page receives context variables from the UI resource view. If no custom template is configured, the system uses the default page component (oarepo_ui.pages.RecordSearch), which extends your model’s base record_search.html template.
The following context variables are available:
| Variable | Description |
|---|---|
theme | Theme configuration |
search_app_config | Search app configuration (endpoint, facets, sort options) |
ui_config | UI model configuration |
ui_resource | Reference to the UI resource instance |
ui_links | UI links |
extra_context | Additional context from resource components |
webpack_entry | Webpack entry point for the search app JavaScript |
The default component (oarepo_ui/pages/RecordSearch.jinja) declares these variables in a {# def #} block and extends your model’s base template ({model_name}/record_search.html), which is provided by nrp-model-copier and extends oarepo_ui/record_search.html.
Building Your Search Result Item
The nrp-model-copier template provides a ResultsListItem component that displays each search result. You customize this component to match your record’s metadata. For available search components, see Search UI Components.
import React from "react";
import PropTypes from "prop-types";
import _get from "lodash/get";
import _join from "lodash/join";
import { Grid, Item, Label } from "semantic-ui-react";
import { i18next } from "@translations/i18next";
export const ResultsListItem = ({ result, ...rest }) => {
const accessRights = _get(result, "ui.access_status", null);
const createdDate = _get(
result,
"ui.created_date_l10n_short",
"No creation date found."
);
const languages = _get(result, "metadata.languages", []);
const version = _get(result, "metadata.version", null);
const title = _get(result, "metadata.title", i18next.t("No title"));
return (
<Item key={result.id} data-testid="result-item">
<Item.Content>
<Grid>
<Grid.Row>
<Grid.Column className="results-list item-main">
<div className="justify-space-between flex">
<Item.Header as="h2">
<a href={result.links.self_html}>{title}</a>
</Item.Header>
<div className="item-access-rights">
{result.state && (
<Label title={result.state_timestamp}>{result.state}</Label>
)}
{accessRights && accessRights.id !== "open" && (
<Label title={`${accessRights.description_l10n}`}>
{accessRights.title_l10n}
</Label>
)}
</div>
</div>
<Item.Meta>
<Grid columns={1}>
<Grid.Column>
<Grid.Row className="ui separated">
<span
aria-label={i18next.t("Languages")}
title={i18next.t("Languages")}
>
{_join(languages.map((l) => l.title), ", ")}
</span>
</Grid.Row>
</Grid.Column>
</Grid>
</Item.Meta>
<Item.Extra>
<div>
<small>
<p>
{createdDate && (
<>
{i18next.t("Uploaded on")} <span>{createdDate}</span>{" "}
{version && `(${i18next.t("version")}: ${version})`}
</>
)}
</p>
</small>
</div>
</Item.Extra>
</Grid.Column>
</Grid.Row>
</Grid>
</Item.Content>
</Item>
);
};
ResultsListItem.propTypes = {
result: PropTypes.object.isRequired,
};
export default ResultsListItem;Component Overrides
Components can be overridden server-side via UIComponent (recommended) or client-side in JavaScript. For more on server-side overrides, see Component Overrides.
Client-side overrides:
import {
parseSearchAppConfigs,
createSearchAppsInit,
} from "@js/oarepo_ui/search";
import ResultsListItem from "./ResultsListItem";
/** NOTE: This reads configs for any search app present on a page
* In HTML/Jinja, a search app instance is represented
* by a configuration object in the data-invenio-search-config attribute
*/
const [{ overridableIdPrefix }] = parseSearchAppConfigs();
export const componentOverrides = {
/** NOTE: Then you can replace any existing search ui
* component with your own implementation, e.g.:
*/
[`${overridableIdPrefix}.ResultsList.item`]: ResultsListItem,
};
createSearchAppsInit({ componentOverrides });Common override keys:
| Key | Description |
|---|---|
{Model}.Search.ResultsList.item | Individual result item |
{Model}.Search.SearchApp.results | Main results container |
{Model}.Search.SearchApp.facets | Facets panel |
{Model}.Search.SearchApp.searchbar | Search bar component |
Optional: Customize Search Page Template
By default, the search page uses the default page component (oarepo_ui.pages.RecordSearch), which extends your model’s base record_search.html template. You only need to create a custom template if you want to customize the page layout or add custom content.
When you use the templates configuration to specify a custom search template, you are replacing the default JinjaX page component with your own. Your custom template must be a valid JinjaX component with a proper {# def #} block declaring all context variables.
For the search page, the following context variables must be declared in the {# def #} block: search_app_config, ui_config, ui_resource, ui_links, webpack_entry, and extra_context. Without the {# def #} block, JinjaX will not recognize these variables and the template will fail to render.
Step 1: Create the JinjaX page component
Add a search tips banner that shows only on the first visit:
{# def search_app_config, extra_context, model_name, ... #}
{% extends model_name ~ "/record_search.html" %}{% extends "oarepo_ui/record_search.html" %}
{% block page_body %}
{# Show tips banner only if no search query #}
{% if not search_app_config.get('initialQuery') %}
<div class="ui message info">
<i class="icon info circle"></i>
{{ _("Tip: Use quotes for exact phrases, OR to combine terms") }}
</div>
{% endif %}
{{ super() }}
{% endblock %}Step 2: Register the template
In your UI resource config, specify the custom template:
class MymodelUIResourceConfig(RecordsUIResourceConfig):
# ... other config ...
templates = {
"search": "mymodel.RecordSearch",
}Note the dot notation: mymodel.RecordSearch refers to the JinjaX component at mymodel/RecordSearch.jinja.
JinjaX Component Required: When using templates configuration, you must create a valid JinjaX page component (e.g., mymodel/RecordSearch.jinja) with a {# def #} block declaring all context variables. The component should extend your model’s base template. Without the {# def #} block, the template will fail to render.
Custom JavaScript
Add model-specific JavaScript to the search page without overriding the entire template:
Create ui/mymodel/templates/semantic-ui/mymodel/record_search/javascript.html:
<script>
// Custom search behavior
document.addEventListener('DOMContentLoaded', function() {
console.log('Custom search page behavior');
});
</script>The base template automatically includes this file if it exists at {model_name}/record_search/javascript.html.
Further Reading
Model backend search customizations
Search configurationSearch UI component reference
Search UI ComponentsUnderstanding UI resources
UI resource architectureModel framework repository
oarepo-modelSearch library documentation
react-searchkitInvenio search UI components
invenio-search-uiSearch components source
oarepo-ui search components