CLI toolkit for automating and managing Domo business intelligence instances. Bulk operations on DataFlows, DataSets, Streams, PDP policies, content sharing, and more — all through a single entry point.
This package is not published to the npm registry. Install it directly from GitHub.
npm install github:brycewc/domo-scripts # latest main
npm install github:brycewc/domo-scripts#v1.0.0 # pinned to a release tagor in your package.json:
"dependencies": {
"domo-scripts": "github:brycewc/domo-scripts#v1.0.0"
}There is no build step, so a plain npm install (or yarn add) is all that's needed.
Then create a .env in your project's root (the directory you run commands from) with
your Domo credentials:
DOMO_INSTANCE=your-instance
DOMO_ACCESS_TOKEN=your-access-token
The instance name is the subdomain from your Domo URL (https://<instance>.domo.com).
Config (.env / .env.<name>), logs/, and id-mappings/ all resolve against your
project's working directory, not the installed package. You can also supply
DOMO_INSTANCE / DOMO_ACCESS_TOKEN as real environment variables (e.g. in CI) with no
.env file at all.
Use the CLI — the package installs a domo-scripts bin:
npx domo-scripts --help
npx domo-scripts bulk-share-content --file content.csv --user 12345 --content-type cardUse it as a library — require('domo-scripts') returns the shared modules:
const { api, readCSV, resolveIds, createLogger, config } = require('domo-scripts');git clone https://github.com/brycewc/domo-scripts.git
cd domo-scripts
yarn install
cp .env.example .envEdit .env with your Domo credentials (same variables as above), then run commands with
node cli.js <command>.
To target more than one Domo instance, create a per-environment file alongside .env:
.env.prod # DOMO_INSTANCE=acme-prod, DOMO_ACCESS_TOKEN=...
.env.sandbox # DOMO_INSTANCE=acme-sandbox, DOMO_ACCESS_TOKEN=...
Select one at runtime with --env <name>:
node cli.js --env prod bulk-rename-datasets --file ds.csv
node cli.js --env sandbox bulk-rename-datasets --file ds.csv --dry-run.env.* files are git-ignored. A bare .env is still loaded as a fallback for any shared defaults.
Migration commands (e.g. transfer-stream) operate on two instances at once. Pass both env names:
node cli.js transfer-stream --source-env prod --target-env sandbox --stream-id 12345
node cli.js transfer-stream --source-env prod --target-env sandbox --file streams.csv --dry-runEach transfer records old→new IDs in id-mappings/<source>_to_<target>.json (also git-ignored), so multiple instance pairs can coexist (prod_to_sandbox.json, prod_to_dev.json, etc.). Some kinds — accounts, users, providers — must be pre-populated by hand before transferring assets that reference them; commands abort with a clear error if a required mapping is missing.
node cli.js [--env <name>] <command> [options]
node cli.js --help # List all commands
node cli.js <command> --help # Command-specific options# Update stream schedules to once daily between 6 AM and 8 PM
node cli.js bulk-update-stream-schedules --file "streams.csv" --start-hour 6 --end-hour 20
# Add trigger conditions to dataflows from a CSV
node cli.js bulk-add-dataflow-trigger-condition --file "dataflows.csv" --column "DataFlow ID"
# Or pass a single ID for debug logging
node cli.js bulk-add-dataflow-trigger-condition --id 123
# Upload data to a dataset in batches
node cli.js upload-dataset --file "data.csv" --dataset-id "abc-123" --batch-size 50000
# Share content with a user
node cli.js bulk-share-content --file "content.csv" --user "12345" --content-type "card"
# Preview destructive changes with dry run
node cli.js bulk-delete-content --file "datasets.csv" --object-types "dataset" --id-column "DataSet ID" --dry-run
# Filter CSV input before processing
node cli.js bulk-update-stream-schedules --file "streams.csv" --filter-column "status" --filter-value "ACTIVE"| Command | Description |
|---|---|
build-activity-log-type-map |
Build a map of activity-log object types to their available event types (actions), written to a dated JSON file |
bulk-add-dataflow-tags |
Add tags to dataflows from a CSV or by owner |
bulk-add-dataflow-trigger-condition |
Add DATAFLOW_LAST_RUN trigger conditions to dataflow triggers |
bulk-add-dataset-tags |
Add tags to datasets from a CSV or by owner |
bulk-apply-pdp-policies |
Copy PDP policies from a source dataset to target datasets |
bulk-convert-stream-provider |
Convert streams from one connector type to another |
bulk-delete-content |
Delete mixed content (dataflows, cards, datasets, groups, beast modes, variables, pages, alerts, scheduled reports, app-studio apps, worksheets, custom apps, Code Engine packages, Jupyter workspaces, AI projects, AI models, workflows, projects, project tasks, goals, metrics, filesets, AppDB collections, accounts, workspaces) from a CSV, routing each row to the right endpoint by type — consumes the bulk-list-user-content CSV directly; --delete-output-datasets also deletes each dataflow's output datasets |
bulk-delete-dataflow-triggers |
Remove all triggers from dataflows listed in a CSV |
bulk-delete-users |
Delete users listed in a CSV. Does not check or transfer ownership — prompts for confirmation. |
bulk-export-dataset-versions |
Export historical versions of a dataset |
bulk-list-user-content |
List everything a set of users own (datasets, cards, pages, etc.) into a single Excel or CSV file — one row per (user, object), including a clickable link to each object (--format, default xlsx) |
bulk-rename-dataflows |
Find/replace in dataflow names across the instance |
bulk-rename-datasets |
Find/replace in dataset names across the instance, or rename explicit IDs to new names from a CSV (--file) |
bulk-revoke-access-tokens |
Revoke developer access tokens by ID, CSV, owner, expiration, or deleted owner |
bulk-unshare-content |
Unshare content in bulk |
bulk-share-content |
Share content (cards, datasets, pages, dataflows) with users/groups |
bulk-transfer-ownership |
Transfer ownership of a user's content (datasets, cards, pages, etc.) to a new owner — a user or a group — either all discovered from the user, from a CSV (per-row owner id + USER/GROUP type supported), or an ad-hoc list of IDs via --id/--ids. --verify re-reads every transferred object afterwards to confirm the owner actually moved; beast-mode link pruning (which can delete a formula) is opt-in via --prune-invalid-functions |
bulk-update-column-pdp-policy |
Update users/groups on a column-based PDP policy |
bulk-update-stream-schedules |
Change stream schedules to daily (randomized times), manual, or restore arbitrary schedules from a CSV (--mode from-file) |
bulk-update-stream-update-method |
Change stream update mode from Replace to Append |
bulk-update-users |
Bulk update user attributes from a CSV via PATCH (one row per user) |
check-credentials |
Validate the configured API credentials against the selected instance |
clear-logs |
Delete log files under logs/, keeping dry-run plans and retryable run logs unless --include-plans (supports --dry-run and --command filter) |
delete-dashboard-tree |
Delete a dashboard and every subdashboard beneath it, deepest first (cards on the pages are left in place); --dry-run previews the tree and --from-dry-run deletes exactly that list |
delete-unused-beast-modes |
Find and bulk-delete beast modes with no active links (not used in any card or view), optionally filtered by owner, dataset, or creation date; variables excluded unless --include-variables |
export-dashboard-content |
Walk a dashboard and every subdashboard, exporting each page as a PowerPoint deck, each card as a PDF render, a PNG render and its definition as JSON (chart cards get the Analyzer definition: chart config, the query each component runs, slicers, beast modes, drill path, dataset columns; notebook cards get their markup and rendered HTML), the original file behind every doc/image card, every file embedded in a notebook card, and every dataset the cards read from as Excel. Output mirrors the page hierarchy; datasets are deduplicated at the root, and a cards.xlsx index there maps every card to the dataset behind it and to its exported files (--index-format csv, --no-card-index). A re-run redoes everything by default: --skip-existing reuses what is already on disk so a killed run can be resumed, --clear wipes each page folder first. A dataset Domo has vaulted cannot be queried, so a failed dataset export is retried once after requesting a defrost and waiting for it to thaw (--defrost-timeout <minutes>, default 30, 0 to skip) |
extract-card-ids |
Extract card IDs from a page export JSON |
replace-function-references |
Replace every reference to one beast mode / variable with another across the instance — rewrites the formula and links of each function that nests it, then saves in batches |
run-workflow-from-csv |
Convert a CSV to workflow input format and run a Domo workflow |
swap-input-in-dataflows |
Replace a dataset input across all consuming dataflows |
transfer-beast-modes-to-content-owners |
Transfer a user's beast modes to whoever owns the card or dataset each one lives on. Because a beast mode cannot be group-owned, a group-owned resource falls through a cascade: the producing dataflow's owner when the dataset is a dataflow output, else the departing user's manager if they are in the group, else the group member owning the most beast modes (ties go to the older account). --dry-run plus --output writes the full routing plan to a CSV for review; --verify re-reads every beast mode afterwards |
transfer-stream |
Copy a stream (and its input dataset) from one instance to another |
upload-dataset |
Upload CSV data to a dataset in configurable batches |
Most bulk commands that process a list of IDs support these options:
| Option | Description |
|---|---|
--file, -f |
Path to a CSV file containing IDs |
--column, -c |
CSV column name to extract IDs from |
--<entity>-id |
Single ID (enables detailed debug logging) |
--<entity>-ids |
Comma-separated IDs |
--filter-column |
Filter CSV rows: column name to match |
--filter-value |
Filter CSV rows: required value |
--dry-run |
Preview changes without applying them |
--from-dry-run [file] |
Run exactly what a dry run planned, without rediscovering it (default: the latest dry run log) |
--retry-errors [file] |
Retry the failed and unreached items of an earlier run (default: the latest run log) |
--max-age <hours> |
Allow --from-dry-run / --retry-errors to use a log older than 24 hours |
Dry runs of large jobs can take a long time, and most of that time is discovery (searching, fetching, deciding what to change). Instead of repeating it, run the plan the dry run already wrote:
node cli.js delete-unused-beast-modes --created-before 2026-06-01 --dry-run # slow: scans every beast mode
node cli.js delete-unused-beast-modes --from-dry-run # deletes exactly that listIf a real run has failures, or is interrupted (Ctrl-C, crash, lost connection), retry just what did not finish:
node cli.js delete-unused-beast-modes --retry-errorsHow it works:
- A dry run's log (
logs/<command>/dry_run_<timestamp>.json) is the plan.--from-dry-runtakes itsdry-runrows and runs them through the command's normal execute step. - A real run checkpoints its log every 30 seconds and on exit, recording planned items it never reached under
unreached.--retry-errorstakes theerrorrows plus those unreached items. Items that failed before any change was attempted (a failed lookup during discovery) are listed but not retried. Retrying a retry works the same way. - The options recorded in the log are reused. Flags that change what the run selects or how it acts (filters, input files, IDs,
--dry-run) are rejected alongside these flags. Execution-only flags such as--batch-size,--concurrencyand--yesare still allowed. - The log must come from the same command and the same instance as the current
--env, and be less than 24 hours old unless--max-ageis passed. You are warned when a later run already used the same log, and asked to confirm before anything changes. - Commands that save whole objects (dataflows, streams, beast mode templates) re-fetch each object, check it still matches what the dry run saw, and skip it if it changed, so a plan never overwrites newer edits.
- Commands that fetch and change each item in one step support only
--retry-errors, since replaying their dry run would save nothing. Runnode cli.js <command> --helpto see which flags a command supports. clear-logskeeps plans and retryable run logs unless--include-plansis passed.
domo-scripts/
├── cli.js # Entry point, dispatches to commands
├── lib/
│ ├── index.js # Re-exports all shared modules
│ ├── config.js # Environment config and auth
│ ├── api.js # Authenticated Domo API client (get/put/post/del)
│ ├── csv.js # CSV parsing with optional filtering
│ ├── input.js # Resolve IDs from CSV/flags
│ ├── log.js # Debug and run log utilities
│ └── plan.js # Loads dry-run plans and run logs for --from-dry-run / --retry-errors
├── commands/ # One file per command (29 total)
├── logs/ # Generated run/debug logs (git-ignored)
├── .env # Your credentials (git-ignored)
├── .env.<name> # Per-environment credentials, selected with --env (git-ignored)
├── .env.example # Template for .env
└── id-mappings/ # Per-env-pair old→new ID mappings, written by transfer commands (git-ignored)
- Create
commands/your-command.js - Import shared libs:
const { api, resolveIds, createLogger } = require('../lib'); - Add
--helphandling before any API calls - Register it in the
commandsmap incli.js
Commands that support logging write JSON files to logs/<commandName>/:
- Run logs (
run_<timestamp>.json): summary of all items processed in a bulk run. Written as the run goes, so an interrupted run still leaves a log that--retry-errorscan finish. - Debug logs (
debug_<itemId>_<timestamp>.json): detailed per-item logs when using--<entity>-id - Dry-run variants are prefixed with
dry_. Dry runs always write a run log (even for a single ID), because it is the plan--from-dry-runreads.