programmatic-filters について
programmatic-filters は、サーバー側フィルターとデータセット切り替えで埋め込み Domo コンテンツを保護するスキルです。閲覧者ごとの埋め込みトークンを生成し、Domo にデータアクセス制限を適用させる方法を開発者に案内します。
主な機能
- 01閲覧者別の埋め込みトークン生成
- 02標準フィルターと SQL フィルターの適用
- 03埋め込み先データセットの切り替え
- 04ユーザー別およびロール別フィルタリング
- 05認証とトークン生成をサーバー側で実施
活用シーン
- 01閲覧者やロールに応じた埋め込みダッシュボードの制限
- 02埋め込みデータへの複雑な SQL 条件の適用
- 03共有ダッシュボードでテナント別データセットを提供
- 04認証済みカードやダッシュボードの埋め込み生成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ stahura/ domo-ai-vibe-rules --skill programmatic-filtersインストール後、Claude Code で/programmatic-filtersを入力して読み込まれたか確認します。
同梱ファイルreferences/api-endpoints.md
詳細
Domo のプログラムによるフィルタリング
サーバー側のフィルターとデータセットの切り替えを使って、埋め込み Domo コンテンツで各閲覧者に表示するデータを制御します。Domo によって強制されるため、エンドユーザーが回避することはできません。クライアント側のフィルタリングについては、jsapi-filters を参照してください。
仕組み
プロキシユーザー(サービスアカウント)が、すべての閲覧者の代理として処理します。サーバーでは次の処理を行います。
- Domo で認証します(OAuth クライアント認証情報 → アクセストークン)
- 閲覧者ごとのフィルターを指定して埋め込みトークンをリクエストします
- トークンをクライアントに返し、クライアントが iframe に POST します
前提条件
CLIENT_IDとCLIENT_SECRETを持つ Domo API クライアント(developer.domo.com > My Account > New Client)- 埋め込み ID(埋め込みダイアログに表示される 5 文字の ID。ページ URL ではありません)
- フィルタリングに使うデータセットの列名/ID
すべての認証処理はサーバー側で行う必要があります(CORS の制限があるため)。クライアント側に認証情報を公開しないでください。
埋め込みトークンのフロー
ステップ 1:アクセストークンを取得する
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"]
ステップ 2:フィルター付きの埋め込みトークンをリクエストする
アクセストークンを使って埋め込みトークンをリクエストし、authorizations ペイロードにフィルターを含めます。
ダッシュボードの場合:
POST https://api.domo.com/v1/stories/embed/auth
Authorization: Bearer {access_token}
Content-Type: application/json
カードの場合:
POST https://api.domo.com/v1/cards/embed/auth
Authorization: Bearer {access_token}
Content-Type: application/json
ペイロードの構造:
{
"sessionLength": 1440,
"authorizations": [
{
"token": "<embed_id>",
"permissions": ["READ", "FILTER", "EXPORT"],
"filters": [],
"policies": []
}
]
}
sessionLength:トークンの有効期間(分単位。1440 = 24 時間)token:ダッシュボードまたはカードの埋め込み IDpermissions:付与する権限の配列 —READ、FILTER、EXPORTfilters:標準フィルターオブジェクトの配列(下記参照)policies:適用する PDP ポリシー ID の配列(任意)
レスポンスには、埋め込みトークンを含む authentication プロパティが含まれます。
ステップ 3:埋め込みコンテンツを表示する
iframe をターゲットにした非表示の POST フォームで送信します。ダッシュボードの場合:https://public.domo.com/embed/pages/{embed_id}、カードの場合: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 送信により、URL やブラウザーの履歴にトークンが表示されるのを防げます。
フィルターの種類
フィルターには、埋め込みトークンのリクエストで使用する標準フィルターとSQL フィルターの 2 種類があります。
標準フィルター
{
"column": "Region",
"operator": "IN",
"values": ["West", "East"]
}
必須プロパティ:
| プロパティ | 型 | 説明 |
|---|---|---|
column | string | データセット内の正確な列名 |
operator | string | 比較演算子(下表を参照) |
values | array | フィルタリング対象の値 |
任意のプロパティ:
| プロパティ | 型 | 説明 |
|---|---|---|
datasourceId | string (UUID) | フィルターの適用先を特定のデータセットに限定します。指定しない場合、埋め込みコンテンツ内で一致する列名を持つすべてのデータセットに適用されます。 |
演算子
| 演算子 | 説明 | 一般的な用途 |
|---|---|---|
IN | 列の値がリストに含まれる | 複数の値との一致 |
NOT_IN | 列の値がリストに含まれない | 除外 |
EQUALS | 列の値が一致する | 単一値との完全一致 |
NOT_EQUALS | 列の値が一致しない | 単一値の除外 |
GREATER_THAN | 列の値が指定値より大きい | 数値/日付の範囲 |
GREATER_THAN_EQUALS_TO | 列の値が指定値以上 | 数値/日付の範囲 |
LESS_THAN | 列の値が指定値より小さい | 数値/日付の範囲 |
LESS_THAN_EQUALS_TO | 列の値が指定値以下 | 数値/日付の範囲 |
標準フィルターの例
単一のフィルター — 西部地域のみを表示:
{
"filters": [
{
"column": "Region",
"operator": "IN",
"values": ["West"]
}
]
}
複数のフィルター — 西部地域かつ売上が 10000 を超えるデータを表示:
{
"filters": [
{
"column": "Region",
"operator": "IN",
"values": ["West"]
},
{
"column": "Revenue",
"operator": "GREATER_THAN",
"values": [10000]
}
]
}
データセットを指定したフィルター — 複数のデータセットを含むダッシュボード内の 1 つのデータセットのみに適用:
{
"filters": [
{
"column": "Region",
"operator": "IN",
"values": ["West"],
"datasourceId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
}
SQL フィルター
複雑なロジック(OR、BETWEEN、LIKE、ネストされた式)には、sqlFilters を使って SQL の WHERE 構文を指定します(filters と同階層で指定し、入れ子にはしません)。
{
"authorizations": [
{
"token": "<embed_id>",
"permissions": ["READ", "FILTER", "EXPORT"],
"filters": [],
"sqlFilters": [
{
"sqlFilter": "`Region` IN ('West', 'East') AND `Revenue` > 10000",
"datasourceIds": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]
}
]
}
]
}
必須プロパティ:
| プロパティ | 型 | 説明 |
|---|---|---|
sqlFilter | string | SQL の WHERE 句の構文 |
任意のプロパティ:
| プロパティ | 型 | 説明 |
|---|---|---|
datasourceIds | string[] | フィルターの適用先となるデータセット UUID の配列 |
構文ルール
列名はバッククォートで囲み、文字列はシングルクォートで囲み、数値は引用符なしで記述します。次の演算子に対応しています:IN、NOT IN、BETWEEN、LIKE、AND、OR、IS NULL、IS NOT NULL。グループ化には括弧を使用します。
SQL フィルターの例
OR 条件(標準フィルターだけでは実現できません):
{
"sqlFilter": "`Region` = 'West' OR `Department` = 'Sales'"
}
日付範囲に対する BETWEEN:
{
"sqlFilter": "`Order Date` BETWEEN '2024-01-01' AND '2024-12-31'"
}
部分一致に対する LIKE:
{
"sqlFilter": "`Product Name` LIKE '%Pro%'"
}
複雑なネストされたロジック:
{
"sqlFilter": "(`Region` IN ('West', 'East') AND `Revenue` > 10000) OR `Priority` = 'Critical'"
}
特定のデータセットを対象にする:
{
"sqlFilter": "`Status` = 'Active'",
"datasourceIds": [
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
]
}
標準フィルターと SQL フィルターの組み合わせ
1 つの認可で両方を併用できます。標準フィルターが先に適用され、その後 SQL フィルターでさらに絞り込みます。
{
"token": "<embed_id>",
"permissions": ["READ", "FILTER", "EXPORT"],
"filters": [
{
"column": "Region",
"operator": "IN",
"values": ["West"]
}
],
"sqlFilters": [
{
"sqlFilter": "`Revenue` > 10000 OR `Priority` = 'Critical'"
}
]
}
よく使われるパターン
ユーザーごとのフィルタリング
データベースにユーザーとフィルターの対応関係を保存し、埋め込み時に参照します。
// 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
}
ロールベースのフィルタリング
個別の割り当てではなく、ユーザーのロールに応じて異なるフィルターを適用します。
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'] }]
}
}
データセットの切り替え
埋め込み時に埋め込みダッシュボードの基になるデータセットを切り替えます。これは、各顧客が同じスキーマの独自データセットを持つマルチテナントアーキテクチャで役立ちます。
仕組み
認可オブジェクトでdatasetRedirectsを渡します(元のデータセット ID → 対象のデータセット 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"
}
}
]
}
対象のデータセットは、元のデータセットと同じスキーマ(列名と型)である必要があります。
フィルターとの組み合わせ
リダイレクトが先に適用され、その後、切り替え後のデータセットに対してフィルターが実行されます。
{
"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"] }
]
}
]
}
ユーザーごとのデータセット切り替え
各テナントをそれぞれのデータセット ID に対応付けます。
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
}
}
重要な注意事項
- スキーマは一致している必要があります — 列名と型は完全に同一である必要があります(カード、フィルター、計算フィールドは名前で参照されます)
- 一部のリダイレクトでも問題ありません — ダッシュボードのデータセットすべて、一部、またはどれもリダイレクトしないように設定できます
- すべてのフィルタータイプと併用できます — リダイレクトが先に適用され、その後、標準/SQL/PDP フィルターが適用されます
注意点とベストプラクティス
- JWT のサイズ制限(約 8 KB): 値のリストが長いと制限に達します。解決策:Domo で列を集約する、SQL フィルターを使用する、または
datasourceIdで対象を分割する。 - 列名の一致: 大文字と小文字を含め、正確に一致させる必要があります。
datasourceIdがない場合、フィルターは一致する列を持つすべてのデータセットに適用されます。 - トークンの更新: アクセストークンをキャッシュし、有効期限前に更新します。埋め込みトークンはリクエストごとに生成します。
- 空のフィルター:
[]はフィルタリングなしを意味します(ユーザーにはすべてが表示されます)。必ずfiltersキーを含め、省略しないでください。 - フィルターの検証:
valuesは(EQUALSの場合も)配列である必要があります。数値は文字列ではなく数値として指定します。演算子名は正確に指定してください(GREATER_THAN_OR_EQUALではなくGREATER_THAN_EQUALS_TO)。 - セキュリティ: クライアント側でトークンを生成しないでください。埋め込み ID の認可とフィルター値をサーバー側で検証します。必要最小限の
permissionsを使用してください(不要ならEXPORTを省略します)。
TypeScript 型定義
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[]
}
クイックリファレンス
プログラムによるフィルタリングで使用する Domo API エンドポイントの完全な一覧は、references/api-endpoints.mdを参照してください。