Google Ads Adapter

The Google Ads adapter provides access to advertising data using OAuth2 authentication and Google Ads Query Language (GAQL).

Installation

npm install @openetl/google-ads

Features

  • OAuth2 Authentication - Secure authentication with automatic token refresh
  • GAQL Support - Query data using Google Ads Query Language
  • Multiple Resources - Access campaigns, ad groups, ads, keywords, and more
  • Flexible Queries - Custom GAQL for advanced data retrieval

Authentication

The Google Ads adapter uses oauth2 authentication with additional credentials:

const vault = {
  'google-ads-auth': {
    id: 'google-ads-auth',
    type: 'oauth2',
    credentials: {
      client_id: process.env.GOOGLE_CLIENT_ID,
      client_secret: process.env.GOOGLE_CLIENT_SECRET,
      access_token: process.env.GOOGLE_ACCESS_TOKEN,
      refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
      developer_token: process.env.GOOGLE_ADS_DEVELOPER_TOKEN,
    },
  },
};

Note: You need a Google Ads developer token. Apply at Google Ads API Center.

Quick Start

import { Orchestrator } from 'openetl';
import { googleAds } from '@openetl/google-ads';

const vault = {
  'google-ads-auth': {
    id: 'google-ads-auth',
    type: 'oauth2',
    credentials: {
      client_id: process.env.GOOGLE_CLIENT_ID,
      client_secret: process.env.GOOGLE_CLIENT_SECRET,
      access_token: process.env.GOOGLE_ACCESS_TOKEN,
      refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
      developer_token: process.env.GOOGLE_ADS_DEVELOPER_TOKEN,
    },
  },
};

const etl = Orchestrator(vault, { googleAds });

const pipeline = {
  id: 'export-campaigns',
  source: {
    id: 'campaigns-source',
    adapter_id: 'google-ads',
    endpoint_id: 'campaigns',
    credential_id: 'google-ads-auth',
    config: {
      customer_id: '1234567890',  // Your Google Ads customer ID (no dashes)
    },
    fields: ['campaign.id', 'campaign.name', 'campaign.status', 'metrics.impressions', 'metrics.clicks'],
    pagination: { type: 'offset', itemsPerPage: 1000 },
  },
};

const result = await etl.runPipeline(pipeline);
console.log(`Exported ${result.data.length} campaigns`);

Endpoints

campaigns

Retrieve campaign data.

Config Property Required Description
customer_id Yes Google Ads customer ID (without dashes)
const connector = {
  adapter_id: 'google-ads',
  endpoint_id: 'campaigns',
  credential_id: 'google-ads-auth',
  config: {
    customer_id: '1234567890',
  },
  fields: [
    'campaign.id',
    'campaign.name',
    'campaign.status',
    'campaign.advertising_channel_type',
    'metrics.impressions',
    'metrics.clicks',
    'metrics.cost_micros',
  ],
};

ad_groups

Retrieve ad group data.

const connector = {
  adapter_id: 'google-ads',
  endpoint_id: 'ad_groups',
  credential_id: 'google-ads-auth',
  config: {
    customer_id: '1234567890',
  },
  fields: [
    'ad_group.id',
    'ad_group.name',
    'ad_group.status',
    'campaign.id',
    'metrics.impressions',
    'metrics.clicks',
  ],
};

ads

Retrieve ad data.

const connector = {
  adapter_id: 'google-ads',
  endpoint_id: 'ads',
  credential_id: 'google-ads-auth',
  config: {
    customer_id: '1234567890',
  },
  fields: [
    'ad_group_ad.ad.id',
    'ad_group_ad.ad.type',
    'ad_group_ad.status',
    'ad_group.id',
    'campaign.id',
    'metrics.impressions',
  ],
};

keywords

Retrieve keyword data.

