A major release: Google Ads API v26.1, an ES module-only package, and fixes that change the shape of several methods. Every breaking change below says what to change in your code.
- Upgraded google-ads-api version to v26.1. Refer to Google Ads release notes here for changes.
- Upgraded google-ads-node dependency to v26.1.0
- Upgraded google-gax dependency to v6.16.0 and google-auth-library to v11.3.3
- Upgraded stream-json to v3 (CVE-2026-71429, #554); the package now declares stream-chain as a dependency
-
ES modules only. The package no longer ships a CommonJS build.
import { GoogleAdsApi } from "google-ads-api"works as before. CommonJS code can still callrequire("google-ads-api"),require("google-ads-api/enums")andrequire("google-ads-api/fields"): Node.js loads the ES module through its built-inrequire(esm)support and returns the same module instance asimport. -
Requires Node.js 22.12 or newer, the first 22.x release where
require(esm)works without a flag. -
TypeScript CommonJS projects need
module: "nodenext"(TypeScript 5.8 or newer) ormodule: "node20"(TypeScript 5.9 or newer) to import the package;node16,node18, andnodenextbefore TypeScript 5.8 reportTS1479because the package ships only ES modules.moduleResolution: "node10"resolves the root import but, as in 25.x, not the/enumsand/fieldssubpaths. -
Jest in CommonJS mode cannot load the package as published. Jest uses its own module loader instead of Node.js's
require(esm), so Jest 29 fails withSyntaxError: Unexpected token 'export'and Jest 30 withMust use import to load ES Module. Either run Jest in ESM mode (NODE_OPTIONS=--experimental-vm-modules), or transform google-ads-api and its ES module-only dependenciesstream-jsonandstream-chain, for example with ts-jest:transform: { "^.+\\.tsx?$": "ts-jest", "/node_modules/(google-ads-api|stream-json|stream-chain)/.+\\.js$": [ "ts-jest", { tsconfig: { allowJs: true, esModuleInterop: true } }, ], }, transformIgnorePatterns: ["/node_modules/(?!(google-ads-api|stream-json|stream-chain)/)"],
-
Single-operation services take one resource. The API accepts exactly one operation per request for
accountBudgetProposals,accountLinks,batchJobs,billingSetups,customerBudgetNetworkSettings,customerClientLinks,customers,customerSkAdNetworkConversionValueSchemas,customerUserAccessesandcustomerUserAccessInvitations. Theircreate,updateandremovehelpers now take one resource (or one resource name forremove) instead of an array. These helpers never worked with an array (see Fixes), so call them once per resource:// 25.x await customer.customers.update([{ resource_name, tracking_url_template }]); // 26.x await customer.customers.update({ resource_name, tracking_url_template });
-
Mutation hooks receive
mutateOptions.onMutationStart,onMutationEndandonMutationErrorget the effective mutate options (validate_only,partial_failure,response_content_type) on every path, including changes made witheditOptions. FormutateResourcesthe hooks previously received only the operations array, so a hook could not tell avalidate_onlydry run from a real mutation. Read the options frommutateOptionsinstead ofmutationormutations:// 25.x: always undefined for mutateResources const dryRun = options.isServiceCall ? options.mutation.validate_only : options.mutations.validate_only; // 26.x const dryRun = options.mutateOptions.validate_only;
BaseMutationHookArgsrequiresmutateOptions, so code that builds hook arguments itself (for example in tests) must include it.mutateResourcesno longer writeseditOptionschanges into the options object the caller passed in. -
Helpers for services without mutate options (
batchJobs,billingSetups,customerUserAccesses,customerUserAccessInvitations) type theiroptionsparameter asRecord<string, never>instead ofnever. -
Update helpers build the update mask from the object you pass.
customer.campaigns.update([{ resource_name, url_custom_parameters: [] }])now clearsurl_custom_parameters; previously the mask dropped the empty list and the update changed nothing. As withmutateResources, every key of a plain object goes into the mask, so the API now rejects a key that names no field of the resource instead of silently ignoring it. Message instances (new resources.Campaign({...})) behave as before: their unset repeated fields stay out of the mask, so clear a list by passing a plain object. Keys set toundefinedstay out of the mask, so an unset variable leaves its field alone;nullputs the field in the mask and clears it. The 25.x update helpers ignored both, whilemutateResourcesput both in the mask, somutateResourcesnow also leavesundefinedkeys alone.
-
Stream methods take only the options searchStream accepts.
SearchGoogleAdsStreamRequesthas nopage_token,page_size,validate_onlyorsearch_settings, soqueryStreamnow takesStreamRequestOptions, andreportStreamandreportStreamRawtakeReportStreamOptions(report options plussummary_row_setting).onStreamStart'seditOptionstakesStreamRequestOptions, andonStreamStartandonStreamErrorreceivereportOptionstyped asReportStreamOptions; the query hooks keepReportOptions. Request a summary row in a stream withsummary_row_setting:// 25.x: sent search_settings to searchStream, which does not take it customer.reportStream({ entity: "campaign", metrics, search_settings: { return_summary_row: true }, }); // 26.x customer.reportStream({ entity: "campaign", metrics, summary_row_setting: enums.SummaryRowSetting.SUMMARY_ROW_WITH_RESULTS, });
-
queryandreportsendpage_tokenandvalidate_onlytosearch. Both went tosearchStream, which supports neither. Avalidate_onlyquery now validates the query, and apage_tokenquery starts from that page. -
REST results parse repeated enum and INT64 fields. Each element of a repeated enum now parses to its enum number and each element of a repeated INT64 to a number, matching scalar fields and gRPC results. For example
campaign.excluded_parent_asset_field_typesreturns[13]instead of["SITELINK"], andcampaign.shopping_setting.advertising_partner_idsreturns[123]instead of["123"]. Compare againstenumsvalues instead of strings. -
REST results take each field's type from its exact proto message.
recommendation.forecasting_set_target_cpa_recommendation,forecasting_set_target_roas_recommendation,set_target_cpa_recommendationandset_target_roas_recommendationnow parsecampaign_budget.current_amount_microsandrecommended_new_amount_microsas numbers; 25.x had no type for these fields and returned the strings the API sends. -
Unknown enum names survive REST parsing. An enum name this version does not know keeps its string instead of becoming
undefined. -
Query hooks leave the caller's options alone.
editOptionsinonQueryStartandonStreamStartno longer writes into the options object passed toqueryorqueryStream; the edits apply to that request only. -
max_reporting_rowsapplies toqueryandreport. Both counted rows only approximately (or, for paginated searches, not at all), so queries returned results above the cap. They now throw the sameExceeded the maximum number of rows set by "max_reporting_rows"error asqueryStreamandreportStreamonce the decoded rows exceed the cap. A GAQLLIMITstill bypasses the cap. -
REST errors become
GoogleAdsFailures only when the API says so.query,reportand the stream methods look for the error detail whose@typenamesGoogleAdsFailure, wherever it sits indetails. Other errors, such asgoogle.rpc.ErrorInfowithSERVICE_DISABLED, became an emptyGoogleAdsFailure(errors: [], no message). They now surface, on every REST path, as anErrorwith the API message and the error body ascause. Code that relied oninstanceof errors.GoogleAdsFailurefor these errors must handle a plainError. -
REST errors no longer expose the axios error. A request that fails without an API error body (a network error or a timeout) throws an
Errorwhosecauseholds the axioscodeand the HTTPstatus. 25.x rethrew the axios error for these failures and for API errors without aGoogleAdsFailure, and itsconfig.headerscarried the OAuth access token and the developer token into anything that logged or serialised the error. Code that readerror.responseorerror.configmust readerror.causeinstead.
-
Paginated methods return one page as the RPC response.
keywordPlanIdeas.generateKeywordIdeas,batchJobs.listBatchJobResults,campaignDrafts.listCampaignDraftAsyncErrorsandexperiments.listExperimentAsyncErrorsreturned an array of every result across all pages, while their types promised the response message. They now return the response message for one page, withresults(orerrors),next_page_tokenand fields such astotal_sizeandaggregate_metric_resultsthat the array dropped. Passpage_tokento fetch the next page:// 25.x: an array, despite the declared response type const ideas = await customer.keywordPlanIdeas.generateKeywordIdeas(request); // 26.x const ideas = []; let page_token: string | undefined; do { const response = await customer.keywordPlanIdeas.generateKeywordIdeas({ ...request, page_token, }); ideas.push(...response.results); page_token = response.next_page_token || undefined; } while (page_token);
-
Long-running methods carry gax operation types.
batchJobs.runBatchJob,campaignDrafts.promoteCampaignDraft,experiments.scheduleExperiment,experiments.promoteExperimentandofflineUserDataJobs.runOfflineUserDataJobreturn google-gax'sLROperation<Result, Metadata>, the object they always returned at runtime.await operation.promise()now type-checks;longrunning.Operationmembers such astoJSON()never existed on the returned object. -
LocalServices getters have new names:
customer.localsLeadServicesbecomescustomer.localServicesLeads, and the newLocalServicesEligibilityServicelives atcustomer.localServicesEligibilities. The generator removed the firstServicefrom the name instead of the trailing one.
ClientOptions["developer_token"]now has the typestring | undefined. TypeScript code that reads the option back as astringneeds a guard or a default.- Removed fields: the hotel criteria on
ad_group_bid_modifier(hotel_advance_booking_window,hotel_check_in_date_range,hotel_check_in_day,hotel_date_selection_type,hotel_length_of_stay) andcustomer.conversion_tracking_setting.enhanced_conversions_for_leads_enabled.
developer_tokenbecomes optional (#550, #553). Google sunset developer tokens on 9 September 2026: API access levels now belong to the Google Cloud project that owns your OAuth client, and the API ignores the token. When the option holds nothing, an empty string or only whitespace, the client sends nodeveloper-tokenheader. Previously an unset token went out as the string"undefined"and the API rejected an empty one withDEVELOPER_TOKEN_PARAMETER_MISSING. Configurations that pass a token behave as before; Google will reject the header in a future major API version, so remove it when convenient.- Mutation hooks receive
mutateOptions(see Breaking Changes). - New exported types:
StreamRequestOptionsandReportStreamOptions. - New services:
CustomerBudgetNetworkSettingService,LocalServicesEligibilityService - New resource names:
CustomerBudgetNetworkSetting,SearchLiftKeywordBundle - New enums include
BenchmarksCustomerPercentileTier,BudgetNetworkIneligibleReason,ChannelPriorityTier,DateSelectionType,NetworkChannelType,OfflinePromotionCodeType,ReachMetricState,SearchLiftKeywordBundleStatusandYouTubeVideoUploadGenerativeContentDisclosure
- Mutate helpers for single-operation services send their operation. They sent it as
operations, which these requests do not have, so serialisation dropped the mutation and the API received onlycustomer_id(#289, #316, #501, #518). mutateResourceskeepsexempt_policy_violation_keyswhen the mutation omitsoperation. Omitting it meanscreate, but the library only forwarded the keys for an explicitoperation: "create".- A REST error without failure details (for example a plain
PERMISSION_DENIED) now surfaces as anErrorwith the API message instead of becomingTypeError: Cannot read properties of undefined (reading '0'). - REST results keep numeric enum values. A field that arrived as a number (
status: 2, or a value this version does not know such as999) becameundefined; the parser now keeps the number, as protobuf decoding does. - REST calls refresh the OAuth access token at its real expiry instead of after a fixed 50 minutes: the library now keeps one google-auth-library
OAuth2Clientper OAuth client and refresh token, which refreshes five minutes before theexpires_inthe token endpoint returns. Concurrent calls with the same refresh token share one refresh instead of each requesting a token. - Every gRPC call gets its own metadata. Without a developer token, login customer id or linked customer id, google-gax handed calls on methods with no routing header one shared metadata object, so instrumentation that edits metadata in place (OpenTelemetry, dd-trace) carried trace headers and baggage from one call into the next.
- The lockfile resolves patched
@grpc/grpc-js(1.14.7),follow-redirects,brace-expansionandfast-uri.
- Upgraded google-ads-api version to v25.1. Refer to Google Ads release notes here for changes.
- Upgraded google-ads-node dependency to v25.1.0
- Upgraded google-gax dependency to v6.3.0 and google-auth-library to v11
- Service clients are pinned to the
googleapis.comuniverse domain, so the new auth stack does not probe for application default credentials (about three seconds per new service client, plus aMetadataLookupWarningoutside Google Cloud) when a service is first created
- The package ships CommonJS and ES module builds with an
exportsmap:import { GoogleAdsApi } from "google-ads-api"resolves to native ESM andrequire("google-ads-api")to CommonJS. - Loading both flavours in one process yields two module instances with separate service caches; proto message and error classes come from google-ads-node and stay shared, so
instanceofchecks keep working across them. - New subpath entries
google-ads-api/enumsandgoogle-ads-api/fieldsexpose the generated enums and field types without loading the gRPC service clients. googleAdsVersionis exported from the package root.- New
grpc_channel_optionsclient option forwards gRPC channel settings such as keepalive to every service client; cached service clients are keyed by client id, refresh token and these options (#547). - New service:
MultiPartyAuthReviewService - New resource names:
LiftMeasurementAgeRange,LiftMeasurementCampaign,LiftMeasurementConfig,LiftMeasurementDevice,LiftMeasurementFlight,LiftMeasurementGender,LiftMeasurementVideo,MultiPartyAuthReview - New enums include
AdSubFormatType,BrandLiftMeasurementType,LiftMetricType,LoyaltyMembership,MultiPartyAuthReviewStatus,Sentiment,SyntheticContentSourceandThirdPartyConversionAttributionIntegrationPartner
- Node.js 22 or newer is required.
- Deep imports of build files (for example
google-ads-api/build/src/protos/autogen/enums) no longer resolve. Use the package root,google-ads-api/enumsorgoogle-ads-api/fieldsinstead. - TypeScript projects on
moduleResolution: node(node10) cannot type-resolve thegoogle-ads-api/enumsandgoogle-ads-api/fieldssubpaths; usenode16,nodenextorbundler. - Removed services and resources:
CampaignLifecycleGoalService,CustomerLifecycleGoalService,CampaignLifecycleGoal,CustomerLifecycleGoal(useGoal). Removed enum:CustomerAcquisitionOptimizationMode.
getGoogleAdsError()no longer throws aTypeErrorwhen an error carries noGoogleAdsFailuretrailer (for example REST-stylegoogle.rpc.ErrorInfoerrors such asSERVICE_DISABLED); the original error is returned instead (#548).- google-ads-api does not pin
jws; a fresh install of this release resolvesjws4.0.1, which addresses the advisory reported in #529. Refresh your lockfile if it still holds 4.0.0.
- Upgraded google-ads-api version to v24.1. Refer to Google Ads release notes here for changes.
- Upgraded google-ads-node dependency to v24.1.0
- Upgraded google-gax dependency to v5.0.7
- @isaacs/ttlcache now requires ^1.4.0
- New services:
ReservationService,YouTubeVideoUploadService - New resource names:
AppTopCombinationView,CartDataSalesView,VideoEnhancement,YouTubeVideoUpload - New enums include
ReservationRequestType,PreviewType, and newExperimentTypevalues (ADOPT_AI_MAX,ADOPT_BROAD_MATCH_KEYWORDS,OPTIMIZE_ASSETS,PMAX_REPLACEMENT_SHOPPING) - New v24.1 fields such as
segments.mobile_device_platformandcustomer_user_access.passkey_enabled
parse()now returns plain objects for message-typed leaves (e.g.change_event.old_resource) instead of protobuf message instances, and converts all nested int64 values from Long objects to JavaScript numbers. Code reading.low/.highfrom nested values, or relying on protobuf prototypes, must use the plain values instead. Note that int64 values aboveNumber.MAX_SAFE_INTEGER(2^53 - 1) lose precision when converted, consistent with the existing behaviour for directly-selected fields.
resource_nameattributes for ten view resources (includingkeyword_viewandpaid_organic_search_term_view) were missing from parsed results due to a code generation bug; they are now included.getFieldMask()now throws a descriptiveErroron objects containing circular references instead of overflowing the stack, and treats typed arrays (Buffer,Uint8Array) andLongvalues as leaf values. Previously, update masks built from payloads containingLongvalues included invalid.low/.high/.unsignedpaths that the API rejected.- Report streams now destroy their underlying response stream and JSON parser when consumers exit iteration early
- Rejected
close()calls during gRPC service cache eviction are now caught instead of surfacing as unhandled promise rejections (the dispose callback was previouslyasyncand fire-and-forget) - Cached gRPC service clients past their TTL are no longer returned, and expired entries awaiting purge no longer resolve to
undefined listAccessibleCustomers()closes its one-shotCustomerServiceClientafter the call instead of caching it
- Upgraded google-ads-api version to v23. Refer to Google Ads release notes here for changes.
- Upgraded google-ads-node dependency to v23.0.0 (now aligned with Google Ads API versioning)
- Upgraded google-ads-api version to v22. Refer to Google Ads release notes here for changes.
- Upgraded google-ads-node dependency to v19.0.0
- Upgraded google-gax dependency to v5.0.6
- New service:
AssetGenerationServicewithgenerateTextmethod - New resource names:
CampaignGoalConfig,Goal,TargetingExpansionView - New enums:
YouTubeVideoProperty,CustomerLifecycleOptimizationMode,GoalOptimizationEligibility,GoalType
- Removed field:
asset_group_asset.performance_label
- Optimised
decamelizeKeysin REST response parsing, removedmap-objdependency
- Fixed issue with new v21 resources & fields not being correctly parsed.
- Upgraded google-ads-api version to v21. Refer to Google ads release notes here for changes.
- Upgraded google-ads-node dependency to v18.0.0
- Fixed parsing of FieldMask fields (like
changed_fields) in REST API responses (#519)- REST API returns FieldMask fields as comma-separated strings (e.g.,
"field1,field2") which were not being properly parsed - Now correctly converts them to objects with a
pathsarray format:{ paths: ["field1", "field2"] } - Handles case conversion from camelCase to snake_case to maintain consistency with the rest of the library
- Properly processes nested paths (e.g.,
"ipBlock.ipAddress"→"ip_block.ip_address") - The
changed_fieldsfield now works correctly and follows the same naming conventions as the rest of the library
- REST API returns FieldMask fields as comma-separated strings (e.g.,
- Updated enum definitions to support Google Ads API v21 changes
- Enhanced parser test coverage with additional test cases for change events and FieldMask fields
- Added
skipLibCheckto TypeScript configuration for faster compilation - Added prettier configuration for consistent code formatting
- Upgraded google-ads-api version to v20. Refer to Google ads release notes here for changes.
-
This library now uses the REST API for report, reportStream, search and searchStream. This will result in minor breaking changes to reporting calls:
-
Fields with the enum value
0will now beundefinedinstead -
Array-fields (such as
final_urls) with the value[]will now beundefinedinstead
While these changes are inconvenient, the performance of the REST api is significantly better than the gRPC api, particularly for responses with many rows.
- Upgraded google-ads-api version to v19.1. Refer to Google ads release notes here for changes.
- Fix issue with reportCount() not giving the correct total results count.
- Fix issue with type interface for search_settings not showing any valid fields.
- Upgraded google-ads-api version to v19. Refer to Google ads release notes here for changes.
- We currently do not support
return_total_results_counton search_settings for SearchGoogleAdsRequest. Please usereportCount()instead. This has been reflected in theRequestOptionstype:
export type RequestOptions = Omit<
services.ISearchGoogleAdsRequest,
"customer_id" | "query" | "search_settings"
> & {
search_settings?: Omit<
services.ISearchSettings,
"return_total_results_count"
>;
};For more information please see the SearchSettings reference on the Google document here.