Simple Related List Component
Allows editing and relating mediatype elements that are in some way related to the main mediatype of the page, displayed as a list. The related elements can be sorted as the administrator wants.
The React shell renders it as a table sitting in a sibling tab of the main Form Component. Each row has its own remove button; the toolbar has "Refresh" and "Add Related" (with sub-options Add New, Add Existing). When the list is filterable, a Filters chip-row sits above the table.

Related docs.
System Overview — RenderV2 — the request that returns{schema, data}to this widget.
6.0/React Components — Default List Schema/Data — the React shell renders Related Lists using the same Schema/Data shape as standalone lists.
Form Component — the typical parent component sitting in the same page.
Ordered Related List — variant that adds drag-and-drop reordering.
What the React renderer gives you
- Sortable columns (click the header).
- Filter chip-row above the table (when filtering is enabled).
- ID search at the top of the table (with operator dropdown and search button).
- Server-driven pagination — page numbers, page-size selector.
- "Refresh" toolbar button (toggle via
SHOW_REFRESH_BUTTON). - "Add Related" toolbar button with sub-options:
- "Add New" — opens the create page for the related mediatype
(controlled by
ALLOW_ADD_NEW). - "Add Existing" — opens a side selector to pick existing records
(controlled by
ALLOW_ADD_EXISTING).
- "Add New" — opens the create page for the related mediatype
(controlled by
- Per-row remove button (controlled by
ALLOW_REMOVE). - Optional "Clear All" button to remove all related items at once
(controlled by
ALLOW_CLEAR_ALL). - Header Actions and Item Actions — extension points that let a customer remote add buttons to the component header and to each row, gated by the backend. See the next section.
Header Actions and Item Actions
The Related List widget exposes two extension points for custom buttons that customer customisations can plug in:
| Extension point | Location in the UI | Typical use |
|---|---|---|
| Header Action | The component's top-right toolbar, next to Add Related / Refresh | Operate on the whole list — refresh an external cache, bulk-publish, export to a custom format |
| Item Action | Per-row, next to the trash icon | Operate on one record — preview, run a workflow, send a notification, mirror a state change |
The example below shows a single header action button (a circular arrow icon) added to the right of the Refresh button on a customer deployment's related list:

How the contract works
The contract is defined in the React shell at
packages/modules/src/modules/decorators/related-list/types.ts:
type ItemActions = { [actionKey: string]: ElementType };
type HeaderActions = { [actionKey: string]: ElementType };
export type RelatedList = {
events?: {
delete?: (id: number | string) => void;
deleteAll?: () => void;
refresh?: () => void;
refreshing?: boolean;
onChange?: (value: RelatedListValue) => void;
revalidateView?: () => void;
};
itemActions?: ItemActions;
headerActions?: HeaderActions;
};
A customer remote's main entry exports a relatedList object with
the actions registered under string keys:
// In a customer federated remote (any project's React remote)
// src/core-decorators/related-list/index.ts
import refreshUsersCache from './header-actions/refresh-users-cache';
import kickSession from './item-actions/kick-session';
import previewClip from './item-actions/preview-clip';
const relatedList = {
headerActions: {
refreshUsersCache,
},
itemActions: {
kickSession,
previewClip,
},
};
export default relatedList;
The shell merges every remote's decorator at startup (via the
useDecorators('relatedList') hook) and reads them at render time.
Multiple remotes can register actions independently — each one
deployed alongside the customer's own React widgets.
Backend gates visibility
A registered action is only rendered when the backend signals it should be active. This keeps permission and feature-flag logic on the C# side and lets the React decorator declaration stay static across deployments.
Header actions are filtered against schema.configuration[actionKey]:
// packages/core-components/src/components/related-list/components/header-actions/helpers/...
return Object.entries(actions)
.filter(([name]) => Boolean(configuration[name])) // ← schema flag
.map(([, component]) => component);
So to render a header action registered under the key
refreshUsersCache, the C# component must emit
schema.configuration.refreshUsersCache = true for the page(s)
where the action should appear, and not for other pages.
Item actions are filtered against the row's permissions map:
// packages/core-components/src/components/related-list/components/item-actions/helpers/permissions.ts
return Object.entries(actions)
.filter(([key]) => permissions[key]) // ← row permission
.map(([, component]) => component);
So kickSession appears on a row only when
data.items[i].permissions.kickSession === true. This gives
fine-grained per-row visibility — e.g. don't show Reset PIN on a
row whose underlying record doesn't currently have a session active.
Example — Item Action in a populated row
Per-row item actions render to the right of each row, between the row's data and the standard remove button. Here's a Related List where every row carries a custom Reset PIN item action installed by a customer remote — visible as the small circular-arrow icon at the right edge of each row:

Whether the button renders is gated by
data.items[i].permissions[actionKey] from the backend (the section
below covers the contract). In this example all three rows carry the
flag, so the action is visible on every row; on rows where the
backend omits the flag, the action button is simply absent.
Props the actions receive
Header Action props (from HeaderActions.tsx):
{
schema: RelatedListComponentSchema;
events?: Decorators['relatedList']['events'];
componentKey?: string;
}
Item Action props (from ItemActions.tsx):
{
schema: RelatedListComponentSchema;
row: RelatedListComponentItem;
size?: 'small' | 'middle' | 'large';
items: RelatedListComponentItem[];
events?: Decorators['relatedList']['events'];
}
The events object exposes the shell-level lifecycle hooks that
actions typically call into:
refresh()— re-pull the component's data.delete(id)/deleteAll()— remove rows.onChange(value)— notify the shell of a value change (triggerssetDirty(true)upstream).revalidateView()— full re-validate of the parent form.
Example — a Header Action
The screenshot earlier in this section shows a "refresh users cache" header action installed by a customer deployment. The action is a small button that emits a refresh event on a shared event bus and shows a loading spinner while the work runs. A minimal implementation looks like:
// Example from a customer remote — registered as `refreshUsersCache`
import { type FC, useEffect, useState } from 'react';
import { Tooltip } from '@agilecontent/ui';
import { ReloadOutlined } from '@agilecontent/ui/icons';
import { Button } from './refresh.styled';
import {
emitRefreshEvent,
listenRefreshEventFor,
} from 'common/event-bus/refresh-event';
import { RefreshEventType } from 'common/enums';
// Tag the action so the event bus can scope its fan-out
const pageConfig = {
pageKey: '<your-page-key>',
templateComponentKey: '<your-template-component-key>',
};
const RefreshUsersCache: FC = () => {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const stop = listenRefreshEventFor(
RefreshEventType.UsersCacheRefreshCompleted,
pageConfig,
() => setIsLoading(false),
);
return stop;
}, []);
const handleClick = () => {
setIsLoading(true);
emitRefreshEvent(RefreshEventType.UsersCacheRefresh, pageConfig);
};
return (
<Tooltip placement="top" title="Refresh users cache">
<Button icon={<ReloadOutlined />} loading={isLoading} onClick={handleClick} />
</Tooltip>
);
};
export default RefreshUsersCache;
The corresponding C# code must add refreshUsersCache: true to the
schema's configuration for the relevant component on the relevant
page(s) — typically by extending the
MapSchema's configuration dictionary on a customer-specific
subclass of the Related List component.
Example — an Item Action
A typical per-row action receives the row data and runs an API call against the underlying record. Minimal implementation:
// Example from a customer remote — registered as `kickSession`
import { type FC, useState } from 'react';
import { Tooltip, useModal } from '@agilecontent/ui';
import { DisconnectOutlined } from '@agilecontent/ui/icons';
import { useContentContext, type Decorators } from '@agilecontent/mib-modules';
import { Button } from './kick-session.styled';
import { Kick } from './api';
type Props = {
row: Record<string, unknown>;
size?: 'small' | 'middle' | 'large';
events?: Decorators['relatedList']['events'];
};
const KickSession: FC<Props> = ({ row, size }) => {
const [modal, modalContext] = useModal();
const contentContext = useContentContext();
const [isLoading, setIsLoading] = useState(false);
const userIds = contentContext.pageIds?.split(',').map(Number) ?? [];
const userId = userIds[0] ?? null;
const handleClick = () => {
setIsLoading(true);
Kick(userId as number, row.DeviceId as string, row.DeviceType as number)
.then(() => modal.info({ title: 'Kicked', content: 'Session has been kicked' }))
.catch(() => modal.info({ title: 'Error', content: 'Could not kick session' }))
.finally(() => setIsLoading(false));
};
return (
<Tooltip placement="top" title="Kick session">
{modalContext}
<Button size={size} icon={<DisconnectOutlined />} loading={isLoading} onClick={handleClick} />
</Tooltip>
);
};
export default KickSession;
The corresponding C# side must include kickSession: true in
data.items[i].permissions for every row where the action should be
clickable — typically computed at render time based on row state and
user permissions.
Registering an action — recipe
- Write the React component in your federated remote, under
src/core-decorators/related-list/{header,item}-actions/<action-name>/. - Re-export the component from your remote's
src/core-decorators/related-list/index.tsunder the matchingheaderActionsoritemActionskey. - C# backend — extend a customer-specific subclass of the
Related List component (or use a plugin) and add the matching flag:
- Header action →
schema.configuration[actionKey] = true. - Item action →
data.items[i].permissions[actionKey] = true.
- Header action →
- Test in Storybook with mock data:
<RelatedList schema={{ ...mockSchema, configuration: { refreshUsersCache: true } }} data={mockData} /> - Deploy the remote (nginx picks up the new
remoteEntry.js) and deploy the C# customisation if you added schema or permission flags.
The contract is intentionally simple — actions are plain React components with one prop contract and a string registration key. The visibility-gating mechanism keeps coupling between the customisation remote and the C# layer to a single shared flag name per action.
Where this applies
These extension points are scoped to the 'related-list'
decorator namespace in
packages/modules/src/modules/decorators/related-list/types.ts,
consumed by the core React RelatedList widget. They apply to:
- The Simple Related List Component (this doc).
- Any custom widget that opts into the same decorator by calling
useDecorators('relatedList').
They do not apply to the Related Form
component out of the box — Related Form is rendered through a
different path (currently legacy MVC or a custom federated remote)
and does not subscribe to this decorator. If you ship a custom
relatedform widget, you can re-use this namespace or define your
own.
Configuration Keys
These are all available Configuration Keys for the Related List Component.
The configuration must be inserted into the database table:
MIB3UX_PAGE_COMPONENT_CONFIGURATIONS
TITLE
The title rendered in the component header.
ALLOW_ADD_EXISTING
Boolean. True (default): the Add Existing action is enabled.
False: hidden.
ALLOW_ADD_NEW
Boolean. True: the Add New action is enabled. False: hidden.
Note
This pairs with RelatedCopyType for the copy-with-relateds flow
and affects its applicability:
RelatedCopyType.DoNotCopy/ShallowCopy— ifAllowAddNewistrueand the user lacks write permission on the MediaType, the data is not copied.RelatedCopyType.DeepCopy— ifAllowAddNewisfalseor the user lacks write permission, the data is not copied.
ALLOW_BULK_EDIT
Boolean. True (default): page item can be bulk-edited. False:
bulk-edit is disabled for this component.
ALLOW_EDIT
Boolean. True (default): the page item can be edited. False:
component edition is disabled regardless of user permissions.
ALLOW_REMOVE
Boolean. True (default): items can be removed via the per-row remove
button. False: removal is disabled regardless of user permissions.
ALLOW_CLEAR_ALL
Boolean. True: the "Clear All" button is enabled. False (default):
the button is hidden.
ASIDE_FIELDS
Specifies which columns are shown in the related aside (the side-pane opened when adding an existing related record).
CONFIGURATION_KEY: ASIDE_FIELDS
CONFIGURATION_VALUE: ID,NAME,SOURCE,DATEINS,INSTANCE_ID,OWNER
ASIDE_FILTERS
Custom filters for items shown in the related aside.
CONFIGURATION_KEY: ASIDE_FILTERS
CONFIGURATION_VALUE: { Field: 'GVP_GENRES[NAME]', Operator: 'EQUAL', Value: 'action' }
Per-field form:
CONFIGURATION_KEY: ASIDE_FILTERS[<RELATED_FIELD>]
CONFIGURATION_VALUE: <FILTER1>,<FILTER2>
Dynamic values referencing a form field via
[templateComponentKey.columnName] are supported. See the Form
Component for the syntax detail.
BATCH_REFERENCE_IDENTIFIER
Sets the batch reference to something other than the component's
default TemplateComponentKey.
DENY_ON_READONLY
Boolean. True: deny save when the user lacks permission on a related
component or field. False: allow save even if the user lacks
permission on some.
EXCLUDE_FIELDS
A comma-separated list of admField column names to exclude from the
display.
CONFIGURATION_KEY: EXCLUDEFIELDS
CONFIGURATION_VALUE: name,timezone,genres
HIDE_BULK_EDIT
Boolean. True: bulk-edit is disabled. False (default): bulk-edit is
enabled.
IGNORE_ADMFIELDS
Boolean. True: related admFields of the page are not loaded.
False (default): all related mediatypes associated with the main one
are loaded.
For a movies list page that contains related fields source / genres /
content-category, setting IGNORE_ADMFIELDS=true means the API
pre-load will skip those related-field queries.
INCLUDE_FIELDS
A comma-separated list of admField column names. Forces the
component to display only these fields.
IS_CHILD_REQUIRED
Boolean. True: force the user to add at least one register before
saving. False (default): no requirement.
For a movie form with a Simple Related List of genres, setting this on the genre component requires at least one genre to be added before the page can be saved.
MEDIATYPE
The name of the mediatype loaded in the component.
PARENT_BATCH_REFERENCE_IDENTIFIER
Sets the parent batch reference to something other than the default
ParentTemplateComponentKey.
PARENT_MEDIATYPE
Links the main mediatype of the page to the mediatype of this related component.
CONFIGURATION_KEY: PARENT_MEDIA_TYPE
CONFIGURATION_VALUE: MVP_MOVIES
READONLY
Boolean. True: forces the component to be read-only regardless of
user permissions. False (default): honour user permissions.
READONLY_FIELDS
A list of fields to be made read-only regardless of user permissions.
SORTING_COLUMNS
A comma-separated list of columns to sort the list by.
CONFIGURATION_KEY: SORTINGCOLUMNS
CONFIGURATION_VALUE: ID,SOURCE
SORTING_TYPE
ASC (default — ascending) or DESC (descending). Ignored if
SORTING_COLUMNS is not set.
HIDE_IMAGE_PREVIEW
Boolean. Hides the image preview of IMAGES admField cells.
Configurations Exclusive to React (MibReact)
The settings below apply only when the page is rendered through the
React shell (~/app/...).
MAX_FILE_SIZE
Limits upload sizes when the related list has a file mediatype (e.g.
Images). May also be set application-wide via MaxFileSizeDefaultValue.
MAX_FILE_SIZE[FieldName]
Limits upload sizes for related fields that point to file mediatypes.
RELATED_ITEMS_LIMIT
Integer. Limits the maximum quantity of items that can be related.
SHOW_REFRESH_BUTTON
Boolean. Whether to display the "Refresh" toolbar button.
ALLOW_SELECTABLE_SEARCH
Boolean. Toggles the filtering functionality on the list.
FOOTER_INFORMATION
String. Adds a footer message to the component. May contain HTML.
CONFIGURATION_KEY: FOOTER_INFORMATION
CONFIGURATION_VALUE: <b>Footer Information</b>
ASYNC_MODE
Boolean. Default false. When true, pagination is handled by the
backend — data is loaded dynamically as the user navigates pages.
Note
Has no effect when ALLOW_ROW_REORDER is enabled. In that case
pagination always occurs on the front-end regardless of ASYNC_MODE.
REDIRECT_TO_EDIT_PAGE_BY_FIELD
Dictionary<string, string>. Defines a field whose value, when
clicked, redirects to the edit page of the mediatype specified in the
value.
CONFIGURATION_KEY: REDIRECT_TO_EDIT_PAGE_BY_FIELD[ID]
CONFIGURATION_VALUE: react_movies_edit
REFRESH_JSON
Used exclusively by custom related lists that want to use the React
refresh endpoint. Allows creating component-specific payloads that are
sent on the refresh request — the customised JSON becomes
PostbackComponentRequest.JsonData in the C# refresh method.
{
"pageId": {ID},
"refreshAction": "ExecuteCustomRefresh",
"otherCustomProp": 123
}
Custom field validation
Starting with MIB 6.0, custom validation criteria can be implemented for field persistence. See Custom Validation plugin.
Schema and Data shape
The Simple Related List speaks the same Default List Schema
- Default List Data shapes as the standalone List Component — the React shell renders both with the same widget.
The difference is at the BFF: a Related List's data is scoped to the records related to the parent form's current item, while a standalone List's data is the full mediatype query.
Source reference
| React type-key (effective) | relatedList — set via COMPONENT_VIEW_TYPE on the template-component row, since the raw class name resolves to simplerelatedlist which is not directly mapped in the shell's registry |
| BFF assembly | MibServer3.Web |
| BFF class | MibServer3.Web.Component.SimpleRelatedListComponent |
MIB3UX_COMPONENTS.COMPONENT_KEY |
mib_default_simplerelatedlistcomponent |
| React widget source | packages/core-components/src/components/related-list/RelatedList.tsx |
| Schema / data contract | Default List Schema — Related Lists reuse the standalone List schema; row scoping is at the BFF |
Custom backend, core React. Emit the Default List Schema + Data
shape and set COMPONENT_VIEW_TYPE = 'relatedList' on the
MIB3UX_TEMPLATE_COMPONENTS row that points at your custom class. The
RelatedList.tsx widget handles all four related-list variants
(Simple, Ordered, Related Form, Related Auto Complete) — the variant
behavior is driven by flags in the schema (Reorderable, Aside,
Autocomplete) rather than by different React components. So a single
custom backend can render as any variant by setting the right schema
flags.
See also
- Ordered Related List — adds drag-and-drop row reordering (shares most configuration keys with this component)
- Related Form Component — same data relationship but rendered as a form per record
- Related Auto Complete Component — same data relationship but rendered as an autocomplete field
- Form Component — typical parent component
- List Component — standalone-page variant
- Component Creation — author a custom related-list-shaped component
- Custom Validation plugin