Skip to main content

White-Label Analytics Reporting: A Stack for Agencies

Looker Studio templates, BigQuery blending, and a delivery workflow that turns monthly reporting from a chore into a product.

Ask an agency what their monthly reporting costs and you'll usually get a shrug. It's in the retainer. Except it isn't priced into the retainer, it's stapled to it, and it eats the first week of every month for whoever drew the short straw. Twenty clients at three hours each is a full-time job nobody invoices for and nobody enjoys.

The awkward part is that the report is the deliverable clients judge you on most, and it's the one you rush hardest. Fixing that isn't about finding a better reporting tool. It's about treating the report as a product with a spec, a template, a release process, and a maintenance cost, and then building it once. Here's the stack that gets you there, in the order the work has to happen.

Reporting as Unpaid Labour

Start by measuring it honestly, because the case for building a template is made of hours. For one month, have everyone log the time they spend on client reporting: pulling numbers, rebuilding a chart that broke, chasing a discrepancy, writing the email, sitting on the call explaining why GA4 and Ads disagree.

The total is usually worse than anyone guessed, and the shape of it is more interesting than the size. Most of the hours go to three things that have nothing to do with insight: assembling data that should assemble itself, reconciling definitions that should have been agreed once, and answering the same five questions every month.

All three are fixable by standardization. None of them are fixable by working faster. That's the whole argument, and it's worth writing down before you ask anyone to fund a month of internal work.

Standardize the Event Model First

The real reason you can't template a report is that no two client properties measure the same way. One fires generate_lead. Another fires form_submit. A third fires contact_form_success_pricing with the page name baked into the event name, which is its own separate crime. Build a report against the first client and it produces empty charts for the other two.

So write an event spec and make it the agency standard. Four rules carry most of the weight:

Standard eventFires whenRequired parametersKey event
generate_leadForm submission confirmed by the server, not on clickform_name, form_location, valueYes, primary
purchaseOrder confirmedtransaction_id, value, currency, itemsYes, primary
sign_upAccount createdmethodYes, secondary
contact_clickPhone or mailto link clickedcontact_type, page_typeNo
file_downloadGated or ungated asset downloadedfile_name, gatedNo

Be realistic about adoption. You will not get every existing client to remeasure, and you shouldn't try. New clients get the spec during onboarding, where it's part of the tracking setup you're already being paid to do. Existing clients get mapped in the warehouse instead, which is the next section.

Mapping Divergent Clients With BigQuery

Here's the single decision that makes everything else possible: when a client's data doesn't fit the standard, you fork the view, never the report.

Switch on GA4's BigQuery export for each client property (Admin, then Product links, then BigQuery links). Then build one view per client that renames their events into your model and exposes an identical set of columns. Every client's view has the same schema, so every report reads the same shape:

CREATE OR REPLACE VIEW `agency-analytics.std.acme_events` AS
SELECT
  PARSE_DATE('%Y%m%d', event_date)                      AS event_day,
  user_pseudo_id,
  (SELECT value.int_value FROM UNNEST(event_params)
     WHERE key = 'ga_session_id')                       AS session_id,

  -- map this client's vocabulary onto the agency standard
  CASE event_name
    WHEN 'contact_form_success' THEN 'generate_lead'
    WHEN 'quote_request'        THEN 'generate_lead'
    WHEN 'newsletter_signup'    THEN 'sign_up'
    ELSE event_name
  END                                                   AS event_name,

  (SELECT value.string_value FROM UNNEST(event_params)
     WHERE key = 'form_id')                             AS form_name,
  traffic_source.source                                 AS session_source,
  traffic_source.medium                                 AS session_medium,
  IFNULL(ecommerce.purchase_revenue, 0)                 AS revenue
FROM `acme-ga4-export.analytics_318294021.events_*`
WHERE _TABLE_SUFFIX BETWEEN
  FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 400 DAY))
  AND FORMAT_DATE('%Y%m%d', CURRENT_DATE());

The 400 day window gives you a year plus a comparison period without scanning the entire export on every refresh. If the queries get expensive, materialize a daily summary table with a scheduled query and point the report at that instead. Looker Studio refreshing a raw wildcard scan twenty times during a client call is how agencies discover BigQuery pricing.

Put each client's view in its own dataset and use authorized views so a Looker Studio report can only ever read one client's data. This isn't paranoia. A copied report pointing at the wrong dataset is a genuinely bad afternoon, and dataset-level permissions make it structurally impossible.

Not every client will have BigQuery export, since it needs a billing account and somebody willing to enable it. For those, the native GA4 connector in Looker Studio works fine, provided their event names already match the spec. You'll run a two-tier setup for a while. That's normal, and it's still better than twenty bespoke reports.

One Branded Template, Every Client

Build the report once. Then never build it again, only improve it.

Set the agency identity in Theme and layout: your palette, your typeface, your header and footer components copied across every page. Client branding comes from the data, not from manual editing. Keep a small client_meta table in BigQuery with one row per client holding client_name, logo_url, brand_colour, and their targets, then use an Image chart bound to logo_url and text components bound to client_name. Swapping the data source swaps the branding.

