Skip to content

Plugin providers and contribution contracts

Implement only the provider methods your plugin uses. All get… methods below return arrays. Definition methods return structs; contributor methods return CFC objects implementing callbacks. [] means no contribution.

Provider method Returns
getAdminRouteDefinitions() Admin route structs.
getAdminPermissionDefinitions() Permission structs.
getAdminSettingsDefinitions() Settings item structs.
getAdminNavigationDefinitions() Navigation item structs.
getIntegrationDefinitions() Integration entry structs.
getIntegrationStatusResolvers() Objects with resolveIntegrationStatus(integrationDefinition, requestContext).
getAdminOrderContributors() Objects providing order cards or columns.
getStorefrontRouteDefinitions() Storefront route structs.
getStorefrontHeaderActionContributors() Header-action callback objects.
getStorefrontProductBadgeContributors() Product-badge callback objects.
getLineItemConfigurationContributors() Product-detail/configuration callback objects.
getLineItemPricingContributors() Line-price adjustment callback objects.

The last four are described under Storefront contributions.

One entry in getAdminRouteDefinitions()
{
'name' = "plugin.example.hello.index",
'methods' = ["GET"],
'path' = "/plugins/example.hello",
'controller' = "netseller_plugin_example_hello.controllers.hello_controller",
'action' = "index",
'middleware' = ["admin.auth", "admin.permission:plugin.example.hello.view"]
}
Field Type Contract
name string Required; starts with plugin.<plugin-id>.. Use a unique stable name.
path string Required; under /plugins/<plugin-id>. NetSeller prefixes the configured admin base path.
methods string[] HTTP methods; defaults to ["GET"]. Declare POST explicitly for mutations.
controller string Required CFC component path, using the plugin mapping.
action string Required public controller method, called without arguments.
middleware string[] Must contain admin.auth and an admin.permission:<key> entry.
area string Normalized to admin for this provider method.

Use {recordId} in a path for a route parameter. Read it from request.currentRoute.params.recordId and validate it. Generate URLs with request.buildUrl(routeName, params), not a hardcoded /admin prefix.

One entry in getAdminPermissionDefinitions()
{
'key' = "plugin.example.hello.view",
'action' = "view",
'defaultName' = "View Hello",
'defaultDescription' = "Open the Hello plugin.",
'translationKey' = "plugin.example.hello.permission_view"
}

key is required and must begin with plugin.<plugin-id>.. Optional action defaults to the last key segment. defaultName and defaultDescription provide labels; translationKey and descriptionKey translate them. The module is plugin.<plugin-id>; a plugin cannot register permissions under a different module.

The loader also accepts permissionKey, actionKey, labelKey, name and description as the corresponding normalized fields. Prefer the example’s naming consistently.

Settings item
{
'id' = "plugin_example_hello",
'translationKey' = "plugin.example.hello.title",
'defaultName' = "Hello",
'icon' = "box",
'routeName' = "plugin.example.hello.index",
'permissionsAll' = ["plugin.example.hello.view"]
}

Return this from getAdminSettingsDefinitions() for a configuration page. Plugin settings entries appear in the System settings tab and are sorted by their translated names.

For a main navigation entry, return the same basic fields from getAdminNavigationDefinitions() and add:

Navigation-specific fields
'routePrefixes' = ["plugin.example.hello."]
Field Meaning
id Required stable item identifier; use a plugin-specific value.
translationKey, defaultName Label key and fallback.
icon Shared admin icon name, for example box or settings.
routeName Named target route. Without one, the item has no actionable link.
permissionsAll Every listed permission is required.
permissionsAny At least one listed permission is required. If both arrays are present, both rules apply.
routePrefixes Navigation only: route-name prefixes that mark the item active.
children Navigation only: child items with IDs, labels, routes and permission rules.
searchKeywords Settings only: additional searchable strings.
searchKeywordTranslationKey Settings only: translated search terms.

Visibility filtering is not authorization. Protect the destination route independently.

Use an integration entry for an external connection or status-bearing capability. It is separate from the plugin manager.

One entry in getIntegrationDefinitions()
{
'id' = "example.hello",
'groupKey' = "other",
'translationKey' = "plugin.example.hello.title",
'defaultName' = "Hello",
'descriptionKey' = "plugin.example.hello.description",
'defaultDescription' = "Example connection.",
'icon' = "box",
'routeName' = "plugin.example.hello.index",
'sortOrder' = 100,
'ownerType' = "plugin",
'ownerKey' = "example.hello",
'statusResolverKey' = "plugin.example.hello",
'capabilities' = ["example"],
'permissionsAll' = ["plugin.example.hello.view"]
}

Required fields are id, groupKey, at least one label (translationKey or defaultName), ownerType, ownerKey and statusResolverKey. Supply an icon as the visual fallback. IDs must be unique.

Use ownerType = "plugin" and your plugin ID as ownerKey. Group keys include ai, payments, shipping, accounting, search, crm and other. Optional routeParams is a struct of route arguments; logo is {src, alt} for a locally served image. capabilities is an array of descriptive capability strings. Permission arrays follow the same rules as navigation.

The demo returns [this] from getIntegrationStatusResolvers() and implements the callback in the same provider:

Provider callback
public struct function resolveIntegrationStatus(
required struct integrationDefinition,
struct requestContext = {}
) {
if (arguments.integrationDefinition.statusResolverKey neq "plugin.example.hello") {
return {};
}
return { 'status' = "configured", 'details' = "Connection configured." };
}

Return {} for entries you do not own. For your entry return status as configured, missing_configuration, warning or error, and a user-facing details string. Resolve the real state in your plugin service; the constant above illustrates the shape only. Do not expose credentials or raw exceptions.

An optional onPluginEnable(context) method runs after successful migrations and translation import, before activation completes. context.datasource is the target datasource and context.plugin describes the package, including its id.

Use this hook for idempotent provisioning through documented services. It can run again on reactivation or update. Throwing an exception aborts activation. There are no corresponding documented disable/delete hooks.

Registry definitions contain metadata, not inline SQL or HTML. Callback-specific payloads may contain HTML only where an explicit storefront contract calls for it. Do not modify Core files or call arbitrary shared services.