Skip to content

UI extension points

A compiled extension implements IUIExtension and returns a UIManifest. Contributions that reference React components or frontend handlers also need a frontend ESM bundle. Cove imports each enabled extension’s bundle independently, registers its exports under the owning extension id, and combines the manifest contributions from every enabled extension.

This page describes the UI contributions Cove currently renders. The general extension-point catalog covers backend and provider capabilities.

Set JsBundle in extension.json when a contribution references a React component or frontend handler, and set CssBundle when custom styles are needed. Put the React components named by page, tab, slot, settings, and override contributions in the default export’s components map. Contribution ids and keys should be stable across releases because Cove uses them for registration, routing, preferences, and ordering.

Component and action-handler names are local to one extension. Different extensions can export the same name without replacing each other because Cove keys every lookup by the authoritative extension owner. Bundle URLs are versioned, and Cove can replace or unload one owner’s JavaScript, CSS, routes, slots, and handlers without disturbing another extension.

The default export can also provide actionHandlers and asynchronous onLoad and onUnload hooks. The legacy handlers field remains an alias for actionHandlers. Cove waits for lifecycle hooks, rolls back the previous owner registration when replacement fails, and does not apply the replacement manifest until activation succeeds. Keep onUnload idempotent within one load cycle.

Most contribution records contain:

  • ExtensionId, which Cove stamps with the authoritative owning extension id
  • an exported ComponentName when the contribution renders React
  • Order, where lower values render earlier
  • a stable contribution-specific id or key

Extension components render inside an error boundary. They should still handle missing data, failed requests, and permission errors locally.

See Frontend runtime API for supported module imports, authenticated requests, and Cove’s public shared component barrel.

ExtensionLoginMethod adds a host-rendered link to Cove’s unauthenticated login page. Declare it with UIManifestBuilder.AddLoginMethod(id, label, startUrl, order). Cove stamps the owning extension, rejects unsafe or duplicate declarations, and accepts only a same-origin relative start URL. Lower orders render first.

The overload with linkStartUrl also adds a provider action to the authenticated Account page. Cove posts to that same-origin endpoint and then either follows its external redirectUrl or previews its confirmationCode. Pass showOnLoginPage: false for transparent middleware that should remain linkable without showing a redundant unauthenticated login button.

Login methods never load the extension’s JavaScript bundle before authentication. The local start route must therefore be an extension backend endpoint that begins the external flow. See External authentication for the browser-binding, completion-ticket, trusted-middleware, and endpoint-authorization contracts.

UIPageDefinition adds a top-level route.

FieldBehavior
RoutePage key used by Cove routing. Keep it unique and stable.
LabelNavigation and page label.
ComponentNameExported React component. Cove passes onNavigate. One positive numeric child segment is passed as id; one non-numeric child segment is passed as slug.
ShowInNavAdds the page to the main navigation when true. Pages remain directly routable when it is false.
NavOrderPositions the navigation item.
IconOptional navigation icon. The currently recognized values are music and puzzle; other values render no icon.
RequiredPermissionRequires one permission before Cove shows or renders the page.
RequiredPermissions, RequiredPermissionModeRequires all listed permissions by default, or at least one when the mode is Any. A non-empty array takes precedence over RequiredPermission.

Unauthorized pages are omitted from extension navigation. Direct navigation renders Cove’s access-denied page instead of mounting the extension component. DetailRoute remains reserved and does not create a second route.

The child-route contract is intentionally one segment deep. Numeric children are always ids, so a numeric slug is not available, and Cove does not route a deeper child hierarchy. Keep deeper state inside the page or in query parameters.

Page permission checks only control presentation. Protect every API used by the page with the matching server-side convention described in Extension permissions.

UISlotContribution inserts content at a host-published slot. Set ContentType to component with a ComponentName, or to html with Html. Multiple contributions to the same slot render in ascending Order.

The host passes a context object appropriate to the location. Detail slots receive the current entity and onNavigate; shared list slots receive the page key, title, filter state, filter callback, total count, and loading state. Treat context fields as location-specific rather than assuming every slot receives the same props.

Slot keyLocation
app-floating-uiA viewport-level layer that persists across application routes.

This slot receives an empty context object. The host provides a fixed layer above normal application content, keeps empty areas non-interactive, and isolates each contribution’s rendering failure and lifecycle. Components must position themselves (for example, with position: fixed); their rendered controls can receive pointer events without blocking the page outside those controls. Contributions render in ascending Order. The frontend SDK and runtime component barrel export APP_FLOATING_UI_SLOT.

