Skip to main content

Reliability and Error Handling

Client-side SDKs can run in browsers and mobile apps where networks, storage, and signed-definition verification can fail temporarily. The SDKs are designed to keep feature evaluation predictable while making those failures visible.

Server-side SDKs follow the same contract for snapshot/cache and signature failures — see Server-side reliability (shared overview; language-specific APIs are under each SDK).

Shared behavior​

The client-side SDKs follow the same reliability contract where the platform supports the equivalent feature:

  • onError reports fetch, cache, storage, parse, signature, and JWK failures.
  • The last error is stored before onError runs, so synchronous error state like lastError, state.error, or debug output is up to date in the callback.
  • After one successful load, refresh failures preserve the last-known-good flags instead of clearing UI state.
  • Fallback/default/empty flags are not written over persistent cache just because a transient fetch or parse failed.
  • Non-empty feature gates fail closed when no valid flag map is available.
  • Feature UI components subscribe to effective flag changes, so interval, WebSocket sync messages, identity, local-gate, and fallback transitions can re-render.

Error callback signatures​

Most JavaScript-family SDKs expose the same callback shape:

onError?: (message: string, error?: unknown) => void

Flutter includes the Dart stack trace when available:

onError: (String message, Object? error, StackTrace? stackTrace) {
// Report to your monitoring provider
}

React Native provider callbacks receive an Error instance:

<TogglyProvider
onError={(error) => {
// Report to your monitoring provider
}}
>
<App />
</TogglyProvider>

What to report​

Use onError for operational visibility rather than control flow. Good targets include Sentry, Crashlytics, Datadog, LogRocket, or your app logger.

onError: (message, error) => {
logger.warn('Toggly SDK error', { message, error })
}

The callback can fire when:

  • the definitions endpoint is unavailable;
  • a response cannot be parsed;
  • signed definitions cannot be verified;
  • JWKs cannot be fetched or do not contain the expected key;
  • local storage/cache reads or writes fail;
  • a refresh listener or hook throws.

It does not mean a flag is enabled or disabled. Evaluate features through the normal Feature component, hook, directive, or evaluateFeatureGate API.

Last-known-good fallback​

When a refresh fails after flags were already loaded, the SDK keeps evaluating against the previous effective flags and reports the failure through onError or error state. This prevents a temporary network or signature problem from unexpectedly hiding gated UI that was already visible.

On the first load, if no valid cache exists, the SDK falls back to configured defaults when provided. Treat defaults as a safety net, not as a replacement for monitoring onError.

UI updates after refresh​

React, Angular, Vue, Svelte, Astro, Gatsby, Next, Nuxt, Remix, Flutter, and React Native feature UI now subscribe to effective flag changes where those SDKs provide reactive UI. Timer refreshes, WebSocket sync signals, identity changes, and local-gate changes can update rendered content without requiring a page reload or app restart.

When the WebSocket sends sync with unchanged: true, SDKs skip an HTTP round-trip. See WebSocket sync.

For custom UI built directly on the service/client object, prefer the SDK's reactive hook/store/directive/component. If you need lower-level subscriptions, use APIs such as subscribeFeaturesRefresh or React Native's effectiveFlagsChanged event when available.

Docusaurus edge behavior​

The Docusaurus edge worker does not expose an application onError callback, but it follows the cache portion of the same contract. Transient flag or manifest fetch failures do not overwrite a last-known-good manifest or cache with {}. That keeps edge stripping and page gating from converting a temporary fetch failure into broad false/404 behavior.

On a cold start, when no prior successful fetch has populated edge or in-memory cache yet, the worker still returns an empty flag map until the first successful load. Treat that as a separate bootstrap case from refresh-time last-known-good fallback.