Skip to Content

Custom fields easy

Custom fields let you add extra data to a model without changing its metadata schema. They are defined in the repository configuration (invenio.cfg), not in the model’s YAML, so they are useful when the model is shared (for example, a model based on the CCMM or RDM templates) and you only need a few extra fields in your repository.

Custom field values are stored in the record’s top-level custom_fields object, next to metadata:

{ "metadata": { "title": "..." }, "custom_fields": { "myorg:instrument": "HPLC-2000" } }

Enabling custom fields in a model

Custom fields are provided by the custom_fields_preset of oarepo-model. If your model does not include it yet, add it to the presets in the model’s model.py:

equipment/model.py
from oarepo_model.api import model from oarepo_model.presets.custom_fields import custom_fields_preset equipment_model = model( "equipment", presets=[ # ... presets already used by the model custom_fields_preset, ], # ... )

The preset:

  • adds the custom_fields field to the record (and to the draft, if the model has drafts),
  • validates custom_fields against the configured custom fields when a record is created or updated,
  • adds custom_fields to the JSON Schema and to the search index mapping,
  • resolves vocabulary custom fields (VocabularyCF) when the record is saved.

Adding the preset to an existing model changes its search index mapping. Reinitialize the indices afterwards, see Database update.

Defining custom fields

Custom fields are defined in invenio.cfg under the key <MODEL>_CUSTOM_FIELDS, where <MODEL> is the model name in uppercase (dashes and spaces replaced with underscores). For a model called equipment, the key is EQUIPMENT_CUSTOM_FIELDS. The value is a list of custom field instances:

invenio.cfg
from invenio_records_resources.services.custom_fields.text import KeywordCF EQUIPMENT_CUSTOM_FIELDS = [ KeywordCF("myorg:instrument"), ]

The first argument is the name of the field, i.e. its key inside custom_fields. To store a list of values, pass multiple=True.

Only the configured fields are accepted: a record containing an unknown key in custom_fields fails validation.

To add more validation rules, pass field_args (a dictionary of arguments for the underlying marshmallow field). See the marshmallow documentation  for more information.

invenio.cfg
from marshmallow.validate import Length from invenio_records_resources.services.custom_fields.text import KeywordCF EQUIPMENT_CUSTOM_FIELDS = [ KeywordCF("myorg:instrument", field_args={"validate": Length(min=1, max=100)}), ]

Changes to the custom fields take effect after the server is restarted.

Custom field names

Although not required, it is recommended to prefix the custom field names with your own namespace (e.g., myorg:instrument). This avoids conflicts with custom fields from other organizations and simplifies serialization to JSON-LD.

Built-in custom field types

Field typeImport
KeywordCFfrom invenio_records_resources.services.custom_fields.text import KeywordCF
TextCFfrom invenio_records_resources.services.custom_fields.text import TextCF
BooleanCFfrom invenio_records_resources.services.custom_fields.boolean import BooleanCF
EDTFDateStringCFfrom invenio_records_resources.services.custom_fields.date import EDTFDateStringCF
ISODateStringCFfrom invenio_records_resources.services.custom_fields.date import ISODateStringCF
IntegerCFfrom invenio_records_resources.services.custom_fields.number import IntegerCF
DoubleCFfrom invenio_records_resources.services.custom_fields.number import DoubleCF
VocabularyCFfrom invenio_vocabularies.services.custom_fields import VocabularyCF
ComplexCFfrom oarepo_ui.services.custom_fields import ComplexCF

Notes:

  • KeywordCF stores short text that is not analyzed, such as an instrument code, a name or an identifier.
  • TextCF stores text that can be searched with a fulltext search.
  • ISODateStringCF stores a strict ISO date.
  • EDTFDateStringCF stores a date in the EDTF format, i.e. it can store a partial date (only a year, a year and month, etc.).
  • VocabularyCF stores a reference to a controlled vocabulary. Pass vocabulary_id="<vocabulary id>", where <vocabulary id> is the id of the vocabulary. The value is entered as {"id": "<term id>"} and the rest of the term is filled in when the record is saved.

Complex custom fields intermediate

If a custom field needs several sub-fields, use ComplexCF with a list of nested custom fields:

invenio.cfg
from invenio_records_resources.services.custom_fields.text import KeywordCF from oarepo_ui.services.custom_fields import ComplexCF EQUIPMENT_CUSTOM_FIELDS = [ ComplexCF("myorg:instrument", [ KeywordCF("maker"), KeywordCF("serial_number"), ]), # a list of complex values ComplexCF("myorg:calibrations", [ KeywordCF("date"), KeywordCF("performed_by"), ], multiple=True), ]

The value is an object (or a list of objects with multiple=True):

{ "custom_fields": { "myorg:instrument": {"maker": "Acme", "serial_number": "SN-1234"} } }

Searching custom fields

custom_fields is indexed as a dynamic object: OpenSearch creates the mapping of each custom field automatically from the first value it indexes. Search custom fields by their full path, e.g. custom_fields.myorg\:instrument:"HPLC-2000" (the : in the field name must be escaped in the query).

UI configuration

The UI configuration of custom fields is defined in invenio.cfg under the key <MODEL>_CUSTOM_FIELDS_UI (e.g., EQUIPMENT_CUSTOM_FIELDS_UI). It is a list of sections, each with a title and a list of fields:

invenio.cfg
from invenio_i18n import lazy_gettext as _ EQUIPMENT_CUSTOM_FIELDS_UI = [ { "section": _("Instrument"), "fields": [ { "field": "myorg:instrument", "ui_widget": "Input", "props": { "label": _("Instrument"), "placeholder": _("Serial number ..."), "icon": "pencil", "description": _("Specify the serial number of the instrument..."), }, }, ], }, ]
KeyDescription
sectionTitle of the section
hiddenIf True, the section title is not shown on the record detail page
fields[].fieldName of the custom field, without the custom_fields. prefix
fields[].ui_widgetWidget used to edit the field in the deposit form, e.g. Input, TextArea, RichInput, Dropdown or AutocompleteDropdown (for vocabularies)
fields[].propsProperties passed to the widget: label, placeholder, icon, description, …
fields[].props.landing_page_componentOptional name of a JinjaX component that renders the value on the record detail page

Wrap labels and descriptions in lazy_gettext so that they can be translated.

Record detail page intermediate

On the record detail page, the configured sections are rendered by the ICustomFields component from oarepo-ui. Each field is rendered as a label with its value. To render a field differently, create a JinjaX component and set its name in landing_page_component inside props:

invenio.cfg
EQUIPMENT_CUSTOM_FIELDS_UI = [ { "section": _("Instrument"), "fields": [ { "field": "myorg:instrument", "ui_widget": "Input", "props": { "label": _("Instrument"), "landing_page_component": "InstrumentField", }, }, ], }, ]

The component receives the value of the field as d:

ui/templates/InstrumentField.jinja
{# def d #} <strong>{{ d }}</strong>
Last updated on