npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

google-ads-api

v17.1.0-rest-beta

Published

Google Ads API Client Library for Node.js

Downloads

109,020

Readme

Features

  • Simple and easy to use API
  • Uses gRPC and Protocol Buffers internally (recommended by Google)
  • Typescript definitions for all resources, enums, errors and services
  • Provides all API functionality

Installation

npm install google-ads-api

Usage

Create a client

import { GoogleAdsApi } from "google-ads-api";

const client = new GoogleAdsApi({
  client_id: "<CLIENT-ID>",
  client_secret: "<CLIENT-SECRET>",
  developer_token: "<DEVELOPER-TOKEN>",
});

Create a customer instance

const customer = client.Customer({
  customer_id: "1234567890",
  refresh_token: "<REFRESH-TOKEN>",
});

// Also supports login & linked customer ids
const customer = client.Customer({
  customer_id: "1234567890",
  login_customer_id: "<LOGIN-CUSTOMER-ID>",
  linked_customer_id: "<LINKED-CUSTOMER-ID>",
  refresh_token: "<REFRESH-TOKEN>",
});

List accessible customers

This is a special client method for listing the accessible customers for a given refresh token, and is equivalent to CustomerService.listAccessibleCustomers. It returns the resource names of available customer accounts.

const client = new GoogleAdsApi({
  client_id: "<CLIENT-ID>",
  client_secret: "<CLIENT-SECRET>",
  developer_token: "<DEVELOPER-TOKEN>",
});

const refreshToken = "<REFRESH-TOKEN>";

const customers = await client.listAccessibleCustomers(refreshToken);

Retrieve Campaigns with metrics

import { enums } from "google-ads-api";

const campaigns = await customer.report({
  entity: "campaign",
  attributes: [
    "campaign.id",
    "campaign.name",
    "campaign.bidding_strategy_type",
    "campaign_budget.amount_micros",
  ],
  metrics: [
    "metrics.cost_micros",
    "metrics.clicks",
    "metrics.impressions",
    "metrics.all_conversions",
  ],
  constraints: {
    "campaign.status": enums.CampaignStatus.ENABLED,
  },
  limit: 20,
});

Retrieve Campaigns using GAQL

If you prefer to use the Google Ads Query Language (GAQL) the query method is available. Internally report uses this function. More GAQL examples can be found here.

const campaigns = await customer.query(`
  SELECT 
    campaign.id, 
    campaign.name,
    campaign.bidding_strategy_type,
    campaign_budget.amount_micros,
    metrics.cost_micros,
    metrics.clicks,
    metrics.impressions,
    metrics.all_conversions
  FROM 
    campaign
  WHERE
    campaign.status = "ENABLED"
  LIMIT 20
`);

Retrieve Ad Group metrics by date

import { enums } from "google-ads-api";

const campaigns = await customer.report({
  entity: "ad_group",
  metrics: [
    "metrics.cost_micros",
    "metrics.clicks",
    "metrics.impressions",
    "metrics.all_conversions",
  ],
  segments: ["segments.date"],
  from_date: "2021-01-01",
  to_date: "2021-02-01",
});

Retrieve Keywords with an async iterator

Calls searchStream internally but returns the rows one by one in an async iterator.

import { enums } from "google-ads-api";

const stream = customer.reportStream({
  entity: "ad_group_criterion",
  attributes: [
    "ad_group_criterion.keyword.text", 
    "ad_group_criterion.status",
  ],
  constraints: {
    "ad_group_criterion.type": enums.CriterionType.KEYWORD,
  },
});

// Rows are streamed in one by one
for await (const row of stream) {
    // Break the loop to stop streaming
    if (someLogic) {
        break
    }
}

Or use a GAQL query.

const stream = customer.queryStream(`
  SELECT
    ad_group_criterion.keyword.text,
    ad_group_criterion.status
  FROM
    ad_group_criterion
  WHERE
    ad_group_criterion.type = "KEYWORD"
`);

// Rows are streamed in one by one
for await (const row of stream) {
    // Break the loop to stop streaming
    if (someLogic) {
        break
    }
}

Retrieve Keywords with a raw stream

Returns the raw stream so that events can be handled manually. For more information on Google's stream methods please consult their docs.

