react-native-worklets
React Native Worklets is the native runtime layer that moves selected JavaScript functions onto the UI runtime or separate worker runtimes. A Babel transform turns functions marked with a 'worklet' directive into transferable code, while scheduling APIs move serializable arguments and results between runtimes. It is infrastructure for animation, gesture, graphics, and compute-heavy React Native libraries, not a general replacement for promises, Web Workers, or ordinary application state.
Use Worklets directly when you are building the runtime-sensitive layer of a React Native library. Most application teams should install the higher-level library that already depends on it and avoid owning the native rebuild, compatibility, and cross-runtime memory rules themselves.
Use it if
- You are building a React Native library that must run small pieces of JavaScript on the UI runtime without waiting for the RN runtime
- You need a separate native worker runtime for CPU work and can keep the worklet inputs and outputs serializable
- You already use the New Architecture and a supported recent React Native release
- You need the same runtime foundation used by Reanimated, Gesture Handler, or Skia but are writing your own integration
- Your app still uses the Legacy Architecture: the official compatibility guide says Worklets is not tested on Paper and recommends Fabric
- You are outside the supported React Native window: version 0.11.3 declares a react-native peer range of 0.83 through 0.86, and the project generally supports only the latest three minor releases
- You only need animations or gesture callbacks: Reanimated and Gesture Handler expose higher-level APIs and already use Worklets underneath
- You need browser workers: on Web, UI scheduling uses requestAnimationFrame, while worker runtime creation and runtime scheduling are marked native-only
- You expect normal shared JavaScript objects across threads: cross-runtime closures are copied, serializable objects are frozen in development, and mutable cross-runtime state needs a Synchronizable or a higher-level abstraction
Setup reality
Installation changes native and JavaScript build layers. Expo users install the package and run npx expo prebuild, which creates or updates ios and android projects; a normal Expo Go session must use exactly the Worklets version bundled with that Expo SDK. React Native Community CLI users must add 'react-native-worklets/plugin' to the plugins array in babel.config.js, rebuild the native app, run pod install on iOS, and clear Metro's cache. Forgetting any one of those steps can produce 'Failed to create a worklet' or a mismatch among the JavaScript, Babel plugin, C++, and native versions. The published peer range for 0.11.3 is React Native 0.83 through 0.86 and the docs say Paper is not tested. Worklet closures are not ordinary closures once code crosses a runtime: captured data is serialized and copied, later mutations do not appear automatically, globals belong to each runtime, and functions sent back to the RN runtime must have been defined in RN scope. Testing also needs an explicit Jest mock or the package's Web resolver. The API is still pre-1.0 and the getting-started page calls it a transitional period, so avoid deprecated runOnUI and runOnJS in new code; scheduleOnUI and scheduleOnRN are their replacements.
Patterns
Enable worklet transformation in Community CLIconfigure-babel
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: ['react-native-worklets/plugin'],
};Put the entry in plugins, rebuild the native app, and reset Metro's cache. Expo SDK 54 and later starter templates include the plugin, but Community CLI projects must add it themselves.
Schedule a function on the UI runtimeschedule-ui-work
import { scheduleOnUI } from 'react-native-worklets';
function reportFrame(label: string) {
'worklet';
console.log(label, performance.now());
}
scheduleOnUI(reportFrame, 'frame');scheduleOnUI is asynchronous and returns no value. On Web it schedules with requestAnimationFrame rather than moving work to a separate UI thread.
Await a serializable result from the UI runtimeawait-ui-result
import { runOnUIAsync } from 'react-native-worklets';
const width = await runOnUIAsync((left: number, right: number) => {
'worklet';
return right - left;
}, 12, 84);Pass the function and its arguments separately. Calling the function before passing it runs it on the wrong runtime, and rejected worklet errors reject the returned promise.
Run a small synchronous UI queryread-ui-synchronously
import { runOnUISync } from 'react-native-worklets';
const runtimeTime = runOnUISync(() => {
'worklet';
return performance.now();
});This blocks the caller until the UI runtime returns a serializable value. Keep the callback tiny and prefer asynchronous scheduling when an immediate answer is unnecessary.
Create and initialize a worker runtimecreate-worker-runtime
import { createWorkletRuntime } from 'react-native-worklets';
const worker = createWorkletRuntime({
name: 'image-math',
initializer: () => {
'worklet';
globalThis.workerLabel = 'image-math';
},
});Worker runtimes are native-only. Each runtime has its own globals, and the initializer runs synchronously as the runtime is created.
Fire and forget work on a worker runtimeschedule-worker-task
import { scheduleOnRuntime } from 'react-native-worklets';
scheduleOnRuntime(worker, (values: number[]) => {
'worklet';
console.log(values.reduce((sum, value) => sum + value, 0));
}, [4, 8, 15, 16, 23, 42]);The work is queued and no result comes back. Inputs are serialized across runtimes, so do not expect later mutations of the original array to be visible.
Run CPU work and await its resultawait-worker-result
import { runOnRuntimeAsync } from 'react-native-worklets';
const total = await runOnRuntimeAsync(worker, (values: number[]) => {
'worklet';
return values.reduce((sum, value) => sum + value, 0);
}, [10, 20, 30]);This API is native-only and must be called from the RN runtime. Errors thrown by the worklet reject the promise.
Get an immediate result from a worker runtimerun-worker-synchronously
import { runOnRuntimeSync } from 'react-native-worklets';
const answer = runOnRuntimeSync(worker, (a: number, b: number) => {
'worklet';
return a * b;
}, 6, 7);The call blocks the RN runtime and can preempt the worker runtime. Use it only for short operations where synchronous ordering is required.
Update React state from a workletreturn-to-rn-runtime
import { scheduleOnRN, scheduleOnUI } from 'react-native-worklets';
const acceptResult = (value: number) => setProgress(value);
scheduleOnUI(() => {
'worklet';
scheduleOnRN(acceptResult, 100);
});The function passed to scheduleOnRN must be defined in RN runtime scope. Defining it inside the UI callback creates a UI-runtime function that cannot be scheduled back this way.
Share a small value between runtimesshare-pollable-state
import { createSynchronizable, scheduleOnUI } from 'react-native-worklets';
const status = createSynchronizable('idle');
status.setBlocking('working');
scheduleOnUI(() => {
'worklet';
console.log(status.getBlocking());
});Synchronizable values are copied on every access and are not reactive. getBlocking and setBlocking can wait on another thread, while getDirty may return stale data.
Choose behavior by current runtimebranch-by-runtime
import { getRuntimeKind, RuntimeKind, scheduleOnRN } from 'react-native-worklets';
function logMessage(message: string) {
'worklet';
if (getRuntimeKind() === RuntimeKind.ReactNative) {
console.log(message);
} else {
scheduleOnRN(console.log, message);
}
}getRuntimeKind replaces the deprecated _WORKLET global. Prefer the enum or isRNRuntime-style helpers over reading globalThis.__RUNTIME_KIND directly.
Use the packaged Jest mockmock-in-jest
jest.mock('react-native-worklets', () =>
require('react-native-worklets/src/mock')
);This path is the documented TypeScript setup. JavaScript builds use react-native-worklets/lib/module/mock, or set resolver to react-native-worklets/jest/resolver to exercise the Web implementation.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| react-native-reanimated | npm | Choose it for application animation and UI reactions instead of managing runtimes directly |
| react-native-threads | npm | Choose it when you want long-lived React Native worker threads with message passing rather than workletized functions |
| react-native-multithreading | npm | Consider it for an older worklet-style threading API when its React Native compatibility matches an existing app |