Page extensions
Reusable modules expose five extension points. Host applications contribute to built-in pages without editing package files. Ordinary generated business pages define their own columns, controls and CRUD methods.
| Extension point | Module option | Guide |
|---|---|---|
| Columns | entityPropContributors | Table columns |
| Create fields | createFormPropContributors | Form fields |
| Edit fields | editFormPropContributors | Form fields |
| Row actions | entityActionContributors | Entity actions |
| Toolbar | toolbarActionContributors | Toolbar actions |
Start with one contributor
import { EntityProp, PropType, EntityAction } from '@lsw-abpvue/components';
import { LocalizationService } from '@lsw-abpvue/core';
import { IdentityComponents, type IdentityConfigOptions } from '@lsw-abpvue/identity';
import type { IdentityUserDto } from '@lsw-abpvue/identity/proxy';
import { ToasterService } from '@lsw-abpvue/theme-shared';
export const identityOptions = {
entityPropContributors: {
[IdentityComponents.Users]: [
props =>
props.addTail(
EntityProp.create<IdentityUserDto>({
name: 'displayLabel',
type: PropType.String,
displayName: 'AbpIdentity::DisplayName:Name',
valueResolver: data => data.record.name || data.record.userName || '',
}),
),
],
},
entityActionContributors: {
[IdentityComponents.Users]: [
actions =>
actions.addTail(
EntityAction.create<IdentityUserDto>({
text: 'AbpIdentity::UserName',
action: data => {
const localization = data.getInjected(LocalizationService);
data.getInjected(ToasterService).info(
{
key: 'BookStore::SelectedUser',
defaultValue: data.record.userName || '',
},
localization.t('AbpIdentity::Users'),
);
},
}),
),
],
},
} satisfies IdentityConfigOptions;The map key is the exact public component key, such as Identity.UsersComponent. A contributor receives a mutable linked list and changes it. It does not return the new list. Pass its option map to the module route factory, as shown in the users tutorial.
Use a stable field name or localization key when locating an existing entry. Labels rendered in the current language are not stable identifiers.
Assembly order and scope
- The module adds its defaults.
- Supported backend object-extension metadata becomes columns and controls.
- Host contributors add, remove or replace entries.
- The route renders the page under the module's injector.
Entering the route assembles fresh lists. Contributors should describe the result and avoid registering more contributors from inside a contributor. A module initializer adds its menu; the route factory owns the page's extension options.
Callbacks can be outside setup. data.getInjected(Token) resolves services from the extension's injector; calling ordinary inject from a click callback is invalid.
Work with the linked list
props.addAfter(customProp, prop => prop.name === 'userName');
props.dropByValue(prop => prop.name === 'email');
props.addByIndex(customProp, 2);Here props and customProp refer to the typed list and property supplied by your contributor. Relative insertion is more resilient than a numeric position. If addAfter finds no match it appends; addBefore with no match prepends. dropByValue removes the first match; dropByValueAll removes every match.
See extension behavior for callback context, list operations and runtime defaults.
Row and toolbar context
EntityAction<IdentityUserDto> receives one row in data.record. ToolbarAction<readonly IdentityUserDto[]> receives current-page records, not selected rows and not every record in the database. Plain RowAction<R> on a business page receives R directly.
valueResolver may return a value, Promise, Ref or getter. Its result is text. Rich cells use a Vue component, whose props include record, index, prop and value. Form components use a different model contract; see form fields.
Preserve backend behavior
An extra field uses isExtra: true and an actual backend extension property. A new frontend field does not automatically create backend storage. Permission and visibility predicates only control UI; APIs remain authorized on the server.
Contributors change configured entries. A full component replacement owns its UI and commands. See replacement for preserving the built-in page while wrapping it.