Community metadata extensions in B2INST#
Community metadata extensions add fields to an instrument’s standard PIDINST metadata. For example, Sensor.Community records a sensor’s location, station, environment, and mobility. Use the standard instrument fields for information already covered by PIDINST, such as the instrument name, manufacturer, model, and owner.
This guide is for community representatives preparing an extension with the B2INST service administrators. It describes the implementation in the B2INST v3 codebase. Acceptance of a schema definition, display in the deposit form, validation of submitted values, and search indexing are separate capabilities; the current limitations are explained below.
For day-to-day community management, see the community guide. For entering instrument metadata, see the user guide.
Preparing and installing a community metadata extension#
Agree on the fields and their intended values with the community, then send the JSON definition to the B2INST service administrators. Community ownership does not provide a schema editor in the community settings. The current schema upload API is incomplete, so it should not be used as a self-service installation workflow.
The administrator can install a definition from the configured deployment environment:
invenio community-extensions add '<community-id>' sensorcommunity.json
For the Sensor.Community example below, replace <community-id> with that community’s
ID and save the definition as sensorcommunity.json. The command checks the definition
against that community, stores a new schema version, and prints its UUID. Installing
another version does not rewrite existing instrument records. Keep the UUID associated
with each record when interpreting its extension values.
Test the form, draft save, publication, display, and search with representative values on the test service before adopting an extension for routine use.
Schema structure and field names#
The definition has three required top-level entries:
| Entry | Meaning |
|---|---|
community_slug |
The community’s exact slug, as returned by GET /api/communities/<community-id-or-slug>. |
section |
Heading for the extension section in the instrument form. |
fields |
A list of field definitions. |
Each field requires name, type, ui_widget, and ui_props. The ui_props object
must contain a label; it can also contain a description, placeholder, and
required flag. There is no automatic default widget selected by the schema validator.
Use a widget available in the deployed form and test it with the field’s value shape.
Use the lowercase community slug followed by a colon for every top-level field, for
example sensorcommunity:station. A nested field’s children use local names such as latitude
without the prefix. Although the definition validator exempts top-level nested
fields from its prefix check, give those fields the prefix too: record updates use
the prefix to associate extension values with the selected community.
Supported field definitions#
type |
Declared value | Options and limits |
|---|---|---|
text |
A string | field_cls may be stripped, url, email, or enum. Omission uses sanitised text. For enum, provide options. |
date |
A date | The backend field builder uses a date value; its date format is YYYY-MM-DD. The exported JSON Schema currently describes it differently; see validation below. |
integer |
A whole number | Use a JSON number, rather than a quoted numeric string, in API values. |
boolean |
true or false |
Use JSON booleans in API values. |
vocabulary |
A controlled value | Supply either a non-empty options list or a vocabulary_id available in B2INST. |
nested |
An object or repeated objects | Define child fields in properties. Form support has additional limits described below. |
number is not an accepted type in this implementation. There is no declared decimal
number field or dedicated date-interval or coordinate-pair type. Agree an appropriate
representation with the service administrators rather than copying unsupported field
types into a definition.
For a small controlled list, use type: "vocabulary" with options and a Dropdown
widget. Put options on the field definition, not inside ui_props. A
vocabulary_id refers to a vocabulary already configured in the service; it is not a
URL for an arbitrary external vocabulary. If both are supplied, the builder chooses
vocabulary_id.
Required fields and multiple values#
Set ui_props.required to true to declare a required field. This also supplies the
required indication to the form. It is not a guarantee that every API submission will
be checked against the extension definition; see validation.
Set the field’s top-level multiple to true to declare an array. For widgets that use
it, such as Dropdown, also set ui_props.multiple to true: the builder does not
copy the top-level flag into the widget props.
The backend builder can represent repeated text, dates, integers, booleans, and
vocabulary values. This does not automatically provide a suitable repeating form
control for each type. MultiLineInput is the custom repeating text control; a
multi-select Dropdown can be used for a list of explicit vocabulary options.
Test other combinations before offering them to users.
A required array is not automatically required to contain an item. Likewise, marking a text field as required does not impose a non-empty-string or minimum-length rule.
Complete community metadata extension example#
This definition is for Sensor.Community, whose community slug is sensorcommunity.
It is taken from migration/migration_files/schema_extensions/sensorcommunity.json
in the B2INST deployment and defines the Sensor Location Metadata section shown
in the screenshot below. It includes repeated locations, a station number, environment
and mobility options, sensor height, and descriptions of traffic and location.
Save this JSON as sensorcommunity.json. To adapt it for another community, replace
community_slug and every sensorcommunity: prefix with that community’s exact slug.
{
"community_slug": "sensorcommunity",
"section": "Sensor Location Metadata",
"fields": [
{
"name": "sensorcommunity:location",
"type": "nested",
"multiple": true,
"use_as_filter": false,
"ui_widget": "MultiNestedInput",
"ui_props": {
"label": "Location",
"icon": "map marker alternate",
"description": "Geographic position of the sensor.",
"required": false,
"multiple": true,
"properties": {
"_object_name": "Location"
}
},
"properties": [
{
"name": "latitude",
"type": "text",
"use_as_filter": false,
"ui_widget": "Input",
"ui_props": {
"label": "Latitude",
"description": "Latitude in decimal degrees (-90 to 90)."
},
"field_cls": "stripped"
},
{
"name": "longitude",
"type": "text",
"use_as_filter": false,
"ui_widget": "Input",
"ui_props": {
"label": "Longitude",
"description": "Longitude in decimal degrees (-180 to 180)."
},
"field_cls": "stripped"
}
]
},
{
"name": "sensorcommunity:station",
"type": "integer",
"multiple": false,
"use_as_filter": false,
"ui_widget": "Input",
"ui_props": {
"label": "Station",
"icon": "hashtag",
"description": "Station number at which the sensor is installed (non-negative).",
"required": false,
"multiple": false
}
},
{
"name": "sensorcommunity:environment",
"type": "vocabulary",
"field_cls": "enum",
"options": [
"indoor",
"outdoor"
],
"multiple": false,
"use_as_filter": false,
"ui_widget": "Dropdown",
"ui_props": {
"label": "Environment",
"icon": "tree",
"description": "Whether the sensor is positioned indoors or outdoors.",
"required": false,
"multiple": false,
"clearable": true
}
},
{
"name": "sensorcommunity:mobility",
"type": "vocabulary",
"field_cls": "enum",
"options": [
"stationary",
"mobile"
],
"multiple": false,
"use_as_filter": false,
"ui_widget": "Dropdown",
"ui_props": {
"label": "Mobility",
"icon": "exchange",
"description": "Whether the sensor is stationary or mobile (default is stationary).",
"required": false,
"multiple": false,
"clearable": true
}
},
{
"name": "sensorcommunity:sensorLevelAboveGround",
"type": "integer",
"multiple": false,
"use_as_filter": false,
"ui_widget": "Input",
"ui_props": {
"label": "Sensor Level Above Ground (cm)",
"icon": "arrows alternate vertical",
"description": "Height of the sensor above ground in centimeters (non-negative).",
"required": false,
"multiple": false
}
},
{
"name": "sensorcommunity:sensorLocationRelativeToTraffic",
"type": "text",
"field_cls": "stripped",
"multiple": false,
"use_as_filter": false,
"ui_widget": "Input",
"ui_props": {
"label": "Sensor Location Relative To Traffic",
"icon": "car",
"description": "Description of nearby traffic volume or conditions.",
"required": false,
"multiple": false
}
},
{
"name": "sensorcommunity:shortDescriptionOfLocation",
"type": "text",
"field_cls": "stripped",
"multiple": false,
"use_as_filter": false,
"ui_widget": "MultiLineInput",
"ui_props": {
"label": "Short Description of Location",
"icon": "align left",
"description": "Brief description of the sensor location/context.",
"required": false,
"multiple": false
}
}
]
}
Once installed, selecting Sensor.Community in the instrument deposit form loads these community metadata extensions. The section uses the labels and descriptions in the definition.
Sensor.Community’s Sensor Location Metadata section, corresponding to the definition above. Use Add Line under Location to enter latitude and longitude.
All fields in this definition are optional. Coordinate ranges, non-negative station
numbers and heights, and the stated default mobility of stationary appear only in
descriptions; the definition does not enforce those ranges or set that default.
Choose the mobility value explicitly when it is known.
Values stored in an instrument record#
Community metadata extension values belong inside metadata.community_extension.
The selected schema’s UUID belongs in metadata.community_extension_schema. This
excerpt uses illustrative values following the declared field types; it is not a
complete create or update request, and the screenshot does not contain these values:
{
"metadata": {
"community_extension_schema": "<installed-schema-uuid>",
"community_extension": {
"sensorcommunity:location": [
{"latitude": "51.5413", "longitude": "9.9158"}
],
"sensorcommunity:station": 12345,
"sensorcommunity:environment": "outdoor",
"sensorcommunity:mobility": "stationary",
"sensorcommunity:sensorLevelAboveGround": 250,
"sensorcommunity:sensorLocationRelativeToTraffic": "Residential street with light traffic.",
"sensorcommunity:shortDescriptionOfLocation": "Sensor mounted on an exterior wall facing a courtyard."
}
}
}
Coordinates are strings because their fields use type: "text". Station and sensor
height are integers, with height expressed in centimetres. Environment accepts
indoor or outdoor; mobility accepts stationary or mobile.
The source definition has one form/value mismatch: shortDescriptionOfLocation is
declared as a single text value (multiple: false), but its MultiLineInput widget
produces an array of strings and displays Add Line in the screenshot. The excerpt
above follows the declared single-value type. Agree a consistent field definition
and form widget with the service administrators before relying on the same value
shape across form and API submissions.
Replace the schema UUID and illustrative values before use. Keep the namespace in the
record keys: do not move these values into top-level custom_fields or the legacy
community_specific structure. Preserve the other instrument metadata when updating
a draft; see the records API.
For fields backed by a configured vocabulary_id, the backend vocabulary field uses
an object containing an id, or a list of such objects when repeated. Explicit
options use strings, as in the Sensor.Community environment and mobility fields
above. Verify the installed field’s form and API representation before integrating
a service vocabulary.
Nested objects and arrays of objects#
A nested field contains a list of child definitions in properties. Children need
their own type, ui_widget, and labelled ui_props, just like top-level fields.
For MultiNestedInput, the parent also requires
ui_props.properties._object_name, which labels each repeated object.
The sensorcommunity:location field in the complete example uses this structure.
Its latitude and longitude children are text fields, and _object_name is
Location. Setting multiple: true allows more than one location object:
{
"sensorcommunity:location": [
{"latitude": "51.5413", "longitude": "9.9158"},
{"latitude": "51.5420", "longitude": "9.9165"}
]
}
The definition and backend field builder support both a single object
(multiple: false) and an array (multiple: true), including recursive definitions.
The current MultiNestedInput form control always works with an array, however, so it
should not be paired with a single-object definition.
The generated nested widget configuration passes child labels and descriptions but does not preserve the child types, options, required flags, or repeating controls. Consequently, the form generally renders children as text inputs. Do not rely on it to provide typed dates, booleans, vocabulary selectors, or arrays inside a nested object. Recursive nested forms are also not implemented by this control. Prefer flat fields or one level of repeated objects with text children, and test the saved values and error display before adopting a nested extension.
Validation#
The extension-definition validator checks supported types, required definition keys,
community namespacing, vocabulary configuration, and the nested object label. It does
not accept the validators list used in some other schema guides: entries for
length, regex, or range are unknown fields in this implementation. There is no
conditional-required rule in this definition format.
The field builder can create typed validators, including URL and email fields, but
the instrument API currently receives metadata.community_extension as a dictionary
of raw values. It does not apply those generated field validators to the extension
block during normal record schema validation. The update component filters keys by
the selected community’s slug; that is not validation of required values, types, or
controlled terms. Clients and reviewers must therefore check the agreed constraints
rather than assuming a successful API save proves conformance.
You can inspect an installed definition through:
| Request | Result |
|---|---|
GET /api/community-schema-extension/<community-id-or-slug> |
The latest schema UUID and a link to its JSON Schema export. |
GET /api/community-schema-extension/schema/<schema-uuid> |
Generated backend, ui, facets, and search_map definitions. |
GET /api/community-schema-extension/schema/<schema-uuid>/jsonschema |
A JSON Schema export wrapped in a response object. |
For an existing instrument, inspect the UUID stored in its metadata rather than assuming that it uses the community’s latest schema. See the community schema API for requests.
The JSON Schema export has additional limitations:
- It removes the community prefix from top-level property names. Its keys are therefore
different from the namespaced keys submitted in
metadata.community_extension. - It does not retain the
urloremailformat constraints of text fields, and dates are exported asdate-timeeven though the field builder uses dates. - Nested
ui_props.requiredflags are not converted into required child properties by the normal builder/export path. - Configured service vocabularies are not fully represented by the export’s string
enumeration; explicit
optionsare the simpler supported case. - Repeated fields are exported with
uniqueItems: true, while the record API does not itself enforce uniqueness of extension values.
Treat the export as a description that needs these adjustments for client validation, not as an exact schema for an entire API request.
Search and filters#
Extension values are indexed below metadata.community_extension. Query a namespaced
field by escaping its colon, for example:
metadata.community_extension.sensorcommunity\:environment:outdoor
This example only matches if the schema is installed, records contain the field, and
the caller can access those records. Use the full indexed path; a generated short
alias in search_map is not automatically enabled in the search parser. For complete
query examples, see advanced search.
The current record mapping allows dynamic extension fields. Date and numeric string
detection are disabled, so declaring an extension field as date does not itself
install a date index mapping. Use consistent JSON value types and arrange explicit
mapping changes with the service administrators if chronological ranges or other
typed search behaviour are required. The definition’s nested type also does not
automatically create an OpenSearch nested mapping with per-object query semantics.
use_as_filter: true and facet_label let the builder describe a facet for a top-level
field. They do not, by themselves, install a working filter in the search interface.
The dynamic facet wiring is incomplete, generated paths need to match the actual
record mapping, and the generator does not recursively build facets for child fields.
Have the service administrators configure and test both the aggregation and its UI
filter before documenting it as available to users.
Choosing a maintainable community metadata extension#
Prefer a small set of flat fields with clear labels and controlled options where appropriate. Keep field names and JSON value types stable across schema versions. Changes to a definition do not migrate saved records or existing search mappings. Exercise the actual deposit form and API with valid, missing, and incorrectly typed values before depending on any rule. For requirements outside the capabilities above, discuss the necessary implementation work with the service administrators.
Last update: 06.10.2026
Last review: 06.10.2026
