skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
stahura/domo-ai-vibe-rules162 installs

programmatic-filters

Control which data each viewer sees in an embedded Domo dashboard/card via server-side programmatic filters and dataset switching. Covers the OAuth → embed token flow, standard filters, SQL filters (OR/BETWEEN/LIKE), per-dataset targeting, datasetRedirects for multi-tenant architectures, and token size limits. Use for any per-viewer, per-role, or per-tenant data restrictions at embed time. Not for client-side JS API filtering (use jsapi-filters).

How do I install this agent skill?

npx skills add https://github.com/stahura/domo-ai-vibe-rules --skill programmatic-filters
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The skill is a comprehensive documentation and reference guide for implementing server-side programmatic filtering with Domo. It provides templates for OAuth authentication, embed token requests, and filter syntax. The skill correctly identifies and emphasizes security best practices, such as performing all authentication server-side to protect sensitive API credentials.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

  • ZeroLeakswarn

    3 findings · Score: 69/100

What does this agent skill do?

Domo Programmatic Filtering

Control what data each viewer sees in embedded Domo content via server-side filters and dataset switching. Enforced by Domo — end users can't bypass. For client-side filtering, see jsapi-filters.

How It Works

A proxy user (service account) acts on behalf of all viewers. Your server:

  1. Authenticates with Domo (OAuth client credentials → access token)
  2. Requests an embed token with viewer-specific filters
  3. Returns the token to the client, which POSTs it to an iframe

Prerequisites

  • Domo API client with CLIENT_ID and CLIENT_SECRET (developer.domo.com > My Account > New Client)
  • Embed ID (5-char ID from the embed dialog, not the page URL)
  • Dataset column names/IDs for filtering

All auth must happen server-side (CORS restrictions). Never expose credentials client-side.

The Embed Token Flow

Step 1: Get an Access Token

POST https://api.domo.com/oauth/token?grant_type=client_credentials&scope=data%20audit%20user%20dashboard
Authorization: Basic base64(CLIENT_ID:CLIENT_SECRET)

Node.js:

const credentials = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')

const response = await fetch(
  'https://api.domo.com/oauth/token?grant_type=client_credentials&scope=data%20audit%20user%20dashboard',
  {
    method: 'GET',
    headers: { Authorization: `Basic ${credentials}` }
  }
)

const { access_token } = await response.json()

Python:

import requests
from base64 import b64encode

credentials = b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()

response = requests.get(
    "https://api.domo.com/oauth/token",
    params={"grant_type": "client_credentials", "scope": "data audit user dashboard"},
    headers={"Authorization": f"Basic {credentials}"}
)

access_token = response.json()["access_token"]

Step 2: Request an Embed Token with Filters

Use the access token to request an embed token, including your filters in the authorizations payload:

For dashboards:

POST https://api.domo.com/v1/stories/embed/auth
Authorization: Bearer {access_token}
Content-Type: application/json

For cards:

POST https://api.domo.com/v1/cards/embed/auth
Authorization: Bearer {access_token}
Content-Type: application/json

Payload structure:

{
  "sessionLength": 1440,
  "authorizations": [
    {
      "token": "<embed_id>",
      "permissions": ["READ", "FILTER", "EXPORT"],
      "filters": [],
      "policies": []
    }
  ]
}
  • sessionLength: Token validity in minutes (1440 = 24 hours)
  • token: The embed ID for the dashboard or card
  • permissions: Array of granted permissions — READ, FILTER, EXPORT
  • filters: Array of standard filter objects (see below)
  • policies: Array of PDP policy IDs to apply (optional)

The response includes an authentication property containing the embed token.

Step 3: Render the Embed

Submit via hidden POST form targeting an iframe. Dashboard: https://public.domo.com/embed/pages/{embed_id}, Card: https://public.domo.com/cards/{embed_id}

<iframe id="domo-embed" name="domo-embed" width="100%" height="600"></iframe>

<form id="embed-form" action="https://public.domo.com/embed/pages/{embed_id}" method="POST" target="domo-embed">
  <input type="hidden" name="embedToken" value="{embed_token}" />
</form>

<script>document.getElementById('embed-form').submit();</script>

POST submission prevents the token from appearing in URLs or browser history.


Filter Types

Two types: standard filters and SQL filters, both in the embed token request.

Standard Filters

{
  "column": "Region",
  "operator": "IN",
  "values": ["West", "East"]
}

Required properties:

PropertyTypeDescription
columnstringThe exact column name in the dataset
operatorstringThe comparison operator (see table below)
valuesarrayValues to filter against

