Gitly Extensions And Host APIs
This page is about the platform extension model first, with Gitly as the supporting example.
Use it when you want to answer:
- how a customer app should expose extension slots
- how runtime-installed packages bind to those slots
- how to explain host APIs without making one demo app the whole story
The Core Pattern
For runtime-installed extensions, keep the responsibilities split:
- the customer app defines named extension slots in product vocabulary
- the customer app installs packages explicitly in
app.toml - the package manifest binds handlers to those targets
- the runtime enforces grants and host-API boundaries
Gitly demonstrates this especially well because it uses one API slot and one scheduled-job slot.
Canonical Extension Slot Pattern
Gitly’s customer app module in apps/gitly/crates/gitly-app/src/lib.rs defines extension slots
like this:
ExtensionSlotDescriptor::new(
ExtensionSlotKind::Api,
"/api/github/pulse",
"Allows bounded third-party extensions to contribute GitHub-style community pulse API data",
)
and:
ExtensionSlotDescriptor::new(
ExtensionSlotKind::ScheduledJob,
"github.actions.refresh",
"Allows bounded third-party scheduled jobs to simulate GitHub Actions refresh cycles",
)
These snippets are the most important part of the page because they show the real pattern:
- the customer app owns the product slot names
- extensions plug into explicit, app-defined targets
Canonical Install Pattern
The customer app then installs packages in app.toml.
Gitly’s apps/gitly/app.toml uses:
[[extensions]]
id = "gitly-community-pulse"
package_version = "0.1.0"
artifact_sha256 = "..."
customer_app_id = "gitly"
That is the right installation boundary:
- explicit package id
- explicit version
- explicit artifact hash
- explicit customer ownership
Canonical Package Binding Pattern
The package itself binds handlers to extension points in package.toml.
Gitly’s API package example:
[[handlers]]
id = "community-pulse"
export = "exports.community_pulse"
point = "api"
target = "/api/github/pulse"
grants = []
Gitly’s scheduled-job example:
[[handlers]]
id = "nightly-refresh"
export = "exports.nightly_refresh"
point = "scheduled-job"
target = "github.actions.refresh"
grants = []
Those snippets show the complete binding chain:
- app defines slot
- app installs package
- package binds handler to slot target
Gitly As The Supporting Example
App-defined slots
Read:
apps/gitly/crates/gitly-app/src/lib.rs
This is where Gitly defines the product’s extension vocabulary.
Package loading
Read:
apps/gitly/crates/gitly-app/src/extensions.rs
This file shows the customer-app loader path:
- read installed extensions from
app.toml - load
extensions/<id>/package.toml - compile demo artifacts
- build
ExtensionPackage - attach installation data and grants
Installed packages
Read:
apps/gitly/extensions/gitly-community-pulse/package.tomlapps/gitly/extensions/gitly-actions-scheduler/package.toml
Product surfaces
Read:
apps/gitly/templates/gitly/home.htmlapps/gitly/templates/gitly/actions.htmlapps/gitly/theme/assets/site.js
These are the app-visible surfaces that make the extension contributions legible.
Host API Boundary
The general host API and grant model is documented in:
Gitly’s handlers use empty grant sets intentionally. That keeps the demo focused on slot design and package binding instead of grant complexity.
Practical Rules To Copy
- define extension slots in the customer app before shipping packages
- make slot names match real product vocabulary
- install packages explicitly in
app.toml - keep package targets and slot targets aligned exactly
Full Implementation Pointers
apps/gitly/crates/gitly-app/src/lib.rsapps/gitly/crates/gitly-app/src/extensions.rsapps/gitly/app.tomlapps/gitly/extensions/gitly-community-pulse/package.tomlapps/gitly/extensions/gitly-actions-scheduler/package.tomlapps/gitly/templates/gitly/home.htmlapps/gitly/templates/gitly/actions.htmlapps/gitly/theme/assets/site.js