Resources and capabilities

Resources and capabilities are both explicit runtime surfaces, but they solve different problems.
A capability is a typed request for host-owned work, usually emitted from a reducer.
A resource is a build-time declaration that tells the runtime some work should stay mounted while a widget subtree exists.
That difference is the most important thing to keep clear when using this page.

Capabilities

A capability is the public contract for asking the host to perform an operation the shared app should not perform directly.
Examples include opening a web address, opening a user-granted file picker, capturing a photo, scanning a barcode, requesting geolocation, or asking the host for biometric authentication. Fission ships low-level built-ins such as OPEN_URL and PICK_OPEN_FILES plus platform capability groups for notifications, deep links, NFC, biometrics, passkeys, barcode scanning, camera and flashlight, clipboard, geolocation, haptics, microphone, Bluetooth, Wi-Fi, and volume control.
The public types behind this surface include OperationCapability, CapabilityType<C>, and CapabilityCtx.
Large binary capability payloads use FissionDataStream, DataStreamId, and DataStreamRegistry instead of Vec<u8>. A host provider registers a stream with CapabilityCtx::register_data_stream(...) and returns the resulting DataStreamId in the typed success payload. Reducers stay synchronous: they store the handle or pass it to a job or service. The async side then calls JobCtx::open_data_stream(...), ServiceCtx::open_data_stream(...), or CapabilityCtx::open_data_stream(...) and consumes the standard Stream<Item = Result<Bytes, FissionDataStreamError>> implementation.
Built-in APIs that can carry large binary data follow this rule: file picker results, camera captures, microphone captures, rich clipboard content, and barcode image decode requests carry stream handles. Small bounded protocol values such as passkey challenges, NFC records, Bluetooth characteristic values, barcode raw payloads, action payloads, and internal test/control messages can still use byte vectors because they are protocol data, not large user-owned files.
Native operating-system alerts are deliberately not built in as a generic alert capability. If you need an in-app alert, use normal Fission UI such as Modal, Drawer, Toast, or your own overlay. If you need a true host-owned prompt or a product-specific host flow, define a custom capability and register a shell provider for the hosts that support it.
A capability invocation is the act of requesting one of these operations through ctx.effects.capability(...) with a typed request payload. The reducer chooses that operation explicitly, and the runtime routes the eventual success or error back through follow-up actions.
Use a capability when the host boundary is the reason the work exists.
Do not use a capability as a generic async escape hatch for ordinary app-owned background work. If the host does not need to do it, it is probably better modeled as a job or service.

Defining your own operation capability

Application and library authors can add capabilities without changing Fission core crates. A capability is just a typed contract plus a shell-side provider.
use fission::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StartPaymentRequest {
    pub checkout_id: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StartPaymentOk {
    pub receipt_id: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StartPaymentErr {
    pub message: String,
}

pub struct StartPaymentCapability;

impl OperationCapability for StartPaymentCapability {
    type Request = StartPaymentRequest;
    type Ok = StartPaymentOk;
    type Err = StartPaymentErr;
}

pub const START_PAYMENT: CapabilityType<StartPaymentCapability> =
    CapabilityType::new("com.example.payment.start");
Reducers invoke it the same way they invoke built-ins:
ctx.effects
    .capability(
        START_PAYMENT,
        StartPaymentRequest {
            checkout_id: state.checkout_id.clone(),
        },
    )
    .on_ok(ctx.effects.bind(PaymentFinished, reduce_with!(on_payment_finished)))
    .on_err(ctx.effects.bind(PaymentFailed, reduce_with!(on_payment_failed)));
The host shell chooses whether to support that capability and registers the provider:
app.with_async(|asyncs| {
    asyncs.register_operation_capability(
        START_PAYMENT,
        |request: StartPaymentRequest, _ctx| async move {
            let receipt_id = run_host_payment_flow(request.checkout_id).await?;
            Ok(StartPaymentOk { receipt_id })
        },
    );
});
Use reverse-DNS names such as com.example.payment.start for application-specific capabilities. That avoids collisions with framework-owned names such as fission.ui.open_url.

Resources

A resource is a runtime-managed declaration collected during component conversion through BuildCtxHandle resource methods.
Instead of saying "start this work right now because one reducer fired," a resource says "this work should exist while the current widget tree says it should exist."
That difference is what makes resources such a good fit for timers, mounted polling, tree-owned jobs, or tree-owned long-lived services.
The public types in this area include ResourceRegistry, ResourceKey, ResourcePolicy, RuntimeResourceDeclaration, JobResource, ServiceResource, and TimerResource.

Resource kinds

JobResource is the build-owned version of one-shot work. Use it when a job should be mounted because the current user interface state requires it, not because one user action happened at one moment.
ServiceResource is the build-owned version of a long-lived service. Use it when the service lifetime should follow the widget tree.
TimerResource is the periodic wakeup resource. Use it when a subtree needs deterministic ticks while it is mounted.

Resource keys

Every resource has a key, represented by ResourceKey.
The key is how the runtime knows whether a resource declaration on this frame is the same logical resource as one from the previous frame. If the key is unstable or poorly chosen, reconciliation becomes noisy and the runtime cannot preserve or restart work the way you intended.
Stable keys are part of the public contract for correct resource usage.

Resource policy

A resource policy is the rule that tells the runtime how to behave when a resource declaration changes.
Today, the public policy values are RestartOnChange and PreserveOnChange.
RestartOnChange means a meaningful declaration change should restart the resource. This is the default and is usually the right choice when dependency changes mean the work itself must refresh.
PreserveOnChange means keep the live instance even though the declaration changed. Use it carefully when continuity matters more than immediate restart.
These names are short, but the intent matters. Policy is not decoration. It is how you describe lifetime behavior to the runtime.

Resource reconciliation

The runtime reconciles resources by comparing the key, declaration kind, dependency payload, and policy.
That is why resource declarations are safe to make during a pure component conversion method. Declaring a resource does not perform the work immediately. It gives the runtime the information it needs to start, preserve, restart, or stop the resource after the conversion phase.

Choosing between capabilities and resources

Use a capability when the question is "ask the host to do this operation."
Use a resource when the question is "this work should stay mounted while this part of the user interface exists."
If you are deciding between a capability and a job, ask whether the work is host-owned. If yes, capability. If not, job.
If you are deciding between a reducer effect and a resource, ask whether the work should happen because one action occurred or because a subtree should stay alive. If the widget tree should own the lifetime, use a resource.
For reducer-side jobs, services, and commands, continue to Commands, services, and jobs. For built-in device and host capability groups, see Platform capabilities. For the component conversion contract that declares resources, pair this page with Widget authoring boundary. For the broader async teaching guide, see Resources and async. For file-picker streams, see How do I do file uploads in Fission?. For the reducer boundary, see Why reducers are synchronous.
Fission
A cross-platform, GPU-accelerated user interface framework for Rust. MIT licensed.
Copyright (c) 2026 Fission
Ready to use today. Widget APIs are expected to remain stable; some runtime and shell APIs may change before 1.0.0.
Fission 0.7.0