PageSlot keys
Videovideo-detail-actions, video-detail-main-bottom
Performerperformer-detail-actions, performer-detail-bottom
Studiostudio-detail-actions, studio-detail-sidebar-bottom, studio-detail-bottom
Tagtag-detail-actions, tag-detail-sidebar-bottom, tag-detail-bottom
Gallerygallery-detail-actions, gallery-detail-main-bottom, gallery-detail-bottom
Imageimage-detail-actions, image-detail-sidebar-bottom, image-detail-main-bottom
Groupgroup-detail-actions, group-detail-sidebar-bottom, group-detail-main-bottom, group-detail-bottom
Slot keyLocation
media-player-actionsBeside Cove’s native playback controls.
media-player-overlayOver the displayed video content, above the media and below native controls.

Both slots render on detail, quick-view, and compilation player surfaces. The frontend SDK exports MEDIA_PLAYER_ACTIONS_SLOT, MEDIA_PLAYER_OVERLAY_SLOT, and MediaPlayerExtensionContext.

The context contains the host video id, surface, current time, duration, playing state, playback rate, intrinsic media dimensions, and a container-relative contentRect after letterboxing. It also provides bounded play, pause, seek, and setPlaybackRate controls.

Interactive tools can call acquireInteractionMode(options) to request hidden native controls, paused watch tracking, or a one-time playback pause. Keep the returned release function and call it when the interaction ends. Cove also releases the owner-bound lease if the contribution is replaced, fails, leaves fullscreen, changes media identity, or unmounts. Releasing a lease restores native controls and tracking but does not resume playback automatically.

Slot keyLocation
entity-cover-editorInside Cove’s native cover dialog, below the native image control and before page-specific extra actions.

The slot is published for audio, gallery, group, image, performer, segment, studio, tag, text, and video cover dialogs. The frontend SDK exports ENTITY_COVER_EDITOR_SLOT and EntityCoverEditorContext.

The context contains entityType, entityId, coverKey (primary, front, or back), currentImageUrl, and canEdit. The canEdit value reports whether the host surface has a native cover mutation available; it does not grant an extension permission or authorize a separate API. Protect extension mutations on the server and refresh their local state after a successful change.

Slot keyLocation
list-page-toolbar-endEnd of every shared list-page toolbar.
<page-key>-list-toolbar-endEnd of a specific list page’s toolbar.
<page-key>-list-rowShared list-page row or grid extension region.
tag-card-footerFooter of a tag card.
performer-card-footerFooter of a performer card.
studio-card-footerFooter of a studio card.
video-card-contentBounded extension region within a video card.

Use only a slot key Cove publishes. An arbitrary name remains in the manifest but never renders.

UIDashboardWidgetContribution adds a widget to the personal dashboard catalog. Register widgets with UIManifestBuilder.AddDashboardWidget, optionally supplying its default and supported presentations, or pass a contribution record to the same method. Cove stamps the owning extension id, resolves component names within that owner, and preserves saved widget instances when the extension is disabled or unavailable.

FieldBehavior
IdStable widget key within the owning extension. Changing it disconnects existing instances.
Label, Description, IconCatalog metadata and the fallback label shown when the contribution is unavailable.
ComponentNameRender component receiving dashboardId, instanceId, configuration, presentation, and onNavigate.
EditorComponentNameOptional configuration component receiving configuration, presentation, onChange, and onValidityChange. Cove owns the surrounding Save/Cancel dialog.
DefaultConfigurationJSON copied into a newly added widget instance. Cove treats it as opaque extension-owned data.
AllowMultipleWhen false, the catalog allows only one instance of this contribution on a dashboard.
SupportedPresentationsDeclares Flow, Canvas, or both. Omit it for a Flow-only widget; other defaults must declare their supported presentations explicitly.
DefaultPresentationSelects the presentation copied into a new widget instance and must be one of the supported values. It defaults to Flow.
RequiredPermission, RequiredPermissions, RequiredPermissionModeUses the same catalog and render gating as pages and tabs. Protect extension API endpoints independently.

Flow widgets form an ordered vertical stack. The host wrapper establishes container-type: inline-size; use CSS @container rules for internal columns and narrow layouts instead of assuming a viewport width. Flow height is content-driven, and the host does not expose full/half/third sizing or grid coordinates.

Canvas is an exclusive, document-scrolling presentation for a widget that owns the dashboard content area while Cove retains the application shell and compact dashboard controls. A dashboard containing a Canvas widget cannot contain another widget. Contributions that support both presentations receive the saved presentation in their render and editor props so they can adapt deliberately; Cove never stretches an unprepared Flow-only widget into Canvas.

Use GroupItemFeed from @cove/runtime/components when an extension needs a native mixed-media feed backed by an existing static or dynamic group. It owns group-item paging, readable-entity hydration, permission-aware engagement, native feed cards, loading/error/empty states, and document-based infinite loading. Pass a positive groupId and the widget’s onNavigate callback; group editing remains on the group page.

