{"id":10098,"date":"2026-10-25T08:12:00","date_gmt":"2026-10-25T07:12:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?p=10098"},"modified":"2026-10-24T03:23:33","modified_gmt":"2026-10-24T01:23:33","slug":"tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/","title":{"rendered":"Tutorial: GA4 Consent States in BigQuery and Looker Studio"},"content":{"rendered":"<p>A GA4 consent report answers a narrow but useful question: what share of the exported events carries each consent state? A value of 70% for <code>analytics_storage = Yes<\/code> means 70 of every 100 recorded events have that state. It does not mean that 70% of visitors accepted the banner.<\/p>\n<p>This tutorial builds a daily status report from BigQuery and prepares the counts for Looker Studio. The report separates Analytics storage from advertising storage, retains unknown states and exposes missing daily tables. Changes provide a starting point for investigation, not proof of a banner defect or lost conversions.<\/p>\n<figure class=\"lw-diagram\"><img decoding=\"async\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/dia-10098-review-20261024.png\" width=\"1120\" height=\"580\" loading=\"lazy\" alt=\"Daily exported events, separate Analytics and Ads storage states, and weighted dashboard shares\"><figcaption>Event shares describe the export; banner decisions need a separate measure.<\/figcaption><\/figure>\n<h2>1. Definition and prerequisites<\/h2>\n<p>The starting point is an existing GA4 BigQuery link with daily export enabled, access to the dataset and permission to run queries. Consent Mode must communicate the intended default and updated states correctly. Linking BigQuery does not reconstruct historical banner decisions.<\/p>\n<p>The event schema contains <code>privacy_info.analytics_storage<\/code> and <code>privacy_info.ads_storage<\/code>, with documented values <code>Yes<\/code>, <code>No<\/code> and <code>Unset<\/code>. The query also retains null or unexpected values under <code>other<\/code>. An unset state is not an explicit refusal. These two fields alone do not describe every Consent Mode setting: advertising storage is distinct from consent for advertising user data and personalization.<\/p>\n<p>The denominator includes every exported event in the selected daily tables. An active visit contributes more events than a short visit; a default state before a banner decision can differ from the later state. In a basic implementation, blocked traffic is absent from GA4. Advanced implementations can send cookieless pings under denied storage. Browser blocking and collection failures still leave gaps. None of these gaps can be recovered by dividing the remaining rows.<\/p>\n<h2>2. Thirty completed calendar days<\/h2>\n<p>The example uses <code>Europe\/Warsaw<\/code>; that timezone must match the GA4 property. Project and dataset names require replacement. The result contains one row per day, from 30 days ago through yesterday. It excludes intraday tables and the current day.<\/p>\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"sql\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">SQL<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-sql\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-comment\">-- Standard SQL. Replace project\/dataset and property timezone.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">WITH<\/span> days <span class=\"cbb-keyword\">AS<\/span> (\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">SELECT<\/span> day <span class=\"cbb-keyword\">FROM<\/span> <span class=\"cbb-keyword\">UNNEST<\/span>(<span class=\"cbb-function\">GENERATE_DATE_ARRAY<\/span>(\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">DATE_SUB<\/span>(<span class=\"cbb-function\">CURRENT_DATE<\/span>(<span class=\"cbb-string\">'Europe\/Warsaw'<\/span>), <span class=\"cbb-keyword\">INTERVAL<\/span> <span class=\"cbb-number\">30<\/span> DAY),\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">DATE_SUB<\/span>(<span class=\"cbb-function\">CURRENT_DATE<\/span>(<span class=\"cbb-string\">'Europe\/Warsaw'<\/span>), <span class=\"cbb-keyword\">INTERVAL<\/span> <span class=\"cbb-number\">1<\/span> DAY))) <span class=\"cbb-keyword\">AS<\/span> day\n<\/span><span class=\"cbb-line\">), available <span class=\"cbb-keyword\">AS<\/span> (\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">SELECT<\/span> table_name <span class=\"cbb-keyword\">FROM<\/span> <span class=\"cbb-variable\">`project.analytics_XXXXXXXXX.INFORMATION_SCHEMA.TABLES`<\/span>\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">WHERE<\/span> table_type = <span class=\"cbb-string\">'BASE TABLE'<\/span>\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-keyword\">AND<\/span> <span class=\"cbb-function\">REGEXP_CONTAINS<\/span>(table_name, r<span class=\"cbb-string\">'^events_&#91;0-9&#93;{8}$'<\/span>)\n<\/span><span class=\"cbb-line\">), counts <span class=\"cbb-keyword\">AS<\/span> (\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">SELECT<\/span> <span class=\"cbb-function\">PARSE_DATE<\/span>(<span class=\"cbb-string\">'%Y%m%d'<\/span>, _TABLE_SUFFIX) <span class=\"cbb-keyword\">AS<\/span> day,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNT<\/span>(*) <span class=\"cbb-keyword\">AS<\/span> total_events,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNTIF<\/span>(privacy_info.analytics_storage = <span class=\"cbb-string\">'Yes'<\/span>) <span class=\"cbb-keyword\">AS<\/span> analytics_yes,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNTIF<\/span>(privacy_info.analytics_storage = <span class=\"cbb-string\">'No'<\/span>) <span class=\"cbb-keyword\">AS<\/span> analytics_no,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNTIF<\/span>(privacy_info.analytics_storage = <span class=\"cbb-string\">'Unset'<\/span>) <span class=\"cbb-keyword\">AS<\/span> analytics_unset,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNTIF<\/span>(<span class=\"cbb-function\">COALESCE<\/span>(privacy_info.analytics_storage <span class=\"cbb-keyword\">NOT<\/span> <span class=\"cbb-keyword\">IN<\/span> (<span class=\"cbb-string\">'Yes'<\/span>,<span class=\"cbb-string\">'No'<\/span>,<span class=\"cbb-string\">'Unset'<\/span>), <span class=\"cbb-constant\">TRUE<\/span>)) <span class=\"cbb-keyword\">AS<\/span> analytics_other,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNTIF<\/span>(privacy_info.ads_storage = <span class=\"cbb-string\">'Yes'<\/span>) <span class=\"cbb-keyword\">AS<\/span> ads_yes,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNTIF<\/span>(privacy_info.ads_storage = <span class=\"cbb-string\">'No'<\/span>) <span class=\"cbb-keyword\">AS<\/span> ads_no,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNTIF<\/span>(privacy_info.ads_storage = <span class=\"cbb-string\">'Unset'<\/span>) <span class=\"cbb-keyword\">AS<\/span> ads_unset,\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">COUNTIF<\/span>(<span class=\"cbb-function\">COALESCE<\/span>(privacy_info.ads_storage <span class=\"cbb-keyword\">NOT<\/span> <span class=\"cbb-keyword\">IN<\/span> (<span class=\"cbb-string\">'Yes'<\/span>,<span class=\"cbb-string\">'No'<\/span>,<span class=\"cbb-string\">'Unset'<\/span>), <span class=\"cbb-constant\">TRUE<\/span>)) <span class=\"cbb-keyword\">AS<\/span> ads_other\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">FROM<\/span> <span class=\"cbb-variable\">`project.analytics_XXXXXXXXX.events_*`<\/span>\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">WHERE<\/span> _TABLE_SUFFIX <span class=\"cbb-keyword\">BETWEEN<\/span>\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-function\">FORMAT_DATE<\/span>(<span class=\"cbb-string\">'%Y%m%d'<\/span>, <span class=\"cbb-function\">DATE_SUB<\/span>(<span class=\"cbb-function\">CURRENT_DATE<\/span>(<span class=\"cbb-string\">'Europe\/Warsaw'<\/span>), <span class=\"cbb-keyword\">INTERVAL<\/span> <span class=\"cbb-number\">30<\/span> DAY))\n<\/span><span class=\"cbb-line\">    <span class=\"cbb-keyword\">AND<\/span> <span class=\"cbb-function\">FORMAT_DATE<\/span>(<span class=\"cbb-string\">'%Y%m%d'<\/span>, <span class=\"cbb-function\">DATE_SUB<\/span>(<span class=\"cbb-function\">CURRENT_DATE<\/span>(<span class=\"cbb-string\">'Europe\/Warsaw'<\/span>), <span class=\"cbb-keyword\">INTERVAL<\/span> <span class=\"cbb-number\">1<\/span> DAY))\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">GROUP<\/span> <span class=\"cbb-keyword\">BY<\/span> day\n<\/span><span class=\"cbb-line\">)\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">SELECT<\/span> d.day, a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-keyword\">NOT<\/span> <span class=\"cbb-constant\">NULL<\/span> <span class=\"cbb-keyword\">AS<\/span> table_present,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.total_events, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> total_events,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.analytics_yes, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> analytics_yes,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.analytics_no, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> analytics_no,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.analytics_unset, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> analytics_unset,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.analytics_other, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> analytics_other,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.ads_yes, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> ads_yes,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.ads_no, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> ads_no,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.ads_unset, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> ads_unset,\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">IF<\/span>(a.table_name <span class=\"cbb-keyword\">IS<\/span> <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-constant\">NULL<\/span>, <span class=\"cbb-function\">COALESCE<\/span>(c.ads_other, <span class=\"cbb-number\">0<\/span>)) <span class=\"cbb-keyword\">AS<\/span> ads_other\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">FROM<\/span> days <span class=\"cbb-keyword\">AS<\/span> d\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">LEFT<\/span> <span class=\"cbb-keyword\">JOIN<\/span> available <span class=\"cbb-keyword\">AS<\/span> a\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">ON<\/span> a.table_name = <span class=\"cbb-function\">CONCAT<\/span>(<span class=\"cbb-string\">'events_'<\/span>, <span class=\"cbb-function\">FORMAT_DATE<\/span>(<span class=\"cbb-string\">'%Y%m%d'<\/span>, d.day))\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">LEFT<\/span> <span class=\"cbb-keyword\">JOIN<\/span> counts <span class=\"cbb-keyword\">AS<\/span> c <span class=\"cbb-keyword\">USING<\/span> (day)\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">ORDER<\/span> <span class=\"cbb-keyword\">BY<\/span> day;<\/span><\/code><\/pre><\/figure>\n<p><code>table_present = false<\/code> means that no corresponding daily base table exists. Counts remain <code>NULL<\/code> in that case. An existing empty table produces zero counts. Neither an existing table nor a zero count proves that collection was complete. The query requires at least one matching export table and compatible schemas; access or schema failures remain errors rather than fabricated zeros.<\/p>\n<p>The numeric suffix filter limits the selected daily tables. Before a scheduled run, a BigQuery dry run and a bytes-billed limit provide a cost check. Daily tables can receive late events for up to three days after the event date, so recent results remain provisional. A scheduled destination table needs replacement or an upsert of the affected dates; blindly appending this 30-day result every day duplicates counts. A small reporting table reduces repeated raw scans but does not guarantee a fixed bill.<\/p>\n<h2>3. Dashboard setup without a false funnel<\/h2>\n<p>Looker Studio supports native funnel charts. The chart is still unsuitable for the sequence \u201call events \u2192 Analytics Yes \u2192 Ads Yes\u201d: the final two sets need not be nested. Separate bars or time series show the two storage categories without inventing a sequence.<\/p>\n<p>The reporting table connects through the BigQuery connector. <code>day<\/code> is the date dimension; count fields use Sum. Two scorecards calculate the event shares from the sums:<\/p>\n<pre><code>CASE WHEN SUM(total_events) &gt; 0 THEN SUM(analytics_yes) \/ SUM(total_events) ELSE NULL END<\/code><\/pre>\n<p>The fields are formatted as percentages, without another multiplication by 100. The equivalent Ads formula uses <code>ads_yes<\/code>. A zero denominator remains blank. A daily time series can use the same calculation grouped by <code>day<\/code>. A companion table displays Yes, No, Unset and Other counts for each storage category, total events and <code>table_present<\/code>. Missing days stay visible as gaps.<\/p>\n<p>Period comparison uses equally long windows and reports missing days alongside the result. A mean of daily percentages gives small and large days equal weight: 1 of 1 events and 0 of 99 events would average to 50%, while the correct combined event share is 1%. Missing tables must not disappear silently from a period comparison.<\/p>\n<h2>4. Interpretation and boundaries<\/h2>\n<p>A change can reflect the event mix, traffic sources, defaults, banner configuration, tag timing or an export problem. A controlled test of the banner and tag requests, together with release history and export checks, helps distinguish these explanations. Correlation between an event share and total volume does not establish causation.<\/p>\n<p>The report does not reveal whether GA4 behavioral modeling is eligible or active. Google&#8217;s criteria include absolute event and user volumes over several days, implementation requirements and additional model-quality checks. An event percentage alone cannot test these criteria. Behavioral modeling and conversion modeling also answer different questions.<\/p>\n<p>The output contains daily aggregates without user identifiers. Access restrictions and care with small groups remain necessary; aggregation alone is not an automatic anonymity guarantee. A decision-based acceptance rate needs a separately defined CMP measure, including the treatment of repeated prompts, changed choices and unanswered banners.<\/p>\n<div class=\"lw-faq\"><h2>Questions and answers<\/h2>\n<h3>Can a share near 100% prove that the banner is successful?<\/h3><p>No. If only consented traffic reaches GA4, almost all recorded events can carry Yes while many declined visits remain absent. The report describes available event data, not all banner interactions.<\/p>\n<h3>Does Unset belong in the No category?<\/h3><p>No. Keeping Unset and other missing or unexpected values separate makes configuration gaps visible. The displayed Yes share still uses all recorded events as its denominator.<\/p>\n<h3>Does a missing day mean that no events occurred?<\/h3><p>No. A missing export table is a data-availability issue. The query returns null counts for that day; zero is reserved for a present but empty table.<\/p><\/div>\n<div class=\"lw-quellen\">\n<h2>Sources<\/h2>\n<ul>\n<li><a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/ga4\" target=\"_blank\" rel=\"noopener noreferrer\">Google Analytics 4 developer guide<\/a><\/li>\n<li><a href=\"https:\/\/cloud.google.com\/looker\/docs\" target=\"_blank\" rel=\"noopener noreferrer\">Looker documentation<\/a><\/li>\n<li><a href=\"https:\/\/support.google.com\/analytics\/answer\/7029846?hl=en\" target=\"_blank\" rel=\"noopener noreferrer\">GA4 BigQuery export schema<\/a><\/li><li><a href=\"https:\/\/cloud.google.com\/looker\/docs\/studio\/funnel-chart-reference\" target=\"_blank\" rel=\"noopener noreferrer\">Looker Studio native funnel charts<\/a><\/li><li><a href=\"https:\/\/support.google.com\/analytics\/answer\/11161109?hl=en\" target=\"_blank\" rel=\"noopener noreferrer\">GA4 behavioral modeling requirements<\/a><\/li><li><a href=\"https:\/\/developers.google.com\/tag-platform\/security\/concepts\/consent-mode\" target=\"_blank\" rel=\"noopener noreferrer\">Consent Mode concepts and implementations<\/a><\/li><li><a href=\"https:\/\/cloud.google.com\/bigquery\/docs\/querying-wildcard-tables\" target=\"_blank\" rel=\"noopener noreferrer\">BigQuery wildcard table queries<\/a><\/li><li><a href=\"https:\/\/cloud.google.com\/looker\/docs\/studio\/about-calculated-fields\" target=\"_blank\" rel=\"noopener noreferrer\">Looker Studio calculated fields and aggregation<\/a><\/li><\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>A practical build for a report that shows opt-in rates per consent type and how denied consent shrinks the data actually collected \u2014 using GA4&#8217;s BigQuery export and a Looker Studio dashboard, without any user-level tracking of individual consent choices.<\/p>\n","protected":false},"author":1,"featured_media":23373,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[127],"tags":[91040,91279,91258,91353,91219],"class_list":["post-10098","post","type-post","status-publish","format-standard","hentry","category-tutorials-en-digital-marketing","tag-consent-management","tag-prywatnosc-danych-pl","tag-google-analytics-4","tag-looker-studio","tag-tutorial"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Tutorial: GA4 Consent States in BigQuery and Looker Studio | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"A report showing opt-in rates per consent type and how denied consent shrinks the data collected \u2014 built on GA4&#039;s BigQuery export and Looker Studio.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tutorial: GA4 Consent States in BigQuery and Looker Studio | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"A report showing opt-in rates per consent type and how denied consent shrinks the data collected \u2014 built on GA4&#039;s BigQuery export and Looker Studio.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-25T07:12:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/10\/hero-10098-review-20261024-dark.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Lukas Wojcik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lukas Wojcik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/\"},\"author\":{\"name\":\"Lukas Wojcik\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"Tutorial: GA4 Consent States in BigQuery and Looker Studio\",\"datePublished\":\"2026-10-25T07:12:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/\"},\"wordCount\":923,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/hero-10098-review-20261024-dark.png\",\"keywords\":[\"Consent Management\",\"Data Privacy\",\"Google Analytics 4\",\"Looker Studio\",\"Tutorial\"],\"articleSection\":[\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/\",\"name\":\"Tutorial: GA4 Consent States in BigQuery and Looker Studio | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/hero-10098-review-20261024-dark.png\",\"datePublished\":\"2026-10-25T07:12:00+00:00\",\"description\":\"A report showing opt-in rates per consent type and how denied consent shrinks the data collected \u2014 built on GA4's BigQuery export and Looker Studio.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/hero-10098-review-20261024-dark.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/hero-10098-review-20261024-dark.png\",\"width\":1200,\"height\":630,\"caption\":\"Tutorial: GA4 Consent States in BigQuery and Looker Studio\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-marketing\\\/tutorials-en-digital-marketing\\\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tutorial: GA4 Consent States in BigQuery and Looker Studio\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Tutorial: GA4 Consent States in BigQuery and Looker Studio | Lukas Wojcik","description":"A report showing opt-in rates per consent type and how denied consent shrinks the data collected \u2014 built on GA4's BigQuery export and Looker Studio.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/","og_locale":"en_US","og_type":"article","og_title":"Tutorial: GA4 Consent States in BigQuery and Looker Studio | Lukas Wojcik","og_description":"A report showing opt-in rates per consent type and how denied consent shrinks the data collected \u2014 built on GA4's BigQuery export and Looker Studio.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-10-25T07:12:00+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/10\/hero-10098-review-20261024-dark.png","type":"image\/png"}],"author":"Lukas Wojcik","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Lukas Wojcik","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/"},"author":{"name":"Lukas Wojcik","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"Tutorial: GA4 Consent States in BigQuery and Looker Studio","datePublished":"2026-10-25T07:12:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/"},"wordCount":923,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/10\/hero-10098-review-20261024-dark.png","keywords":["Consent Management","Data Privacy","Google Analytics 4","Looker Studio","Tutorial"],"articleSection":["Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/","name":"Tutorial: GA4 Consent States in BigQuery and Looker Studio | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/10\/hero-10098-review-20261024-dark.png","datePublished":"2026-10-25T07:12:00+00:00","description":"A report showing opt-in rates per consent type and how denied consent shrinks the data collected \u2014 built on GA4's BigQuery export and Looker Studio.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/10\/hero-10098-review-20261024-dark.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/10\/hero-10098-review-20261024-dark.png","width":1200,"height":630,"caption":"Tutorial: GA4 Consent States in BigQuery and Looker Studio"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-marketing\/tutorials-en-digital-marketing\/tutorial-visualizing-the-consent-funnel-in-ga4-and-looker-studio\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Tutorial: GA4 Consent States in BigQuery and Looker Studio"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/10098","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=10098"}],"version-history":[{"count":5,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/10098\/revisions"}],"predecessor-version":[{"id":23375,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/10098\/revisions\/23375"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/23373"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=10098"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=10098"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=10098"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}