Skip to content

feat(meta-ads): add meta ads integration for campaign and ad performance queries#3563

Open
waleedlatif1 wants to merge 9 commits intostagingfrom
waleedlatif1/meta-ads-integration
Open

feat(meta-ads): add meta ads integration for campaign and ad performance queries#3563
waleedlatif1 wants to merge 9 commits intostagingfrom
waleedlatif1/meta-ads-integration

Conversation

@waleedlatif1
Copy link
Collaborator

Summary

  • Add Meta Ads integration with 5 tools: get account info, list campaigns, list ad sets, list ads, and get performance insights
  • Add account and campaign selectors with cascading dropdown support (select account → campaigns auto-populate)
  • OAuth configured with minimal ads_read scope for read-only operations
  • Auto-generated docs

Type of Change

  • New feature

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 14, 2026 4:10pm

Request Review

@cursor
Copy link

cursor bot commented Mar 13, 2026

PR Summary

Medium Risk
Adds a new OAuth provider and multiple Meta Graph API-backed tools/endpoints, which can impact auth flows and external API reliability/error handling. Changes are additive but touch credential authorization and token refresh paths.

Overview
Adds a new Meta Ads integration end-to-end: OAuth provider configuration (new env vars, meta-ads provider/service registration, scope description) plus a new MetaAdsBlock wired into the block registry with operations for account info, campaign/ad hierarchy listing, and insights querying.

Introduces five new tool implementations (meta_ads_get_account, meta_ads_list_campaigns, meta_ads_list_ad_sets, meta_ads_list_ads, meta_ads_get_insights) that call the Meta Graph API (v24.0), including helper types/utilities (stripActPrefix, base URL) and registry wiring.

Adds UX support for cascading selectors via new selector keys (meta-ads.accounts, meta-ads.campaigns) backed by new API routes (/api/tools/meta_ads/accounts, /api/tools/meta_ads/campaigns) that authorize credential use and refresh tokens before fetching options. Updates docs/metadata to include meta_ads and adds a MetaAdsIcon used across docs and app.

Written by Cursor Bugbot for commit b339b48. Configure here.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR adds a full Meta Ads integration with 5 read-only tools (get account info, list campaigns, list ad sets, list ads, get performance insights), OAuth wiring via the Facebook Graph API v24.0, and cascading account/campaign dropdown selectors. The implementation is well-structured and consistent with the patterns used by other integrations in the codebase.

Key issues found:

  • Critical bug in apps/sim/lib/auth/auth.ts: The getUserInfo callback for the Meta Ads provider generates id: \${profile.id}-${crypto.randomUUID()}`. Because crypto.randomUUID()produces a different value on every invocation, theproviderAccountIdstored inbetter-authwill never match a previously-created account record. Every login will create a fresh OAuth account entry, breaking account persistence and credential linking. The fix is to useprofile.id` directly.
  • Style: operation leaks into tool params in apps/sim/blocks/blocks/meta_ads.ts — the operation key is included in ...rest and forwarded to tool functions that do not declare it as a parameter. Explicitly destructuring and discarding operation before spreading would be cleaner.
  • Style: missing blank line between MetaAdsIcon and MailchimpIcon in apps/sim/components/icons.tsx, inconsistent with the rest of the file.

Confidence Score: 1/5

  • Not safe to merge — the random UUID in getUserInfo will break Meta OAuth account persistence on every login.
  • A single critical logic bug in the OAuth getUserInfo callback means every login produces a unique, non-repeatable provider account ID. This would prevent users from ever being matched to their existing connected account, effectively breaking the entire OAuth flow for Meta Ads. The rest of the tool implementation (API calls, response mapping, selector dropdowns) is clean and well-structured, but this auth bug must be fixed first.
  • apps/sim/lib/auth/auth.ts requires immediate attention for the random UUID bug in getUserInfo.

Important Files Changed

Filename Overview
apps/sim/lib/auth/auth.ts Adds Meta Ads OAuth provider; critical bug: getUserInfo appends crypto.randomUUID() to the provider user ID, causing a new account record to be created on every login.
apps/sim/blocks/blocks/meta_ads.ts Defines the Meta Ads block config with 5 operations, cascading dropdowns, and OAuth wiring; the operation field leaks into forwarded tool params via ...rest.
apps/sim/tools/meta_ads/get_insights.ts Implements the insights tool with correct parent-ID resolution logic, proper date-range handling, and conversion aggregation from the Meta actions array.
apps/sim/app/api/tools/meta_ads/accounts/route.ts Selector API route for ad accounts; fetches all accounts and annotates non-active ones with status labels. Log label "active" is slightly misleading but functionally correct.
apps/sim/app/api/tools/meta_ads/campaigns/route.ts Selector API route for campaigns; correctly strips the act_ prefix before building the URL and passes workflowId for authorization.
apps/sim/hooks/selectors/registry.ts Adds meta-ads.accounts and meta-ads.campaigns selector definitions with correct cascading dependency (campaigns depend on both credential and accountId).
apps/sim/lib/oauth/oauth.ts Registers the Meta Ads OAuth provider with ads_read scope and correct token endpoint; supportsRefreshTokenRotation: false is appropriate since Meta uses long-lived tokens without a standard refresh flow.
apps/sim/components/icons.tsx Adds MetaAdsIcon SVG component with dynamic gradient IDs via useId; missing blank line before MailchimpIcon.

Sequence Diagram

