> ## Documentation Index
> Fetch the complete documentation index at: https://domoinc-openapi-sync-dataflows.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# domo.js (ryuu.js)

**Version:** [v4](/portal/Apps/App-Framework/Tools/domo-js-v4) | [v5](/portal/Apps/App-Framework/Tools/domo-js-v5) | **v6 (latest)**

> A powerful JavaScript SDK for building custom applications within the Domo platform

[![npm version](https://img.shields.io/badge/npm-v6.0.0-blue?logo=npm)](https://www.npmjs.com/package/ryuu.js/v/6.0.0)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)

domo.js (published as `ryuu.js`) is the official JavaScript SDK for building Custom Apps inside the Domo platform. Apps run inside iframes — domo.js handles authenticated HTTP requests, secure messaging with the parent window, mobile WebView bridges, and provides high-level helpers for Data, AppDB, Code Engine, Workflows, and AI services.

**Zero runtime dependencies. \~28KB UMD bundle. TypeScript included.**

## What's New in v6

| Feature                 | Description                                                                                        |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| `domo.intercept()`      | Register request middleware — logging, retries, custom headers                                     |
| Structured errors       | `DomoHttpError`, `DomoAuthError`, `DomoConnectionError`, `DomoValidationError`, `DomoTimeoutError` |
| `RequestOptions.schema` | Runtime response validation with a custom parse function                                           |
| `domo.debug`            | Category-based debug logging, persisted via `localStorage`                                         |
| Service namespaces      | `domo.data`, `domo.appdb`, `domo.ai`, `domo.workflow`, `domo.codeEngine`                           |
| Optimized bundle        | Reduced bundle size, zero runtime dependencies                                                     |

For a full list of breaking changes, see [Migration from v5](#migration-from-v5).

***

## Quick Start

Get started with ryuu.js in just a few lines:

```javascript theme={"dark"}
import domo from 'ryuu.js';

// Query a dataset by its manifest alias
const rows = await domo.data.query('sales');

// React to page filter changes
domo.onFiltersUpdated((filters) => {
  console.log('Filters changed:', filters);
  loadData();
});
```

***

## Features

* **Data API** — Query datasets with typed options (fields, filters, aggregations, date graining, beast modes)
* **AppDB** — Full CRUD, MongoDB-style queries, bulk operations, partial updates, collection management
* **Code Engine** — Run Code Engine functions by manifest alias
* **Workflows** — Start and monitor Domo Workflows
* **AI Services** — Text generation and text-to-SQL via Domo's AI Service Layer
* **HTTP API Access** — Authenticated requests to Domo datasets, datastores, and APIs
* **Real-time Events** — Listen for dataset updates, filter changes, and variable updates
* **Filter Management** — Get and set page-level filters programmatically
* **Variable Management** — Access and update page variables
* **Custom App Data** — Send custom data between apps on the same page
* **Typed Environment** — `domo.env` with userId, userName, host, platform, enriched from the environment API
* **Navigation** — Programmatically navigate within Domo
* **Mobile Support** — Full iOS and Android compatibility
* **TypeScript Ready** — Complete type definitions with typed callbacks and generics
* **Zero Dependencies** — \~28KB UMD bundle with no runtime dependencies
* **Extensible** — `extend()` propagates overrides to all services (data, appdb, ai, etc.)
* **Structured Errors** — Typed error classes (`DomoHttpError`, `DomoAuthError`, `DomoConnectionError`, `DomoValidationError`) with `instanceof` support
* **Schema Validation** — Optional runtime response validation via `{ schema: { parse } }` (works with zod, valibot, etc.)
* **Request Interceptors** — Middleware pattern via `domo.intercept()` for logging, retry, caching
* **Debug Mode** — `domo.debug.enable()` for category-based logging of HTTP, messages, filters, variables

***

## Installation

**Requirements:**

* Node.js 16+ (for local development with the Domo CLI)
* Modern browsers: Chrome 80+, Firefox 75+, Safari 13+, Edge 80+
* Zero runtime dependencies — no `npm install` required inside your app bundle

### NPM

```bash theme={"dark"}
npm install ryuu.js
```

Then import in your application:

```javascript theme={"dark"}
// ES modules
import domo from 'ryuu.js';

// CommonJS
const domo = require('ryuu.js').default;
```

### CDN / Script Tag

Load the library directly from a CDN. The bundle exposes a global `domo` object:

```html theme={"dark"}
<script src="https://unpkg.com/ryuu.js"></script>
<script>
  domo.data.query('sales').then(rows => console.log(rows));
</script>
```

When using the Domo CLI, `domo init` scaffolds a local `domo.js` file you can reference with `<script src="domo.js"></script>` instead.

### TypeScript

Type definitions are included automatically:

```typescript theme={"dark"}
import domo, {
  Filter, Variable, RequestOptions,
  DomoHttpError, DomoAuthError, DomoValidationError
} from 'ryuu.js';
```

***

## Core Concepts

### Architecture

Your Custom App runs in an `<iframe>` inside a Domo page. domo.js establishes communication between your app and the parent Domo window using two channels:

* **MessageChannel (inbound)** — The SDK creates a `MessageChannel` and transfers `port2` to the parent on subscribe. The parent sends filter updates, variable changes, and acks through `port2`; your app receives them on `port1`.
* **`window.parent.postMessage` (outbound)** — Outbound messages (filter requests, variable requests, navigate) always use `window.parent.postMessage` so the parent can verify `event.origin` for security.

`Domo` is a **static class** — you never call `new Domo()`. All methods are called directly on the class:

```javascript theme={"dark"}
// Correct
const rows = await domo.data.query('sales');

// Wrong — do not instantiate
const instance = new Domo();
```

### ASK-ACK-REPLY Pattern

Async operations like updating filters and variables use a request-tracking pattern:

1. **ASK** — Your app sends a request with a unique ID
2. **ACK** — Parent acknowledges receipt (optional callback)
3. **REPLY** — Parent sends the response when the operation completes (optional callback)

```javascript theme={"dark"}
domo.requestFiltersUpdate(
  filters,
  true,
  () => console.log('Request acknowledged'),
  (response) => console.log('Request completed:', response),
);
```

### Token Injection

A single `MutationObserver` watches `document.documentElement` with `subtree: true`. For every DOM node added at any depth, it automatically injects the Domo session token (`ryuu_sid`) into relative `href` and `src` attributes. Token is fetched once per batch — you don't need to manage auth manually.

### Environment Context

`domo.env` provides typed access to the current user, instance, and page context. Properties are populated immediately from iframe query parameters, then enriched in the background from `GET /domo/environment/v1` (which provides authoritative server-side values like `host`):

```javascript theme={"dark"}
// Available immediately
console.log(domo.env.userId);    // e.g. "123456789"
console.log(domo.env.userName);  // e.g. "Jane Smith"
console.log(domo.env.userEmail); // e.g. "jane.smith@example.com"
console.log(domo.env.customer);  // e.g. "acme"
console.log(domo.env.locale);    // e.g. "en-US"
console.log(domo.env.platform);  // "desktop" | "mobile"
console.log(domo.env.pageId);    // e.g. "987654321"

// Available after environment API loads
console.log(domo.env.host);      // e.g. "acme.domo.com"
console.log(domo.env.loaded);    // true
```

The `loaded` property indicates whether the environment API has finished loading. If it fails (e.g. running locally), query params serve as the fallback.

<Warning>
  Environment properties come from URL parameters and can be spoofed. For secure user identification, always verify with the API:

  ```javascript theme={"dark"}
  const verified = await domo.get('/domo/environment/v1/');
  ```
</Warning>

***

## Error Handling

v6 replaces generic errors with a structured hierarchy. Import only what you need:

```javascript theme={"dark"}
import {
  DomoHttpError,
  DomoAuthError,
  DomoConnectionError,
  DomoValidationError,
  DomoTimeoutError
} from 'ryuu.js';
```

| Class                 | Thrown When                           | Key Properties                            |
| --------------------- | ------------------------------------- | ----------------------------------------- |
| `DomoHttpError`       | Non-2xx HTTP response                 | `status`, `statusText`, `body`, `headers` |
| `DomoAuthError`       | 401 or 403 response                   | Extends `DomoHttpError`                   |
| `DomoConnectionError` | Network failure (fetch rejects)       | —                                         |
| `DomoValidationError` | Invalid input or schema parse failure | `errors[]`                                |
| `DomoTimeoutError`    | Request or message timeout            | `url`                                     |

<Note>
  Always check `DomoAuthError` before `DomoHttpError` in your catch block — `DomoAuthError` extends `DomoHttpError`, so an `instanceof DomoHttpError` check will also match auth errors.
</Note>

**Recommended catch pattern:**

```javascript theme={"dark"}
try {
  const data = await domo.get('/data/v1/sales');
  console.log('Data loaded:', data);
} catch (err) {
  if (err instanceof DomoAuthError) {
    // 401 or 403 — session expired or no permission
    console.error('Auth failed:', err.status);
    showLoginPrompt();
  } else if (err instanceof DomoHttpError) {
    // Any other non-2xx response
    console.error('HTTP error:', err.status, err.statusText);
    console.error('Response body:', err.body);
  } else if (err instanceof DomoConnectionError) {
    // Network failure — no response at all
    console.error('Network error:', err.message);
    showOfflineMessage();
  } else if (err instanceof DomoTimeoutError) {
    console.error('Timed out:', err.url);
  } else if (err instanceof DomoValidationError) {
    console.error('Validation errors:', err.errors);
  } else {
    throw err;
  }
}
```

### Validation Errors

When you pass malformed data to filters or variables, ryuu.js throws a `DomoValidationError` and logs the expected data model to `console.error`:

```javascript theme={"dark"}
import { DomoValidationError } from 'ryuu.js';

try {
  domo.requestFiltersUpdate([{ bad: 'data' }]);
} catch (err) {
  if (err instanceof DomoValidationError) {
    console.error(err.message); // "All filters must be valid Filter objects."
    console.error(err.errors);  // Array of the invalid items
    // console.error also shows the expected object shape automatically
  }
}
```

***

## API Reference

### Service Namespaces

v6 introduces high-level service namespaces that handle URL construction and request formatting for you.

***

#### `domo.data`

Query datasets connected to your app via their [`manifest.json`](/portal/Apps/App-Framework/Guides/manifest) alias. Each dataset must be mapped in `manifest.json` under `datasources` before it can be queried by alias.

##### `domo.data.query(alias, options?)`

Query a dataset by its manifest alias. Supports all Data API query operators.

**Parameters:**

* `alias` (string, required) — Dataset alias from `manifest.json`
* `options` (object, optional)

**Supported options:** `fields`, `filter`, `avg`, `count`, `max`, `min`, `sum`, `unique`, `groupBy`, `dateGrain`, `calendar`, `orderBy`, `limit`, `offset`, `useBeastMode`, `format`

**Returns:** `Promise<any>`

```javascript theme={"dark"}
// Simple query
const rows = await domo.data.query('sales');

// Filtering, aggregation, and pagination
const totals = await domo.data.query('sales', {
  fields: ['region', 'amount'],
  filter: 'amount > 100',
  sum: ['amount'],
  groupBy: ['region'],
  orderBy: 'amount descending',
  limit: 50,
});

// Date graining
const monthly = await domo.data.query('sales', {
  dateGrain: 'orderDate by month',
  sum: ['amount'],
  calendar: 'fiscal',
});

// Beast modes
const bm = await domo.data.query('sales', {
  useBeastMode: true,
  fields: ['myBeastMode', 'reps'],
  sum: ['myBeastMode'],
  groupBy: ['reps'],
});

// CSV format
const csv = await domo.data.query('sales', { format: 'csv' });
```

***

##### `domo.data.sql(alias, sql)`

Run a SQL query against an aliased dataset.

**Parameters:**

* `alias` (string, required) — Dataset alias from `manifest.json`
* `sql` (string, required) — SQL query string. The table name is the dataset alias.

**Returns:** `Promise<any>`

<Note>
  `data.sql` uses a POST request with `Content-Type: text/plain`. Keep this in mind if you're using interceptors that inspect the request method. The SQL API does not support page filters or JOINs.
</Note>

```javascript theme={"dark"}
const result = await domo.data.sql(
  'sales',
  'SELECT region, SUM(amount) as total FROM sales GROUP BY region'
);
```

***

#### `domo.appdb`

Persistent document storage scoped to your Custom App. Each document lives in a **collection** and has a `content` field plus system-managed metadata.

<Note>
  `appdb.create` and `appdb.update` automatically wrap your document in `{ content: ... }` if you pass a plain object — you don't need to wrap it yourself.
</Note>

##### Document CRUD

```javascript theme={"dark"}
const docs    = await domo.appdb.list('Users');
const doc     = await domo.appdb.get('Users', docId);
const created = await domo.appdb.create('Users', { username: 'Bill' }); // auto-wraps in { content: ... }
await domo.appdb.update('Users', docId, { username: 'Ted' });
await domo.appdb.remove('Users', docId);
```

***

##### Query with MongoDB syntax

```javascript theme={"dark"}
// Simple filter
const docs = await domo.appdb.query('Users', { 'content.region': 'West' });

// With aggregations
const results = await domo.appdb.query('campaigns', {}, {
  groupby: 'content.campaignName, content.month',
  count: 'documentCount',
  sum: 'content.clicks sumClicks',
  orderby: 'sumClicks descending',
});
```

***

##### Partial update (MongoDB operators)

```javascript theme={"dark"}
await domo.appdb.partialUpdate('Users',
  { 'content.username': 'Bill' },
  { '$set': { 'content.band': 'Wyld Stallyns' } }
);
```

***

##### Bulk operations

```javascript theme={"dark"}
await domo.appdb.bulkCreate('Users', [{ username: 'Bill' }, { username: 'Ted' }]);
await domo.appdb.bulkUpsert('Users', [
  { id: 'existing-id', username: 'Bill', band: 'Wyld Stallyns' },
  { username: 'Rufus' },
]);
await domo.appdb.bulkDelete('Users', ['id-1', 'id-2', 'id-3']);
```

***

##### Collection management & export

```javascript theme={"dark"}
await domo.appdb.listCollections();
await domo.appdb.createCollection({
  name: 'Users',
  schema: { columns: [{ name: 'username', type: 'STRING' }] },
  syncEnabled: true,
});
await domo.appdb.export(); // manually trigger sync to Domo DataSets
```

***

#### `domo.ai`

Generate text and convert natural language to SQL using Domo's AI services.

##### `domo.ai.generateText(prompt, options?)`

Generate text using Domo's LLM integration.

**Parameters:**

* `prompt` (string, required) — The input prompt
* `options` (object, optional) — Model configuration

**Returns:** `Promise<string>`

```javascript theme={"dark"}
const res = await domo.ai.generateText('Tell me a joke about data');
console.log(res.choices[0].output);
```

***

##### `domo.ai.textToSQL(question, options?)`

Convert a natural language question into a SQL query.

**Parameters:**

* `question` (string, required) — Natural language question
* `options` (object, optional)
  * `dataSourceSchemas` (array) — Dataset schema hints to improve SQL accuracy

**Returns:** `Promise<{ choices: { output: string }[] }>`

```javascript theme={"dark"}
const sql = await domo.ai.textToSQL('Show total sales by region', {
  dataSourceSchemas: [{
    dataSourceName: 'Sales',
    columns: [
      { name: 'Region', type: 'string' },
      { name: 'Amount', type: 'number' }
    ]
  }]
});
console.log(sql.choices[0].output); // "SELECT Region, SUM(Amount) ..."

// Use the generated SQL
const rows = await domo.data.sql('sales', sql.choices[0].output);
```

<Note>
  AI services consume AI credits. See your Domo instance rate card for details.
</Note>

***

#### `domo.workflow`

Trigger and monitor Domo Workflow automations.

##### `domo.workflow.start(alias, input?)`

Trigger a workflow by its alias.

**Parameters:**

* `alias` (string, required) — Workflow alias defined in your app
* `input` (object, optional) — Input data to pass to the workflow

**Returns:** `Promise<{ id: string }>` — The workflow instance

```javascript theme={"dark"}
const instance = await domo.workflow.start('approval-flow', {
  submittedBy: 'jane.smith@example.com',
  amount: 5000
});

console.log('Instance ID:', instance.id);
```

***

##### `domo.workflow.getInstance(alias, instanceId)`

Get the current status of a running workflow instance.

**Parameters:**

* `alias` (string, required) — Workflow alias
* `instanceId` (string, required) — Instance ID returned by `workflow.start()`

**Returns:** `Promise<{ state: string, ... }>`

```javascript theme={"dark"}
const status = await domo.workflow.getInstance('approval-flow', instance.id);
console.log(status.status); // 'IN_PROGRESS' | 'COMPLETED' | 'CANCELED' | 'FAILED' | null
```

<Note>
  Requires a `workflowMapping` entry in your `manifest.json` mapping the alias to a Domo Workflow.
</Note>

***

#### `domo.codeEngine(alias, input?)`

Invoke a Code Engine function by alias.

**Parameters:**

* `alias` (string, required) — Function alias from your app manifest
* `input` (object, optional) — Input parameters

**Returns:** `Promise<any>` — The function's return value

```javascript theme={"dark"}
const result = await domo.codeEngine('calculate-forecast', {
  region: 'West',
  months: 3
});
```

<Note>
  Requires a `packageMapping` entry in your `manifest.json` mapping the alias to a Code Engine function.
</Note>

***

### Event Listeners

All listener methods return an **unsubscribe function**. Call it to stop listening.

***

#### `domo.onFiltersUpdated(callback)`

Listen for page-level filter changes.

**Parameters:**

* `callback` — `(filters: Filter[]) => void`

**Returns:** `function` — Unsubscribe function

<Warning>
  Registering `onFiltersUpdated` sends `requestFiltersUpdate(null)` on first call, which **clears all filters on the parent page**. If you need to read current filters without clearing them, see the [Filters guide](#filters-in-depth) below.
</Warning>

```javascript theme={"dark"}
const unsubscribe = domo.onFiltersUpdated((filters) => {
  console.log('Filters updated:', filters);

  // Find a specific filter
  const regionFilter = filters.find(f => f.column === 'Region');
  if (regionFilter) {
    console.log('Region filter:', regionFilter.values);
    applyRegionFilter(regionFilter.values);
  }
});

// Stop listening
unsubscribe();
```

**Filter object structure:**

```javascript theme={"dark"}
{
  column: 'Region',              // Column name being filtered
  operator: 'IN',                // Filter operator
  values: ['West', 'East'],      // Filter values (always strings on the wire)
  dataType: 'STRING',            // STRING | NUMERIC | DATE | DATETIME
  dataSourceId: '46d91556-...',  // Source dataset ID (optional)
  label: 'Region'                // Display label (optional)
}
```

**Supported operators:**

**String operators:**

* `"IN"` — Value is in list
* `"NOT_IN"` — Value is not in list
* `"CONTAINS"` — Value contains string
* `"NOT_CONTAINS"` — Value doesn't contain string
* `"STARTS_WITH"` — Value starts with string
* `"NOT_STARTS_WITH"` — Value doesn't start with string
* `"ENDS_WITH"` — Value ends with string
* `"NOT_ENDS_WITH"` — Value doesn't end with string

**Numeric / Date operators:**

* `"EQUALS"` — Equals value
* `"NOT_EQUALS"` — Not equals value
* `"GREATER_THAN"` — Greater than value
* `"GREAT_THAN_EQUALS_TO"` — Greater than or equals value
* `"LESS_THAN"` — Less than value
* `"LESS_THAN_EQUALS_TO"` — Less than or equals value
* `"BETWEEN"` — Between two values

***

#### `domo.onVariablesUpdated(callback)`

Listen for page variable changes.

**Parameters:**

* `callback` — `(variables: Variable[]) => void`

**Returns:** `function` — Unsubscribe function

```javascript theme={"dark"}
domo.onVariablesUpdated((variables) => {
  const yearVar = variables.find(v => v.name === 'selectedYear');
  if (yearVar) loadData(yearVar.value);
});
```

**Variable object structure:**

```javascript theme={"dark"}
{
  name: 'selectedYear',   // Variable name
  value: 2024,            // Current value
  functionId: '123'       // Optional: Domo function ID
}
```

***

#### `domo.onDataUpdated(callback)`

Listen for dataset update events. Called when a dataset connected to your app is refreshed.

**Parameters:**

* `callback` — `(alias: string) => void`

**Returns:** `function` — Unsubscribe function

```javascript theme={"dark"}
const unsubscribe = domo.onDataUpdated((alias) => {
  console.log(`Dataset "${alias}" was updated`);
  loadData();
});

// Later, stop listening
unsubscribe();
```

**Use Case:** Refresh your app's visualizations when underlying data changes without requiring a page reload.

***

#### `domo.onAppDataUpdated(callback)`

Listen for custom data sent by other apps on the same Domo page.

**Parameters:**

* `callback` — `(data: any) => void`

**Returns:** `function` — Unsubscribe function

```javascript theme={"dark"}
domo.onAppDataUpdated((data) => {
  if (data.action === 'highlight') {
    highlightRow(data.rowId);
  }
});
```

**Use Case:** Enable communication between multiple Custom Apps on the same Domo page.

***

### Emitters

#### `domo.requestFiltersUpdate(filters, pageStateUpdate?, onAck?, onReply?)`

Push filter changes to the parent Domo page.

**Parameters:**

* `filters` (Filter\[] | null, required) — Filters to apply. Pass `null` to clear all filters.
* `pageStateUpdate` (boolean, optional) — If `false`, only the card-level filter state is updated (default: `true`)
* `onAck` (function, optional) — Called when request is acknowledged
* `onReply` (function, optional) — Called when request completes

**Returns:** `string` — Request ID

```javascript theme={"dark"}
// Apply filters
domo.requestFiltersUpdate([
  { column: 'Region', operator: 'IN', values: ['West'], dataType: 'STRING' }
]);

// Clear all filters
domo.requestFiltersUpdate(null);
```

<Warning>
  Passing `null` clears every filter on the parent page for all apps. This is also what `onFiltersUpdated` triggers automatically on first registration.
</Warning>

***

#### `domo.requestVariablesUpdate(variables, onAck?, onReply?)`

Push variable changes to the parent Domo page.

**Parameters:**

* `variables` (Variable\[], required)
* `onAck` (function, optional)
* `onReply` (function, optional)

**Returns:** `string` — Request ID

```javascript theme={"dark"}
domo.requestVariablesUpdate([
  { name: 'selectedYear', value: 2024 }
]);
```

***

#### `domo.requestAppDataUpdate(data, onAck?, onReply?)`

Send custom data to other apps on the same Domo page.

```javascript theme={"dark"}
domo.requestAppDataUpdate({
  action: 'highlight',
  rowId: 123
});
```

***

### HTTP Methods

All HTTP methods return Promises and support multiple data formats. All methods throw structured errors — see [Error Handling](#error-handling).

The auth header `X-DOMO-Ryuu-Session` is injected automatically.

***

#### `domo.get(url, options?)`

Fetch data from a Domo dataset or API endpoint.

**Parameters:**

* `url` (string) — API endpoint URL
* `options` (object, optional) — Request options

**Returns:** `Promise<ResponseBody>`

**Basic Usage:**

```javascript theme={"dark"}
// Returns array of objects by default
const data = await domo.get('/data/v1/sales');
console.log(data); // [{ id: 1, amount: 100, ... }, ...]
```

**Format Options:**

```javascript theme={"dark"}
// CSV format
const csv = await domo.get('/data/v1/sales', { format: 'csv' });

// Array of arrays with column metadata
const arrayData = await domo.get('/data/v1/sales', { format: 'array-of-arrays' });
console.log(arrayData.columns); // ['id', 'amount', 'date']
console.log(arrayData.rows);    // [[1, 100, '2024-01-01'], ...]

// Excel format (returns Blob)
const excel = await domo.get('/data/v1/sales', { format: 'excel' });
```

**Supported formats:**

* `'array-of-objects'` (default) — `Record<string, any>[]`
* `'array-of-arrays'` — `ArrayResponseBody` with `.columns` and `.rows`
* `'csv'` — CSV string
* `'excel'` — Blob
* `'plain'` — Plain text string

**Query Parameters:**

```javascript theme={"dark"}
const data = await domo.get('/data/v1/sales', {
  query: {
    limit: 100,
    offset: 0,
    fields: 'id,amount,date',
  },
});
```

**Best Practice:** Always filter and paginate large datasets to avoid slow responses:

```javascript theme={"dark"}
const data = await domo.get('/data/v1/sales', {
  query: { limit: 1000, offset: 0, filter: 'date > "2024-01-01"' },
});
```

***

#### `domo.getAll(urls, options?)`

Fetch multiple endpoints in parallel and return results as an array.

```javascript theme={"dark"}
const [sales, inventory, customers] = await domo.getAll([
  '/data/v1/sales',
  '/data/v1/inventory',
  '/data/v1/customers',
]);

console.log(sales);     // First dataset
console.log(inventory); // Second dataset
console.log(customers); // Third dataset
```

**With Options:**

```javascript theme={"dark"}
const results = await domo.getAll(['/data/v1/sales', '/data/v1/inventory'], {
  format: 'csv',
});
// All results will be CSV strings
```

***

#### `domo.post(url, body?, options?)`

```javascript theme={"dark"}
const result = await domo.post(
  '/domo/datastores/v1/collections/notes/documents/',
  { text: 'hello', pinned: false }
);
```

***

#### `domo.put(url, body?, options?)`

```javascript theme={"dark"}
await domo.put(
  '/domo/datastores/v1/collections/notes/documents/abc123',
  { text: 'updated' }
);
```

***

#### `domo.delete(url, options?)`

```javascript theme={"dark"}
await domo.delete('/domo/datastores/v1/collections/notes/documents/abc123');
```

***

#### `domo.domoHttp(method, url, options?, body?)`

Low-level method used internally by all other HTTP helpers. Use this for HTTP methods not covered above (e.g. PATCH).

```javascript theme={"dark"}
import { RequestMethods } from 'ryuu.js';

const result = await domo.domoHttp(
  RequestMethods.PATCH,
  '/custom/endpoint',
  {},
  { field: 'value' }
);
```

***

### Request Interceptors

`domo.intercept()` registers middleware that wraps every HTTP request. Interceptors follow an **onion model** — each interceptor receives the request and a `next` function to call the next layer.

<Note>
  Interceptors wrap the `fetch` call — headers and auth tokens are already set when your interceptor runs.
</Note>

#### `domo.intercept(fn)`

**Parameters:**

* `fn` — `(request: Request, next: (request: Request) => Promise<Response>) => Promise<Response>`

**Returns:** `() => void` — Call to remove the interceptor

The `config` object passed to each interceptor has `{ method, url, headers, body }`. Headers and auth tokens are already set when interceptors run.

```javascript theme={"dark"}
// Logging
const removeLogger = domo.intercept(async (config, next) => {
  console.log(`[${config.method}] ${config.url}`);
  const start = Date.now();
  const response = await next(config);
  console.log(`[${config.method}] ${config.url} - ${Date.now() - start}ms`);
  return response;
});

// Add custom headers
domo.intercept((config, next) => {
  config.headers['X-Custom'] = 'value';
  return next(config);
});

// Short-circuit with a cached response
domo.intercept((config, next) => {
  if (cache.has(config.url)) return Promise.resolve(cache.get(config.url));
  return next(config);
});

// Remove an interceptor
removeLogger();
```

**Multiple interceptors** are applied in registration order (first registered = outermost):

```
outerMiddleware → innerMiddleware → fetch → innerMiddleware → outerMiddleware
```

***

### Schema Validation

Pass a `schema` option to any HTTP request to validate or transform the response at runtime. If `parse` throws, domo.js wraps it as `DomoValidationError`.

```javascript theme={"dark"}
const rows = await domo.get('/data/v1/sales', {
  schema: {
    parse: (raw) => {
      if (!Array.isArray(raw)) throw new Error('Expected array');
      return raw.map(r => ({ region: r.Region, revenue: Number(r.Revenue) }));
    }
  }
});
```

Works with any validation library:

```javascript theme={"dark"}
import { z } from 'zod';

const Schema = z.array(z.object({ region: z.string(), revenue: z.number() }));

const rows = await domo.get('/data/v1/sales', {
  schema: { parse: (raw) => Schema.parse(raw) }
});
```

***

### Navigation

#### `domo.navigate(url, isNewWindow?)`

Navigate the parent Domo page from inside your app iframe.

**Parameters:**

* `url` (string, required) — Domo page URL or route
* `isNewWindow` (boolean, optional) — Open in new tab (default: `false`)

```javascript theme={"dark"}
// Navigate in same window
domo.navigate('/page/123456789');

// Open in new tab
domo.navigate('/page/123456789', true);
```

<Note>
  For mobile web platforms, routes are automatically prefixed with `/m#`. External links are restricted to whitelisted domains — configure in **Admin > Network Security > Custom Apps authorized domains**.
</Note>

***

### Utilities

#### `domo.extend(overrides)`

Override static methods or properties of the Domo class. Also updates the shared transport so overrides propagate to all service namespaces (`data`, `appdb`, `ai`, etc.).

```javascript theme={"dark"}
import domo, { get as originalGet } from 'ryuu.js';

domo.extend({
  get: async function (url, options) {
    console.log(`[fetch] ${url}`);
    return originalGet.call(this, url, options);
  }
});
```

***

#### `domo.getRequests()` / `domo.getRequest(id)`

Inspect the ASK-ACK-REPLY request lifecycle for tracked requests (filter updates, variable updates, etc.).

```javascript theme={"dark"}
const requestId = domo.requestFiltersUpdate(filters);

const request = domo.getRequest(requestId);
console.log(request.request.status); // 'PENDING' | 'SENT'

const all = domo.getRequests();
```

***

## TypeScript Support

All types are exported from `ryuu.js`:

```typescript theme={"dark"}
import domo, {
  // Interfaces
  Filter,
  Variable,
  RequestOptions,

  // Error classes
  DomoHttpError,
  DomoAuthError,
  DomoConnectionError,
  DomoValidationError,
  DomoTimeoutError,

  // Enums
  DataFormats,
  DomoDataTypes,
  RequestMethods,

  // Type guards
  isFilter,
  isFilterArray,
  isVariable,
  isVariableArray,
} from 'ryuu.js';
```

**Typed requests:**

```typescript theme={"dark"}
const options: RequestOptions = {
  format: 'array-of-objects',
  query: { limit: 100 }
};

const data = await domo.get('/data/v1/sales', options);
```

**Typed filters:**

```typescript theme={"dark"}
const filters: Filter[] = [
  { column: 'Region', operator: 'IN', values: ['West'], dataType: 'STRING' },
  { column: 'Revenue', operator: 'GREATER_THAN', values: ['1000'], dataType: 'NUMERIC' }
];

domo.requestFiltersUpdate(filters);
```

**Custom data types:**

```typescript theme={"dark"}
interface SalesRecord {
  region: string;
  revenue: number;
  date: string;
}

const rows = (await domo.data.query('sales')) as SalesRecord[];
```

***

## Mobile Platform Support

domo.js detects the platform and routes messages through the appropriate bridge automatically. No configuration needed.

| Platform          | Bridge used                                                 |
| ----------------- | ----------------------------------------------------------- |
| Desktop / Web     | MessageChannel + `window.parent.postMessage`                |
| iOS               | `webkit.messageHandlers` (`domofilter`, `domovariable`)     |
| Android / Flutter | Global objects (`window.domofilter`, `window.domovariable`) |

**Platform detection:**

```javascript theme={"dark"}
if (domo.env.platform === 'mobile') {
  renderMobileLayout();
} else {
  renderDesktopLayout();
}
```

**Mobile considerations:**

* Navigation routes are automatically prefixed with `/m#` on mobile web
* Test on actual devices or simulators — not just browser DevTools mobile emulation
* Optimize data fetching: mobile devices have limited memory and CPU

***

## Advanced Usage

### Debug Mode

Enable category-based logging to inspect HTTP requests, messages, filters, and variables:

```javascript theme={"dark"}
// Enable all categories
domo.debug.enable();

// Enable specific categories
domo.debug.enable('http');
domo.debug.enable('messages');
domo.debug.enable(['http', 'filters']);

// Disable
domo.debug.disable();
```

You can also enable debug mode without code changes — useful for debugging a live app:

```javascript theme={"dark"}
localStorage.__domo_debug__ = '["all"]';
// Refresh the page — debug logging is now active
```

**Debug categories:**

| Category    | What it logs                                                      |
| ----------- | ----------------------------------------------------------------- |
| `http`      | Every request: method, URL, headers, response status and body     |
| `messages`  | All MessageChannel and postMessage traffic (inbound and outbound) |
| `filters`   | Filter update events and subscriptions                            |
| `variables` | Variable update events and subscriptions                          |
| `all`       | Everything above                                                  |

**Example output:**

```
[domo:http] GET /data/v1/sales
[domo:http] 200 OK /data/v1/sales
[domo:messages] received filtersUpdated { event: 'filtersUpdated', filters: [...] }
[domo:filters] filtersUpdated [{ column: 'region', ... }]
```

***

### Retry Logic

Implement retry logic for transient server errors using interceptors or a wrapper function:

```javascript theme={"dark"}
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await domo.get(url, options);
    } catch (err) {
      if (i === maxRetries - 1) throw err;
      if (err instanceof DomoHttpError && err.status >= 500) {
        console.log(`Retry ${i + 1}/${maxRetries}...`);
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
      } else {
        throw err;
      }
    }
  }
}

const data = await fetchWithRetry('/data/v1/sales');
```

***

### Custom Fetch

Provide your own `fetch` implementation for testing, mocking, or custom network behavior:

```javascript theme={"dark"}
const data = await domo.get('/data/v1/sales', {
  fetch: myCustomFetch,
});
```

***

### Type Guards

Use type guards to validate runtime data before passing it to the SDK:

```javascript theme={"dark"}
import { isFilter, isFilterArray, guardAgainstInvalidFilters, DomoValidationError } from 'ryuu.js';

// Check a single value
if (isFilter(data)) {
  console.log(data.column); // TypeScript knows data is a Filter
}

// Validate an array — throws DomoValidationError if invalid
try {
  guardAgainstInvalidFilters(data);
} catch (err) {
  if (err instanceof DomoValidationError) {
    console.error(err.errors); // The invalid items
  }
}
```

***

## Filters In Depth

### First-registration behavior

`onFiltersUpdated` clears all parent filters on first registration by sending `requestFiltersUpdate(null)`. All subsequent listeners call `connect(true)` which skips this. If you need to subscribe without clearing:

```javascript theme={"dark"}
// Pattern: subscribe to variables instead, or use onDataUpdated to detect
// data-refresh events that happen after filter application
domo.onDataUpdated((alias) => {
  // Triggered after parent applies filters — re-fetch here
  loadData();
});
```

### Wire format

The filter event name on the wire is `"filter"` (not `"filtersUpdated"`). Desktop and mobile use different field names which domo.js normalizes for you:

| Field    | Desktop      | Mobile    |
| -------- | ------------ | --------- |
| Column   | `columnName` | `column`  |
| Operator | `operator`   | `operand` |

***

## Complete Example

A real-world Custom App wiring together data, filters, variables, and navigation:

```javascript theme={"dark"}
import domo from 'ryuu.js';

class SalesDashboard {
  constructor() {
    this.data = [];
    this.filters = [];
    this.initialize();
  }

  async initialize() {
    this.setupEventListeners();
    await this.loadData();
    this.render();
  }

  setupEventListeners() {
    domo.onDataUpdated((alias) => {
      console.log(`Dataset ${alias} updated`);
      this.loadData();
    });

    domo.onFiltersUpdated((filters) => {
      this.filters = filters;
      this.applyFilters();
    });

    domo.onVariablesUpdated((variables) => {
      this.updateTheme(variables);
    });

    document.getElementById('filterBtn').addEventListener('click', () => {
      this.updateFilters();
    });

    document.getElementById('exportBtn').addEventListener('click', () => {
      this.exportData();
    });
  }

  async loadData() {
    try {
      const [sales, customers, products] = await domo.getAll([
        '/data/v1/sales',
        '/data/v1/customers',
        '/data/v1/products',
      ]);
      this.data = { sales, customers, products };
      this.render();
    } catch (err) {
      console.error('Failed to load data:', err);
      this.showError('Unable to load dashboard data');
    }
  }

  applyFilters() {
    const categoryFilter = this.filters.find(f => f.column === 'category');
    const filtered = categoryFilter
      ? this.data.sales.filter(s => categoryFilter.values.includes(s.category))
      : this.data.sales;
    this.renderSales(filtered);
  }

  updateFilters() {
    const selected = Array.from(
      document.querySelectorAll('.category-checkbox:checked')
    ).map(cb => cb.value);

    domo.requestFiltersUpdate([{
      column: 'category',
      operator: 'IN',
      values: selected,
      dataType: 'STRING',
    }]);
  }

  async exportData() {
    const csv = await domo.get('/data/v1/sales', { format: 'csv' });
    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'sales-export.csv';
    a.click();
  }
}

new SalesDashboard();
```

***

## Migration from v5

### Error handling

**v5** — errors were thrown as generic `Error` objects or plain response objects.

**v6** — errors are typed classes. Update your catch blocks:

```javascript theme={"dark"}
// Before (v5)
try {
  await domo.get('/data/v1/sales');
} catch (err) {
  if (err.status === 401) { /* ... */ }
}

// After (v6)
import { DomoAuthError, DomoHttpError } from 'ryuu.js';

try {
  await domo.get('/data/v1/sales');
} catch (err) {
  if (err instanceof DomoAuthError) { /* ... */ }
  else if (err instanceof DomoHttpError) { /* ... */ }
}
```

### Service namespaces (new in v6)

v6 adds high-level service namespaces. You can still use the low-level HTTP methods — the namespaces are additive:

```javascript theme={"dark"}
// v5 — direct HTTP
const rows = await domo.get('/data/v1/sales');

// v6 — service namespace (recommended)
const rows = await domo.data.query('sales');
```

### Debug logging

**v5** — no built-in debug mode.

**v6** — use `domo.debug.enable()`:

```javascript theme={"dark"}
domo.debug.enable('http'); // logs all HTTP traffic
```

### Interceptors (new in v6)

```javascript theme={"dark"}
// v5 — override via extend()
domo.extend({ get: async (url, opts) => { /* custom logic */ } });

// v6 — use intercept() for middleware (preferred)
domo.intercept(async (req, next) => {
  return next(req);
});
```

### Unchanged

* All low-level HTTP methods: `domo.get`, `domo.post`, `domo.put`, `domo.delete`, `domo.getAll`
* Event listener APIs: `onFiltersUpdated`, `onVariablesUpdated`, `onDataUpdated`, `onAppDataUpdated`
* Emitter APIs: `requestFiltersUpdate`, `requestVariablesUpdate`, `requestAppDataUpdate`
* `domo.navigate()`
* `domo.extend()`
* `domo.env` shape
* Static class design — no instantiation
* npm package name (`ryuu.js`) and global (`Domo`)

***

## Getting Help

* **GitHub Issues** — [github.com/DomoApps/domo.js/issues](https://github.com/DomoApps/domo.js/issues): report bugs, request features, or browse known issues
* **Domo Community** — [dojo.domo.com](https://dojo.domo.com): community forums, guides, and answers from other Domo developers
* **Related docs** — [Domo Apps CLI](/portal/Apps/App-Framework/Tools/domo-CLI) | [manifest.json reference](/portal/Apps/App-Framework/Guides/manifest) | [App Framework overview](/portal/Apps/App-Framework)
