A Sage app should ask for only the access its core experience needs. Put essential permissions under `required`, keep enhancements under `optional`, and request an optional grant when the user chooses the feature that needs it.
Choose required or optional
A required permission is part of the app’s minimum contract. If a required user-grantable permission is not granted, the app cannot be installed with that permission set. An optional permission may remain ungranted, so the app must still open and provide a useful fallback without it.
Every permission requested at runtime must already be declared in sage-manifest.json. The app cannot invent a new capability or network destination after installation. It also needs app.request_permission_grants to open Sage’s combined grant flow and app.get_capabilities to read the current capability set.
The combined request method is planned for Sage 0.13.1, so an app that depends on it should set sageVersion.min accordingly. This example treats wallet version information as an optional enhancement and an API origin as optional network access.
{
"sageVersion": {
"min": "0.13.1"
},
"permissions": {
"network": {
"whitelist": {
"optional": ["https://api.example.com"]
}
},
"capabilities": {
"required": [
"app.get_capabilities",
"app.request_permission_grants"
],
"optional": ["wallet.get_version"]
}
}
}
Read the initial capability state
After creating the Sage client, call sage.app.getCapabilities() and render optional features from the returned set. Do this during Sage startup instead of assuming that an optional capability was granted during installation.
A missing optional capability is an ordinary application state, not an error. Keep the feature disabled or show an action that explains why Sage will ask for access.
import type { SageClient } from "sage-app-sdk";
const VERSION_CAPABILITY = "wallet.get_version";
export async function hasVersionPermission(sage: SageClient) {
const capabilities = await sage.app.getCapabilities();
return capabilities.includes(VERSION_CAPABILITY);
}
Request access at the moment of intent
Call requestPermissionGrants() only after the user chooses the optional feature. Sage owns the approval interface. If the user approves, the requested capability becomes available and its method can be called without reinstalling the app.
One request can include multiple capabilities and network targets, so a feature that genuinely needs them together can present one approval. Keep unrelated permissions out of the batch and ask only for access needed by the action the user just chose.
If the user declines, the request rejects with a user_denied Bridge error. Treat that as a normal cancellation: keep the fallback available and let the user try again from the same explicit action later.
import {
formatSageError,
type SageClient,
} from "sage-app-sdk";
const VERSION_CAPABILITY = "wallet.get_version";
export async function enableVersionInfo(sage: SageClient) {
try {
await sage.app.requestPermissionGrants({
capabilities: [VERSION_CAPABILITY],
networkWhitelist: [],
});
return await sage.wallet.getVersion();
} catch (error) {
console.error(formatSageError(error));
return null;
}
}
Listen for capability changes
A user can remove an already granted optional capability in Sage while the app is still running. No request promise is involved in that change, so an app that should react immediately must subscribe with onGrantedCapabilitiesChange().
The event includes added, removed, and full. Derive the interface from full so grants and revocations follow the same path. When a capability disappears, stop calling its methods, disable or replace the affected feature, and clear any permission-dependent state that should no longer remain visible.
The SDK returns an unsubscribe function. Call it when the owning view is removed so repeated mounts do not accumulate listeners.
import type { SageClient } from "sage-app-sdk";
const VERSION_CAPABILITY = "wallet.get_version";
export function watchVersionPermission(
sage: SageClient,
onChange: (granted: boolean) => void,
) {
const publish = (capabilities: readonly string[]) => {
onChange(capabilities.includes(VERSION_CAPABILITY));
};
void sage.app.getCapabilities().then(publish);
return sage.app.onGrantedCapabilitiesChange((event) => {
publish(event.full);
});
}
Try the live permission flow
This panel checks the app’s current wallet.get_version grant and subscribes to onGrantedCapabilitiesChange(). If the capability is missing, its button asks Sage to open the approval interface.
After approval, the panel calls wallet.getVersion() and displays the result. Revoking the capability in Sage updates the panel through the same listener. In a normal browser, the panel stays available as a browser fallback and does not attempt a Bridge call.
Expect network grants to reload the app
Optional network access follows the same declaration-first rule. Include an origin that already appears under an optional network whitelist in requestPermissionGrants().
A change to the effective network whitelist forces Sage to reload the running app so its security policy can be rebuilt. The onGrantedNetworkWhitelistChange() event may arrive immediately before that reload, but do not depend on it for a lasting UI update. Initialize network-dependent state again when the app starts after the reload.
On approval, the page may reload before the request promise resumes. On denial, the promise rejects and the existing app remains open.
import type { SageClient } from "sage-app-sdk";
export async function enableApiAccess(sage: SageClient) {
await sage.app.requestPermissionGrants({
capabilities: [],
networkWhitelist: [
{
entry: {
scheme: "https",
host: "api.example.com",
},
},
],
});
}
- Save ordinary UI state before starting a network grant if losing it would surprise the user
- Recheck the active network and available backend access after startup
- Do not treat a network-whitelist change like a live capability toggle
Planned for Sage 0.13.1
As of August 23, 2026, this change has not been merged and Sage 0.13.1 has not been released. Treat the API below as planned behavior until the patch ships.
Sage 0.13.1 introduces requestPermissionGrants() and the app.request_permission_grants capability. The new method can request capabilities and network targets together in one approval.
It deprecates requestCapabilityGrant() and requestNetworkWhitelistGrant(). Apps that still support Sage 0.13.0 can continue using those older methods; apps that depend on the combined request should require Sage 0.13.1 or newer.
Bridge APIs and client behavior can evolve. Verify implementation details against the Sage version you support, then test in both Sage and a normal browser.