import { enums, parse } from "google-ads-api";

const reportOptions = {
  entity: "ad_group_criterion",
  attributes: [
    "ad_group_criterion.keyword.text", 
    "ad_group_criterion.status",
  ],
  constraints: {
    "ad_group_criterion.type": enums.CriterionType.KEYWORD,
  },
};

const stream = customer.reportStreamRaw(reportOptions);

// Rows are streamed in 10,000 row chunks
stream.on("data", (chunk) => {
  const parsedResults = parse({
    results: chunk.results,
    reportOptions,
  });
});

stream.on("error", (error) => {
  throw new Error(error);
});

stream.on("end", () => {
  console.log("stream has finished");
});

Create an expanded text ad

import { resources, enums, ResourceNames } from "google-ads-api";

const ad = new resources.Ad({
  expanded_text_ad: {
    headline_part1: "Cruise to Mars",
    headline_part2: "Best Space Cruise Line",
    description: "Buy your tickets now!",
    path1: "cruises",
    path2: "mars",
  },
  final_urls: ["https://example.com"],
  type: enums.AdType.EXPANDED_TEXT_AD,
});

const adGroup = ResourceNames.adGroup(cus.credentials.customerId, "123");

const adGroupAd = new resources.AdGroupAd({
  status: enums.AdGroupAdStatus.PAUSED,
  ad_group,
  ad,
});

// Returns an array of newly created resource names if successful
const { results } = await cus.adGroupAds.create([adGroupAd]);

Create a Campaign & Budget atomically

import {
  resources,
  enums,
  toMicros,
  ResourceNames,
  MutateOperation,
} from "google-ads-api";

// Create a resource name with a temporary resource id (-1)
const budgetResourceName = ResourceNames.campaignBudget(
  customer.credentials.customer_id,
  "-1"
);

const operations: MutateOperation<
  resources.ICampaignBudget | resources.ICampaign
>[] = [
  {
    entity: "campaign_budget",
    operation: "create",
    resource: {
      // Create a budget with the temporary resource id
      resource_name: budgetResourceName,
      name: "Planet Express Budget",
      delivery_method: enums.BudgetDeliveryMethod.STANDARD,
      amount_micros: toMicros(500),
    },
  },
  {
    entity: "campaign",
    operation: "create",
    resource: {
      name: "Planet Express",
      advertising_channel_type: enums.AdvertisingChannelType.SEARCH,
      status: enums.CampaignStatus.PAUSED,
      manual_cpc: {
        enhanced_cpc_enabled: true,
      },
      // Use the temporary resource id which will be created in the previous operation
      campaign_budget: budgetResourceName,
      network_settings: {
        target_google_search: true,
        target_search_network: true,
      },
    },
  },
];

const result = await customer.mutateResources(operations);

Add Policy Exemption Rules

import {
  resources,
  enums,
  toMicros,
  ResourceNames,
  MutateOperation,
} from "google-ads-api";

// Ad Group to which you want to add the keyword
const adGroupResourceName = "customers/123/adGroups/456";

const keyword = "24 hour locksmith harlem";

const operations: MutateOperation<
  resources.IAdGroupCriterion & {
    exempt_policy_violation_keys?: google.ads.googleads.v17.common.IPolicyViolationKey[];
  }
>[] = [
  {
    entity: "ad_group_criterion",
    operation: "create",
    resource: {
      // Keyword with policy violation exemptions
      ad_group: adGroupResourceName,
      keyword: {
        text: "24 hour locksmith harlem",
        match_type: enums.KeywordMatchType.PHRASE,
      },
      status: enums.AdGroupStatus.ENABLED,
    },
    exempt_policy_violation_keys: [
      {
        policy_name: "LOCAL_SERVICES",
        violating_text: keyword,
      },
    ],
  },
];

const result = await customer.mutateResources(operations);

Uploading Click Conversions

const clickConversion = {
  gclid: "<GOOGLE-CLICK-ID>",
  conversion_action: "customers/1234567890/conversionActions/111222333",
  conversion_date_time: "2022-01-11 00:00:00",
  conversion_value: 123,
  currency_code: "GBP",
};

const request = new services.UploadClickConversionsRequest({
  customer_id: customerId,
  conversions: [clickConversion],
});

