Extension point catalog
Cove extensions start with IExtension and opt into one or more capability interfaces. An extension can combine capabilities—for example, a scraper can also expose settings, an API, and a background worker.
This page catalogs the public extension contracts in Cove.Plugins. Use it to choose the smallest hooks that fit your feature.
Core lifecycle and services
Section titled “Core lifecycle and services”Every compiled extension implements IExtension.
| Contract | Use it to |
|---|---|
IExtension.ConfigureServices | Register extension-owned services in the extension’s dependency-injection container. |
IExtension.InitializeAsync | Start work that requires the built service provider. |
IExtension.ShutdownAsync | Stop cleanly when the extension is disabled, uninstalled, or Cove exits. |
IExtension.OnInstallAsync | Seed data or create non-database resources during installation. Keep this hook idempotent because reinstall and hot-replacement paths may invoke it again. |
IExtension.OnUninstallAsync | Remove non-database resources before uninstall completes. |
IManifestAware | Source identity, compatibility, categories, and dependency metadata from extension.json. Extension base classes normally handle this for you. |
ExtensionContext supplies Cove configuration, the extension data directory, the running Cove version, and the UI registry. Dependencies declared by extension id and semantic-version range control initialization order and enable/disable behavior.
Extensions may register their own services. They should treat host services as an explicit compatibility boundary, not assume every internal Cove service is stable. IExtensionServiceExchange is the supported opt-in exchange for publishing and resolving services across extension containers.
Provider generations and tracked scopes
Section titled “Provider generations and tracked scopes”Cove builds an isolated service-provider generation for each runtime extension. Enable, disable, replacement, and unload transitions are serialized per extension. Requests, endpoints, middleware, events, jobs, scrapers, downloaders, scans, workers, and contributed providers stay bound to the exact generation on which their work started. Cove retires an old generation only after its tracked work drains.
Inject IExtensionServiceScopeFactory into extension services when nested or long-lived work needs a scope. Its CreateScope and CreateAsyncScope methods keep the selected provider generation alive until the returned scope is disposed. Do not inject the container engine’s IServiceScopeFactory for extension-owned work because that bypasses generation tracking.
Create and dispose one tracked scope per background-work unit. A queued IJobService callback can outlive the request that enqueued it, so do not capture HttpContext or request-scoped services; create a fresh tracked scope inside the callback and honor its cancellation token.
Metadata and declarations read outside active execution must remain immutable and provider-independent. In particular, identity, dependencies, GetUIManifest(), IJobExtension.Jobs, and IActionExtension.GetActions() must not resolve scoped services or depend on a mutable provider generation.
Access host library data
Section titled “Access host library data”Extensions can read and write Cove entities through the host-provided EF Core DbContext. Register an extension-owned scoped service and inject DbContext; use Set<TEntity>() with entity types from Cove.Core rather than depending on Cove’s concrete data layer:
using Cove.Core.Entities;using Cove.Plugins;using Cove.Sdk;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.DependencyInjection;
public sealed class LibraryReader(DbContext db){ public Task<List<Video>> RecentVideosAsync( CancellationToken ct = default) => db.Set<Video>() .AsNoTracking() .OrderByDescending(video => video.CreatedAt) .Take(20) .ToListAsync(ct);}
public sealed class ExampleExtension : CoveExtensionBase{ public override void ConfigureServices( IServiceCollection services, ExtensionContext context) { services.AddScoped<LibraryReader>(); }}Resolve LibraryReader only inside an active extension scope. For a request or host-dispatched operation, use the scoped service provider supplied for that operation. For queued or long-lived work, create a fresh tracked scope with IExtensionServiceScopeFactory as described above. Do not retain a DbContext beyond its scope or use one context concurrently.
CoveContext and the concrete services in Cove.Data are host implementation details, not extension contracts. Cove.Data is intentionally not published as a NuGet package. A standalone extension should reference Cove.Sdk and use the DbContext base type plus entities and public interfaces from Cove.Core; it should not clone the Cove repository or add a project reference to Cove.Data.
Normal EF Core rules apply to writes: track or add entities through the scoped context, call SaveChangesAsync, honor cancellation, and keep multi-step mutations transactional where partial completion would be invalid. Enforce the appropriate Cove permission on extension endpoints and actions before accessing host data; resolving the context does not authorize an operation by itself.
SDK base classes
Section titled “SDK base classes”Cove.Sdk provides manifest-aware base classes and a UI builder. They reduce lifecycle and dispatch boilerplate; they do not add capabilities that their implemented interfaces do not list.
| Base | Implements | Choose it when |
|---|---|---|
CoveExtensionBase | IUIExtension, IManifestAware | The extension needs normal lifecycle and service registration, an optional UI manifest, or a base for one manually implemented capability. |
DataExtensionBase | CoveExtensionBase, IDataExtension | The extension owns relational tables or forward-only SQL migrations and does not need the full combined base. |
EventExtensionBase | CoveExtensionBase, IEventExtension | The extension mainly routes entity lifecycle events to handlers. |
JobExtensionBase | CoveExtensionBase, IJobExtension | The extension mainly declares named, host-run jobs. |
FullExtensionBase | Data, API, UI, events, jobs, state, and scan participation | One extension genuinely needs several of those capabilities. All listed capabilities have safe no-op defaults, but the extension still advertises every listed interface to the host. |
There are no capability-specific SDK bases for API endpoints, background workers, middleware, actions, scrapers, downloaders, or permission contributions. Derive from CoveExtensionBase and implement only the needed interface for those cases.
CoveExtensionBase
Section titled “CoveExtensionBase”The host applies extension.json immediately after constructing a manifest-aware extension. Id, Name, Version, description, author, URL, categories, MinCoveVersion, and dependencies then come from that manifest. Manifest is available from ConfigureServices onward.
The main override and helper signatures are:
public virtual void ConfigureServices( IServiceCollection services, ExtensionContext context);
public virtual UIManifest GetUIManifest();
public virtual Task InitializeAsync( IServiceProvider services, CancellationToken ct = default);
public virtual Task ShutdownAsync(CancellationToken ct = default);public virtual Task OnInstallAsync( IServiceProvider services, CancellationToken ct = default);public virtual Task OnUninstallAsync( IServiceProvider services, CancellationToken ct = default);
protected UIManifestBuilder ManifestBuilder();protected void PublishContributions<T>(IServiceProvider services) where T : class;Lifecycle methods and GetUIManifest() default to no work. Override ConfigureServices for extension-owned DI registrations. Call PublishContributions<T> during InitializeAsync only for a shared contract that sibling extensions should resolve through IExtensionServiceExchange; the host withdraws published instances when the extension stops.
Capability-specific bases
Section titled “Capability-specific bases”DataExtensionBase requires ConfigureModel(ModelBuilder) and provides DefineMigrations() plus
Migration(name, sql). Migrations are forward-only and ordered as added. Cove commits each SQL
script and its name receipt in one transaction, runs that transaction inside the configured database
retry strategy, and verifies the receipt before replaying an ambiguous attempt.
EventExtensionBase calls RegisterHandlers() from its constructor. Register canonical event names with On, or use OnCreated, OnUpdated, and OnDeleted. Handlers receive the event and cancellation token. See Extension events for the supported names, payloads, and delivery guarantees.
JobExtensionBase calls DefineJobs() from its constructor. Job(...) binds an id and label to a handler and can declare parameters, description, and showInTaskList. Do not rely on derived-class fields that have not initialized yet from either constructor-time registration method.
FullExtensionBase combines the data helpers with MapEndpoints, DefineEventHandlers, DefineJobs, Store, and ScanAsync. It does not implement IBackgroundExtension, IMiddlewareExtension, IActionExtension, provider interfaces, or permission contributors. Add those interfaces explicitly when needed, or use a narrow base if most combined capabilities would remain empty.
UIManifestBuilder
Section titled “UIManifestBuilder”UIManifestBuilder creates a UIManifest and automatically stamps supported contributions with the extension id. A manifest-backed extension normally starts with the protected ManifestBuilder() helper:
public sealed class ReportsExtension : CoveExtensionBase{ public override UIManifest GetUIManifest() => ManifestBuilder() .AddPage( route: "example-reports", label: "Reports", componentName: "ReportsPage", icon: "puzzle") .AddSettingsTab( key: "example-reports-settings", label: "Example reports") .Build();}Representative signatures are:
public UIManifestBuilder AddPage( string route, string label, string componentName, string? icon = null, string? detailRoute = null, bool showInNav = true, int navOrder = 100);
public UIManifestBuilder AddSlot( string slot, string componentName, string? id = null, int order = 100);
public UIManifestBuilder AddLoginMethod( string id, string label, string startUrl, int order = 100);
public UIManifestBuilder AddLoginMethod( string id, string label, string startUrl, int order, string? linkStartUrl, bool showOnLoginPage);
public UIManifestBuilder AddTab( string pageType, string key, string label, string componentName, int order = 100, string? countEndpoint = null, string? icon = null, string[]? manualContexts = null);
public UIManifest Build();The builder also has methods for actions, settings tabs and panels, tutorial topics, features, and list filters and sorts. OverrideComponent is currently usable only with Cove’s published entity.media target. AddPane and other component-override targets remain reserved; see UI extension points before choosing a method.
UIManifestBuilder does not replace extension.json. In particular, package frontend files with manifest jsBundle and cssBundle. Although the builder exposes WithJsBundle, WithCssBundle, and WithRuntimeVersion, the aggregated host manifest supplies asset URLs from the package manifest and supplies its own frontend runtime version. Do not use those methods as package configuration.
Backend capabilities
Section titled “Backend capabilities”Implement only the interfaces your extension needs.
| Contract | Extension point |
|---|---|
IApiExtension | Map minimal-API endpoints with MapEndpoints. Cove adds and removes the routes at runtime and enforces any attached Cove authorization conventions before endpoint execution. |
IMiddlewareExtension | Inspect, transform, or short-circuit every HTTP request and response. Call next to continue the pipeline. |
IJobExtension | Declare named jobs and run them with parameters, cancellation, and progress reporting. ShowInTaskList can expose a job in Cove’s task UI. |
IBackgroundExtension | Run a host-managed long-lived worker until its cancellation token is signaled. |
IEventExtension | Receive post-persistence entity lifecycle events such as video.created, performer.updated, or tag.deleted. See Extension events for exact coverage and delivery behavior. |
IScanParticipant | Join a core library scan, receive resolved scan paths and exclusions, and report progress within the scan job. |
IStatefulExtension | Receive an extension-scoped IExtensionStore for persistent string key/value data. |
IDataExtension | Add extension-owned EF Core model configuration and ordered, forward-only SQL migrations. Do not drop core tables or columns. |
IActionExtension | Contribute toolbar and bulk actions backed by an API endpoint or frontend handler. The contract declares context-menu actions, but the frontend does not currently render them. |
IExtensionEntityFilterProvider | Resolve an owner-namespaced tag-filter predicate against host-authorized candidate batches. |
Service and storage choices
Section titled “Service and storage choices”Choose storage by shape:
- use
IExtensionStorefor small durable key/value state - use
IDataExtensionfor structured, queryable extension-owned tables - use manifest
settingsfor administrator-editable configuration
IBlobService stores host-managed binary media and preserves its normalized MIME type. Treat the returned blob id as opaque: do not infer a filesystem path or extension. GetBlobAsync returns a seekable stream suitable for an ASP.NET Core file result with range processing enabled, and callers own the returned stream.
When replacing a referenced blob, store the new payload first, persist the new id, and delete the old blob only after the metadata update succeeds. Delete the new blob if that update fails. Honor cancellation while storing data and do not retain a stream after its request, job, or extension scope ends.
Background workers should create an IExtensionServiceScopeFactory scope per unit of work and persist checkpoints rather than relying on in-memory state. Event handlers, jobs, middleware, and endpoints should honor cancellation and keep failure messages local to the operation.
External authentication
Section titled “External authentication”Authentication extensions keep provider-specific protocols outside Cove core. For an interactive
flow, declare a login-page button with UIManifestBuilder.AddLoginMethod and map its local start URL
through IApiExtension. Cove exposes only owner-stamped methods with a same-origin relative URL;
the unauthenticated page does not load the extension’s frontend bundle.
Resolve IExtensionLoginSessionService in the start and callback endpoints. Call
BeginBrowserSession before redirecting to the provider, bind the returned value into the
extension’s single-use state, and call IsBrowserSession before exchanging an authorization code.
After the extension has validated the external protocol, call CompleteAsync with an
ExtensionIdentityAssertion. The assertion contains the extension id, stable provider authority,
exact case-sensitive subject, method, and display-only provider/account labels. Do not substitute a
username or email for the authority’s stable subject and do not trim or case-normalize that subject.
Cove resolves only an identity that a user explicitly linked to an existing active, unlocked local
account and returns a
short-lived one-time code for a redirect to /login#external_login_code=.... Put the code in the URL
fragment, not the query string, so browsers do not send it to Cove, reverse proxies, or access logs. The same browser
redeems that code through Cove’s generic auth API; only then does Cove recheck the account, issue
normal tokens, and write the login success audit event. Abandoned or expired callbacks therefore
create no Cove session.
Set linkStartUrl when the provider supports account linking. The authenticated start endpoint calls
IExtensionIdentityLinkService.BeginLink, binds its intent token and browser value into the
extension’s single-use provider state, and redirects to the provider. After validation, the callback
calls PrepareLinkAsync with the same exact identity assertion. Preparation creates only a short-lived
candidate; Cove’s Account page previews it and the same Cove user must explicitly confirm before the
host persists a link. For an identity validated on the authenticated start request itself, such as a
trusted direct-proxy header, use PrepareDirectLinkAsync; Cove still returns a confirmation code.
One external identity cannot be linked to two Cove users, while one user can link multiple identities
and providers.
For request-by-request authentication such as a reverse-proxy identity header, implement
IMiddlewareExtension and call HttpContext.TrySetExtensionIdentityAssertion. Establish trust from the
direct network peer and a narrowly configured allowlist; never use a forwarded-address header to
decide whether the assertion itself is trusted. The first valid extension assertion wins, but an
already valid Cove share, bearer, API, or access-cookie principal takes precedence. Cove again owns
local account status, roles, permissions, and content access. Cove probes its /api/auth/me endpoint
once during UI startup, so a valid ambient assertion can enter the application without a second Cove
login or a provider-specific frontend integration.
Transparent middleware can remain linkable without rendering a redundant login button: declare its
link URL and pass showOnLoginPage: false to the full AddLoginMethod overload. Cove still exposes
the method to the authenticated Account page.
Provider credentials, raw callback queries, authorization codes, tokens, and login tickets must not be logged. Interactive flows should use state, browser binding, nonce, PKCE where supported, exact redirect URIs, strict issuer and audience validation, signed tokens, bounded response reads, and short expirations. Mark only the start and callback endpoints anonymous; protect settings and test operations with an explicit Cove permission.
Extensions that commit video-segment changes outside Cove’s built-in controllers should resolve
ISegmentSpanCacheInvalidator from their service provider. Call InvalidateVideo(videoId) only
after the database transaction commits. Use InvalidateAll() for a bulk mutation only when the
affected videos cannot be identified. The service invalidates raw, resolved, display-rule, and
derived-query projections, including an in-flight result that started before the invalidation; it
does not authorize or persist the mutation.
Metadata and intake providers
Section titled “Metadata and intake providers”These provider interfaces integrate with Cove’s built-in review, provenance, and intake workflows.
| Contract | Extension point |
|---|---|
IScraperProvider | Declare scrapers and scrape or search videos, performers, galleries, images, groups, audio, and text. |
IDownloaderProvider | Match source URLs, offer quality choices, and download videos, images, galleries, audio, or text into Cove’s intake flow. |
Scrapers declare supported entities, URL patterns, invocation capabilities, preference sites, and a risk level. Cove passes a ScraperPermissions value with each request. The current host derives the allowed network host from a URL input and leaves JavaScript and browser debugging disabled; name-only searches receive no allowed host. Providers must honor that request value.
Downloaders declare supported entities, URL patterns, and capabilities such as resume, range requests, multiple qualities, or inline metadata. Cove supplies a temporary directory, HTTP clients, logging, and progress reporting through IDownloaderHost.
For implementation guidance, see Create a scraper and Create a downloader.
Extension-owned entity filters
Section titled “Extension-owned entity filters”Pair IExtensionEntityFilterProvider with an IUIExtension declaration created by UIManifestBuilder.AddExtensionListFilter. Cove owner-keys provider registrations automatically, verifies that the provider’s Filters collection matches the owner-stamped manifest declaration, and pins the exact provider generation for the complete criterion execution.
The current executable boundary is limited to tags. Providers receive authorized candidate ids in bounded batches and return membership plus a stable revision; they do not own query authorization, sorting, counts, or pagination. See UI extension points for the request contract, limits, cancellation rules, and saved-filter behavior.
Specialized service contributions
Section titled “Specialized service contributions”Some host-consumed contracts live in Cove.Core.Interfaces instead of deriving from IExtension. Publish their implementations through IExtensionServiceExchange when building these specialized integrations.
| Contract | Extension point |
|---|---|
ITextEncoder | Encode text into vectors for a named embedding family. |
IFaceSuggester | Supply single-face and batch performer suggestions. |
IFaceSuggestionDecisionHandler | Handle accept, reject, or merge decisions for provider-owned face suggestions. |
IFaceLifecycleParticipant | Clean up provider state when faces are deleted or AI face data is purged. |
These are shared service contracts rather than standalone extension lifecycles; the providing package still implements IExtension and publishes the service instance during initialization.
Frontend contributions
Section titled “Frontend contributions”Implement IUIExtension.GetUIManifest to declare frontend contributions. Ship an ESM bundle when a contribution references a React component or frontend handler, and a CSS asset when the extension needs custom styles. A single UIManifest can contain any combination of the following contributions.
See UI extension points for the fields, supported targets, runtime behavior, and limitations of each contribution. Use the frontend runtime API for supported module imports, authenticated requests, and shared UI primitives.
Additive UI
Section titled “Additive UI”| Contribution | Where it appears |
|---|---|
UIPageDefinition | A new route with an optional navigation item. |
UISlotContribution | React component or HTML inserted into a host-defined named slot. |
UITabContribution | A tab on a supported entity detail page, optionally with a count badge. |
UISettingsTab | A dedicated tab in the Extensions settings group, with either panel or full-page layout. |
UISettingsPanel | A settings component in an extension tab or a targeted built-in tab and section. |
ExtensionLoginMethod | A host-rendered button on the unauthenticated login page that starts an extension-owned local authentication route. |
ExtensionAction | A toolbar or bulk action. Actions may also come from IActionExtension; the declared context-menu type is currently reserved. |
UITutorialTopic | An in-app Cove User Guide topic made from slides, Markdown, images, and links. |
UIListFilterContribution | A first-class filter criterion on a built-in entity list, including custom-field-backed filters. |
UIListSortContribution | A first-class sort option on a built-in entity list, including custom-field-backed sorts. |
UIFeatureDefinition | Named feature metadata and string options that the host UI can inspect. |
Slots, tabs, and page overrides only render where Cove publishes the corresponding stable slot, page type, or page key. Use the host-defined identifiers rather than inventing a target name.
Pages and tabs can declare one permission or an All/Any permission set. Use the record-accepting UIManifestBuilder overloads for those richer immutable definitions; the builder stamps the real extension owner. These frontend checks do not replace endpoint authorization.
Appearance
Section titled “Appearance”| Contribution | Use it to |
|---|---|
UIThemeDefinition | Add a theme with CSS variables, an optional stylesheet, component and layout layers, background animation, and color scheme. |
UIComponentStyleDef | Add a named component-style preset. |
UILayoutStyleDef | Add a named layout preset. |
Page replacements
Section titled “Page replacements”Page overrides are intentionally broader than additive contributions. Prefer a page, slot, or tab when that meets the need.
| Contribution | Replaces |
|---|---|
UIPageOverride | A built-in page identified by its exact page key. |
When multiple extensions target the same page, priority determines the winner. Page overrides carry greater compatibility risk because they replace host-owned UI rather than add to it.
Entity media replacement
Section titled “Entity media replacement”UIComponentOverride can target the published entity.media boundary to replace or wrap primary media on cards, lists, detail heroes, dialogs, pickers, recommendations, and hovers. The component receives the entity identity, surface and presentation hints, native image URL, and a renderDefault function that delegates to the next override and ultimately native media.
Other component target strings remain reserved. See UI extension points for the complete runtime contract and hover aspect-ratio convention.
Reserved UI declarations
Section titled “Reserved UI declarations”UIManifest also defines panes, selector overrides, dialog overrides, component-override targets other than entity.media, and a DetailRoute value on page definitions. Cove currently transports these declarations but does not consume them in the frontend, so they are not usable extension points yet. The * target documented on UIPageOverride is likewise not a working application-shell override; use an exact built-in page key.
Manifest-only contributions
Section titled “Manifest-only contributions”Not every package needs a DLL. extension.json can describe a manifest-only bundle or scraper-pack, and can contribute:
- generic settings surfaced in the Extensions settings UI
- in-app Cove User Guide topics
- external tool, runtime, service, environment-variable, and configuration requirements
- extension dependencies and Cove version compatibility
- declarative network, scraper-runtime, and downloader-runtime requirements
A YAML scraper pack contributes scraper definitions without compiling a .NET provider. A bundle can group contributions and dependencies under one installable identity.
Frontend JavaScript and CSS require a loaded IUIExtension; declaring bundle paths on a manifest-only package does not load those assets.
The manifest permission lists are not a runtime authorization boundary. See Extension permissions for their exact fields and current behavior.
Choosing extension points
Section titled “Choosing extension points”Start from the user-visible behavior:
| Goal | Start with |
|---|---|
| Add a screen or panel | IUIExtension with a page, tab, slot, or settings contribution |
| Add an HTTP operation | IApiExtension |
| Run work on demand | IJobExtension |
| Run continuously | IBackgroundExtension |
| React to library changes | IEventExtension |
| Participate in scans | IScanParticipant |
| Enrich existing items | IScraperProvider |
| Bring media into Cove | IDownloaderProvider |
| Store small durable state | IStatefulExtension |
| Own relational data | IDataExtension |
| Add external login or trusted-proxy authentication | IUIExtension plus IApiExtension and/or IMiddlewareExtension, using Cove’s authentication bridges |
| Customize existing UI | An additive UI contribution first; an override only when replacement is required |
Most full-featured extensions pair one workflow capability with IUIExtension, IApiExtension, and one storage option. Keep permissions narrow, preserve metadata provenance, and package pre-built backend and frontend artifacts as described in Packaging.