Skip to Content

UI Resource Views advanced

InvenioRDM uses the flask-resources framework to structure its REST APIs using resources, resource configs, and resource components.
NRP-based Invenio repositories extend this architecture with UI resources — resource classes that render HTML front-end views in addition to API responses (typically JSON).

UI resources provide a structured and declarative way to implement page-level views, keeping routing logic, permissions, and template context preparation consistent, configurable & extensible.

If you are interested in how to build custom API resources, you’ll find more in the InvenioRDM — Building Resources  docs.

What are UI Resources?

UI resources mirror the structure of Invenio’s API resources but serve rendered templates rather than JSON. They consist of:

  • UIResource - the view/controller class
  • UIResourceConfig - configuration describing routes, templates, permissions, and components
  • UIResourceComponent - specialised rendering logic that hooks into specific stage of view rendering. Prepares or modifies template context before rendered, e.g. by calling to service layer

Reference implementations:

Why UI Resources?

UI resources define how front-end pages are routed & rendered, they are responsible for:

  • routing and view configuration
  • clean separation of logic through resource components
  • invocation of service layer & preparation of context data for UI templates
  • rendering of Jinja templates

Structure Overview

A typical UI resource module includes:

    • __init__.py
    • components.py
  • __init__.py - Contains definitions for both the MymodelUIResourceConfig and MymodelUIResource, this is how your views for mymodel record model gets routed and rendered.
  • components.py - If present, contains any custom UI resource components implementation, to customize template context before render.
  • templates - Contains Jinja (or JinjaX) templates registered and rendered by MymodelUIResource

Resource Configuration

UIResourceConfig declares the behavior of a UI resource:

Following options may be typically present and configurable:

  • blueprint_name - name of Flask blueprint
  • url_prefix - prefix applied to all routes handled by a resource
  • routes — route map configuration for Flask
  • templates - maps route names to specific Jinja templates
  • components — list of registered resource components
  • search_component - declares the React component used to render search result items of that specific record model.

Example:

ui/articles/__init__.py
from oarepo_ui.resources.base import UIResourceConfig from .components import ArticleContextComponent class ArticleUIResourceConfig(UIResourceConfig): blueprint_name = "articles" url_prefix = "/articles" template = "articles/detail.html" components = [ArticleContextComponent] #...

Resource Class

UIResource implements controller-like methods:

ui/articles/__init__.py
from oarepo_ui.resources.base import UIResource class ArticleUIResource(UIResource): """UI resource for Article records pages."""

This controller is responsible for rendering pages related to certain record model, like:

routes: Mapping[str, str] = { "search": "", "deposit_create": "/uploads/new", "deposit_edit": "/uploads/<pid_value>", "record_detail": "/records/<pid_value>", "record_latest": "/records/<pid_value>/latest", "record_export": "/records/<pid_value>/export/<export_format>", "published_file_preview": "/records/<pid_value>/files/<path:filepath>/preview", "draft_file_preview": "/records/<pid_value>/preview/files/<path:filepath>/preview", } """Routes for records resource, mapping route names to URL patterns."""

For every view method, it can also define rendering lifecycle hooks (which can later be leveraged by Resource Components), e.g:

def deposit_edit( self, **kwargs: Any, ) -> ResponseReturnValue: """Return edit page for a record.""" #... extra_context: dict[str, Any] = {} # Here any registered components have access to # & can customize any passed context arg self.run_components( "before_ui_edit", extra_context=extra_context, #... )

Workflow of such controller view methods typically looks like:

  • validate & parse HTTP request args & headers
  • call into the service layer to fetch data
  • call registered resource components to process or generate template context
  • render the assigned view Jinja template with processed context

Resource Components

Components extend the behavior of a UI resource via lifecycle hooks:

ui/articles/components.py
from oarepo_ui.resources.components import UIResourceComponent class ArticleContextComponent(UIResourceComponent): def before_detail(self, resource, request, extra_context, **kwargs): extra_context["publisher_highlight_color"] = "red"

Common rendering lifecycle hooks include:

  • before_detail
  • after_detail
  • before_search
  • before_render_template
  • before_ui_edit
  • before_ui_create

More hooks can be found or created by defining them in the UIResource-based class view methods.

E.g. to define a before_my_custom_page hook:

ui/articles/__init__.py
from oarepo_ui.resources.base import UIResource, UIResourceConfig from .components import ArticleContextComponent from flask import current_oarepo_ui class ArticleUIResourceConfig(UIResourceConfig): blueprint_name = "articles" url_prefix = "/articles" template = "articles/detail.html" components = [ArticleContextComponent] # Bind view method to URL route routes = { "my_custom_page": "/custom/<pid_value>", } # Optionally set a custom template for this view templates = { "my_custom_page": "articles.custom_page", # JinjaX uses dot notation } class ArticleUIResource(UIResource): def my_custom_page(self): # Prepare template context my_context = { "record": self.service.read(), "custom_data": "some value", } # Run components (lifecycle hooks) self.run_components( "before_my_custom_page", extra_context=my_context, ) # Render the template using JinjaX catalog return current_oarepo_ui.catalog.render( self.get_jinjax_macro( "my_custom_page", # Uses templates["my_custom_page"] or falls back to macro name ), **my_context, )

This creates a complete binding:

  1. URL route: routes["my_custom_page"] maps the view to /custom/<pid_value>
  2. Template: templates["my_custom_page"] specifies the JinjaX template using dot notation (articles.custom_page)
  3. View method: my_custom_page() in ArticleUIResource creates the context and renders

The template file would be located at ui/articles/templates/articles/custom_page.jinja and export a macro named custom_page.

We now implement the hook as a method of a resource component:

ui/articles/components.py
from oarepo_ui.resources.components import UIResourceComponent class ArticleContextComponent(UIResourceComponent): def before_detail(self, resource, request, extra_context, **kwargs): extra_context["publisher_highlight_color"] = "red" def before_my_custom_page(self, extra_context): extra_context["foo"] = "bar"

Further reading

Last updated on