await customer.conversionUploads.uploadClickConversions(request);

Summary Row

If a summary row is requested in the report method, it will be included as the first row of the results.

const [summaryRow, ...response] = await customer.report({
  entity: "campaign",
  metrics: ["metrics.clicks", "metrics.all_conversions"],
  summary_row_setting: enums.SummaryRowSetting.SUMMARY_ROW_WITH_RESULTS,
});

If a summery row is requested in the reportStream method, it will be included as the final iterated row of the results.

const stream = customer.reportStream({
  entity: "campaign",
  metrics: ["metrics.clicks", "metrics.all_conversions"],
  summary_row_setting: enums.SummaryRowSetting.SUMMARY_ROW_WITH_RESULTS,
});

const accumulator = [];
for await (const row of stream) {
  accumulator.push(row);
}

const summaryRow = accumulator.slice(-1)[0];

Total Results Count

The reportCount method acts like report but returns the total number of rows that the query would have returned (ignoring the limit). This replaces the return_total_results_count report option. Hooks are not called in this function to avoid cacheing conflicts.

const totalRows = await customer.reportCount({
  entity: "search_term_view",
  attributes: ["search_term_view.resource_name"],
});

Report Results Order

There are 2 methods of sorting the results of report. The prefered method is to use the order key, which should be an array of objects with a field key and an optional sort_order key. The order of the items in the array will map to the order of the sorting keys in the GAQL query, and hence the priorities of the sorts.

const response = await customer.report({
  entity: "campaign",
  attributes: ["campaign.id"],
  metrics: ["metrics.clicks"],
  segments: ["segments.date"],
  order: [
    { field: "metrics.clicks", sort_order: "DESC" },
    { field: "segments.date", sort_order: "ASC" },
    { field: "campaign.id" }, // default sort_order is descending
  ],
});

The other method is to use the order_by and sort_order keys, however this will be deprecated in a future version of the API.

const response = await customer.report({
  entity: "campaign",
  attributes: ["campaign.id"],
  metrics: ["metrics.clicks"],
  segments: ["segments.date"],
  order_by: "metrics.clicks",
  sort_order: "DESC",
});

Resource Names

The library provides a set of helper methods under the ResourceNames export. These are used for compiling resource names from ids. Arguments can be of the type string, number, or a mix of both. If you have a client.Customer instance available, you can get the customer id with customer.credentials.customerId.

import { ResourceNames } from "google-ads-api";

const customerId = "1234567890";
const campaignId = "3218318373";

ResourceNames.campaign(customerId, campaignId);
// "customers/1234567890/campaigns/3218318373"

ResourceNames.adGroup(123, 123);
// "customers/123/adGroups/123"

ResourceNames.adGroupAd("1", "2", "3");
// "customers/1/adGroupAds/2~3"

const amsterdamLocationId = 1010543;
ResourceNames.geoTargetConstant(amsterdamLocationId);
// "geoTargetConstants/1010543"

ResourceNames.accountBudget(customer.credentials.customer_id, 123);
// "customers/1234567890/accountBudgets/123"

Hooks

The library provides hooks that can be executed before, after or on error of a query, stream or a mutation.

Query/stream hooks:

  • onQueryStart
  • onQueryError
  • onQueryEnd
  • onStreamStart
  • onStreamError

These hooks have access to the customerCredentials argument, containing the customer_id, login_customer_id and linked_customer_id.

These hooks also have access to the query argument, containing the GAQL query as a string.

These hooks also have access the the reportOptions argument. This will be undefined when using the query method.

Mutation hooks:

  • onMutationStart
  • onMutationError
  • onMutationEnd

These hooks have access to the customerCredentials argument, containing the customer_id, login_customer_id and linked_customer_id.

These hooks also have access to the method argument, containing the mutation method as a string.

Service hooks:

  • onServiceStart
  • onServiceError
  • onServiceEnd

These hooks have access to the customerCredentials argument, containing the customer_id, login_customer_id and linked_customer_id.

These hooks also have access to the method argument, containing the mutation method as a string.

Pre-request hooks:

  • onQueryStart - query and report
  • onStreamStart - reportStream and reportStreamRaw
  • onMutationStart
  • onServiceStart

