Skip to main content
RapidDev - Software Development Agency
bubble-integrationsBubble API Connector

Google Ads

Connect Bubble to Google Ads by building a token-refresh Backend Workflow that exchanges your OAuth refresh token for a short-lived access token, then configuring the API Connector with Authorization (Private), developer-token (Private), and Content-Type headers. Use GAQL POST queries to pull campaign and keyword metrics into Bubble repeating groups. Expect a 1–5 business day wait for developer token approval before accessing real account data.

What you'll learn

  • How to create a Google Cloud project with the Google Ads API enabled and generate OAuth client credentials
  • How to apply for a Google Ads API developer token and what to expect during the approval process
  • How to obtain an OAuth refresh token for the Google Ads account you want to access
  • How to build a token-refresh Backend Workflow in Bubble that stores the access token with an expiry timestamp
  • How to configure the API Connector with three separate Private authentication headers
  • How to write GAQL queries and parse campaign metrics (including cost_micros conversion) into Bubble repeating groups
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced16 min read2–3 hours (plus 1–5 business days for developer token approval)MarketingLast updated July 2026RapidDev Engineering Team
TL;DR

Connect Bubble to Google Ads by building a token-refresh Backend Workflow that exchanges your OAuth refresh token for a short-lived access token, then configuring the API Connector with Authorization (Private), developer-token (Private), and Content-Type headers. Use GAQL POST queries to pull campaign and keyword metrics into Bubble repeating groups. Expect a 1–5 business day wait for developer token approval before accessing real account data.

Quick facts about this guide
FactValue
ToolGoogle Ads
CategoryMarketing
MethodBubble API Connector
DifficultyAdvanced
Time required2–3 hours (plus 1–5 business days for developer token approval)
Last updatedJuly 2026

Why Google Ads API Needs Three Credential Layers — and How Bubble Handles Them

Google Ads API is the most credential-intensive integration in the marketing category. Unlike SendGrid (one API key) or Mailchimp (one API key in Basic Auth), Google Ads requires you to manage three separate auth layers simultaneously: a Google Cloud OAuth client, a developer token, and per-account user tokens. This complexity is intentional — Google tightly controls programmatic access to ad account data.

Here is what each layer does. The Google Cloud OAuth client (client_id and client_secret) identifies your application to Google's auth servers. The developer token grants your application permission to use the Google Ads API at all — it requires manual approval from Google, which takes 1–5 business days. The refresh token represents the specific Google Ads account you are querying, obtained when the account owner grants your app access via OAuth.

Bubble's approach to handling this is more manual than tools like Retool (which has a native Google Ads connector) or Zapier (which manages OAuth flows for you), but it gives you full control. The key architectural pattern is a Backend Workflow in Bubble that exchanges the refresh token for a new access token before dashboard data loads — because access tokens expire after one hour and Bubble does not auto-refresh them.

Once the auth plumbing is in place, the querying model is clean: you POST GAQL (Google Ads Query Language) queries to a single endpoint and get back structured JSON with campaign names, click counts, impression totals, and spend data. Bubble can display this in repeating groups, update it on a schedule, and let team members filter by date range — all without them needing Google Ads account access.

Integration method

Bubble API Connector

Bubble's API Connector calls the Google Ads API v17 from Bubble's servers. The access token, developer token, and client credentials all live in Private headers — never in the browser. OAuth token refresh is handled by a Bubble Backend Workflow.

Prerequisites

  • A Bubble account on a paid plan — Backend Workflows (required for token refresh) are not available on the free plan
  • A Google account with access to the Google Ads account you want to connect
  • A Google Cloud Console project with the Google Ads API enabled (console.cloud.google.com → APIs & Services → Enable APIs → search 'Google Ads API')
  • An OAuth 2.0 client created in Google Cloud Console (APIs & Services → Credentials → Create Credentials → OAuth client ID → Web application)
  • A developer token from Google Ads API Center (In Google Ads: Tools → API Center → apply for Basic access) — approval takes 1–5 business days; test token available immediately but only queries test accounts
  • An OAuth refresh token for the target Google Ads account — obtained by completing an OAuth flow using your client_id, client_secret, and the accounts.google.com authorization URL with scope https://www.googleapis.com/auth/adwords

Step-by-step guide

1

Set Up Google Cloud, Enable the API, and Apply for a Developer Token

