
Bulk Operations
Pipe HubSpot CLI JSONL through jq to bulk update, delete, upsert, and associate CRM records without one-off UI clicks.
Install
npx skills add https://github.com/hubspot/agent-cli-skills --skill bulk-operationsWhat is this skill?
- JSONL patterns for hubspot objects list, search, get, update, delete, and upsert
- Read → update pipeline with jq nesting under .properties
- CSV → upsert contacts with idProperty email and dry-run safety
- Batch association create from search results without xargs loops
- Server-side gaps filled with jq select (e.g. revenue thresholds, regex filters)
Adoption & trust: 1 installs on skills.sh; 2 GitHub stars; trending (+100% hot-view momentum).
Recommended Skills
Agent Browservercel-labs/agent-browser
Lark Imlarksuite/cli
Lark Calendarlarksuite/cli
Lark Sheetslarksuite/cli
Lark Vclarksuite/cli
Lark Contactlarksuite/cli
Journey fit
Primary fit
Bulk CRM reshapes are classic grow-phase lifecycle ops—segment fixes, lead stage updates, and data hygiene at scale. Lifecycle covers marketing/sales funnel fields, MQL promotion, and contact–company association patterns shown in the skill.
SKILL.md
READMESKILL.md - Bulk Operations
# JSON reshape patterns All examples assume JSONL input from `hubspot objects list|search|get`. Output always nests under `.properties`; `--properties a,b` limits the field set returned. These are the reshapes you actually use. Skip anything you can derive. --- ## Read → update ```bash hubspot objects search --type contacts --filter "industry=Tech" \ | jq -c '{id, properties:{lifecyclestage:"marketingqualifiedlead"}}' \ | hubspot objects update --type contacts ``` ## Read → delete ```bash hubspot objects search --type contacts --filter "!email" \ | jq -c '{id}' \ | hubspot objects delete --type contacts --dry-run ``` ## Read → batch get (one call, no xargs) ```bash hubspot associations list --from companies:67890 --to contacts \ | jq -c '{id}' \ | hubspot objects get --type contacts --properties email,firstname ``` ## CSV → upsert ```bash # external.csv: email,firstname,lastname,company tail -n +2 external.csv \ | jq -R -c 'split(",") | {idProperty:"email", id:.[0], properties:{firstname:.[1], lastname:.[2], company:.[3]}}' \ | hubspot objects upsert --type contacts --dry-run ``` ## Read → association create ```bash hubspot objects search --type contacts --filter "company~acme" \ | jq -c '{from:("contacts:"+.id), to:"companies:456"}' \ | hubspot associations create ``` ## Numeric / regex filtering server-side can't express ```bash # Companies with revenue > 1M hubspot objects list --type companies \ | jq -c 'select((.properties.annualrevenue // "0") | tonumber > 1000000)' # Exclude obvious junk emails (server-side ~ is whole-token only) hubspot objects list --type contacts \ | jq -c 'select(.properties.email | test("test|noreply|placeholder"; "i") | not)' ``` ## Union and de-dupe two searches ```bash ( hubspot objects search --type contacts --filter "lifecyclestage=lead" hubspot objects search --type contacts --filter "lifecyclestage=marketingqualifiedlead" ) | jq -s -c 'unique_by(.id)[]' ``` ## Frequency table (count by field) ```bash hubspot objects list --type contacts --properties lifecyclestage \ | jq -r '.properties.lifecyclestage // "(unset)"' \ | sort | uniq -c | sort -rn ``` ## Export to CSV / TSV ```bash hubspot objects list --type contacts --properties email,firstname,lastname \ | jq -r '[.properties.email, .properties.firstname, .properties.lastname] | @csv' ``` #!/bin/bash # pagination-loop.sh # # Generic pagination loop for hubspot CLI commands. # # The CLI returns at most 100 records per call. When --format json is used, # the response envelope looks like: # { "data": [...], "meta": { "next": "<cursor>" } } # # When meta.next is absent or null, there are no more pages. # Pass the cursor value back as --after on the next call. # # USAGE: # bash pagination-loop.sh <object_type> <output_file> [properties] [extra_flags...] # # EXAMPLES: # bash pagination-loop.sh contacts /tmp/contacts.jsonl # bash pagination-loop.sh contacts /tmp/contacts.jsonl email,firstname,lastname # bash pagination-loop.sh contacts /tmp/leads.jsonl email,firstname '--filter lifecyclestage=lead' # # To use search instead of list, pass --filter flags as extra_flags. set -eo pipefail OBJECT_TYPE="${1:?Usage: pagination-loop.sh <object_type> <output_file> [properties] [extra_flags...]}" OUTPUT_FILE="${2:?Usage: pagination-loop.sh <object_type> <output_file> [properties] [extra_flags...]}" PROPERTIES="${3:-}" shift 3 2>/dev/null || shift $# EXTRA_FLAGS=("$@") LIMIT=100 # ── Build base command args ────────────────────────────────────────────────── # Auto-detect: use "objects search" when --filter is present, "objects list" otherwise SUBCOMMAND="list" for flag in "${EXTRA_FLAGS[@]}"; do if [ "$flag" = "--filter" ]; then SUBCOMMAND="search" break fi done BASE_ARGS=(hubspot objects "$SUBCOMMAND" --type "$OBJECT_TYPE" --limit "$LIMIT" --format json) if [ -n "$PROPERTIES" ]; then BASE_ARGS+=(--properties "$PROPERTIES") fi if [ ${#EXTRA_FLAGS[@]} -gt 0 ]; then BASE_ARGS+=("${EXTRA_FLAGS[@]}") fi