Skip to content

Capabilities

Capabilities are metadata tags attached to service registrations. They exist so you can inspect what a service claims to support without instantiating it.

That sounds minor until you start building:

  • settings UIs
  • extension manifests
  • plugin discovery panels
  • slot inspectors
  • routing logic based on declared behavior

At that point, you definitely want to know what a service claims before you pay the cost of constructing it.

This page comes after services and settings. Once a plugin has something useful to expose, capabilities make that usefulness discoverable to the host app.

Capabilities live on the registration wrapper, not on the service instance.

That means you can inspect them through resolveRaw(...) without constructing the service.

const tooling = Namespace('tooling');
final wrapper = context.registry.resolveRaw(tooling('formatter'));
final caps = wrapper.capabilities;

That makes capabilities safe for discovery code. The host can walk the registry without accidentally starting services, opening sockets, or triggering lazy singletons too early.

Capability is an empty abstract base class. You subclass it to describe whatever facts about a slot matter to your app. The runtime does not interpret the fields, does not validate them, and does not know which subclasses exist. It just stores them.

That is the point. You are not registering against a fixed schema Plugin Kit defined for you. You are stapling arbitrary tags onto a slot and reading them back later on your own terms.

class SupportsFileFormats extends Capability {
/// The set of file extensions this service supports.
final Set<String> extensions;
/// Creates a capability declaring support for [extensions].
const SupportsFileFormats(this.extensions);
}

Any of those is a real capability. All three can coexist on the same registration. The runtime treats them identically.

Pass a set of capabilities when you register the service.

class FormatterCapability extends Capability {
final List<String> supportedLanguages;
const FormatterCapability(this.supportedLanguages);
}

Later, the host reads them back:

final wrapper = context.registry.resolveRaw(const ServiceId('formatter'));
final cap = wrapper.capabilities.getOfType<FormatterCapability>();

No instance of SqlFormatter was ever created.

CapabilityLookup adds two small but useful helpers on Set<Capability>.

  • hasType<T>()
  • getOfType<T>()
/// Inspects a wrapper for [IsSlowCapability] and warns if slow.
void warnIfSlow(PluginContext context) {
final wrapper = context.registry.resolveRaw<CodeLinter>(
const ServiceId('formatter'),
);
final caps = wrapper.capabilities;
if (caps.hasType<IsSlowCapability>()) {
final slow = caps.getOfType<IsSlowCapability>()!;
if (slow.isSlow) {
// Warn the user before invoking this slot in a latency-sensitive path.
print('Warning: ${slow.reason}');
}
}
}

Cleaner than manual casting, and clearer at the call site.

void inspectWrapper(ServiceRegistry registry) {
final wrapper = registry.resolveRaw<CodeLinter>(const ServiceId('linter'));
final caps = wrapper.capabilities;
final slow = caps.getOfType<CanBeSlow>();
print('slow reason: ${slow?.reason}');
print('has languages: ${caps.hasType<SupportsLanguages>()}');
}
APIWhat it gives you
resolveRaw(...)the current winning wrapper for one slot
listCapabilitiesOfNamespace(...)all capabilities seen under a namespace

Both return data without instantiating services. Combine them with your own hasType<T>() / getOfType<T>() reads to build whatever discovery surface your app needs.

Capabilities work best when they describe stable facts about a slot.

Good examples

  • supported languages or file formats
  • supported runtimes
  • feature tier (“free”, “pro”, “internal”)
  • whether the slot is slow or safe to call on the UI thread

Less good examples

  • ephemeral runtime state
  • data that only makes sense after the service has started

Rule of thumb: if the answer depends on a running instance, it belongs on the service. If the answer should be discoverable before instantiation, it belongs in a capability.