Start in Google Cloud Console (console.cloud.google.com). Create a new project for your Bubble app or use an existing one. Navigate to APIs & Services → Library, search for 'Google Ads API', and click Enable. Next, create your OAuth client. Go to APIs & Services → Credentials → Create Credentials → OAuth client ID. Choose 'Web application' as the application type. Add a redirect URI — for testing, you can use `https://developers.google.com/oauthplayground`. Download the client JSON and note your `client_id` and `client_secret`. Now apply for the developer token. Log in to your Google Ads account (the Manager Account / MCC level if you manage multiple accounts). Navigate to Tools → API Center. Fill out the application form describing your use case — 'Building a performance dashboard in a Bubble web app to display campaign metrics for internal team members.' Submit. Google typically approves Basic access in 1–5 business days. Until approval, you receive a test developer token. This test token can only query test accounts (sandbox accounts) — it will return PERMISSION_DENIED errors on real production ad accounts. Plan your build timeline accordingly and apply for the token before writing any Bubble code.

Pro tip: In your developer token application, be specific about what data you're reading and why. Vague descriptions delay approval. Mention that you're only reading campaign performance data (not making writes) for internal reporting.

Expected result: Google Cloud project has Google Ads API enabled. You have client_id and client_secret from OAuth 2.0 credentials. Developer token application is submitted (or approved). You have a test token for development.

2

Obtain an OAuth Refresh Token for the Google Ads Account

The refresh token represents the specific Google Ads account's authorization for your app to access its data. The easiest way to get this for a Bubble build is using Google's OAuth 2.0 Playground. Go to developers.google.com/oauthplayground. Click the gear icon in the top right → check 'Use your own OAuth credentials' → enter your client_id and client_secret. In the left panel under 'Step 1', find the scope field and paste: `https://www.googleapis.com/auth/adwords`. Click 'Authorize APIs'. Log in with the Google account that has access to the Google Ads account. After authorization, click 'Exchange authorization code for tokens' in Step 2. You'll see a JSON response containing `access_token` and `refresh_token`. Copy and securely store the `refresh_token` — this is a long-lived token that doesn't expire unless revoked. The `access_token` expires in 1 hour. Also note your Google Ads Customer ID (the 10-digit number at the top of your Google Ads account, formatted as XXX-XXX-XXXX). Remove the dashes — the API uses it as a plain 10-digit number. Store this value in a Bubble App Data configuration table (create a 'Config' data type with a text field 'ads_customer_id' and one record with your Customer ID).

oauth-token-exchange.json
1{
2 "POST to exchange auth code": "https://oauth2.googleapis.com/token",
3 "body": {
4 "code": "<authorization_code_from_step_1>",
5 "client_id": "<your_client_id>",
6 "client_secret": "<your_client_secret>",
7 "redirect_uri": "https://developers.google.com/oauthplayground",
8 "grant_type": "authorization_code"
9 },
10 "response_contains": {
11 "access_token": "<expires in 1 hour>",
12 "refresh_token": "<store this — long-lived>",
13 "expires_in": 3599
14 }
15}

Pro tip: Store the refresh_token in Bubble's Data tab as a text field on a Config or Settings data type with a single record. Never hardcode it in the API Connector — that would expose it. The Backend Workflow in the next step reads it from the database.

Expected result: You have a refresh_token stored in your Bubble database (Config table). You have the customer ID without dashes stored as well. The OAuth Playground flow was successful.

3

Build the Token-Refresh Backend Workflow