Optional properties:

PropertyTypeDescription
datasourceIdstring (UUID)Restrict filter to a specific dataset. Without this, the filter applies to all datasets in the embed that have a matching column name.

Operators

OperatorDescriptionTypical Use
INColumn value is in the listMulti-value match
NOT_INColumn value is not in the listExclusion
EQUALSColumn value equalsSingle-value exact match
NOT_EQUALSColumn value does not equalSingle-value exclusion
GREATER_THANColumn value is greater thanNumeric/date range
GREATER_THAN_EQUALS_TOColumn value is greater than or equalNumeric/date range
LESS_THANColumn value is less thanNumeric/date range
LESS_THAN_EQUALS_TOColumn value is less than or equalNumeric/date range

Standard Filter Examples

Single filter — show only West region:

{
  "filters": [
    {
      "column": "Region",
      "operator": "IN",
      "values": ["West"]
    }
  ]
}

Multiple filters — West region, revenue over 10000:

{
  "filters": [
    {
      "column": "Region",
      "operator": "IN",
      "values": ["West"]
    },
    {
      "column": "Revenue",
      "operator": "GREATER_THAN",
      "values": [10000]
    }
  ]
}

Dataset-specific filter — only apply to one dataset in a multi-dataset dashboard:

{
  "filters": [
    {
      "column": "Region",
      "operator": "IN",
      "values": ["West"],
      "datasourceId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
  ]
}

SQL Filters

For complex logic (OR, BETWEEN, LIKE, nested expressions), use SQL WHERE syntax via sqlFilters (sibling to filters, not nested).

{
  "authorizations": [
    {
      "token": "<embed_id>",
      "permissions": ["READ", "FILTER", "EXPORT"],
      "filters": [],
      "sqlFilters": [
        {
          "sqlFilter": "`Region` IN ('West', 'East') AND `Revenue` > 10000",
          "datasourceIds": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]
        }
      ]
    }
  ]
}

Required properties:

PropertyTypeDescription
sqlFilterstringSQL WHERE clause syntax

Optional properties:

PropertyTypeDescription
datasourceIdsstring[]Array of dataset UUIDs to apply the filter to

Syntax Rules

Column names in backticks, strings in single quotes, numerics unquoted. Supports: IN, NOT IN, BETWEEN, LIKE, AND, OR, IS NULL, IS NOT NULL. Use parentheses for grouping.

SQL Filter Examples

OR condition (not possible with standard filters alone):

{
  "sqlFilter": "`Region` = 'West' OR `Department` = 'Sales'"
}

BETWEEN for date ranges:

{
  "sqlFilter": "`Order Date` BETWEEN '2024-01-01' AND '2024-12-31'"
}

LIKE for partial matching:

{
  "sqlFilter": "`Product Name` LIKE '%Pro%'"
}

Complex nested logic:

{
  "sqlFilter": "(`Region` IN ('West', 'East') AND `Revenue` > 10000) OR `Priority` = 'Critical'"
}

Targeting specific datasets:

{
  "sqlFilter": "`Status` = 'Active'",
  "datasourceIds": [
    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
  ]
}

Combining Standard and SQL Filters

Both can coexist in one authorization. Standard filters apply first, then SQL filters narrow further:

{
  "token": "<embed_id>",
  "permissions": ["READ", "FILTER", "EXPORT"],
  "filters": [
    {
      "column": "Region",
      "operator": "IN",
      "values": ["West"]
    }
  ],
  "sqlFilters": [
    {
      "sqlFilter": "`Revenue` > 10000 OR `Priority` = 'Critical'"
    }
  ]
}

Common Patterns

Per-User Filtering

Store user-to-filter mappings in your database, look up at embed time:

// Pseudocode — adapt to your framework and data layer
async function getEmbedTokenForUser(userId: string, embedId: string) {
  const user = await getUser(userId)
  const dashboard = user.dashboards.find(d => d.embedId === embedId)

  const filters = dashboard.filters.map(f => ({
    column: f.column,
    operator: f.operator,
    values: f.values
  }))

  const accessToken = await getDomoAccessToken()
  const embedToken = await getDomoEmbedToken(accessToken, embedId, filters)

  return embedToken
}

Role-Based Filtering

Apply different filters based on user role rather than individual assignments:

function getFiltersForRole(role: string): Filter[] {
  switch (role) {
    case 'regional-manager':
      return [{ column: 'Region', operator: 'IN', values: [user.region] }]
    case 'executive':
      return [] // No filters — sees everything
    case 'analyst':
      return [{ column: 'Department', operator: 'EQUALS', values: [user.department] }]
    default:
      return [{ column: 'Public', operator: 'EQUALS', values: ['true'] }]
  }
}

Dataset Switching

Redirect the underlying dataset of an embedded dashboard at embed time — useful for multi-tenant architectures where each customer has their own dataset with the same schema.

How It Works

Pass datasetRedirects in the authorization object (original dataset ID → target dataset ID):

{
  "sessionLength": 1440,
  "authorizations": [
    {
      "token": "<embed_id>",
      "permissions": ["READ", "FILTER", "EXPORT"],
      "filters": [],
      "datasetRedirects": {
        "original-dataset-uuid-1": "target-dataset-uuid-1",
        "original-dataset-uuid-2": "target-dataset-uuid-2"
      }
    }
  ]
}

Target datasets must match the original schema (column names and types).

Combining with Filters

Redirects apply first, then filters run against the swapped dataset:

{
  "sessionLength": 1440,
  "authorizations": [
    {
      "token": "<embed_id>",
      "permissions": ["READ", "FILTER", "EXPORT"],
      "datasetRedirects": {
        "a19d0ef1-ca31-4bfd-b168-018b93109671": "a19d0ef1-ca31-4bfd-b168-018b93109672"
      },
      "filters": [
        { "column": "Color", "operator": "IN", "values": ["Red"] },
        { "column": "Model", "operator": "IN", "values": ["Mountain", "Road", "Commuter"] }
      ]
    }
  ]
}

Per-User Dataset Switching

Map each tenant to their dataset IDs:

function getDatasetRedirects(tenant: Tenant): Record<string, string> {
  // The template dashboard was built on these "original" datasets
  const templateDatasets = {
    sales: 'aaaa-bbbb-cccc-dddd',
    inventory: 'eeee-ffff-gggg-hhhh'
  }

  // Each tenant has their own copies
  return {
    [templateDatasets.sales]: tenant.salesDatasetId,
    [templateDatasets.inventory]: tenant.inventoryDatasetId
  }
}

Important Notes

  • Schema must match — column names and types must be identical (cards, filters, and calculated fields reference by name)
  • Partial redirects OK — redirect all, some, or none of a dashboard's datasets
  • Compatible with all filter types — redirects apply first, then standard/SQL/PDP filters

Gotchas and Best Practices

  • JWT size limit (~8KB): Long value lists hit this. Solutions: aggregate columns in Domo, use SQL filters, or split with datasourceId targeting.
  • Column name matching: Exact spelling and case required. Without datasourceId, filters apply to all datasets with a matching column.
  • Token refresh: Cache access tokens, refresh before expiry. Generate embed tokens per-request.
  • Empty filters: [] means no filtering (user sees everything). Always include the filters key — don't omit it.
  • Filter validation: values must be an array (even for EQUALS). Numerics must be numbers, not strings. Operator names are exact (GREATER_THAN_EQUALS_TO, not GREATER_THAN_OR_EQUAL).
  • Security: Never generate tokens client-side. Validate embed ID authorization and filter values server-side. Use minimum permissions (omit EXPORT if not needed).

TypeScript Type Definitions

type FilterOperator =
  | 'IN'
  | 'NOT_IN'
  | 'EQUALS'
  | 'NOT_EQUALS'
  | 'GREATER_THAN'
  | 'GREATER_THAN_EQUALS_TO'
  | 'LESS_THAN'
  | 'LESS_THAN_EQUALS_TO'

interface StandardFilter {
  column: string
  operator: FilterOperator
  values: (string | number)[]
  datasourceId?: string
}

interface SqlFilter {
  sqlFilter: string
  datasourceIds?: string[]
}

type EmbedPermission = 'READ' | 'FILTER' | 'EXPORT'

interface EmbedAuthorization {
  token: string
  permissions: EmbedPermission[]
  filters: StandardFilter[]
  sqlFilters?: SqlFilter[]
  policies?: string[]
  datasetRedirects?: Record<string, string>  // originalDatasetId → targetDatasetId
}

interface EmbedTokenRequest {
  sessionLength: number
  authorizations: EmbedAuthorization[]
}

Quick Reference

Read references/api-endpoints.md for the complete list of Domo API endpoints used in programmatic filtering.

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/stahura/domo-ai-vibe-rules/programmatic-filters">View programmatic-filters on skillZs</a>