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. 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."

Blend vs source group — quick decision

GoalUse
Sources are ALL the same channel (5 Google Ads accounts) → one virtual sourceSource group (manage-source-groups)
Combine the same field across sources/channels into one total, no row-level join neededSource group (cross-channel rollup)
Sources are DIFFERENT channels that must be JOINED on a shared dimension (campaign, date) for side-by-side rowsBlend

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

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.

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 omit the join definition.

  • Error: "Join of type X requires conditions or groups to define how sources are connected..."
  • Fix: add conditions (or groups), or switch the type to cross if a cartesian product is what you want.

Source must be in items

  • Error: "Join references source ID S which is not in the items list."
  • Fix: the left_source_id/right_source_id (or a key's integration_source_id) must be one of the items integration source ids.

Updating

manage-blends action=update blend_id=<id>
   items=[...]  joins=[...]

Replace-style — full items and joins lists replace previous values.

Duplicating

manage-blends action=duplicate blend_id=<id>

Useful for blend variants ("Inner version of the full blend" to compare).

Unified dimensions and metrics across sub-sources

A blend is only useful when the sub-sources expose equivalent dimensions and metrics that can be joined and aggregated. In practice that means:

  • Dimensions: pick the same set of universal dimensions on every sub-source (e.g. universal_dimension_1137 = Date on every channel, plus a shared key like campaign name or channel name).
  • Metrics: pick the same set of universal metrics on every sub-source (e.g. universal_metric_1 = Impressions, universal_metric_2 = Clicks, universal_metric_3 = Spend). The blend then exposes one aggregated metric per universal slot across the whole blend.
  • Types and summability: the joined metrics must be the same data type (integer/float/currency) and summable — counts, impressions, clicks, spend. Average or ratio metrics should not be blended directly; blend the numerator and denominator separately, then build a custom data_formula metric on top of the blend.

Using the blend in widgets

A blend is a virtual source with its own integration_source_id (the id returned by list-blends). Attach it to the report first, then create the widget against the returned report-local source_id:

manage-reports action=attach_source report_id=<id>
   integration_source_id=<blend_id>
# response.source_id is the report-local id

manage-widgets action=create report_id=<id> tab_id=<tab_id>
   channel_id=<blend_channel_id>
   source_id=<that 

---

*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)019dNo flagsAdvanced
segment-cdp26moNo flagsIntermediate
developing-in-lightdash110dReviewIntermediate
coingecko17moNo 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