Render and editor components run inside extension error boundaries. A missing, disabled, unauthorized, or failed contribution remains as an unavailable placeholder so the user can remove it without Cove discarding its configuration automatically.

UITabContribution adds a tab to supported detail pages. Cove currently consumes page types video, performer, studio, tag, gallery, and group.

FieldBehavior
KeyStable tab key. Cove prefixes it internally with ext:.
LabelVisible tab label.
PageTypeHost detail-page type receiving the tab.
ComponentNameExported component; receives entityId and onNavigate.
OrderInterleaves the tab with built-in tabs on performer, studio, tag, gallery, and group pages.
CountEndpointOn performer, studio, tag, gallery, and group pages, an optional endpoint template using {entityId}; return JSON containing count.
IconOptional icon name used by extension count-summary cards on performer, studio, and tag pages.
ManualContextsManual contexts active while the tab is selected.
RequiredPermissionRequires one permission before Cove returns the tab to a detail page.
RequiredPermissions, RequiredPermissionModeRequires all listed permissions by default, or at least one when the mode is Any. A non-empty array takes precedence over RequiredPermission.

Video renders contributed tabs after its built-in history tab and ignores Order, CountEndpoint, and Icon. The contract also names image and settings, but those page types do not currently mount contributed detail tabs. Use settings contributions for settings UI.

UISettingsTab creates a route under /settings/<key> in the Extensions settings group.

FieldBehavior
KeyStable route segment and tab key.
Label, DescriptionNavigation and page copy.
IconOptional icon name. Supported normalized values are database, download, filetext, folderopen, harddrive, history, info, keyboard, keyround, layers, monitor, playcircle, plug, scrolltext, search, searchcode, server, shield, upload, usercog, and users; unknown values fall back to plug.
ParentTabKeyNests the tab beneath another contributed settings tab.
SearchKeywords, AliasesMakes settings search and alternate URLs easier to discover.
LayoutPanels wraps contributions in cards; Page gives panels a full-width canvas.
OrderPositions the tab within the Extensions group.

UISettingsPanel supplies the rendered component. With no TargetTab, it appears on the built-in Extensions page. A panel can target a contributed settings-tab key or one of the explicitly mounted built-in keys: extensions, library, or ai-data. The only currently published built-in section target is TargetTab = "library" with TargetSection = "extensions"; other built-in route and section keys do not create mount points automatically. Panels receive no universal save contract; the component owns its data loading, validation, permissions, and mutations.

ExtensionAction adds executable UI actions. Contributions may come from UIManifest.Actions or IActionExtension.GetActions().

FieldBehavior
Id, LabelStable action identity and visible label.
ActionTypeUse toolbar for single-entity actions or bulk for selected list entities.
EntityTypesLimits the action to matching entity types; an empty array matches all.
PagesOptionally limits toolbar actions to matching page keys. Bulk action lookup does not apply this field.
ApiEndpointEndpoint Cove invokes with action, entity, page, and selected-id data.
HandlerNameExported frontend handler used instead of the endpoint when found.
RequiredPermissionHides the action when permission is absent.
SuppressSuccessAlertSkips Cove’s default queued-action success alert.
OrderSorts matching actions.

Toolbar rendering is currently available for video and image entity actions. Bulk actions render for selectable entity lists. context-menu is declared by the contract but has no frontend consumer yet.

UIListFilterContribution adds an advanced-filter row to the list matching EntityType.

  • Use FilterKey to target a filter key understood by that list’s backend request contract.
  • For extension-owned custom fields, set CustomFieldKey and CustomFieldType; Cove creates an extension-scoped filter key.
  • For an extension-owned predicate, set FilterId through UIManifestBuilder.AddExtensionListFilter and implement the matching IExtensionEntityFilterProvider.
  • CriterionType controls the editor, with Modifiers, Options, and EntityReferenceType refining supported operations.
  • Order positions the filter among other extension contributions.

UIListSortContribution adds a sort choice for the matching entity type. Set either a backend-supported SortKey or a CustomFieldKey with its CustomFieldType. A visible sort option does not implement backend sorting by itself; the corresponding server-side list contract must understand the generated key.

Executable FilterId contributions currently support the tag list and tag graph. Cove namespaces the saved-filter key with the authoritative extension id and filter id. If the owning declaration or provider is unavailable, the saved criterion remains visible as unsupported instead of being discarded or silently ignored.

