ga4_run_report
Run custom GA4 reports by specifying any dimensions and metrics. Build reports beyond preset tools using flexible date ranges, filters, and sorting.
Instructions
Flexible GA4 Data API runReport. Pass any dimensions + metrics. Use for custom reports the preset tools don't cover.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dimensions | Yes | GA4 dimension API names, e.g. ['sessionSource','sessionMedium','landingPage'] | |
| metrics | Yes | GA4 metric API names, e.g. ['sessions','totalUsers','conversions'] | |
| 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 | |
| dimension_filter | No | Optional string filter: 'dimensionName=value' (exact) or 'dimensionName~regex' | |
| order_by | No | Metric or dimension to sort by, prefix with '-' for desc. Default: first metric desc |
Implementation Reference
- src/tools/reports.ts:57-68 (handler)The main handler function `runReport` that executes the GA4 Data API runReport with provided dimensions, metrics, date range, filter, and order. Calls `getClient().runReport()` and formats the result.
export async function runReport(args: z.infer<z.ZodObject<typeof runReportSchema>>) { const [res] = await getClient().runReport({ property: getProperty(args.property_id), dateRanges: toDateRange(args.start_date, args.end_date), dimensions: args.dimensions.map((name) => ({ name })), metrics: args.metrics.map((name) => ({ name })), dimensionFilter: parseFilter(args.dimension_filter), orderBys: parseOrderBy(args.order_by, args.metrics[0]), limit: args.limit as unknown as number, }); return formatReport(res); } - src/tools/reports.ts:33-39 (schema)Zod schema `runReportSchema` defining the input validation: dimensions array, metrics array, date range, optional dimension_filter, optional order_by.
export const runReportSchema = { dimensions: z.array(z.string()).describe("GA4 dimension API names, e.g. ['sessionSource','sessionMedium','landingPage']"), metrics: z.array(z.string()).describe("GA4 metric API names, e.g. ['sessions','totalUsers','conversions']"), ...dateRange, dimension_filter: z.string().optional().describe("Optional string filter: 'dimensionName=value' (exact) or 'dimensionName~regex'"), order_by: z.string().optional().describe("Metric or dimension to sort by, prefix with '-' for desc. Default: first metric desc"), }; - src/index.ts:46-53 (registration)Tool registration for 'ga4_run_report' using `server.tool()` with the schema and a handler that delegates to `runReport()`.
server.tool( "ga4_run_report", "Flexible GA4 Data API runReport. Pass any dimensions + metrics. Use for custom reports the preset tools don't cover.", runReportSchema, async (args) => { try { return ok(await runReport(args)); } catch (e) { return err(e); } } ); - src/tools/reports.ts:15-29 (helper)Helper function `formatReport` that transforms the API response into a cleaned array of rows with dimension/metric headers.
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/tools/reports.ts:41-48 (helper)Helper functions `parseFilter` and `parseOrderBy` used by the handler to parse the optional dimension filter and ordering.
function parseFilter(f?: string) { if (!f) return undefined; const exact = f.match(/^([^=~]+)=(.+)$/); if (exact) return { filter: { fieldName: exact[1].trim(), stringFilter: { value: exact[2].trim() } } }; const re = f.match(/^([^~]+)~(.+)$/); if (re) return { filter: { fieldName: re[1].trim(), stringFilter: { matchType: "FULL_REGEXP" as const, value: re[2].trim() } } }; return undefined; }