Skip to main content

Documentation Index

Fetch the complete documentation index at: https://domoinc-openapi-sync-documents.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Version: v4 | v5 | v6 (latest)
A powerful JavaScript SDK for building custom applications within the Domo platform
npm version TypeScript 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

FeatureDescription
domo.intercept()Register request middleware — logging, retries, custom headers
Structured errorsDomoHttpError, DomoAuthError, DomoConnectionError, DomoValidationError, DomoTimeoutError
RequestOptions.schemaRuntime response validation with a custom parse function
domo.debugCategory-based debug logging, persisted via localStorage
Service namespacesdomo.data, domo.appdb, domo.ai, domo.workflow, domo.codeEngine
Optimized bundleReduced bundle size, zero runtime dependencies
For a full list of breaking changes, see Migration from v5.

Quick Start

Get started with ryuu.js in just a few lines:
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 Environmentdomo.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
  • Extensibleextend() 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 Modedomo.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

npm install ryuu.js
Then import in your application:
// 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:
<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:
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:
// 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)
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):
// 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.
Environment properties come from URL parameters and can be spoofed. For secure user identification, always verify with the API:
const verified = await domo.get('/domo/environment/v1/');

Error Handling

v6 replaces generic errors with a structured hierarchy. Import only what you need:
import {
  DomoHttpError,
  DomoAuthError,
  DomoConnectionError,
  DomoValidationError,
  DomoTimeoutError
} from 'ryuu.js';
ClassThrown WhenKey Properties
DomoHttpErrorNon-2xx HTTP responsestatus, statusText, body, headers
DomoAuthError401 or 403 responseExtends DomoHttpError
DomoConnectionErrorNetwork failure (fetch rejects)
DomoValidationErrorInvalid input or schema parse failureerrors[]
DomoTimeoutErrorRequest or message timeouturl
Always check DomoAuthError before DomoHttpError in your catch block — DomoAuthError extends DomoHttpError, so an instanceof DomoHttpError check will also match auth errors.
Recommended catch pattern:
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:
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 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>
// 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>
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.
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.
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.
Document CRUD
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
// 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)
await domo.appdb.partialUpdate('Users',
  { 'content.username': 'Bill' },
  { '$set': { 'content.band': 'Wyld Stallyns' } }
);

Bulk operations
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
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>
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 }[] }>
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);
AI services consume AI credits. See your Domo instance rate card for details.

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
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, ... }>
const status = await domo.workflow.getInstance('approval-flow', instance.id);
console.log(status.status); // 'IN_PROGRESS' | 'COMPLETED' | 'CANCELED' | 'FAILED' | null
Requires a workflowMapping entry in your manifest.json mapping the alias to a Domo Workflow.

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
const result = await domo.codeEngine('calculate-forecast', {
  region: 'West',
  months: 3
});
Requires a packageMapping entry in your manifest.json mapping the alias to a Code Engine function.

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
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 below.
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:
{
  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
domo.onVariablesUpdated((variables) => {
  const yearVar = variables.find(v => v.name === 'selectedYear');
  if (yearVar) loadData(yearVar.value);
});
Variable object structure:
{
  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
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
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
// Apply filters
domo.requestFiltersUpdate([
  { column: 'Region', operator: 'IN', values: ['West'], dataType: 'STRING' }
]);

// Clear all filters
domo.requestFiltersUpdate(null);
Passing null clears every filter on the parent page for all apps. This is also what onFiltersUpdated triggers automatically on first registration.

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
domo.requestVariablesUpdate([
  { name: 'selectedYear', value: 2024 }
]);

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

Send custom data to other apps on the same Domo page.
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. 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:
// Returns array of objects by default
const data = await domo.get('/data/v1/sales');
console.log(data); // [{ id: 1, amount: 100, ... }, ...]
Format Options:
// 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:
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:
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.
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:
const results = await domo.getAll(['/data/v1/sales', '/data/v1/inventory'], {
  format: 'csv',
});
// All results will be CSV strings

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

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

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

await domo.put(
  '/domo/datastores/v1/collections/notes/documents/abc123',
  { text: 'updated' }
);

domo.delete(url, options?)

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).
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.
Interceptors wrap the fetch call — headers and auth tokens are already set when your interceptor runs.

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.
// 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.
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:
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) }
});

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)
// Navigate in same window
domo.navigate('/page/123456789');

// Open in new tab
domo.navigate('/page/123456789', true);
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.

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.).
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.).
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:
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:
const options: RequestOptions = {
  format: 'array-of-objects',
  query: { limit: 100 }
};

const data = await domo.get('/data/v1/sales', options);
Typed filters:
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:
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.
PlatformBridge used
Desktop / WebMessageChannel + window.parent.postMessage
iOSwebkit.messageHandlers (domofilter, domovariable)
Android / FlutterGlobal objects (window.domofilter, window.domovariable)
Platform detection:
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:
// 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:
localStorage.__domo_debug__ = '["all"]';
// Refresh the page — debug logging is now active
Debug categories:
CategoryWhat it logs
httpEvery request: method, URL, headers, response status and body
messagesAll MessageChannel and postMessage traffic (inbound and outbound)
filtersFilter update events and subscriptions
variablesVariable update events and subscriptions
allEverything 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:
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:
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:
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:
// 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:
FieldDesktopMobile
ColumncolumnNamecolumn
Operatoroperatoroperand

Complete Example

A real-world Custom App wiring together data, filters, variables, and navigation:
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:
// 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:
// 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():
domo.debug.enable('http'); // logs all HTTP traffic

Interceptors (new in v6)

// 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