Extension packaging
Cove installs extensions from ZIP archives. A package can contain a compiled .NET extension, a manifest-only bundle, or a scraper pack, but every package uses extension.json as its identity and compatibility contract.
For a first working package, follow Create an extension. This page is the format reference.
Accepted archive layouts
Section titled “Accepted archive layouts”The portable layout puts the manifest at the archive root. A manifest at the archive root works with all installation workflows, including registry installation, Install from URL, and local extension-directory discovery. A registry release ZIP must therefore contain its manifest at the archive root.
com.example.reports.zip├── extension.json├── Example.Reports.dll├── Example.Reports.deps.json├── Private.Dependency.dll└── assets ├── ui.mjs └── ui.cssA single wrapper has narrower support. Exactly one top-level wrapper is accepted only by Install from URL:
com.example.reports.zip└── com.example.reports ├── extension.json ├── Example.Reports.dll └── assets ├── ui.mjs └── ui.cssAfter validating a URL-installed archive, Cove flattens the wrapper by moving that directory to the final extension directory. Do not publish this layout as a registry release: the registry extracts archive paths directly into the extension directory, and normal discovery looks for extension.json directly in that directory.
For Install from URL, a manifest at release/com.example.reports/extension.json is too deep, and manifests in two top-level directories make the package root ambiguous. The URL installer also rejects ZIP entries that attempt path traversal outside its temporary extraction directory.
List the finished archive before publishing it:
python3 -m zipfile \ --list artifacts/com.example.reports.zipPaths in entryDll, jsBundle, and cssBundle are relative to the directory containing extension.json. Preserve path case: Cove may run on a case-sensitive filesystem.
Manifest reference
Section titled “Manifest reference”JSON property matching is case-insensitive, but use the camel-case names shown here. Empty collections are the defaults unless noted.
| Field | Required or default | Meaning |
|---|---|---|
id | Required | Stable extension identity. A reverse-domain value is recommended. The URL installer rejects empty ids, rooted paths, .., and platform-invalid filename characters. |
name | Required | Human-readable name shown in extension management. |
version | Required | Installed extension version. Use a numeric semantic version such as 1.2.0. |
description | Optional | Short user-facing purpose. |
author | Optional | Author or organization. |
url | Optional | Project or source URL. |
iconUrl | Optional | Extension icon URL. |
kind | extension | extension for compiled code; bundle and scraper-pack are manifest-only kinds. |
categories | [] | Labels used for registry and installed-extension filtering. Prefer Cove’s well-known category strings where they fit. |
minCoveVersion | Any Cove version | Declared minimum running Cove version. Install from URL rejects an incompatible package, and registry selection excludes or rejects incompatible versions. See the startup-discovery limitation below. |
dependencies | {} | Map of extension id to one supported semantic-version range: exact, =, >, >=, <, or <= followed by a version. Cove uses it for dependency validation and initialization order. |
externalDependencies | [] | User-visible tools, runtimes, services, executables, environment variables, or configuration requirements. These describe requirements; the extension remains responsible for probing what it uses. |
settings | [] | Generic settings shown in the Extensions settings UI. Each item names the setting and type and can include a display name, description, and applicable extension ids. |
tutorialTopics | [] | In-app Cove Manual topics contributed by this package. |
entryDll | Auto-discover root DLLs | DLL containing an IExtension implementation. Set it for compiled packages so Cove loads one intentional entry assembly instead of inspecting every root DLL. |
sharedAssemblies | [] | Assembly simple names, without .dll, that multiple extensions must load as one shared type identity. Use only for a deliberately shared cross-extension contract. |
jsBundle | Optional | Relative path to the frontend ESM bundle for a loaded IUIExtension. |
cssBundle | Optional | Relative path to the frontend stylesheet for a loaded IUIExtension. |
permissions | Empty permission lists | Declarative network, scraperRuntime, and downloaderRuntime string lists. See Extension permissions for their current runtime boundary. |
checksum | Optional legacy; read if present | Retained in the manifest schema for compatibility. The package checksum used to verify a registry download lives in external registry metadata, not this field. |
registryUrl | Optional legacy; read if present | Retained for legacy source inference. Current URL and registry installation source is persisted separately in Cove’s installation state. |
Current installers do not write or populate either legacy field in extension.json. Leave both out of newly built packages. Registry release metadata supplies each version’s SHA-256 checksum, and Cove records a successful install’s source as registry or url in its separate installation state.
Complete compiled example
Section titled “Complete compiled example”{ "id": "com.example.reports", "name": "Example reports", "version": "1.2.0", "description": "Adds library reports to Cove.", "author": "Example", "url": "https://example.invalid/reports", "kind": "extension", "categories": ["analytics", "tools"], "minCoveVersion": "1.0.0", "entryDll": "Example.Reports.dll", "dependencies": { "com.example.shared-contract": ">=1.1.0" }, "externalDependencies": [], "settings": [], "tutorialTopics": [], "sharedAssemblies": ["Example.SharedContract"], "jsBundle": "assets/ui.mjs", "cssBundle": "assets/ui.css", "permissions": { "network": [], "scraperRuntime": [], "downloaderRuntime": [] }}Keep the same id across releases. Cove connects installation state, settings, contributed permissions, and stored extension data to that value.
Backend assemblies
Section titled “Backend assemblies”Publish the project; do not package only the main DLL. A normal publish directory contains the extension assembly, its .deps.json, and any extension-private dependencies.
dotnet publish src/Example.Reports/Example.Reports.csproj \ --configuration Release \ --output artifacts/extensionReferencing the Cove.Sdk package imports its packaging targets. Those targets include private dependencies while removing host-provided assemblies such as Cove.Sdk, Cove.Plugins, Cove.Core, EF Core, Npgsql, and Pgvector. The running Cove process supplies one copy of those contract assemblies.
Do not add host-provided assemblies back to the ZIP. Cove resolves its own copy and logs a warning when a package bundles one. Shipping a second copy wastes space and risks incompatible type identities. A library used only inside your extension is a private dependency and should stay in the publish output.
sharedAssemblies is a different mechanism. Use it only when types from a third-party contract intentionally cross between extension containers. Every participating package must ship a compatible assembly with the same simple name. Cove chooses one preferred file and shares that loaded identity, so incompatible versions can break every participant. Ordinary implementation libraries belong to each extension’s private load context instead.
Frontend assets
Section titled “Frontend assets”Set jsBundle and cssBundle to files inside the package. Cove serves those files through its authenticated extension-asset route and combines assets from enabled compiled UI extensions.
The JavaScript file must be an ESM module. Components named by pages, tabs, slots, settings panels, and overrides belong in the default export’s components map. Frontend action handlers belong in actionHandlers (the legacy handlers alias is also read). Build production assets before creating the ZIP; Cove does not run npm or a bundler during install.
import ReportsPage from './ReportsPage.js';
export default { components: { ReportsPage }, actionHandlers: {},};Declaring bundle paths is not enough for a manifest-only bundle or scraper-pack: JavaScript and CSS are collected only for a loaded, enabled IUIExtension. See UI extension points for component and contribution fields.
Dependencies and compatibility
Section titled “Dependencies and compatibility”Each dependencies value is one comparison, not an npm-style compound expression. These are supported:
{ "dependencies": { "com.example.base": ">=1.4.0", "com.example.schema": "=2.0.0" }}Cove orders installed dependencies before dependents and reports missing extensions or version mismatches. Registry installs can resolve and install declared dependencies when the user approves that plan. A direct URL package should be tested with its dependencies already installed.
minCoveVersion compares the running Cove version with the minimum in the package. It is not a comparison between the extension’s own version and minCoveVersion. Use the oldest Cove version against which the built binary and its public host contracts were actually tested.
Supported installers enforce minCoveVersion: the Install from URL endpoint rejects a package whose parsed floor is above the running host version, while registry version selection filters incompatible candidates and rejects an explicitly requested incompatible version. Startup discovery of a compiled extension already placed on disk is less strict: dependency validation reports the mismatch, but the current initialization order does not exclude that extension solely for the Cove-version mismatch, so it may still initialize. Manifest-only bundle and scraper-pack packages are not added to the compiled extension set checked by dependency validation, so startup does not report their Cove-version mismatch. Do not use direct filesystem placement to bypass the supported installers or to claim compatibility.
Install-time validation and failures
Section titled “Install-time validation and failures”For an Install from URL package, Cove performs these checks before replacing the installed directory:
- Explicit trust confirmation is present and the URL is absolute HTTP or HTTPS.
- The download succeeds.
- Every ZIP entry remains inside the temporary extraction directory.
- Exactly one accepted package root contains
extension.json. - The manifest parses, has a valid
id, and does not require a newer Cove version.
After those validations, replacement is not transactional. Cove unloads the existing same-id extension, deletes its directory, moves the new package into place, discovers it, and initializes it. Endpoint registration, installation-source persistence, and scraper reload happen only after initialization succeeds.
The installer returns 400 for an invalid URL, unsafe or ambiguous archive, invalid manifest id, or incompatible Cove version. It returns 409 if the existing directory cannot be deleted because files remain locked. If discovery or initialization fails, it returns 500; by then the old extension has already been removed, and the new package files can remain installed while the extension is disabled or not loaded.
Review the Cove logs for discovery, assembly-load, dependency, and initialization details. Correct the archive and install the same URL again, or reinstall a known-good package with the same id to recover from a failed replacement.
Release workflow
Section titled “Release workflow”Use a reproducible release pipeline:
- Update the manifest
version, changelog, andminCoveVersionwhen the compatibility floor changes. - Restore, test, build production frontend assets, and run
dotnet publish. - Copy
extension.jsonand frontend assets into the publish directory. - Create the ZIP from inside that directory so
extension.jsonis at the archive root, then inspect its file listing. - Install the package into the oldest supported Cove version and a current Cove version. Exercise its permissions, settings, UI, jobs, and uninstall behavior.
- Publish an immutable ZIP for the matching version tag. Let release or registry automation calculate its checksum.
- For development iteration, rebuild the archive and install the same URL again; Cove hot-replaces that extension.
The official single-extension repository template includes a tag-driven release workflow. Registry metadata is a separate publication step after the versioned extension release exists.