> ## Documentation Index
> Fetch the complete documentation index at: https://docs.base44.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Internal links on these pages omit the .md extension. Append .md to a docs page URL, or send an Accept: text/markdown header, to get that page as markdown.

# useChat

***

## Overview

> **useChat**(`options`): `UseChatResult`

Runs an app's AI chat in your own React UI. Requires a browser environment and can't be used in
the backend.

The hook is headless. It gives you the conversation and the [actions](#returned-actions), and your components render them:

* Follows the app live, and renews its socket session when it expires.
* Turns Base44's messages into [items](#items), and shows each open [question](#questions) with the step that asked it.
* Reports what the AI is doing in [`status`](#status), and the live connection in [`connection`](#connection).
* Sends prompts, creates apps, and answers questions through the [backend](#backend-calls) you pass, so no credential reaches the browser.

### Example

```tsx Render the chat and answer an approval theme={null}
import { useChat } from "@base44/platform/react";
import Markdown from "react-markdown";

// ...

// Inside your component
const chat = useChat({ appId, backend, onAppCreated: selectApp });
return (
  <>
    {chat.items.map((item) => (
      <div key={item.id} className={item.role}>
        {item.text && <Markdown>{item.text}</Markdown>}
        {item.steps.map((step) =>
          step.question?.kind === "approval" ? (
            <button key={step.id} onClick={step.question.approve}>Allow {step.question.action}</button>
          ) : (
            <small key={step.id}>{step.label}</small>
          ),
        )}
      </div>
    ))}
    {chat.error && <p role="alert">{chat.error.message}</p>}
    <button onClick={() => (appId ? chat.send(prompt) : chat.create(prompt))}>Send</button>
  </>
);
```

## Parameters

**options** `ChatOptions` *required*

The app to follow, your backend's calls, and what to do with a created app.

> **Properties**
>
> **appId** `string | null` *required*
>
> ID of the app to follow, or `null` until an app exists. While it's `null`, the chat follows the
> app that [`create()`](#create) made, so you don't have to pass the new ID back.
> Changing `appId` stops following that app, so setting it back to `null` starts a new chat.
> Switching to another app closes the old session and starts over.
>
> **backend** `ChatBackend` *required*
>
> The functions the hook calls to reach Base44 through your backend, described in
> [Backend calls](#backend-calls). The hook uses the `backend` from the latest render, so you can
> pass an inline object without memoizing it.
>
> **onAppCreated** `(app: ChatApp) =\> void`
>
> Called with the new app after [`create()`](#create) succeeds. The chat already follows the new
> app. To keep its ID in your own state, pass it back as `appId`.

## Returned state

The hook returns one object, of type `UseChatResult`. Its state is below, and its functions are
under [Returned actions](#returned-actions).

### items

`ChatItem[]`

The conversation, oldest first. Each open question appears with the step that asked it.

> **Properties**
>
> **id** `string` *required*
>
> ID of the message.
>
> **role** `"user" | "assistant"` *required*
>
> Who wrote the message.
>
> **text** `string` *required*
>
> The message text, as raw markdown. Show it before the message's steps. It's empty when the message only holds tool calls.
>
> **createdAt** `string` *required*
>
> When the message was created, as an ISO 8601 timestamp in UTC. A timestamp from Base44 has no `Z`
> or offset, so add a `Z` before parsing it with `new Date()`. A pending prompt has the browser's
> time, which already ends in `Z`.
>
> **steps** `ChatStep[]` *required*
>
> One step per tool call, in order.
>
> > **Properties**
> >
> > **id** `string` *required*
> >
> > ID of the tool call.
> >
> > **tool** `string` *required*
> >
> > Name of the tool, such as `write_file`.
> >
> > **files** `string[]` *required*
> >
> > The file paths the tool call touched.
> >
> > **label** `string` *required*
> >
> > A line to show for the step. It's Base44's description of the step when there is one. Otherwise
> > it's the tool name followed by the entity the step touched and its record count, such as
> > `create_records Task (3)`, or by the step's files.
> >
> > **status** `"error" | "running" | "stopped" | "waiting" | "done"` *required*
> >
> > The step's progress. A `waiting` step needs the user to answer its question. A `stopped` step was
> > declined, or its turn was stopped. A status this release doesn't know counts as `done`.
> >
> > **question** `ChatQuestion`
> >
> > The [question](#questions) the step needs answered, with the actions that answer it. It's present only on a
> > `waiting` step in the latest assistant reply, and goes away once answered. A `waiting` step in an
> > older message has no question. It's also absent for a question kind this release doesn't know.
> >
> > **answered** `ChatAnswer[]`
> >
> > The user's answers to a choice question, one for each question the step asked. It stays after the question closes, so you can show the answers in the chat's history.
> >
> > > **Properties**
> > >
> > > **question** `string` *required*
> > >
> > > The question text.
> > >
> > > **labels** `string[]` *required*
> > >
> > > The labels the user picked.
> > >
> > > **text** `string`
> > >
> > > The free text the user wrote, if any.
>
> **pending** `boolean` *required*
>
> Whether this is a stand-in for a prompt you just sent. When you call [`send()`](#send) or
> [`create()`](#create), the prompt appears in `items` right away as a pending item, and Base44's
> copy of the message replaces it once it arrives. A failed send removes it. A prompt sent while the
> AI is working waits in Base44's queue, so it stays pending until it runs.

***

### status

`ChatStatus`

What the AI is doing.

When several hold at once, the first in this list wins.

* `error`: Base44 reports that the last build failed.
* `waiting`: A question waits for the user's answer.
* `building`: The AI is working.
* `submitted`: A prompt, a new app, or an answer is on its way. It lasts until Base44 reports work, a reply, or a queue change, or until the call fails.
* `ready`: None of the above.

***

### connection

`ChatConnection`

Whether the chat follows the app live.

* `connecting`: The chat waits for the app's first snapshot.
* `connected`: The first snapshot arrived, and updates follow live.
* `disconnected`: Live updates stopped for good. The session couldn't open, the retries ran out,
  or Base44 ended the session or the app's subscription. Check [`error`](#error) to see why.

Watching another app, or mounting the chat again, starts over.

***

### error

`ChatError | null`

The failure to show, or `null`. It's set when a call fails, when the live connection fails, and
whenever `status` is `error`. When more than one of these conditions is true, `error` holds the
failed call first, then the connection error, then the failed build. A failed call's error
clears on the next action, and a connection error stays until [`clearError()`](#clearerror). A
failed build's error stays while Base44 reports it. Switching to another app clears it.

> **Properties**
>
> **message** `string` *required*
>
> A readable description of what went wrong, such as `The build failed`.
>
> **code** `string`
>
> A value your code can check to handle the error, such as `build_failed` for a failed build. A
> connection error has one of the session or app error codes listed under the chat module's
> [Errors](/developers/references/platform-sdk/docs/interfaces/chat#errors). A failed call to
> your backend has no code.
>
> **cause** `unknown`
>
> The error the failure came from, such as the one your backend threw. Log it to debug the failure. It's absent when the hook found the failure itself, such as a failed build or a send with no app.

## Returned actions

Functions on the same object, which you call to act on the chat, as in `chat.send(prompt)`. They
never throw. A failure sets [`error`](#error).

### send()

> **send**(`prompt`): `Promise`\<`void`>

Sends a prompt to the app the chat follows. The prompt appears in [`items`](#items) right away
as a pending item, and the reply arrives there too. Base44 queues the prompt while the AI is
working. If the chat doesn't follow an app yet, it sets [`error`](#error) instead. Use
[`create()`](#create) for the first prompt.

#### Parameters

**prompt** `string` *required*

The prompt to send.

#### Returns

`Promise<void>`

Promise resolving once Base44 accepts the prompt, or once the send fails and sets [`error`](#error). It never rejects.

#### Example

```tsx Send the prompt from a form theme={null}
function PromptForm({ chat }: { chat: UseChatResult }) {
  const [prompt, setPrompt] = useState("");
  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        chat.send(prompt);
        setPrompt("");
      }}
    >
      <input value={prompt} onChange={(event) => setPrompt(event.target.value)} />
      <button type="submit" disabled={!prompt.trim()}>Send</button>
      {chat.status === "building" && <small>The AI is working. New prompts wait in the queue.</small>}
    </form>
  );
}
```

***

### create()

> **create**(`prompt`): `Promise`\<`void`>

Creates an app from its first prompt. The prompt appears in [`items`](#items) right away as a
pending item. Once the app exists, the hook passes it to [`onAppCreated`](#param-onappcreated). If
`appId` is `null`, the chat then follows the new app. Otherwise it stays on the app it follows.

#### Parameters

**prompt** `string` *required*

The first prompt, which describes the app.

#### Returns

`Promise<void>`

Promise resolving once the app exists, or once creating it fails and sets [`error`](#error). It never rejects.

#### Example

```tsx Create the app from the first prompt, and send the next prompts to it theme={null}
const [appId, setAppId] = useState<string | null>(null);
const chat = useChat({ appId, backend, onAppCreated: (app) => setAppId(app.id) });

const submit = (prompt: string) => (appId ? chat.send(prompt) : chat.create(prompt));
```

***

### clearError()

> **clearError**(): `void`

Clears a failed call's or the connection's [`error`](#error). A failed build's error stays while Base44 reports it.

#### Returns

`void`

#### Example

```tsx Show the error, with a dismiss button unless the build failed theme={null}
{chat.error && (
  <div role="alert">
    {chat.error.message}
    {chat.error.code !== "build_failed" && <button onClick={chat.clearError}>Dismiss</button>}
  </div>
)}
```

## Backend calls

The functions you write for the `backend` option. The hook calls them when it needs Base44, and
each one usually forwards to a route on your own server, which adds your credentials.

Keeping the Base44 requests on your server means the browser never holds a workspace API key or
an access token. Your server can be written in any language.

Each function's route calls one Base44 endpoint:

* [`createApp()`](#createapp): [Create app](/api-reference/create-app), with the first prompt as `initial_message`.
* [`openLiveSession()`](#openlivesession): [Create socket session](/api-reference/create-socket-session) with `{ app_ids: [appId] }`.
  Return its `session_token`, `socket_url`, and `socket_path`, and keep `session_id` on your server.
  None of them is the workspace API key.
* [`sendMessage()`](#sendmessage): [Send chat message](/api-reference/send-chat-message).
* [`submitToolCallInput()`](#submittoolcallinput): [Submit tool-call input](/api-reference/submit-tool-call-input). Send an
  `X-Request-ID` built from the tool call ID, so a retry resumes the turn only once.

Base44 keeps the Send chat message and Submit tool-call input requests open until the AI
finishes the turn. Resolve your call once Base44 accepts the request, not when it returns. The
chat follows the rest live. A rejected promise from any call sets [`error`](#error).

#### Example

```typescript Your four functions, behind your own API routes theme={null}
async function post<T>(path: string, body: unknown): Promise<T> {
  const response = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
  if (!response.ok) throw new Error(`${path} failed: ${response.status}`);
  return response.json();
}

const backend: ChatBackend = {
  createApp: (prompt) => post("/api/chat/apps", { prompt }),
  openLiveSession: (appId) => post("/api/chat/session", { appId }),
  sendMessage: (appId, content) => post("/api/chat/message", { appId, content }),
  submitToolCallInput: (appId, answer) => post("/api/chat/answer", { appId, answer }),
};
```

### createApp()

> **createApp**(`prompt`): `Promise`\<`ChatApp`>

Creates an app from its first prompt. The AI starts building inside this call.

#### Parameters

**prompt** `string` *required*

The first prompt, which describes the app.

#### Returns

`ChatApp`

An app the AI chat works on.

Promise resolving to the new app.

> **Properties**
>
> **id** `string` *required*
>
> ID of the app, 24 lowercase hexadecimal characters.
>
> **name** `string` *required*
>
> Display name of the app.

#### Example

```typescript Your server route for createApp(), with a personal access token theme={null}
export async function POST(request: Request) {
  const { prompt } = await request.json();
  const response = await fetch("https://app.base44.com/api/apps", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.BASE44_ACCESS_TOKEN}`, "Content-Type": "application/json" },
    body: JSON.stringify({ initial_message: { content: prompt } }),
  });
  if (!response.ok) return new Response("Create app failed", { status: response.status });
  const app = await response.json();
  return Response.json({ id: app.id, name: app.name });
}
```

***

### openLiveSession()

> **openLiveSession**(`appId`): `Promise`\<`SocketSession`>

Opens a read-only socket session for one app. The chat calls it on the first connect and again
whenever the session expires. Sessions last one hour.

#### Parameters

**appId** `string` *required*

ID of the app to follow.

#### Returns

`SocketSession`

The three fields of a [Create socket session](/api-reference/create-socket-session) response that
the browser needs. Keep the response's `session_id` on your server.

Promise resolving to the session.

> **Properties**
>
> **session\_token** `string` *required*
>
> Token that authenticates the socket. The client keeps it in memory and sends it only when the socket connects.
>
> **socket\_url** `string` *required*
>
> Origin of the platform socket, with no path, query, fragment, or credentials.
>
> **socket\_path** `string` *required*
>
> Absolute path the socket connects on, such as `/ws/socket.io/`.

#### Example

```typescript Your server route for openLiveSession(), with a workspace API key theme={null}
export async function POST(request: Request) {
  const { appId } = await request.json();
  const response = await fetch("https://app.base44.com/api/v1/socket-sessions", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.BASE44_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ app_ids: [appId] }),
  });
  if (!response.ok) return new Response("Create socket session failed", { status: response.status });
  const { session_token, socket_url, socket_path, session_id } = await response.json();
  await saveSession(appId, session_id); // Keep it on your server to change or end the session
  return Response.json({ session_token, socket_url, socket_path });
}
```

***

### sendMessage()

> **sendMessage**(`appId`, `content`): `Promise`\<`void`>

Sends a prompt to an existing app. Base44 queues a prompt sent while the AI is working. If Base44
refuses the prompt, for example because the app is busy with an operation that blocks new
messages, reject the promise. The hook then sets [`error`](#error), so your UI can show the
failure.

#### Parameters

> **Properties**
>
> **appId** `string` *required*
>
> ID of the app.
>
> **content** `string` *required*
>
> The prompt.

#### Returns

`Promise<void>`

Promise resolving once Base44 accepts the prompt.

***

### submitToolCallInput()

> **submitToolCallInput**(`appId`, `answer`): `Promise`\<`void`>

Answers or declines a question the AI waits on.

#### Parameters

> **Properties**
>
> **appId** `string` *required*
>
> ID of the app.
>
> **answer** `ToolCallAnswer` *required*
>
> The tool call and the answer to it.
>
> > **Properties**
> >
> > **toolCallId** `string` *required*
> >
> > ID of the tool call being answered.
> >
> > **messageId** `string` *required*
> >
> > ID of the assistant message that holds the tool call.
> >
> > **approve** `boolean` *required*
> >
> > Whether to let the tool call run (`true`) or turn it down (`false`).
> >
> > **extraUserInput** `Record\<string, unknown\>`
> >
> > What the question asked for. A choice sends `{ answers }`, and a secrets question sends `{ secrets }`.

#### Returns

`Promise<void>`

Promise resolving once Base44 accepts the answer.

## Questions

> **ChatQuestion** = [`ChoiceQuestion`](#choicequestion) | [`SecretsQuestion`](#secretsquestion) | [`ApprovalQuestion`](#approvalquestion)

An open question the AI waits on. A `waiting` step carries it as its `question`, and
[`status`](#status) becomes `waiting`. Check `kind` to see which of the three kinds it is.

Each kind has only the actions that fit it, and every kind can be declined. Calling an action
closes the question right away and sends the answer through your
[`submitToolCallInput()`](#submittoolcallinput). The actions never throw. A failure sets
[`error`](#error), and the question opens again.

The hook declines a question kind this release doesn't know, once per tool call, when Base44
names that kind on the last assistant message. If the decline fails, [`error`](#error) says
so, and the hook tries again after it reconnects.

### ChoiceQuestion

The AI offers options and waits for the user to pick.

**kind** `'choice'` *required*

Kind of question.

**questions** `ChoiceEntry[]` *required*

The questions, in order.

> **Properties**
>
> **text** `string` *required*
>
> The question text.
>
> **description** `string`
>
> Supporting text for the question.
>
> **type** `string`
>
> Base44's type of question, such as `text_input` for one that takes only a free-text answer.
>
> **options** `string[]` *required*
>
> The option labels to pick from.
>
> **multi** `boolean` *required*
>
> Whether more than one option can be picked.

#### answer()

> **answer**(`picked`, `texts?`): `Promise`\<`void`>

Answers with the picked labels, and free text where the user wrote some. For a question whose
`multi` is `false`, only the first picked label counts.

##### Parameters

> **Properties**
>
> **picked** `string[][]` *required*
>
> One list of picked labels per question, in the order of `questions`.
>
> **texts** `string[]`
>
> One free-text answer per question, in the same order. Use it for a `text_input` question, or as a note beside a pick. A question with no pick and no text is skipped.

##### Returns

`Promise<void>`

Promise resolving once Base44 accepts the answer, or once sending it fails and sets [`error`](#error). It never rejects.

##### Example

```tsx Answer a one-question choice with a button per option theme={null}
function ChoiceButtons({ question }: { question: ChoiceQuestion }) {
  const [entry] = question.questions;
  return (
    <div>
      <p>{entry.text}</p>
      {entry.options.map((label) => (
        <button key={label} onClick={() => question.answer([[label]])}>{label}</button>
      ))}
      <button onClick={question.decline}>Skip</button>
    </div>
  );
}
```

***

#### decline()

> **decline**(): `Promise`\<`void`>

Declines the question. The AI continues without the answer.

##### Returns

`Promise<void>`

Promise resolving once Base44 accepts the decline, or once sending it fails and sets [`error`](#error). It never rejects.

##### Example

```tsx Skip the question theme={null}
<button onClick={question.decline}>Skip</button>
```

### SecretsQuestion

The AI needs secrets, such as API keys. The values go to Base44, and the hook keeps none of them.

**kind** `'secrets'` *required*

Kind of question.

**fields** `SecretField[]` *required*

The fields to fill.

> **Properties**
>
> **name** `string` *required*
>
> Name of the field, which keys its value in [`submit()`](#submit).
>
> **description** `string`
>
> Where to get the value.

#### submit()

> **submit**(`values`): `Promise`\<`void`>

Submits the values.

##### Parameters

**values** `Record\<string, string\>` *required*

The values, keyed by each field's `name`.

##### Returns

`Promise<void>`

Promise resolving once Base44 accepts the values, or once sending it fails and sets [`error`](#error). It never rejects.

##### Example

```tsx Ask for each secret, then submit the values by name theme={null}
function SecretsForm({ question }: { question: SecretsQuestion }) {
  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const form = new FormData(event.currentTarget);
        question.submit(Object.fromEntries(question.fields.map((field) => [field.name, String(form.get(field.name) ?? "")])));
      }}
    >
      {question.fields.map((field) => (
        <label key={field.name}>
          {field.name}
          <input name={field.name} type="password" placeholder={field.description} />
        </label>
      ))}
      <button type="submit">Save</button>
      <button type="button" onClick={question.decline}>Skip</button>
    </form>
  );
}
```

***

#### decline()

> **decline**(): `Promise`\<`void`>

Declines the question. The AI continues without the values.

##### Returns

`Promise<void>`

Promise resolving once Base44 accepts the decline, or once sending it fails and sets [`error`](#error). It never rejects.

##### Example

```tsx Skip without entering the secrets theme={null}
<button onClick={question.decline}>Skip</button>
```

### ApprovalQuestion

The AI asks for permission to run an action.

**kind** `'approval'` *required*

Kind of question.

**action** `string` *required*

Name of the tool that wants to run.

**reason** `string` *required*

Why the action needs approval. It's Base44's reason when there is one, otherwise the `summary` in `details`, and empty when neither is given.

**guard** `string` *required*

The check that paused the action, such as `exec_tool_send_email` or `entity_rls_guard`.

**details** `ApprovalDetails` *required*

What the action would do, as far as the check publishes it. Show it so the user knows what they approve.

> **Properties**
>
> **summary** `string`
>
> The AI's summary of what the code does.
>
> **entityName** `string`
>
> The entity whose records or access rules change.
>
> **changeType** `string`
>
> How the access rules change.
>
> **changedOps** `string[]`
>
> The access operations whose rules change.
>
> **functionName** `string`
>
> The backend function under test.
>
> **method** `string`
>
> The entity method the code calls, such as `delete`.
>
> **inspectionFailed** `boolean`
>
> Whether the check couldn't verify the function's source.

#### approve()

> **approve**(): `Promise`\<`void`>

Lets the action run.

##### Returns

`Promise<void>`

Promise resolving once Base44 accepts the approval, or once sending it fails and sets [`error`](#error). It never rejects.

##### Example

```tsx Show what the action would do, then allow or decline it theme={null}
function ApprovalPrompt({ question }: { question: ApprovalQuestion }) {
  return (
    <div>
      <p>The AI wants to run {question.action}.</p>
      {question.reason && <p>{question.reason}</p>}
      <button onClick={question.approve}>Allow</button>
      <button onClick={question.decline}>Decline</button>
    </div>
  );
}
```

***

#### decline()

> **decline**(): `Promise`\<`void`>

Declines the action. It never runs, and the AI continues.

##### Returns

`Promise<void>`

Promise resolving once Base44 accepts the decline, or once sending it fails and sets [`error`](#error). It never rejects.

##### Example

```tsx Decline the action theme={null}
<button onClick={question.decline}>Decline</button>
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.