Fix the page order and don't negotiate it:

  1. Executive summary
  2. Acquisition: channels, campaigns, cost
  3. Behaviour: landing pages, content, site health
  4. Conversions: key events, funnels, value
  5. Campaign detail, for the people who want the rows
  6. Definitions and data sources

Use report-level date controls with a previous-period comparison enabled by default, and set the default date range to last complete month. Every chart inherits it. Client targets come from client_meta so a scorecard can show progress against a number that's specific to them without anyone editing a chart.

The Summary Page and the Appendix

These two pages do more work than the other four combined, for opposite reasons.

The summary page is written for someone who will read exactly one page and then forward it. Four to six scorecards with period comparison, one trend chart, one commentary box. The rule that keeps it useful: nothing appears on the summary page that the reader can't act on. Qualified leads, cost per lead, revenue, ROAS, and one leading indicator belong here. Sessions do not. Sessions go on page two where the analysts live.

The definitions appendix is the unglamorous page that pays for itself every month. For each metric on the report: what it counts, which source it comes from, the attribution model, and the known caveats, in plain language. An entry that actually helps looks like this:

Conversions: GA4 key events named generate_lead and purchase, attributed by last non-direct click, from property 318294021 in Europe/London time. Google Ads reports its own conversions using data-driven attribution on the click date, so the two numbers will not match and are not meant to.

That paragraph ends most of the "why doesn't this match" threads before they start. Add the refresh schedule and the property timezone while you're there, since between them they explain the majority of the rest.

Onboarding as a Data Source Swap

Once the template exists, a new client's first report should be a checklist, not a project:

  1. Enable BigQuery export on their GA4 property, wait for the first daily table.
  2. Create their dataset and their standardized view, mapping any divergent event names in the CASE block.
  3. Add their row to client_meta: name, logo URL, brand colour, targets, fiscal year start.
  4. Copy the master template with Make a copy, and use the data source mapping dialog to repoint each source at the new view.
  5. Rename, set the schedule, QA every page against the GA4 interface, and check for empty charts.

Under an hour including QA. If it takes a day, the template isn't parameterized enough and the fix is in the template, not in the process.

Two governance habits keep this from decaying. Keep a master report named something unmissable like TEMPLATE (do not edit), and require that improvements land there first. And keep a template changelog, because the moment you improve the template you face a real decision: re-copy for existing clients, or apply the change by hand across twelve reports. That decision cost is exactly why the next section matters.

Scheduled Delivery With Written Commentary

In Looker Studio, Share, then Schedule delivery, PDF, monthly. Set it for the second or third business day so GA4 data has settled and ad platform costs are final. Sending on the 1st guarantees you'll be correcting numbers on the 2nd.

A PDF with no words in it is not a report, it's a data dump with your logo on it. The commentary box on the summary page is the actual deliverable, and it should answer four things: what moved, why it moved, what you're doing about it next month, and what you need from the client. Three to five bullets. Written by a human who looked at the data.

The scheduling trick is to put the writing in the calendar, not the sending. The schedule fires on the 3rd. The analyst has a recurring block on the 2nd to write commentary. Automating the delivery of an empty template is how agencies end up with clients who don't open the email.

Send the PDF and the live link together, PDF attached, link in the body. People open attachments and ignore links, but the client who wants to slice something themselves will thank you for the link.

Between reports, set up an alert rather than waiting a month. A scheduled query that compares this week's key events to the trailing four week average and posts to Slack when it drops sharply will catch a broken tag in days instead of on the 3rd of next month, in a PDF, in front of the client.

Where to Draw the Customization Line

This is where standardized reporting quietly dies. Every request sounds small. Twenty small requests later you have twenty bespoke reports again, plus the maintenance burden of pretending you don't.

TierExamplesHow it's handled
IncludedLogo, brand colour, targets, date defaults, which standard pages appear, commentaryData-driven, no template edit
BillableNew page, CRM or call tracking source, Shopify or Stripe revenue, custom attribution viewScoped and quoted as a project, with a maintenance line
DeclinedRenaming standard metrics for one client, client-specific definitions of conversion, one-off chart typesRedirected into the standard, or refused politely

The wording that works when declining: "That's a report change rather than a template change. We can build it as a custom page, it's roughly X hours to build and it becomes something we maintain separately for you." Naming the ongoing cost is the honest part, and it converts about half of these requests into "actually, never mind".

Log every request even when you decline it. When the fourth client asks for the same thing, that's not a customization anymore, that's a gap in the template, and it goes into the master for everyone. That feedback loop is what separates a product from a pile of copies.

What the Standard Stack Buys Back

The first month of this is real work: writing the spec, building the views, designing the template, and having a slightly tense conversation about which client requests you're no longer accommodating. Nobody enjoys it and it doesn't bill.

What you get afterwards is that reporting time collapses to the part that was always the point, which is a person looking at the numbers and writing what they mean. The second-order benefit is the one worth planning for: because every client now sits on the same event model, cross-client benchmarking becomes possible for the first time. Median cost per lead by vertical, conversion rate by industry, the effect of a channel mix change across a portfolio. That's an insight product you can sell, built entirely out of work you were already doing for free.