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

# The ana.* Functions

> The built-in functions a Data App uses to read data, run compute, remember state, and ask Ana

Every [Data App](/core/how-it-works/data-apps) talks to the platform through one small, built-in SDK: the `ana.*` functions. They're how an app queries its data sources, invokes compute functions, remembers a viewer's settings, shows who's online, and even starts a real conversation with Ana from inside the app.

Ana writes these calls itself when it builds your app, so you never have to touch them to get a working Data App. But knowing what's in the toolbox pays off twice: your prompts can ask for capabilities by name ("remember my filters," "let me ask Ana about this row"), and if you ever edit the code directly, this is the API you'll be working with.

The SDK has two halves, one on each side of the app:

* **In the front end**, every page gets a global `ana` object in JavaScript: rendering helpers, live queries, viewer identity, presence, and deep links.
* **In compute functions**, the server-side Python code gets an injected `ana` client: governed reads, declared writes, per-viewer state, file uploads, and asks.

## In the Front End

The browser-side `ana` object covers rendering, data, and everything live:

| Function                          | What it does                                                                                                                                                                 |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ana.ui.chart / table / metric`   | House-styled visuals: theme-aware ECharts charts, formatted tables, and metric tiles that pick up your org's brand accent automatically                                      |
| `ana.data.get / sources`          | The app's baked-in data: every declared data source, by name, exactly as it was when the app was last saved                                                                  |
| `ana.data.query(sql)`             | A live SQL `SELECT` over the app's declared sources (table names match source names), returning a handle the UI can subscribe to and refresh                                 |
| `ana.compute.run(name, params)`   | Invokes one of the app's declared compute functions and resolves with its result: the bridge to everything server-side                                                       |
| `ana.state.load / save`           | Per-viewer saved settings (filters, layout, preferences) that survive reloads; saves are debounced and capped at 64KB                                                        |
| `ana.activity`                    | A shared, append-only activity log: `record` an event, `list` your own history, `subscribe` to everyone's as they happen                                                     |
| `ana.presence.join(zone)`         | A live roster of who's viewing, by named zone, with a callback on every change                                                                                               |
| `ana.realtime`                    | A host-relayed WebSocket for apps that talk to an external live service                                                                                                      |
| `ana.route`                       | Deep links: the app's URL tail updates as someone navigates, so a specific view, filter, or record can be bookmarked and shared                                              |
| `ana.navigate(appId, opts)`       | Sends the viewer to a *different* Data App. `opts` picks the target's root or a deep-link route inside it, so a set of apps can link to each other like pages of one product |
| `ana.ask.watch(chatId, handler)`  | Streams a live Ana thread into the app, cell by cell (see [Asking Ana](#asking-ana-from-inside-an-app) below)                                                                |
| `ana.viewer`                      | The signed-in viewer's display identity (`memberId`, `displayName`, `email`), or `null` on anonymous views                                                                   |
| `ana.grants`                      | Which gated sources and functions this viewer may invoke, for hiding tabs the viewer can't use (the server enforces the real gate)                                           |
| `ana.download(filename, content)` | Hands a file to the browser's download flow from inside the sandboxed app                                                                                                    |
| `ana.theme`                       | The app's resolved design tokens (colors, fonts, chart palette), for custom visuals that should match everything else                                                        |

Not every context is fully live: a public share link or a screenshot renders from the app's static snapshot, with no server behind it. The SDK is built for that: each live namespace exposes an `available` flag, baked-in data still renders, and a well-built app degrades to a read-only view instead of breaking. Ana wires in those checks by default.

## In Compute Functions

Server-side Python gets its own `ana` client. This is where anything live, private, or write-shaped happens:

| Function                                        | What it does                                                                                                                            |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `ana.query(source_name, **params)`              | Runs one of the app's declared data sources live, with parameters, and returns a DataFrame                                              |
| `ana.sql(query)`                                | Runs SQL against the app's private [App Database](/core/how-it-works/data-apps#app-database)                                            |
| `ana.write(name, **params)`                     | Executes a declared write template against a connector: the persistence behind "mark as done" and "save this form"                      |
| `ana.notify(name, **params)`                    | Sends a branded email from a declared template; the recipient is always the person using the app, never an arbitrary address            |
| `ana.state.get / set / delete / keys`           | Server-side key-value state, per-viewer by default or shared app-wide with `shared=True`; 64KB per value                                |
| `ana.assets.put(file_name, data, content_type)` | Uploads a generated file (a CSV export, a rendered PDF, an image) and returns a signed URL; 10MB cap, and nothing scriptable is allowed |
| `ana.ask(prompt)`                               | Starts a real Ana thread on the viewer's behalf and returns a handle with `.poll()`, `.wait()`, and `.followup()`                       |
| `ana.ask_poll(ask_id, cursor)`                  | Re-polls an ask started earlier, even from a different invocation, so long-running asks don't have to block                             |

## Asking Ana from Inside an App

`ana.ask` is the one function that puts Ana herself inside your app. A compute function can hand her a prompt, and she runs it as a real thread: same agent, same tools, same governed data access as a chat you'd start yourself.

```python theme={null}
def explain_account(account_id):
    run = ana.ask(f"Why did account {account_id}'s usage drop this month?")
    result = run.wait()
    return {
        "summary": result["text"],
        "charts": result["charts"],
        "chat_id": result["chat_id"],
    }
```

The handle gives you three ways to consume the answer:

* **`run.wait()`** blocks until the thread finishes and returns everything at once: the markdown text, any charts as URLs, and the thread's `chat_id`.
* **`run.poll()`** (or `ana.ask_poll` from a later invocation) returns whatever new typed blocks (markdown, chart, link) have arrived since the last cursor, so an app can show progress instead of a spinner.
* **`run.followup(prompt)`** appends to the same thread, so "now break that down by region" keeps its context.

And because the ask is a real thread, the front end can stream it live instead of waiting on the server: pass the `chat_id` up to the UI and watch it render cell by cell.

```javascript theme={null}
const unwatch = ana.ask.watch(chatId, (event) => {
  if (event.kind === "cell") renderCell(event.cell);
  if (event.kind === "status") showStatus(event.status);
});
```

Two properties make asks safe to put in front of a whole team:

* **Asks run as the viewer, not the app's creator.** The thread is created under whoever clicked the button, with their permissions; they can only see what they could already see in a chat of their own. Anonymous views (public share links) get no ask at all.
* **Asks are rate-limited** (20 per hour per app session), so a runaway loop can't burn through your org's usage.

In a prompt, this is the difference between a static app and one with an analyst inside it: "add an *Explain this* button that asks Ana why the number changed" is all it takes.

## The Security Model, Briefly

Nothing in a Data App carries ambient authority. Every operation beyond a plain read (`write`, `notify`, server-side `state`, `assets`, `ask`) is a **capability**: declared in the app's config when Ana builds it, and re-checked by the platform on every call. The practical consequences:

* Capabilities expand what app code can *do*, never whose permission it runs with. Connector access is re-checked live against the app's creator (revoking access bites immediately), and every action is attributed to the viewer who triggered it.
* `ana.write` and `ana.notify` are gated off by default at the org level; an admin turns them on before any app can persist to a warehouse or send email.
* Writes and notifications are audit-logged.

You don't manage any of this by hand. It's why "let people mark a row as done" in a prompt turns into a governed, declared write rather than an app with raw warehouse credentials in it.

## Getting Support

If you have questions about the `ana.*` functions or run into issues, reach out to [support@textql.com](mailto:support@textql.com) or visit the [customer support page](/core/get-started/customer-support).