Register an IExtensionEntityFilterProvider in ConfigureServices. Its Filters collection declares the stable filter ids and entity types it implements, and ResolveAsync receives:

  • the authoritative extension id, normalized entity type, and filter id
  • the validated modifier and JSON value
  • a bounded batch of candidate ids already filtered by the caller’s tag-read authorization
  • a principal snapshot containing the current user identity, roles, and permissions

Return only matching ids from the supplied batch plus a non-empty Revision. The revision must remain identical across every batch in one query so Cove does not combine memberships from changing provider state. Cove retains ownership of authorization, core-filter composition, stable sorting, counts, and pagination.

Executable declarations currently accept bool/boolean, integer, number, string, and enum values together with the modifiers declared in the manifest. The host accepts at most 16 extension criteria, rejects a query whose core tag filter produces more than 5,000 candidates, invokes providers in batches of 256, and applies one two-second provider deadline across the query. A provider must honor cancellation, must not return ids outside its batch, and must not perform a second authorization policy that expands the host-supplied candidate set.

UIThemeDefinition contributes a selectable theme. It can set CSS variables, load a theme stylesheet, select a component-style layer and layout-style layer, name a background animation, and declare a light or dark color scheme.

UIComponentStyleDef and UILayoutStyleDef add named appearance-layer choices. Their ids connect UI preferences to CSS behavior shipped by the extension; the records themselves only provide identity, labels, and descriptions.

Theme, component-style, and layout ids become stored user preferences. Keep them stable, and provide sensible behavior when a user retains an id after an extension is disabled.

UITutorialTopic contributes a topic to the in-app Cove User Guide. Topics can be scoped by page and runtime context, nested with ParentTopicId, and ordered alongside built-in and other extension topics.

Each UITutorialSlide can contain a title, caption, bullet points, Markdown body, image, mock kind, and external links. Set Kind to setup when the topic is an installation or first-run guide that Cove should surface after install.

UIFeatureDefinition publishes a case-insensitive feature key and string options. Host UI code must explicitly look up that key; declaring one does not create visible UI on its own. Use a feature declaration when Cove already defines the integration key, and pair custom behavior with another visible contribution.

UIComponentOverride currently supports the stable target entity.media. Use UIManifestBuilder.OverrideComponent("entity.media", componentName, priority) and export that component from the owning extension’s frontend bundle. The frontend SDK exports ENTITY_MEDIA_TARGET for code that needs the same target string.

The component receives EntityMediaRenderProps: the entity type and id, surface, native image URL, alt text, fit, loading hint, class name, and renderDefault. Surfaces currently include card, hero, list, picker, recommendation, dialog, and hover. Call renderDefault() when the extension does not handle the entity or surface. That delegates to the next lower-priority override and eventually Cove’s native media.

Cove orders overrides by descending priority and isolates every layer in an error boundary. A failed contribution falls through without hiding lower-priority contributions or native media. Navigation, selection, cover controls, card overlays, and preview playback remain outside the replacement boundary.

Hover previews use a 4:3 frame by default. When contributed hover media has another intrinsic shape, put data-entity-media-aspect-ratio="width:height" on an element rendered by the override. Cove validates positive numeric values and uses the declaration to size its host-owned hover frame.

UIPageOverride replaces the component for an exact built-in page key. Cove chooses the highest Priority when multiple extensions target the same page, then renders the named component with onNavigate.

Prefer additive pages, tabs, and slots when possible. An override owns the whole page body and therefore couples more closely to routing and host expectations. The declared * target is not currently implemented and does not replace the application shell.

The public manifest also contains these declarations, but Cove does not currently render them:

  • UIPaneContribution
  • UIComponentOverride targets other than entity.media
  • UISelectorOverride
  • UIDialogOverride
  • UIPageDefinition.DetailRoute
  • UIPageOverride with target *
  • ExtensionAction with action type context-menu

Treat them as reserved contract surface, not working extension points. Shipping one today leaves the contribution unused rather than falling back to another location.

GoalPreferred contribution
Add a new workflowUIPageDefinition
Add a small element to an existing screenUISlotContribution
Add entity-specific detail contentUITabContribution
Add configuration UIUISettingsTab and UISettingsPanel
Add an external sign-in buttonExtensionLoginMethod
Run an operation from an entity or selectionExtensionAction
Extend list queryingUIListFilterContribution or UIListSortContribution
Add visual presetsUIThemeDefinition, UIComponentStyleDef, or UILayoutStyleDef
Add in-app helpUITutorialTopic
Advertise a host-known capabilityUIFeatureDefinition
Replace primary entity mediaUIComponentOverride targeting entity.media
Replace a built-in pageUIPageOverride, only when additive points cannot work

After choosing the contribution, see Frontend runtime API for supported imports, Packaging for the asset layout, and Permissions for access design.