# Custom Backend
URL: /docs/react-native/custom-backend

Connect your React Native app to your own backend API.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

By default, `useLocalRuntime` manages threads and messages on-device. You can connect to your own backend in two ways depending on your needs.

## Option 1: ChatModelAdapter only

The simplest approach — keep thread management local, but send messages to your backend for inference.

```
import type { ChatModelAdapter } from "@assistant-ui/react-native";

export const myChatAdapter: ChatModelAdapter = {
  async *run({ messages, abortSignal }) {
    const response = await fetch("https://my-api.com/chat", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ messages }),
      signal: abortSignal,
    });

    const reader = response.body?.getReader();
    if (!reader) throw new Error("No response body");

    const decoder = new TextDecoder();
    let fullText = "";

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      const chunk = decoder.decode(value, { stream: true });
      fullText += chunk;
      yield { content: [{ type: "text", text: fullText }] };
    }
  },
};
```

```
import { useLocalRuntime } from "@assistant-ui/react-native";
import { myChatAdapter } from "@/adapters/my-chat-adapter";

export function useAppRuntime() {
  return useLocalRuntime(myChatAdapter);
}
```

This gives you:

- Streaming chat responses from your API
- In-memory thread and message management
- Automatic features like message editing, reloading, and branch switching

## Option 2: Full backend thread management

When you want your backend to own thread state (e.g. for cross-device sync, team sharing, or server-side history), implement a `RemoteThreadListAdapter`.

1. ### Implement the adapter

   ```
   import type { RemoteThreadListAdapter } from "@assistant-ui/react-native";
   import { createAssistantStream } from "assistant-stream";

   const API_BASE = "https://my-api.com";

   export const myThreadListAdapter: RemoteThreadListAdapter = {
     async list() {
       const res = await fetch(`${API_BASE}/threads`);
       const threads = await res.json();
       return {
         threads: threads.map((t: any) => ({
           remoteId: t.id,
           status: t.archived ? "archived" : "regular",
           title: t.title,
         })),
       };
     },

     async initialize(localId) {
       const res = await fetch(`${API_BASE}/threads`, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ localId }),
       });
       const { id } = await res.json();
       return { remoteId: id, externalId: undefined };
     },

     async rename(remoteId, title) {
       await fetch(`${API_BASE}/threads/${remoteId}`, {
         method: "PATCH",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ title }),
       });
     },

     async archive(remoteId) {
       await fetch(`${API_BASE}/threads/${remoteId}/archive`, {
         method: "POST",
       });
     },

     async unarchive(remoteId) {
       await fetch(`${API_BASE}/threads/${remoteId}/unarchive`, {
         method: "POST",
       });
     },

     async delete(remoteId) {
       await fetch(`${API_BASE}/threads/${remoteId}`, { method: "DELETE" });
     },

     async fetch(remoteId) {
       const res = await fetch(`${API_BASE}/threads/${remoteId}`);
       const t = await res.json();
       return {
         remoteId: t.id,
         status: t.archived ? "archived" : "regular",
         title: t.title,
       };
     },

     async generateTitle(remoteId, unstable_messages) {
       return createAssistantStream(async (controller) => {
         const res = await fetch(`${API_BASE}/threads/${remoteId}/title`, {
           method: "POST",
           headers: { "Content-Type": "application/json" },
           body: JSON.stringify({ messages: unstable_messages }),
         });
         const { title } = await res.json();
         controller.appendText(title);
       });
     },
   };
   ```

2. ### Compose the runtime

   ```
   import {
     useLocalRuntime,
     useRemoteThreadListRuntime,
   } from "@assistant-ui/react-native";
   import { myChatAdapter } from "@/adapters/my-chat-adapter";
   import { myThreadListAdapter } from "@/adapters/my-thread-list-adapter";

   export function useAppRuntime() {
     return useRemoteThreadListRuntime({
       runtimeHook: () => useLocalRuntime(myChatAdapter),
       adapter: myThreadListAdapter,
     });
   }
   ```

3. ### Use in your app

   ```
   import { AssistantRuntimeProvider } from "@assistant-ui/react-native";
   import { useAppRuntime } from "@/hooks/use-app-runtime";

   export default function App() {
     const runtime = useAppRuntime();
     return (
       <AssistantRuntimeProvider runtime={runtime}>
         {/* your chat UI */}
       </AssistantRuntimeProvider>
     );
   }
   ```

## Adapter methods

| Method                                       | Description                                          |
| -------------------------------------------- | ---------------------------------------------------- |
| `list()`                                     | Return all threads on mount                          |
| `initialize(localId)`                        | Create a thread server-side, return `{ remoteId }`   |
| `rename(remoteId, title)`                    | Persist title changes                                |
| `archive(remoteId)`                          | Mark thread as archived                              |
| `unarchive(remoteId)`                        | Restore archived thread                              |
| `delete(remoteId)`                           | Permanently remove thread                            |
| `fetch(remoteId)`                            | Fetch single thread metadata                         |
| `generateTitle(remoteId, unstable_messages)` | Return an `AssistantStream` with the generated title |

## Which option to choose?

|                       | Option 1: ChatModelAdapter     | Option 2: RemoteThreadListAdapter                   |
| --------------------- | ------------------------------ | --------------------------------------------------- |
| **Thread storage**    | In-memory                      | Your backend                                        |
| **Message storage**   | In-memory                      | In-memory (can add history adapter for server-side) |
| **Cross-device sync** | No                             | Yes                                                 |
| **Setup complexity**  | Minimal                        | Moderate                                            |
| **Best for**          | Single-device apps, prototypes | Production apps with user accounts                  |