Extension Package Format
Coil runtime-installed extensions are declared in two places:
- the customer app manifest installs them
- each extension package describes its own artifact, manifest, handlers, and grants
Start with the minimal pair:
# app.toml
[[extensions]]
id = "shoppr-waitlist-tools"
package_version = "0.1.0"
artifact_sha256 = "a86d1a1646e818bf600653ba6cf3585162157517acf74d76c20ecba3328694dc"
customer_app_id = "shoppr"
[[extensions.handlers]]
id = "home.waitlist.banner"
grants = []
# package.toml
[manifest]
id = "shoppr-waitlist-tools"
display_name = "Shoppr Waitlist Tools"
version = "0.1.0"
host_api_version = "1.0.0"
[[handlers]]
id = "home.waitlist.banner"
export = "exports.home_waitlist_banner"
point = "render-hook"
target = "cms.page.render"
grants = []
That pair tells Coil:
- which package is installed
- which handler is activated
- which artifact hash is expected
- which host API contract the package expects
- which grants are requested and approved
Use this page when you want to answer:
- what goes in
package.toml - what goes in
app.toml - how handlers map to extension points
- how grants are declared and installed
- where Shoppr and Gitly provide concrete examples
The Two Files You Always Need
1. The customer app install entry
Customer apps install extensions through [[extensions]] blocks in app.toml.
Example from Gitly:
[[extensions]]
id = "gitly-community-pulse"
package_version = "0.1.0"
artifact_sha256 = "ef2b0bc15aa0baf178df23d3671bf0a2914c618e394f985441e27a5fdd7c89d7"
customer_app_id = "gitly"
[[extensions.handlers]]
id = "community-pulse"
grants = []
This file says:
- which package is installed
- which artifact hash is expected
- which customer app owns the installation
- which handlers are activated
- which grants the installation approves for each handler
2. The extension package manifest
Each package has its own package.toml under the app’s extensions/ directory.
The package manifest is the extension's own contract. The app manifest is the installation contract. You need both.
What package.toml Contains
Important top-level fields:
publisher- human and operational provenance for the package
artifact- relative path to the
.wasmartifact
- relative path to the
artifact_sha256- content hash pinned by the installer
source_wat- demo-only source hint used by the checked-in sample apps
[manifest]- extension identity and contract versioning
[[handlers]]- each exported handler inside the package
Manifest Fields
The manifest layer maps to ExtensionManifest in
crates/coil-wasm/src/manifest/manifests.rs.
The main fields are:
id- package identity
display_name- human-facing name
version- package version
host_api_version- the host API contract the package expects
Gitly example:
[manifest]
id = "gitly-actions-scheduler"
display_name = "Gitly Actions Scheduler"
version = "0.1.0"
host_api_version = "1.0.0"
This is the part that makes compatibility explicit. A package should not assume the host ABI by guesswork.
Handler Fields
Each [[handlers]] block maps to HandlerManifest.
Important fields:
id- handler identity inside the package
export- exported function name in the artifact
point- extension point kind
target- route, slot, or scheduled job target depending on point kind
grants- requested host grants
Examples:
Shoppr render hook
apps/shoppr/extensions/shoppr-waitlist-tools/package.toml
[[handlers]]
id = "home.waitlist.banner"
export = "exports.home_waitlist_banner"
point = "render-hook"
target = "cms.page.render"
grants = []
Gitly API handler
apps/gitly/extensions/gitly-community-pulse/package.toml
[[handlers]]
id = "community-pulse"
export = "exports.community_pulse"
point = "api"
target = "/api/github/pulse"
grants = []
Gitly scheduled job
apps/gitly/extensions/gitly-actions-scheduler/package.toml
[[handlers]]
id = "nightly-refresh"
export = "exports.nightly_refresh"
point = "scheduled-job"
target = "github.actions.refresh"
grants = []
Installation Grants
Packages request grants, but the customer app installation still decides which grants are actually approved for the activated handler.
That boundary is enforced through:
crates/coil-wasm/src/grants.rsapps/shoppr/crates/shoppr-app/src/extensions.rsapps/gitly/crates/gitly-app/src/extensions.rs
Current grant kinds include:
- data read/write
- auth checks and tuple writes
- storage read/write
- render fragment access
- metadata write
- cache hint write
- outbound HTTP by named integration
- secret read
- job enqueue
In the checked-in demo packages, the handlers intentionally use empty grant sets so the package format is easy to understand first.
How To Read The Format
Read it in this order:
- installation block in
app.toml - package
[manifest] [[handlers]]- requested and approved grants
That sequence tells you:
- whether the package is installed
- what the package claims to be
- what code paths it exposes
- what the runtime will actually let it do
Defaults And Constraints
- The customer app must pin
artifact_sha256. - The extension package
manifest.idmust match the installedid. - Each handler id must be valid and unique within the package.
host_api_versionis required.- Resource limits are assigned from
ResourceLimits::baseline_for(...)in the app loaders today. - Demo apps currently compile simple WAT examples into the runtime extension directory from
apps/.../crates/*-app/src/extensions.rs.
What A Good Extension Package Looks Like
A good package is:
- easy to identify
- easy to pin
- explicit about handlers
- narrow in grants
- tied to one bounded use case
If a package starts looking like a replacement application backend, it is crossing the wrong boundary.
Common Mistakes
- Do not describe a handler in
package.tomland forget to install it inapp.toml. - Do not request grants in a package and assume they are active automatically.
- Do not treat
targetas a free-form comment.- it must match the semantics of the selected extension point
- Do not use WASM for first-party customer logic that should be linked Rust.
- use Customer Rust Vs Third-Party WASM to choose the boundary
Full Implementation
Customer app install examples:
apps/shoppr/app.tomlapps/gitly/app.toml
Package examples:
apps/shoppr/extensions/shoppr-waitlist-tools/package.tomlapps/gitly/extensions/gitly-community-pulse/package.tomlapps/gitly/extensions/gitly-actions-scheduler/package.toml
Runtime model code:
crates/coil-wasm/src/manifest/package.rscrates/coil-wasm/src/manifest/manifests.rs
App loaders:
apps/shoppr/crates/shoppr-app/src/extensions.rsapps/gitly/crates/gitly-app/src/extensions.rs