Because Google OAuth access tokens expire after 1 hour, Bubble needs a Backend Workflow that fetches a fresh access token before each Google Ads API call. This is the most Bubble-specific step in this entire integration. First, create a data structure to store the token. In the Data tab, add a field to your Config data type: 'google_ads_access_token' (text) and 'google_ads_token_expiry' (date). Next, create the refresh API call. In the API Connector, add a new API named 'Google OAuth'. Add a call named 'Refresh Access Token' with method POST and path `/token`. Set the base URL of this API to `https://oauth2.googleapis.com`. Set Body type to JSON and add the body shown in the code block. The `refresh_token`, `client_id`, and `client_secret` fields should be set as dynamic parameters so they can be passed in from the Backend Workflow. Initialize the call by entering test values and confirming you get a 200 response with an `access_token` field. Now go to Settings → Backend workflows (if you don't see this, you are on the free plan — upgrade to Starter). Create a new API Workflow named 'Refresh Google Ads Token'. Add steps: 1. Call the Google OAuth 'Refresh Access Token' action with your stored values from the Config table. 2. 'Make changes to a Thing' → modify Config → set google_ads_access_token = Result of Step 1's access_token, set google_ads_token_expiry = Current date/time + 55 minutes. In your dashboard page workflow (Page is loaded), add a step: 'Only when Config's google_ads_token_expiry < Current date/time + 5 minutes' → Schedule API Workflow 'Refresh Google Ads Token'. RapidDev's team has built this refresh pattern across hundreds of Bubble apps connecting to OAuth-based APIs — if you need a more robust implementation (retry logic, error notifications), our team is available at rapidevelopers.com/contact.

google-oauth-refresh-call.json
1{
2 "method": "POST",
3 "url": "https://oauth2.googleapis.com/token",
4 "body": {
5 "refresh_token": "<dynamic: refresh_token>",
6 "client_id": "<dynamic: client_id>",
7 "client_secret": "<dynamic: client_secret>",
8 "grant_type": "refresh_token"
9 }
10}

Pro tip: Set the token expiry check to 5 minutes before actual expiry (55 minutes after issue) rather than exactly at the 60-minute mark. This prevents race conditions where a call fires just as the token expires.

Expected result: The 'Refresh Google Ads Token' Backend Workflow runs successfully and updates the Config record with a fresh access_token and a new expiry timestamp 55 minutes from now.

4

Configure the Google Ads API Connector with Three Private Headers

Now configure the main Google Ads API Connector. In Plugins → API Connector, add a new API and name it 'Google Ads'. Set the base URL to `https://googleads.googleapis.com`. Add three shared headers — all three must be marked as 'Private': 1. `Authorization`: value `Bearer [dynamic]` — this will be replaced at the call level with the access token from your Config table. For now, enter a placeholder value; you'll map the real token when creating the actual call. 2. `developer-token`: value is your Google Ads API developer token string. Mark as Private. 3. `Content-Type`: value `application/json` — this doesn't need to be Private, but adding it here ensures all calls inherit it. For calls that access a manager account's sub-accounts, add a fourth header: `login-customer-id` with the manager account's customer ID (without dashes). This is only needed if you're managing multiple sub-accounts through one manager account. Now create the main data call. Click 'Add a new call'. Name it 'Get Campaign Metrics'. Method = POST. Path = `/v17/customers/<dynamic: customerId>/googleAds:search`. Set Body type to JSON. The body is a GAQL query string (see code block). Change 'Use as' to 'Data'. Initialize call by entering your customer ID and a simple GAQL query. If your developer token is still in test mode, you must use a test account customer ID — real account IDs return PERMISSION_DENIED until Basic access is approved.

google-ads-campaign-query.json
1{
2 "method": "POST",
3 "url": "https://googleads.googleapis.com/v17/customers/<dynamic: customerId>/googleAds:search",
4 "headers": {
5 "Authorization": "Bearer <private: from Config access_token>",
6 "developer-token": "<private>",
7 "Content-Type": "application/json"
8 },
9 "body": {
10 "query": "SELECT campaign.name, campaign.status, metrics.clicks, metrics.impressions, metrics.cost_micros, metrics.ctr FROM campaign WHERE campaign.status = 'ENABLED' AND segments.date DURING LAST_30_DAYS"
11 }
12}

Pro tip: GAQL resource compatibility: not all field combinations are valid. For example, campaign_budget.amount_micros cannot be queried in the same SELECT as metrics.clicks. If a query fails with a FIELD_ERROR, test it first in Google's API Explorer at developers.google.com/google-ads/api/fields/v17.

Expected result: The Google Ads API shows in your API Connector with three Private headers. The 'Get Campaign Metrics' call initializes successfully and Bubble detects the 'results' array in the response with campaign name, status, and metric fields.

5

Parse Campaign Data and Build the Bubble Dashboard

With the API call initialized, build the reporting interface in Bubble. On your admin page, add a Repeating Group. Set Type to 'External API response' and Data Source to 'Get data from external API → Google Ads - Get Campaign Metrics'. Map the customer ID input to your Config table's 'ads_customer_id' field, and the Authorization header dynamic value to 'Config's google_ads_access_token'. In the Repeating Group cells, add text elements and map them: - Campaign name: `Current cell's campaign > name` - Clicks: `Current cell's metrics > clicks` - Impressions: `Current cell's metrics > impressions` - CTR: `Current cell's metrics > ctr` (multiply by 100 and format with 2 decimal places to show as percentage) - Spend: `Current cell's metrics > cost_micros` — divide by 1,000,000 to get the dollar amount. In Bubble, create a formula: `Current cell's metrics cost_micros / 1000000`. Format as currency. The cost_micros conversion is critical — the raw value looks like 4523000, which is actually $4.52. Displaying the raw number makes spend look 1,000 times higher than it is. Always divide by 1,000,000 before display. Add a page-load workflow: on Page is loaded → Only when Config's token expiry < Now + 5 minutes → Schedule Backend Workflow 'Refresh Google Ads Token'. This ensures fresh tokens before the repeating group loads data.

google-ads-metric-conversions.json
1{
2 "bubble_expression_for_cost": "Current cell's metrics cost_micros / 1000000",
3 "bubble_expression_for_ctr": "Current cell's metrics ctr * 100",
4 "example_raw_cost_micros": 4523000,
5 "example_actual_cost_dollars": 4.52,
6 "example_raw_ctr": 0.0234,
7 "example_display_ctr_percent": "2.34%"
8}

Pro tip: Add a date range input to your dashboard and pass it to the GAQL query as a segments.date BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD' clause. This lets team members filter by custom date ranges without loading all historical data.

Expected result: The dashboard repeating group displays campaign names, clicks, impressions, formatted CTR percentages, and correct dollar-denominated spend values. Data refreshes on page load after a token refresh check.

Common use cases

Campaign Performance Dashboard

Build a Bubble admin page that shows all active campaigns with their clicks, impressions, CTR, and spend — refreshed on page load using the GAQL POST endpoint. Team members can see ad performance without needing Google Ads account access, and you can add Bubble logic to flag underperforming campaigns automatically.

Bubble Prompt

On Admin Dashboard page load, first run the 'Refresh Google Ads Token' Backend Workflow. Then get data from external API → Google Ads 'Get Campaign Metrics' call. Bind results to a Repeating Group showing Campaign Name, Clicks, Impressions, and Cost (Current cell's cost_micros / 1000000 formatted as currency).

Copy this prompt to try it in Bubble

Keyword Performance Tracker

Query keyword-level metrics to see which search terms are driving conversions. Use a GAQL query filtered to specific campaigns, displaying keyword text, quality score, average CPC, and conversion count in a sortable Bubble repeating group. Add a date range picker to let users filter by time period.

Bubble Prompt

Create an API Connector call 'Get Keyword Metrics' with GAQL: SELECT ad_group_criterion.keyword.text, metrics.clicks, metrics.average_cpc, metrics.conversions FROM ad_group_criterion WHERE ad_group_criterion.type = 'KEYWORD' AND campaign.status = 'ENABLED'. Display results in a repeating group sortable by Clicks descending.

Copy this prompt to try it in Bubble

Budget Utilization Monitor

Pull daily spend data and compare it against campaign budgets to surface over-spending or underspending campaigns. Use GAQL to query campaign_budget.amount_micros alongside metrics.cost_micros, calculate the spend percentage in Bubble expressions, and highlight campaigns where spend exceeds 90% of budget with a conditional color change.

Bubble Prompt

Create a GAQL call: SELECT campaign.name, campaign_budget.amount_micros, metrics.cost_micros FROM campaign WHERE segments.date DURING TODAY. In the repeating group, add a conditional: When (Current cell's metrics_cost_micros / Current cell's campaign_budget_amount_micros) > 0.9 → change background color to orange.

Copy this prompt to try it in Bubble

Troubleshooting

PERMISSION_DENIED error when querying a real Google Ads account

Cause: The developer token is still in test mode. Test tokens can only query test accounts created in Google Ads API Center, not real production ad accounts. Basic access approval from Google is required for real account data.

Solution: If you haven't applied for Basic access yet, do so at Tools → API Center in your Google Ads account. While waiting for approval (1–5 business days), develop and test against a Google Ads test account (API Center → Test accounts). Once Basic access is approved, your developer token works against real accounts.

401 Unauthorized errors on every Google Ads API call even though credentials seem correct

Cause: The access token has expired (tokens last only 1 hour) or the token-refresh Backend Workflow has not run before the API call fired.

Solution: Verify that the token-refresh Backend Workflow runs before data loads. Check the Logs tab → Workflow logs to see if the refresh workflow ran and what response it received. If the refresh token itself is invalid, you need to go through the OAuth flow again to get a new refresh token.

GAQL query returns a FIELD_ERROR about incompatible resources

Cause: GAQL has resource compatibility rules — not all field combinations can appear in the same SELECT statement. For example, campaign_budget fields and metrics fields cannot always be combined in the same query.

Solution: Test your GAQL query in Google's API Field Explorer at developers.google.com/google-ads/api/fields/v17 before using it in Bubble. The Field Explorer shows which fields are compatible with each other and will highlight incompatible combinations. Split complex queries into two separate API calls if needed.

Campaign spend values appear to be about 1,000,000 times higher than expected

Cause: Google Ads API returns monetary values in micros — millionths of the account's currency unit. A spend of $4.52 is returned as 4520000. Displaying the raw cost_micros value without dividing by 1,000,000 makes $4.52 look like $4,520,000.

Solution: In every Bubble text element displaying monetary values from the Google Ads API, divide the field value by 1000000. In Bubble's formula editor: 'Current cell's metrics cost_micros / 1000000'. Format the result as currency with 2 decimal places.

Backend Workflows option is missing from Settings menu

Cause: Backend Workflows are only available on paid Bubble plans. The free plan does not include this feature.

Solution: Upgrade to Bubble's Starter plan ($32/month) or higher to access Backend Workflows. The token-refresh pattern requires Backend Workflows — there is no workaround on the free plan for automated token refresh. Without Backend Workflows, you could trigger a page-level workflow to call the refresh endpoint, but this would only work when a user is actively on the page.

Best practices

  • Store the OAuth refresh token in Bubble's database (not hardcoded in the API Connector) so it can be updated without modifying your API configuration if the token is ever revoked.
  • Always divide cost_micros by 1,000,000 before displaying monetary values — displaying raw micros makes spend look 1 million times higher than it is.
  • Validate GAQL queries in Google's Field Explorer before adding them to Bubble — incompatible field combinations cause FIELD_ERROR responses that can be hard to diagnose.
  • Apply for the developer token before starting to build — the 1–5 business day approval window is the longest lead time in this integration and cannot be sped up.
  • Set up token expiry monitoring with a 5-minute buffer: refresh when token expiry is within 5 minutes of Now, not exactly at 60 minutes, to avoid race conditions.
  • Use GAQL's date range filtering (segments.date DURING LAST_30_DAYS or BETWEEN) to limit query response sizes — querying all historical data on every page load is expensive in WU and slow.
  • Mark all three credential headers (Authorization, developer-token, client credentials) as Private in Bubble's API Connector — none of these should ever reach the browser.
  • Check the Logs tab → Workflow logs after the first successful run to verify WU usage. Token refresh workflows add WU cost each time they run — schedule refreshes efficiently.

Alternatives

Frequently asked questions

How long does it take to get approved for the Google Ads API developer token?

Basic access approval typically takes 1–5 business days after submitting your application in Google Ads → Tools → API Center. During this period, you receive a test developer token that works against test accounts only. Real production ad account data requires Basic (or higher) access. Plan your build timeline to account for this wait.

Why do I need three different credentials for Google Ads, when most APIs only need one key?

Google Ads API uses three layers for security: the OAuth client (client_id + client_secret) identifies your application, the developer token grants API access at the developer level, and the refresh token/access token represents the specific ad account owner's authorization. This multi-layer model lets Google revoke access at any layer independently and ensures each level of access is explicitly granted.

Does Bubble's free plan support Google Ads integration?

Partially. The API Connector works on all plans, so you can make GAQL calls from page-level workflows. However, the token-refresh Backend Workflow (essential for production use) requires a paid Bubble plan. On the free plan, tokens expire after 1 hour with no automated way to refresh them server-side. The Starter plan ($32/mo) enables Backend Workflows.

Can I use this integration to create or modify Google Ads campaigns, not just read them?

Yes — the Google Ads API supports campaign creation, budget changes, ad group modifications, and keyword management. However, write operations require Standard or higher developer token access (beyond Basic), and the approval process is more rigorous. For a Bubble dashboard use case (read-only reporting), Basic access is sufficient. Discuss your use case with Google when applying.

What does cost_micros mean and why do all the spend values look wrong?

cost_micros is Google's way of representing monetary values without floating-point precision errors. The value is in millionths of your account's currency unit — so $4.52 is returned as 4520000. Always divide by 1,000,000 before displaying in Bubble. Build this conversion into your repeating group formula from the start.

My GAQL query works in the API Explorer but fails in Bubble with a 400 error

The most common cause is that the GAQL query string needs to be sent as a JSON property, not as a raw string. Confirm that your API Connector call body is set to 'JSON body' type and the query is the value of a 'query' key: {"query": "SELECT..."}. Also ensure special characters like single quotes in GAQL filters are properly escaped in the JSON body.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire Bubble integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.