sequenceDiagram
    participant UI as Block UI
    participant Selector as Selector Registry
    participant AccountsAPI as /api/tools/meta_ads/accounts
    participant CampaignsAPI as /api/tools/meta_ads/campaigns
    participant Tool as Tool Runner
    participant MetaAPI as graph.facebook.com/v24.0

    UI->>Selector: Credential selected
    Selector->>AccountsAPI: POST {credential, workflowId}
    AccountsAPI->>MetaAPI: GET /me/adaccounts
    MetaAPI-->>AccountsAPI: [{id, account_id, name, account_status}]
    AccountsAPI-->>Selector: {accounts: [{id, name}]}
    Selector-->>UI: Populate account dropdown

    UI->>Selector: Account selected
    Selector->>CampaignsAPI: POST {credential, workflowId, accountId}
    CampaignsAPI->>MetaAPI: GET /act_{id}/campaigns
    MetaAPI-->>CampaignsAPI: [{id, name, status}]
    CampaignsAPI-->>Selector: {campaigns: [{id, name, status}]}
    Selector-->>UI: Populate campaign dropdown

    UI->>Tool: Execute (operation, accountId, campaignId, ...)
    Tool->>MetaAPI: GET /act_{id}/{resource}?fields=...
    MetaAPI-->>Tool: {data: [...]}
    Tool-->>UI: Transformed output
Loading

Comments Outside Diff (3)

  1. apps/sim/lib/auth/auth.ts, line 1045 (link)

    Random UUID breaks account identity on every login

    crypto.randomUUID() is called every time getUserInfo runs, meaning the id returned is different on each authentication. better-auth uses this id as the providerAccountId to look up or link an existing OAuth account. Because the value changes on every login, the account lookup will never match a previously stored record — a brand-new account entry will be created in the database for every single login, and the user will appear to lose their connected account each time.

    The id should be the stable provider user ID (profile.id) so repeated logins are correctly matched to the same account:

  2. apps/sim/blocks/blocks/meta_ads.ts, line 786-801 (link)

    operation field passed through to tool params

    ...rest will include the operation field (plus accountId, campaignId, adSetId, level, startDate, endDate, status). None of the individual tool configs (meta_ads_get_account, meta_ads_list_campaigns, etc.) define an operation parameter, so it gets forwarded as an unknown field. Depending on how strict the tool runner's param validation is, this could cause a validation warning or be silently ignored.

    Consider explicitly excluding operation from the forwarded params:

  3. apps/sim/components/icons.tsx, line 4188-4189 (link)

    Missing blank line between icon functions

    Every other icon function in this file is separated by a blank line. The closing } of MetaAdsIcon is immediately followed by export function MailchimpIcon with no blank line, breaking the consistent formatting.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Last reviewed commit: b339b48

…nce queries

- Add 5 tools: get_account, list_campaigns, list_ad_sets, list_ads, get_insights
- Add account and campaign selectors with cascading dropdown support
- Add OAuth config with ads_read scope
- Generate docs
…um date preset to docs

- Pass access token via Authorization header instead of URL query param in getUserInfo, matching all other providers
- Add missing 'maximum' date preset to tool param description and docs
@waleedlatif1 waleedlatif1 force-pushed the waleedlatif1/meta-ads-integration branch from f11f141 to bb8b314 Compare March 14, 2026 13:28
@waleedlatif1
Copy link
Collaborator Author

Addressing Review Comments

1. crypto.randomUUID() in getUserInfoNot a bug

Every OAuth provider in the codebase uses this exact same ${profile.id}-${crypto.randomUUID()} pattern (Google, Google Ads, Slack, LinkedIn, Asana, etc.). This is an established convention in auth.ts, not a defect specific to Meta Ads.

2. Access token in URL query parameter — Fixed (893bc40)

Moved to Authorization: Bearer header in the getUserInfo fetch call, matching every other provider in the codebase.

3. maximum date preset undocumented — Fixed (893bc40)

Added maximum to the tool's param description in get_insights.ts and to the generated docs in meta_ads.mdx.

@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

- Use useId() for MetaAdsIcon SVG gradient IDs to prevent collisions when multiple instances render on the same page
- Filter conversions to only count actual conversion action types (offsite_conversion, onsite_conversion, app_custom_event) instead of summing all actions
@waleedlatif1
Copy link
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Collaborator Author

@greptile review

…rity, account statuses, DELETED filter

- Add stripActPrefix() helper to prevent act_ double-prefix bug when users provide prefixed IDs
- Clarify totalCount descriptions to indicate response-level count (not total in account)
- Show all ad accounts in selector with status badges instead of silently filtering to active only
- Add DELETED to status filter dropdown options

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Collaborator Author

All 4 issues from the Greptile review have been addressed in b339b48:

1. act_ prefix double-prepend bug — Fixed. Added stripActPrefix() helper in types.ts that strips act_ if present before re-prepending. Applied across all 5 tool files and the campaigns API route.

2. Misleading totalCount field — Fixed. Updated descriptions in all 4 list/insights tools to clarify it's the response-level count: "Number of X returned in this response (may be limited by pagination)".

3. Accounts selector silently excludes non-active accounts — Fixed. Now shows all accounts regardless of status. Non-active accounts get a status badge appended to their name (e.g., "My Account (Disabled)", "My Account (Pending Risk Review)").

4. DELETED missing from status dropdown — Fixed. Added DELETED option to the status filter dropdown in the block config.

@waleedlatif1
Copy link
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Collaborator Author

@greptile review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

…th connect

Meta's auth code flow returns a short-lived token (~1-2h) with no refresh token.
Add fb_exchange_token call in account.create.after hook to exchange for a
long-lived token (~60 days), following the same pattern as Salesforce's
post-connect token handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s in conversions

The conversion filter was only matching offsite_conversion.* subtypes but
missing onsite_conversion.* and app_custom_event.* subtypes, which the Meta
API commonly returns at the subtype level.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant