diff --git a/.cspell-wordlist.txt b/.cspell-wordlist.txt
index 9daa4dc166..36855896be 100644
--- a/.cspell-wordlist.txt
+++ b/.cspell-wordlist.txt
@@ -180,3 +180,4 @@ nˈɛvəɹ
ˈɛniwˌʌn
ˈɛls
Synchronizable
+stringifying
diff --git a/docs/docs/01-fundamentals/01-getting-started.md b/docs/docs/01-fundamentals/01-getting-started.md
index b82322dd8c..1ec3f29499 100644
--- a/docs/docs/01-fundamentals/01-getting-started.md
+++ b/docs/docs/01-fundamentals/01-getting-started.md
@@ -38,13 +38,17 @@ For supported React Native and Expo versions, see the [Compatibility table](../0
## Installation
-Installation is pretty straightforward, just use your favorite package manager.
+Installation is pretty straightforward, use your package manager of choice to install the package and some peer dependencies required to streamline model downloads. If you want to implement your custom model fetching logic, see [this document](../08-resource-fetcher/02-custom-adapter.md).
```
npm install react-native-executorch
+ # For Expo projects
+ npm install react-native-executorch-expo-resource-fetcher
+ # For bare React Native projects
+ npm install react-native-executorch-bare-resource-fetcher
```
@@ -52,6 +56,11 @@ Installation is pretty straightforward, just use your favorite package manager.
```
pnpm install react-native-executorch
+ # For Expo projects
+ pnpm install react-native-executorch-expo-resource-fetcher
+ # For bare React Native projects
+ pnpm install react-native-executorch-bare-resource-fetcher
+
```
@@ -59,11 +68,29 @@ Installation is pretty straightforward, just use your favorite package manager.
```
yarn add react-native-executorch
+ # For Expo projects
+ yarn install react-native-executorch-expo-resource-fetcher
+ # For bare React Native projects
+ yarn install react-native-executorch-bare-resource-fetcher
```
+:::warning
+Before using any other API, you must call `initExecutorch` with a resource fetcher adapter at the entry point of your app:
+
+```js
+import { initExecutorch } from 'react-native-executorch';
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+// or BareResourceFetcher for Expo projects
+
+initExecutorch({ resourceFetcher: ExpoResourceFetcher });
+```
+
+Calling any library API without initializing first will throw a `ResourceFetcherAdapterNotInitialized` error.
+:::
+
Our library offers support for both bare React Native and Expo projects. Please follow the instructions from [Loading models section](./02-loading-models.md) to make sure you setup your project correctly. We encourage you to use Expo project if possible. If you are planning to migrate from bare React Native to Expo project, the link (https://docs.expo.dev/bare/installing-expo-modules/) offers a guidance on setting up Expo Modules in a bare React Native environment.
If you plan on using your models via require() instead of fetching them from a url, you also need to add following lines to your `metro.config.js`:
@@ -89,7 +116,7 @@ Because we are using ExecuTorch under the hood, you won't be able to build iOS a
Running the app with the library:
```bash
-yarn run expo: -d
+yarn -d
```
## Supporting new models in React Native ExecuTorch
diff --git a/docs/docs/05-utilities/resource-fetcher.md b/docs/docs/05-utilities/resource-fetcher.md
deleted file mode 100644
index 1dfea89b3f..0000000000
--- a/docs/docs/05-utilities/resource-fetcher.md
+++ /dev/null
@@ -1,218 +0,0 @@
----
-title: Resource Fetcher
----
-
-This module provides functions to download and work with downloaded files stored in the application's document directory inside the `react-native-executorch/` directory. These utilities can help you manage your storage and clean up the downloaded files when they are no longer needed.
-
-## fetch
-
-Fetches resources (remote URLs, local files or embedded assets), downloads or stores them locally for use by React Native ExecuTorch.
-
-### Reference
-
-```typescript
-import { ResourceFetcher } from 'react-native-executorch';
-
-const uris = await ResourceFetcher.fetch(
- (progress) => console.log('Total progress:', progress),
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-);
-```
-
-### Parameters
-
-- `callback: (downloadProgress: number) => void` - Optional callback to track progress of all downloads, reported between 0 and 1.
-- `...sources: ResourceSource[]` - Multiple resources that can be strings, asset references, or objects.
-
-### Returns
-
-`Promise`:
-
-- If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded/stored resources (without file:// prefix).
-- If the fetch was interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`.
-
-:::info
-If the resource is an object, it will be saved as a JSON file on disk.
-:::
-
-## pauseFetching
-
-Pauses an ongoing download of files.
-
-### Reference
-
-```typescript
-import { ResourceFetcher } from 'react-native-executorch';
-
-const uris = ResourceFetcher.fetch(
- (progress) => console.log('Total progress:', progress),
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-).then((uris) => {
- console.log('URI resolved to: ', uris); // since we pause the fetch, uris is resolved to null
-});
-
-await ResourceFetcher.pauseFetching(
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-);
-```
-
-### Parameters
-
-- `...sources: ResourceSource[]` - The resource identifiers used when calling `fetch`.
-
-### Returns
-
-`Promise` – A promise that resolves once the download is paused.
-
-## resumeFetching
-
-Resumes a paused download of files.
-
-### Reference
-
-```typescript
-import { ResourceFetcher } from 'react-native-executorch';
-
-const uris = ResourceFetcher.fetch(
- (progress) => console.log('Total progress:', progress),
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-).then((uris) => {
- console.log('URI resolved as: ', uris); // since we pause the fetch, uris is resolved to null
-});
-
-await ResourceFetcher.pauseFetching(
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-);
-
-const resolvedUris = await ResourceFetcher.resumeFetching(
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-);
-//resolvedUris is resolved to file paths to fetched resources, unless explicitly paused/cancel again.
-```
-
-### Parameters
-
-- `...sources: ResourceSource[]` - The resource identifiers used when calling `fetch`.
-
-### Returns
-
-`Promise`:
-
-- If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded resources (without file:// prefix).
-- If the fetch was again interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to null.
-
-:::info
-The other way to resume paused resources is to simply call `fetch` again. However, `resumeFetching` is faster.
-:::
-
-## cancelFetching
-
-Cancels an ongoing/paused download of files.
-
-### Reference
-
-```typescript
-import { ResourceFetcher } from 'react-native-executorch';
-
-const uris = ResourceFetcher.fetch(
- (progress) => console.log('Total progress:', progress),
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-).then((uris) => {
- console.log('URI resolved as: ', uris); // since we cancel the fetch, uris is resolved to null
-});
-
-await ResourceFetcher.cancelFetching(
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-);
-```
-
-### Parameters
-
-- `...sources: ResourceSource[]` - The resource identifiers used when calling `fetch()`.
-
-### Returns
-
-`Promise` – A promise that resolves once the download is cancelled.
-
-## deleteResources
-
-Deletes downloaded resources from the local filesystem.
-
-### Reference
-
-```typescript
-import { ResourceFetcher } from 'react-native-executorch';
-
-await ResourceFetcher.deleteResources('https://.../llama3_2.pte');
-```
-
-### Parameters
-
-- `...sources: ResourceSource[]` - The resource identifiers used when calling `fetch`.
-
-### Returns
-
-`Promise` – A promise that resolves once all specified resources have been removed.
-
-## getFilesTotalSize
-
-Fetches the info about files size. Works only for remote files.
-
-### Reference
-
-```typescript
-import { ResourceFetcher } from 'react-native-executorch';
-
-const totalSize = await ResourceFetcher.getFilesTotalSize(
- 'https://.../llama3_2.pte',
- 'https://.../qwen3.pte'
-);
-```
-
-### Parameters
-
-- `...sources: ResourceSource[]` - The resource identifiers (URLs).
-
-### Returns
-
-`Promise` – A promise that resolves to combined size of files in bytes.
-
-## listDownloadedFiles
-
-Lists all the downloaded files used by React Native ExecuTorch.
-
-### Reference
-
-```typescript
-import { ResourceFetcher } from 'react-native-executorch';
-
-const filesUris = await ResourceFetcher.listDownloadedFiles();
-```
-
-### Returns
-
-`Promise` - A promise, which resolves to an array of URIs for all the downloaded files.
-
-## listDownloadedModels
-
-Lists all the downloaded models used by React Native ExecuTorch.
-
-### Reference
-
-```typescript
-import { ResourceFetcher } from 'react-native-executorch';
-
-const modelsUris = await ResourceFetcher.listDownloadedModels();
-```
-
-### Returns
-
-`Promise` - A promise, which resolves to an array of URIs for all the downloaded models.
diff --git a/docs/docs/08-resource-fetcher/01-usage.md b/docs/docs/08-resource-fetcher/01-usage.md
new file mode 100644
index 0000000000..135b196113
--- /dev/null
+++ b/docs/docs/08-resource-fetcher/01-usage.md
@@ -0,0 +1,236 @@
+---
+title: Usage
+---
+
+This page documents the resource fetcher APIs exposed by `react-native-executorch-expo-resource-fetcher` and `react-native-executorch-bare-resource-fetcher`. These adapters handle downloading and managing model files on disk.
+
+:::info
+All examples below use `ExpoResourceFetcher`. If you're on bare React Native, replace the import with:
+
+```typescript
+import { BareResourceFetcher } from 'react-native-executorch-bare-resource-fetcher';
+```
+
+The public API is identical between both adapters.
+:::
+
+## fetch
+
+Fetches resources (remote URLs, local files, or embedded assets) and stores them locally for use by React Native ExecuTorch.
+
+### Reference
+
+```typescript
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+
+const uris = await ExpoResourceFetcher.fetch(
+ (progress) => console.log('Total progress:', progress),
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+);
+```
+
+### Parameters
+
+- `callback: (downloadProgress: number) => void` - Optional callback to track progress of all downloads, reported between 0 and 1.
+- `...sources: ResourceSource[]` - Multiple resources that can be strings, asset references, or objects.
+
+### Returns
+
+`Promise`:
+
+- If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded/stored resources (without `file://` prefix).
+- If the fetch was interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`.
+
+:::info
+If the resource is an object, it will be saved as a JSON file on disk.
+:::
+
+## pauseFetching
+
+Pauses an ongoing download.
+
+:::info
+Bare Resource Fetcher doesn't support this feature on Android.
+:::
+
+### Reference
+
+```typescript
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+
+const uris = ExpoResourceFetcher.fetch(
+ (progress) => console.log('Total progress:', progress),
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+).then((uris) => {
+ console.log('URI resolved to: ', uris); // null, since we paused
+});
+
+await ExpoResourceFetcher.pauseFetching(
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+);
+```
+
+### Parameters
+
+- `...sources: ResourceSource[]` - The resource identifiers used when calling `fetch`.
+
+### Returns
+
+`Promise` – A promise that resolves once the download is paused.
+
+## resumeFetching
+
+:::info
+Bare Resource Fetcher doesn't support this feature on Android.
+:::
+
+Resumes a paused download.
+
+### Reference
+
+```typescript
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+
+const uris = ExpoResourceFetcher.fetch(
+ (progress) => console.log('Total progress:', progress),
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+).then((uris) => {
+ console.log('URI resolved as: ', uris); // null, since we paused
+});
+
+await ExpoResourceFetcher.pauseFetching(
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+);
+
+const resolvedUris = await ExpoResourceFetcher.resumeFetching(
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+);
+// resolvedUris resolves to file paths, unless paused/cancelled again
+```
+
+### Parameters
+
+- `...sources: ResourceSource[]` - The resource identifiers used when calling `fetch`.
+
+### Returns
+
+`Promise`:
+
+- If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded resources (without `file://` prefix).
+- If the fetch was again interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`.
+
+:::info
+You can also resume a paused download by calling `fetch` again with the same sources. However, `resumeFetching` is faster as it resumes from where it left off.
+:::
+
+## cancelFetching
+
+Cancels an ongoing or paused download.
+
+### Reference
+
+```typescript
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+
+const uris = ExpoResourceFetcher.fetch(
+ (progress) => console.log('Total progress:', progress),
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+).then((uris) => {
+ console.log('URI resolved as: ', uris); // null, since we cancelled
+});
+
+await ExpoResourceFetcher.cancelFetching(
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+);
+```
+
+### Parameters
+
+- `...sources: ResourceSource[]` - The resource identifiers used when calling `fetch()`.
+
+### Returns
+
+`Promise` – A promise that resolves once the download is cancelled.
+
+## deleteResources
+
+Deletes downloaded resources from the local filesystem.
+
+### Reference
+
+```typescript
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+
+await ExpoResourceFetcher.deleteResources('https://.../llama3_2.pte');
+```
+
+### Parameters
+
+- `...sources: ResourceSource[]` - The resource identifiers used when calling `fetch`.
+
+### Returns
+
+`Promise` – A promise that resolves once all specified resources have been removed.
+
+## getFilesTotalSize
+
+Fetches the combined size of remote files. Works only for remote URLs.
+
+### Reference
+
+```typescript
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+
+const totalSize = await ExpoResourceFetcher.getFilesTotalSize(
+ 'https://.../llama3_2.pte',
+ 'https://.../qwen3.pte'
+);
+```
+
+### Parameters
+
+- `...sources: ResourceSource[]` - The resource identifiers (URLs).
+
+### Returns
+
+`Promise` – A promise that resolves to the combined size of files in bytes.
+
+## listDownloadedFiles
+
+Lists all files downloaded by React Native ExecuTorch.
+
+### Reference
+
+```typescript
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+
+const filesUris = await ExpoResourceFetcher.listDownloadedFiles();
+```
+
+### Returns
+
+`Promise` - A promise that resolves to an array of URIs for all downloaded files.
+
+## listDownloadedModels
+
+Lists all downloaded model files (`.pte`).
+
+### Reference
+
+```typescript
+import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
+
+const modelsUris = await ExpoResourceFetcher.listDownloadedModels();
+```
+
+### Returns
+
+`Promise` - A promise that resolves to an array of URIs for all downloaded model files.
diff --git a/docs/docs/08-resource-fetcher/02-custom-adapter.md b/docs/docs/08-resource-fetcher/02-custom-adapter.md
new file mode 100644
index 0000000000..36d25a888d
--- /dev/null
+++ b/docs/docs/08-resource-fetcher/02-custom-adapter.md
@@ -0,0 +1,56 @@
+---
+title: Custom Adapter
+---
+
+If the built-in `BareResourceFetcher` and `ExpoResourceFetcher` don't fit your needs, you can implement your own adapter and plug it into React Native ExecuTorch. This is useful if you want to use a different download library, fetch from a private server, or add custom caching logic.
+
+## The ResourceFetcherAdapter interface
+
+Your adapter must implement the `ResourceFetcherAdapter` interface exported from `react-native-executorch`. This interface defines every method which is used under the hood by React Native ExecuTorch:
+
+```typescript
+import {
+ ResourceFetcherAdapter,
+ ResourceSource,
+} from 'react-native-executorch';
+
+interface ResourceFetcherAdapter {
+ fetch(
+ callback: (downloadProgress: number) => void,
+ ...sources: ResourceSource[]
+ ): Promise;
+
+ readAsString(path: string): Promise;
+}
+```
+
+### `fetch`
+
+This is the core method called by every model hook and module whenever it needs to resolve a model or resource to a local file path.
+
+- `callback` — called with a progress value between `0` and `1` as downloads proceed. Called with `1` when complete.
+- `...sources` — one or more `ResourceSource` values:
+ - `string` — a remote URL or an absolute local file path.
+ - `number` — a bundled asset reference from `require('./model.pte')`.
+ - `object` — an inline JS object (e.g. a tokenizer config) that should be JSON-serialized and written to disk. Your adapter is responsible for stringifying it and saving it as a file, then returning the local path. This allows callers to pass configs inline instead of hosting them at a URL.
+- **Returns** an array of absolute local file paths (without `file://` prefix), one per source, in the same order. Return `null` if the fetch was intentionally interrupted (e.g. cancelled by the user).
+
+### `readAsString`
+
+Called internally to read configuration files (e.g. tokenizer configs) that were previously downloaded via `fetch`.
+
+- `path` — absolute path to the file on disk.
+- **Returns** the file contents as a UTF-8 string.
+
+## Registering your adapter
+
+Pass your adapter to `initExecutorch` at the entry point of your app, before any other library API is called:
+
+```typescript
+import { initExecutorch } from 'react-native-executorch';
+import { MyCustomFetcher } from './MyCustomFetcher';
+
+initExecutorch({ resourceFetcher: MyCustomFetcher });
+```
+
+Any model hook or module used after this point will route all resource fetching through your adapter.
diff --git a/docs/docs/08-resource-fetcher/_category_.json b/docs/docs/08-resource-fetcher/_category_.json
new file mode 100644
index 0000000000..e6f56e8790
--- /dev/null
+++ b/docs/docs/08-resource-fetcher/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "Resource Fetcher",
+ "link": {
+ "type": "generated-index"
+ }
+}
diff --git a/docs/sidebars.js b/docs/sidebars.js
index d1b6c57637..76e1313467 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -40,6 +40,11 @@ const sidebars = {
label: 'Utilities',
items: [{ type: 'autogenerated', dirName: '05-utilities' }],
},
+ {
+ type: 'category',
+ label: 'Resource Fetcher',
+ items: [{ type: 'autogenerated', dirName: '08-resource-fetcher' }],
+ },
{
type: 'category',
label: 'Other',
diff --git a/packages/react-native-executorch/src/utils/ResourceFetcher.ts b/packages/react-native-executorch/src/utils/ResourceFetcher.ts
index 52a3d1ca9a..cc3cfcb1a2 100644
--- a/packages/react-native-executorch/src/utils/ResourceFetcher.ts
+++ b/packages/react-native-executorch/src/utils/ResourceFetcher.ts
@@ -106,7 +106,7 @@ export class ResourceFetcher {
static getAdapter(): ResourceFetcherAdapter {
if (!this.adapter) {
const errorMessage =
- 'ResourceFetcher adapter is not initialized. Please call initExecutorch({ resourceFetcher: ... }) with a valid adapter, e.g., from react-native-executorch-expo-resource-fetcher or react-native-executorch-bare-resource-fetcher. For more details please refer: https://docs.swmansion.com/react-native-executorch/docs/next/fundamentals/loading-models';
+ 'ResourceFetcher adapter is not initialized. Please call initExecutorch({ resourceFetcher: ... }) with a valid adapter, e.g., from react-native-executorch-expo-resource-fetcher or react-native-executorch-bare-resource-fetcher. For more details please refer to: https://docs.swmansion.com/react-native-executorch/docs/next/fundamentals/loading-models';
// for sanity :)
Logger.error(errorMessage);
throw new RnExecutorchError(