ga4_paid_search_performance
Retrieve paid search traffic metrics including sessions, conversions, cost, and CPC. Break down results by campaign, landing page, keyword, or source to analyze Google Ads performance.
Instructions
Paid: sessions/conversions/cost/CPC for Paid Search traffic, broken down by campaign (default), landing page, keyword, or source.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| start_date | No | Start date: YYYY-MM-DD, NdaysAgo, yesterday, or today | 28daysAgo |
| end_date | No | End date: YYYY-MM-DD, NdaysAgo, yesterday, or today | yesterday |
| property_id | No | Override GA4_PROPERTY_ID env var for this call | |
| limit | No | Max rows to return | |
| breakdown | No | Dimension to break paid search traffic down by | sessionCampaignName |
Implementation Reference
- src/index.ts:64-71 (registration)Registration of the ga4_paid_search_performance tool, mapping the tool name to the paidSearchPerformance handler and paidSearchSchema.
server.tool( "ga4_paid_search_performance", "Paid: sessions/conversions/cost/CPC for Paid Search traffic, broken down by campaign (default), landing page, keyword, or source.", paidSearchSchema, async (args) => { try { return ok(await paidSearchPerformance(args)); } catch (e) { return err(e); } } ); - src/tools/paid.ts:28-34 (schema)Zod schema for paid search performance, defining input params: start_date, end_date, property_id, limit, and breakdown (campaign/page/keyword/source).
export const paidSearchSchema = { ...common, breakdown: z .enum(["sessionCampaignName", "landingPage", "sessionGoogleAdsKeyword", "sessionSource"]) .default("sessionCampaignName") .describe("Dimension to break paid search traffic down by"), }; - src/tools/paid.ts:36-62 (handler)Actual handler for ga4_paid_search_performance. Calls GA4 runReport API filtered to Paid Search channel, fetching sessions, users, engagement, conversions, revenue, clicks, cost, and CPC, broken down by campaign/page/keyword/source.
export async function paidSearchPerformance(args: z.infer<z.ZodObject<typeof paidSearchSchema>>) { const [res] = await getClient().runReport({ property: getProperty(args.property_id), dateRanges: [{ startDate: args.start_date, endDate: args.end_date }], dimensions: [{ name: args.breakdown }], metrics: [ { name: "sessions" }, { name: "totalUsers" }, { name: "engagementRate" }, { name: "conversions" }, { name: "keyEvents" }, { name: "totalRevenue" }, { name: "advertiserAdClicks" }, { name: "advertiserAdCost" }, { name: "advertiserAdCostPerClick" }, ], dimensionFilter: { filter: { fieldName: "sessionDefaultChannelGroup", stringFilter: { value: "Paid Search" }, }, }, orderBys: [{ metric: { metricName: "sessions" }, desc: true }], limit: args.limit as unknown as number, }); return formatReport(res); } - src/tools/paid.ts:11-25 (helper)Helper function that formats the GA4 API response into a clean JSON structure with rowCount and rows arrays. Used by the paidSearchPerformance handler.
function formatReport(res: any) { const rows = (res.rows ?? []).map((r: any) => { const out: Record<string, string | number> = {}; (res.dimensionHeaders ?? []).forEach((h: any, i: number) => { out[h.name] = r.dimensionValues?.[i]?.value ?? ""; }); (res.metricHeaders ?? []).forEach((h: any, i: number) => { const v = r.metricValues?.[i]?.value ?? "0"; const n = Number(v); out[h.name] = Number.isFinite(n) ? n : v; }); return out; }); return { rowCount: res.rowCount ?? rows.length, rows }; } - src/client.ts:12-28 (helper)Helper functions getClient (returns a singleton GA4 BetaAnalyticsDataClient) and getProperty (resolves property ID string), used by the handler to initialize the API client.
export function getClient(): BetaAnalyticsDataClient { if (cachedClient) return cachedClient; if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) { throw new GA4Error("GOOGLE_APPLICATION_CREDENTIALS is not set"); } cachedClient = new BetaAnalyticsDataClient(); return cachedClient; } export function getProperty(override?: string): string { const id = override ?? process.env.GA4_PROPERTY_ID; if (!id) throw new GA4Error("GA4_PROPERTY_ID is not set and no property_id was passed"); return id.startsWith("properties/") ? id : `properties/${id}`; } export const DEFAULT_START = "28daysAgo"; export const DEFAULT_END = "yesterday";