These hooks are executed before a query/stream/mutation/service.

These hooks have access to the cancel method, which can cancel the action before it is done. The query and mutation pre-request hooks allow an optional argument to be passed into the cancel method, which will be used as an alternative return value for the query/mutation. A good use case for this method would be to cancel with a cached result.

These hooks also have access to the editOptions method which allows the request options to be changed before the request is sent. Keys included in the object passed to editOptions will be changed, and the rest will be maintained. A good use case for this method would be to set validateOnly as true when not in production.

import { OnQueryStart } from "google-ads-api";

const onQueryStart: OnQueryStart = async ({ cancel, editOptions }) => {
  if (env.mode === "test") {
    cancel([]); // Cancels the request. The supplied argument will become the alternative return value in query and mutation hooks
  }
  if (env.mode === "dev") {
    editOptions({ validate_only: true }); // Edits the request options
  }
};

const customer = client.Customer(
  {
    clientOptions,
    customerOptions,
  },
  { onQueryStart }
);

On error hooks:

  • onQueryError - query and report
  • onStreamError - reportStream (but not reportStreamRaw)
  • onMutationError
  • onServiceStart

These hooks are executed when a query/stream/mutation/service throws an error. If the error is a Google Ads failure then it will be converted to a GoogleAdsFailure first. The error can be accessed in these hooks with the error argument. Note that the onStreamError hook will not work with the reportStreamRaw method to avoid blocking the thread.

import { OnQueryError } from "google-ads-api";

const onQueryError: OnQueryError = async ({ error }) => {
  console.log(error.message); // An Error or a GoogleAdsFailure
};

const customer = client.Customer(
  {
    clientOptions,
    customerOptions,
  },
  { onQueryError }
);

Post-request hooks:

  • onQueryEnd - query and report
  • onMutationEnd
  • onServiceEnd

These hooks are executed after a query, mutation or service. This library does not contain an onStreamEnd hook to avoid accumulating the results of streams, and also so that we don't block the thread by waiting for the end event to be emitted.

import { OnQueryEnd } from "google-ads-api";

const onQueryEnd: OnQueryEnd = async ({ response, resolve }) => {
  const [first] = response; // The results of the query/mutation
  resolve([first]); // The supplied argument will become the alternative return value
};

const customer = client.Customer(
  {
    clientOptions,
    customerOptions,
  },
  { onQueryEnd }
);

Error handling

All errors, apart from GRPC specific cases (such as a connection problem or timeout, see more here), are instances of a GoogleAdsFailure.

You can find a list of all error types for a specific version in the official documentation, as well as more information about handling errors here.

import { errors } from "google-ads-api";

try {
  await customer.query(`
      SELECT campaign.bad_field FROM campaign
    `);
} catch (err) {
  if (err instanceof errors.GoogleAdsFailure) {
    console.log(err.errors); // Array of errors.GoogleAdsError instances

    // Get the first one and explicitly check for a certain error type
    const [firstError] = err.errors;
    if (
      firstError.error_code.query_error ===
      errors.QueryErrorEnum.QueryError.UNRECOGNIZED_FIELD
    ) {
      console.log(
        `Error: using invalid field "${firstError.trigger}" in query`
      );
    }
  }
}

Updating this library to the latest Google Ads API version

  1. Update google-ads-node & publish to NPM. Check that package for instructions.
  2. Update google-ads-node & google-gax in package.json. google-gax must be the same version as google-ads-node, Otherwise you are likely to get a TypeError: Channel credentials must be a ChannelCredentials object error.
  3. Update the version in version.ts.
  4. Update the versions in src/protos/index.ts.
  5. Run yarn compile to update the generated files. The tsc command might fail -- if so, temporarily add @ts-nocheck to the top of problematic files so that the compile script can continue. After the compile script has run, those @ts-nocheck lines should no longer be required. The compile step depends on some environment variables being set (see scripts/fields.ts), so make sure to set those.
  6. Prettify the generated files so the the diffs are easier to read.
  7. Check that the diffs are expected and that the generated files are correct. New Gads versions will often add new services, fields, and enums.
  8. Test with yarn test.
  9. Test by linking to a real project and making some real requests.
  10. Once confident, bump the major version & publish to NPM.