const connector = {
  adapter_id: 'google-ads',
  endpoint_id: 'keywords',
  credential_id: 'google-ads-auth',
  config: {
    customer_id: '1234567890',
  },
  fields: [
    'ad_group_criterion.keyword.text',
    'ad_group_criterion.keyword.match_type',
    'ad_group_criterion.status',
    'metrics.impressions',
    'metrics.clicks',
  ],
};

custom_query

Execute a custom GAQL query.

const connector = {
  adapter_id: 'google-ads',
  endpoint_id: 'custom_query',
  credential_id: 'google-ads-auth',
  config: {
    customer_id: '1234567890',
    custom_query: `
      SELECT
        campaign.id,
        campaign.name,
        segments.date,
        metrics.impressions,
        metrics.clicks,
        metrics.cost_micros
      FROM campaign
      WHERE segments.date DURING LAST_30_DAYS
      ORDER BY metrics.impressions DESC
      LIMIT 100
    `,
  },
  fields: [],
};

Common Fields

Metrics

Field Description
metrics.impressions Number of impressions
metrics.clicks Number of clicks
metrics.cost_micros Cost in micros (divide by 1,000,000 for actual cost)
metrics.ctr Click-through rate
metrics.average_cpc Average cost per click
metrics.conversions Number of conversions

Segments

Field Description
segments.date Date segment
segments.device Device type
segments.ad_network_type Network type

Filtering

Use GAQL WHERE clause in custom queries:

config: {
  customer_id: '1234567890',
  custom_query: `
    SELECT campaign.id, campaign.name, metrics.clicks
    FROM campaign
    WHERE campaign.status = 'ENABLED'
      AND metrics.clicks > 100
  `,
}

Complete Example

import { Orchestrator, Pipeline } from 'openetl';
import { googleAds } from '@openetl/google-ads';
import { postgresql } from '@openetl/postgresql';

const vault = {
  'google-ads-auth': {
    id: 'google-ads-auth',
    type: 'oauth2',
    credentials: {
      client_id: process.env.GOOGLE_CLIENT_ID,
      client_secret: process.env.GOOGLE_CLIENT_SECRET,
      access_token: process.env.GOOGLE_ACCESS_TOKEN,
      refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
      developer_token: process.env.GOOGLE_ADS_DEVELOPER_TOKEN,
    },
  },
  'warehouse': {
    id: 'warehouse',
    type: 'basic',
    credentials: {
      host: 'localhost',
      database: 'analytics',
      username: 'etl',
      password: process.env.DB_PASSWORD,
    },
  },
};

const etl = Orchestrator(vault, { googleAds, postgresql });

const pipeline: Pipeline = {
  id: 'sync-campaign-metrics',
  source: {
    id: 'campaigns-source',
    adapter_id: 'google-ads',
    endpoint_id: 'custom_query',
    credential_id: 'google-ads-auth',
    config: {
      customer_id: process.env.GOOGLE_ADS_CUSTOMER_ID,
      custom_query: `
        SELECT
          campaign.id,
          campaign.name,
          segments.date,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros
        FROM campaign
        WHERE segments.date DURING LAST_7_DAYS
      `,
    },
    fields: [],
  },
  target: {
    id: 'metrics-target',
    adapter_id: 'postgresql',
    endpoint_id: 'table_insert',
    credential_id: 'warehouse',
    config: { schema: 'marketing', table: 'google_ads_campaigns' },
    fields: ['campaign_id', 'campaign_name', 'date', 'impressions', 'clicks', 'cost'],
  },
  error_handling: {
    max_retries: 3,
    retry_interval: 2000,
    fail_on_error: true,
  },
};

const result = await etl.runPipeline(pipeline);
console.log(`Synced ${result.data.length} campaign records`);

Transformations

Convert cost_micros to actual cost:

// cost_micros is in millionths of the account currency
// To convert: cost_micros / 1,000,000 = actual cost

source: {
  // ...
  transform: [
    {
      type: 'toNumber',
      options: { field: 'cost_micros' },
    },
  ],
},
// Then divide by 1000000 in post-processing or target

Resources

Related