Creates virtual data blends by joining separate marketing platforms on shared dimensions.

Install

mkdir -p .claude/skills/whatagraph-blends && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/18768" && unzip -o skill.zip -d .claude/skills/whatagraph-blends && rm skill.zip

Installs to .claude/skills/whatagraph-blends

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Combine data from different channels (Google Ads + Meta + GA4) into one virtual source by joining on shared dimensions (date, campaign name, etc.). Use when a widget needs to show cross-channel rows side-by-side or a computed metric needs numerator/denominator from separate sources.
283 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Join 2+ sources into a single virtual source
  • Combine data from different channels (e.g., Google Ads + Meta + GA4)
  • Group data by shared dimensions (e.g., date, campaign name)
  • Enable widgets to show cross-channel rows side-by-side
  • Allow computed metrics to use numerator/denominator from separate sources
  • List existing blends

How it works

The skill joins two or more data sources into a single virtual source by matching shared dimensions. This allows widgets and custom metrics to treat the combined data as a unified entity.

Inputs & outputs

You give it
multiple data sources from different channels
You get back
a single virtual source with joined data

When to use whatagraph-blends

  • Blending cross-channel ad spend
  • Calculating blended metrics like ROAS
  • Comparing campaign performance side-by-side

About this skill

Blends

Tools covered: list-blends, manage-blends.

A blend joins 2+ sources into a single virtual source, keeping each source's own columns. Sources can be from different channels (e.g. Google Ads + Meta + GA4) or from the same channel (e.g. two Google Ads accounts, one column each). The blend has its own integration source id, so widgets and custom metrics can treat the blend like any other source.

Use this when

  • "Put Google Ads spend and Meta Ads spend in the same table grouped by date."
  • "Compute Blended ROAS = GA4 revenue / (Google Ads spend + Meta Ads spend)." (Blend first, custom metric second.)
  • "Cross-channel performance widget that groups by campaign theme across 3 platforms."
  • "Show these two Google Ads accounts side by side, matched on campaign name." (Same-channel blend — a source group would sum them into one column.)
  • "Combined numbers I can read right now, without waiting for a sync."

Blend vs source group — quick decision

Source groupBlend
Operationsums sources into one totaljoins rows on a shared dimension
Outputthe total, plus a per-channel and per-source variant of every metric (Impressions for Google Ads only) and Channel/Source name dimensionseach sub-source keeps its own columns
Channelssame or acrosssame or across
Scalemany — dozens to hundredsa handful — 2–5 typical
Freshnessstored — ETL-written, warms up, as fresh as the last synclive — computed in-request, ready immediately
Read idsuniversal_* (+ _integration_<id> / _integration_source_<id>)aggregation_* combined, blend_* per sub-source

Sum → source group. Join → blend. Live → blend. Many sources → source group.

GoalUse
5 Google Ads accounts → 1 virtual "Google Ads Total"Source group (same-channel)
Meta + Google + Reddit + TikTok → 1 aggregated source with unified metricsSource group (cross-channel)
Sum of spend across Google + Meta into one total, no row-level joinSource group (cross-channel)
40 GBP locations / 25 client ad accounts → 1 rollupSource group — a blend here would need 39 joins
Google Ads + Meta Ads joined/matched on campaign name (side-by-side rows)Blend (cross-channel)
2 Google Ads accounts joined on campaign name, one column per accountBlend (same-channel)
Combined numbers that must be live — today's data, no warmup waitBlend (same- or cross-channel)

Common mistake: source group ≠ same-channel-only, blend ≠ cross-channel-only. Both do both — channel count decides nothing.

Why scale differs: a group needs one ETL config per channel, not per source and serves precomputed data; a blend needs N−1 joins and re-fetches every sub-source per request. No hard cap either way. When live and many collide, scale wins — build the group and accept the warmup, or blend only the few sources watched live and roll the rest up.

Listing

list-blends action=list                # paginated; includes source_count, channel_names per blend
list-blends action=show blend_id=<id>  # full sub-sources, joins, widgets_count, per-sub-source filter

Only show reports each sub-source's filter. list does not, so never conclude a blend is unfiltered from a list response.

Creating a blend

manage-blends action=create
   name="Google Ads + Meta Ads — Campaign Blend"
   description="Cross-channel campaign performance"
   currency="USD"
   items=[
     {
       "integration_source_id": <google_ads_source>,
       "report_type": "campaign",
       "dimensions": ["universal_dimension_1137", "campaign_name"],
       "metrics":    ["universal_metric_1", "universal_metric_3"]
     },
     {
       "integration_source_id": <meta_ads_source>,
       "dimensions": ["universal_dimension_1137", "campaign_name"],
       "metrics":    ["universal_metric_1", "universal_metric_3"]
     }
   ]
   joins=[
     {
       "type": "full",
       "conditions": [
         {
           "left_source_id":  <google_ads_source>,
           "left_dimension":  "universal_dimension_1137",
           "right_source_id": <meta_ads_source>,
           "right_dimension": "universal_dimension_1137"
         },
         {
           "left_source_id":  <google_ads_source>,
           "left_dimension":  "campaign_name",
           "right_source_id": <meta_ads_source>,
           "right_dimension": "campaign_name"
         }
       ]
     }
   ]

items — each sub-source

  • integration_source_id — id from list-sources action=list.
  • report_type — the source's report type external id. Auto-resolved when source has exactly one report type; required when multiple exist. Omit entirely when the source has zero report types (e.g. Facebook Ads, GA4) — run list-sources action=list_report_types to check.
  • dimensions — array of dimension external ids.
  • metrics — array of metric external ids.
  • filter_id — optional. Narrows THIS sub-source's rows before the join runs. See "Filtering one sub-source" below.

joins — how sub-sources connect

  • type: left, inner, full, or cross.
  • conditions: list of {left_source_id, left_dimension, right_source_id, right_dimension} pairs.

All conditions in one join are ANDed together. For more complex joins, add multiple join objects.

Join type — the most important blend decision

typeBehaviorWhen to use
fullEvery row from both sides is kept; missing dimension pairs appear as nullsDefault; safest for "show everything"
innerOnly rows present in both sidesWhen you want ONLY campaigns that ran on both channels
leftAll rows from left + matching from rightWhen the left source is the "primary" view
crossCartesian productRare; use only when you know why

Most blends should use full — it avoids excluding rows that only exist in one source. inner is a common source of "my data disappeared after blending" questions.

Write-time validation

manage-blends validates join topology when you create or update (not just at fetch time). A blend that breaks these rules is rejected up front with a specific message. Below is each rule, the error you get, and the fix.

Allowed topologies

TopologyOK?Format to use
2 distinct sources, 1 joinconditions (simplified)
3+ distinct sources, chainedone join object per adjacent pair: joins=[{A–B},{B–C}]
Self-join (same source twice)groups/keys only (see below)
One join spanning 3+ sourcessplit into one join per pair
A sub-source with no joinmust connect into the chain (unless a cross join is present)

Shape minimums

create requires at least 2 items and at least 1 join. Each groups/keys group needs at least 2 keys (one per side).

Self-join requires groups/keys (not conditions)

A self-join uses the same integration_source_id in two different items (e.g. one source compared this-month vs last-month). The simplified conditions format keys a join by source id alone, so with the source repeated it can't tell which item you mean. The groups/keys format carries col_index (the 0-based position in items), which disambiguates.

  • Error (using conditions with a duplicated source): "Multiple items reference the same integration_source_id. The simplified conditions format cannot disambiguate which item to join. Use the legacy groups/keys format with explicit col_index instead."
  • Fix — use groups/keys:
joins=[
  {
    "type": "inner",
    "groups": [
      { "keys": [
        { "col_index": 0, "integration_source_id": <S>, "external_id": "<dim>" },
        { "col_index": 1, "integration_source_id": <S>, "external_id": "<dim>" }
      ] }
    ]
  }
]

col_index 0 and 1 point at the first and second item that both carry source <S>.

One join connects exactly one pair of sources

Each join relates two different sub-sources. A single join cannot span three or more.

  • Error (a join condition touches the same source on both sides or only one source): "Each join condition must connect exactly two different sub-sources..."
  • Error (one join spans 3+ sources): "A single join can only relate one pair of sub-sources, but join N spans more than two — this blend would be saved but every data fetch would fail with 'Incorrect blend setup'."
  • Fix: one join object per pair. To chain A–B–C: joins=[{A–B join},{B–C join}].

Every sub-source must be connected

All sub-sources must link (directly or transitively) into one chain.

  • Error: "Sub-source X is not connected to the rest of the blend by any join. Every sub-source must be linked into a single chain — add a join condition relating it to an already-connected sub-source."
  • Fix: add a join tying the orphaned source to one already in the chain. Exception: a cross join in the blend skips this connectivity check.

Date dimensions join only to date dimensions

A temporal dimension (date, datetime, month, week, …) cannot be matched against a non-temporal one.

  • Error: "Join N matches incompatible dimension types: <dim> (date) on source A cannot be joined to <dim> (string) on source B. A date/time dimension must be joined to another date/time dimension."
  • Fix: join date→date and the other key (campaign name, etc.) separately. Keys whose type can't be resolved are skipped, not rejected. (Note: this check currently runs on create only.)

Join keys must be declared dimensions

Every dimension named in a join must also appear in that sub-source's dimensions array.

  • Error: "Join condition references dimension D which is not declared in the dimensions of source ID S..."
  • Fix: add the join-key dimension to that item's dimensions.

Non-cross joins need conditions or groups

Only a cross join may om


Content truncated.

When not to use it

  • When combining sources of the same channel (e.g., 5 Google Ads accounts) into one virtual source
  • When combining the same field across sources/channels into one total without row-level joining
  • When a Cartesian product is not explicitly desired

Limitations

  • Requires at least 2 items and at least 1 join for creation
  • Self-joins require the `groups/keys` format, not `conditions`
  • A sub-source without a join must connect into the chain

How it compares

This skill creates a virtual source by joining data from *different* channels on shared dimensions, enabling side-by-side comparisons and blended metrics, unlike simply grouping multiple sources from the *same* channel.

Compared to similar skills

whatagraph-blends side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
whatagraph-blends (this skill)02moNo flagsAdvanced
segment-cdp27moNo flagsIntermediate
developing-in-lightdash11moReviewIntermediate
coingecko18moNo flagsBeginner

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

segment-cdp

davila7

Expert patterns for Segment Customer Data Platform including Analytics.js, server-side tracking, tracking plans with Protocols, identity resolution, destinations configuration, and data governance best practices. Use when: segment, analytics.js, customer data platform, cdp, tracking plan.

212

developing-in-lightdash

lightdash

Build, configure, and deploy Lightdash analytics projects. Supports both dbt projects with embedded Lightdash metadata and pure Lightdash YAML projects without dbt. Create metrics, dimensions, charts, and dashboards using the Lightdash CLI.

112

coingecko

2025Emma

CoinGecko API documentation - cryptocurrency market data API, price feeds, market cap, volume, historical data. Use when integrating CoinGecko API, building crypto price trackers, or accessing cryptocurrency market data.

14

wellally-tech

huifer

Integrate digital health data sources (Apple Health, Fitbit, Oura Ring) and connect to WellAlly.tech knowledge base. Import external health device data, standardize to local format, and recommend relevant WellAlly.tech knowledge base articles based on health data. Support generic CSV/JSON import, provide intelligent article recommendations, and help users better manage personal health data.

14

groq-cost-tuning

jeremylongshore

Optimize Groq costs through tier selection, sampling, and usage monitoring. Use when analyzing Groq billing, reducing API costs, or implementing usage monitoring and budget alerts. Trigger with phrases like "groq cost", "groq billing", "reduce groq costs", "groq pricing", "groq expensive", "groq budget".

12

omero-integration

davila7

Microscopy data management platform. Access images via Python, retrieve datasets, analyze pixels, manage ROIs/annotations, batch processing, for high-content screening and microscopy workflows.

12

Search skills

Search the agent skills registry