AdSense and the GDPR in WordPress: How to Build a Setup That Holds Up
Putting ads and AdSense into WordPress without losing yourself in theme code, loading time and consent questions. A practical guide with a step by step setup.
You want ads or Google AdSense on your WordPress site, and you want a setup that is fast, maintainable and defensible. The usual route is to paste an AdSense snippet somewhere into the theme and hope it holds. That bill arrives later: at the next update, in the loading time, and on the consent question. This post shows how to steer ads from one place instead of sprinkling code across your files, what to watch on the privacy side, and what a clean setup looks like step by step.
One thing before the details: this is a practical orientation, not legal advice. The GDPR applies across the European Union in the same wording, but the rule that actually governs ad cookies comes from the ePrivacy Directive, and every member state wrote its own national version of it. Where the fine print of your own market matters, a lawyer in that market is the right address.
Why you should steer ads instead of scattering code
The moment you glue the first ad block into a theme file or a post, a small mess begins. At the next theme update the code may simply be gone. If you want one ad in ten places, you maintain it ten times. And if you want to know whether a position earns anything at all, you have no basis for the answer, because nothing is being measured.
Three reasons speak for managing ads in one place:
- Maintainability: one ad unit, one source. You change the code once and the ad updates everywhere you placed it.
- Performance: ad scripts are often the heaviest elements on a page. Loading them deliberately, lazy loading being the keyword, protects your loading time and with it your rankings.
- Law: advertising, personalised advertising in particular, touches data protection. Central control makes it far easier to release ads only after consent, instead of remembering it in every single snippet.
This is exactly where an ad manager such as Adjet comes in. You create ads as their own blocks, decide centrally about placement and rules, and see the effect in a local statistic. No more hunting through theme files. How that compares with the best known tool for inserting code is set out in our post on Ad Inserter.
One note on the name before you search your backend in vain: in the official WordPress directory the free edition is listed as Hafenstudios Ads, not as Adjet. You install it under Plugins, Add New, searching for “Hafenstudios Ads”, or straight from wordpress.org/plugins/hafenstudios-ads/. As of early September 2026 the listing shows version 2.5.0 with at least 100 active installations and 14 reviews averaging 5.0; in August 2026 WordPress.org put the plugin on the featured list.
What the GDPR and the ePrivacy rules actually ask of an ad
Personalised ads, the kind AdSense serves by default, usually set cookies or read similar identifiers to recognise a visitor again. Under European law that needs an active decision by the visitor, the consent everybody talks about. Two layers stack up here, and mixing them is the most common misunderstanding.
The GDPR governs the processing of personal data and lists the possible legal bases. The consent for storing or reading anything on the visitor device comes from the ePrivacy Directive, which is not directly applicable and had to be transposed by every member state on its own. In Germany that is the TDDDG, the successor to the TTDSG. In Spain it is article 22.2 of the LSSI. In France it is article 82 of the Loi Informatique et Libertés, enforced by the CNIL. In Italy it is article 122 of the Codice privacy, in the Netherlands article 11.7a of the Telecommunicatiewet. In the United Kingdom the pair is the UK GDPR and PECR. Different laws, different regulators, the same starting point: non essential technology only after the visitor agrees.
In practice that means:
- You normally need a consent banner, or a consent management platform (CMP), through which visitors can agree or refuse. Refusing should be as easy as agreeing: one click on the same layer, not a hidden detour through a settings screen. Which of the common tools fits your site is compared in the post on cookie consent plugins for WordPress.
- Advertising that relies on consent based technologies should load only after that agreement, not before it.
- Ads are advertising and should be recognisable as such. European consumer law asks that commercial intent be identifiable, and a plain advertising label is the simplest way to satisfy it. Germany treats a clear label as good practice, the UK works through the CAP Code and the ASA, other markets have their own bodies. The same thinking applies to paid links, which we cover in affiliate link disclosure in WordPress.
The sore spot in most setups is timing. If the ad script is loaded at page load, it is often already active before anybody agreed to anything. That is why consent gating, which ties ads to the actual consent signal, is worth the ten minutes it takes to switch on.
Consent Mode v2 and Google rules of its own
Since March 2024 Google expects Consent Mode v2 from sites that show Google ads or measure with Google tags and have visitors from the EEA. Technically it passes four signals to the Google tags: ad_storage, analytics_storage, ad_user_data and ad_personalization. Without them you risk incomplete measurement, weaker remarketing and, in the worst case, restrictions on your Google Ads or AdSense account.
Google adds a rule of its own on top of the law. Publishers who serve ads to visitors in the EEA and the UK are expected to use a consent management platform from Google’s certified list, and the message itself is set up under Privacy & messaging in the AdSense account. Check that list before you settle on a banner, because swapping a consent tool after launch is a lot more work than picking the right one at the start.
Setting up AdSense in WordPress, step by step
Here is how a clean setup goes in seven steps. They follow Adjet, but the principle carries over to any well built ad setup. Steps three to five are the actual privacy work, and throwing them together is how you build exactly the gap this post is about.
1. Create the ad unit
You have two ways in. Either you paste your own ad code, from a direct advertiser for example, or you use the AdSense wizard. For banners you sell yourself, a rotation plugin is the usual route, and we went through that option in our post on AdRotate. There you enter your publisher ID and the slot ID and get a responsive ad that adapts to the space it sits in. Where those two IDs come from, and what has to happen in the AdSense account first, is walked through in adding Google AdSense to WordPress. If you want, you can additionally load Google Auto ads, where Google itself picks the positions. AdSense also expects an ads.txt file in your domain root; that file has nothing to do with consent, but it belongs to a complete setup, and we walk through it in the post on setting up ads.txt in WordPress.
2. Choose placement and targeting
Now you decide where the ad appears. For automatic placement the usual positions are ready: before or after the content, after a given paragraph, or after the first image. If you want tighter control, you place manually with a shortcode, a Gutenberg block or a widget:
[adjet_ad id="1"]
For rotating ads you bundle several units into a group that is served by weighting:
[adjet_ad_group name="header"]
Targeting narrows things further: desktop, tablet or mobile only, certain post types, categories, tags or terms, each with exclusions as well. That way you can show a mobile ad only inside one category and keep it off your sales pages.
The consent part: three jobs, three steps
Now comes the part most people skip, and it is three jobs that technically have nothing to do with each other. Something has to collect and store the visitor decision. That decision has to be reported to Google tags. And the ads have to be held back until then. A banner alone only does the first job. That is exactly where most setups fail: the visitor clicks refuse, the banner disappears looking satisfied, and in the background the ad script has been running for a while.
3. Collect and store the consent signal
You have two routes here, and you pick exactly one of them.
- The built in banner: Adjet ships a “Cookie consent” template. You create it as a block and enable it, and from then on the banner stores the decision in a cookie called
adjet_consent, with a one year lifetime andSameSite=Lax. Agree and refuse sit next to each other as two buttons on the first layer; refusing does not hide behind a settings link. In the free edition this is an all or nothing decision; splitting it into categories (necessary, statistics, marketing, preferences) with individual checkboxes belongs to Pro. - An external CMP: already running a consent tool? Then you only enter, in the Adjet settings, the name of the cookie your tool sets after agreement, and optionally the value it has to contain. From that point Adjet checks exactly that and shows no banner of its own. Once a cookie name is entered, that cookie decides whether an ad may load, even if the built in banner were still active.
Running both at once is a bad idea. Two banners asking the same question confuse the visitor, and you end up documenting two decisions that can drift apart.
4. Report the decision to Google: Consent Mode v2
A cookie on the visitor device means nothing to Google. Consent Mode v2 works with signals that are either granted or denied. The free edition emits exactly these four:
| Signal | What it covers |
|---|---|
ad_storage | Storing and reading advertising cookies on the device |
ad_user_data | Sending user data to Google for advertising purposes |
ad_personalization | Personalised advertising and remarketing |
analytics_storage | Storage for analytics, Google Analytics for example |
Two further signals belong to the standard, functionality_storage and personalization_storage. Only Adjet Pro emits those, together with the banner categories that map them onto preferences. In the source of the free edition neither of them appears at all.
Order is where most implementations fall over. Before the first Google tag there has to be a consent default with these four signals on denied. Only then may a consent update report the visitor decision. Without the default the tags fire from page load with full storage access, and Google Tag Assistant reports “consent update called before default”. Adjet therefore prints the default very early in the <head>, ahead of any gtag.js, and re-announces a decision already stored in the cookie as an update straight away. Without that re-announcement a refusal would last exactly one page view, because a page cache serves every visitor the same HTML and cannot know the cookie.
Google also distinguishes two operating modes, and that is a real decision:
- Basic: the Google tags do not load at all while there is no consent. Nothing flows without agreement, and the data is missing entirely. This is the route Adjet takes with the built in banner: the AdSense loader is held back and injected into the page only after agreement.
- Advanced: the tags load immediately but send only cookieless pings before consent, from which Google models the missing conversions. More usable data, but a request to Google before the visitor has decided. Whether that already falls under the national ePrivacy rule without any storage access is disputed. What is certain is that basic never raises the question.
There is a third route that Adjet offers as a switch and that guides almost always leave out: non personalised ads. With the setting on, Adjet sets Google’s NPA flag ahead of the AdSense loader while no consent exists. The visitor then sees advertising without personalisation instead of an empty space, and the Consent Mode signals stay on denied. The switch is off by default, and the setting says why itself: non personalised ads still set cookies for frequency capping, so in the EU you may well need consent for them too.
5. Actually hold the ads back
Only now does the gate come in. With gating active, Adjet does not render the ad code into the page but puts it inside a template element, which the browser does not execute. Once consent arrives, the frontend script takes the code out and inserts it. Without consent the slot stays empty in the literal sense: there is no waiting script inside it listening for a signal. With Auto ads, where Google picks the positions itself and there is no block to anchor to, Adjet holds back the loader address instead and adds it site wide after the decision.
6. Switch on lazy loading and the ad label
Enable lazy loading so ads load only when they move into the visible area. Technically an IntersectionObserver watches whether the block is about to become visible. That saves loading time, especially for an ad far down the page. Switch on the optional advertising label as well, so ads stay recognisable as ads.
7. Check the effect in the statistics
After going live you look at the local statistic, a 30 day chart rendered as a plain SVG that shows impressions and clicks. The data stays in your database, there are no external calls to Adjet or hafenstudios. So you see which position and which format works for your readers, and you can switch off weak placements instead of guessing. For time limited campaigns there is campaign scheduling with an optional start and end time. One number to expect: AdSense reports Ad requests in its own dashboard, and an ad request that goes unfilled never becomes an impression. The two dashboards will therefore never match exactly, and that is normal.
Proving that the gate actually holds
A checkbox in the settings is not proof. The proof is in your browser developer tools, and it takes under ten minutes. Two addresses matter: the AdSense loader comes from pagead2.googlesyndication.com, a Google Ad Manager slot from securepubads.g.doubleclick.net. Those are what you filter for.
- Open a fresh private window. Otherwise you are testing against a decision you made yourself weeks ago. Then press F12 and switch to the Network tab.
- Set the filter and reload. Type
googlesyndicationinto the filter box and load the page. Before any decision the list has to stay empty. If a line is already there, AdSense is loading too early, and no banner in the world changes that. - Look at the cookies. Under Application (Chrome) or Storage (Firefox), the cookie list should hold nothing but entries from your own domain before the decision, the WordPress session for example. Nothing from Google.
- Click refuse. The network filter stays empty, and the
adjet_consentcookie now readsdenied. - Reload without deleting anything. Still nothing from Google, and the banner must not come back. Surprisingly many setups lose this one: a banner that asks again after a reload never stored the refusal properly.
- Clear cookies, reload, agree. Now the line has to appear in the Network tab,
adjet_consenthas to readgranted, and the slot has to fill. If it does not, the gate is tight but something else is wrong. - Cross check Consent Mode. Type
dataLayerinto the console and expand the entries. The first consent entry has to be adefaultwith those four signals ondenied, theupdatecarrying your decision comes after it. If the order is reversed, Google Tag Assistant will report it too.
?adjet_debug=1 appended. Adjet then writes the first reason for every skipped position right where the ad would be: post type, page type, schedule, word count, consent or device. Only you can see it, and that view never enters a page cache. It saves you guessing whether the cause is consent or something else entirely.The banner has to be operable, not just lawful
A consent banner stands between the visitor and the entire content. Anyone who cannot operate it does not get to the site and consequently cannot consent either. This is where privacy and accessibility turn out to be the same building site: consent that can only be given with a mouse and with good eyesight is not really a free decision. Five things you can check yourself in a few minutes:
- Keyboard: load the page and press nothing but Tab. Do you reach agree and refuse, and do both fire with Enter or Space? That assumes real
buttonelements rather than clickabledivcontainers. In the built in Adjet banner they are buttons. - Visible focus: can you see where you are while tabbing? A designed away
outline: noneis the most common fault on otherwise clean banners. - Obscuring: keep tabbing into the page with the banner open. A banner glued to the bottom edge loves to cover exactly the element that currently holds focus. WCAG 2.2 has a success criterion for it, 2.4.11 Focus Not Obscured (Minimum) at level AA.
- Contrast: banner text needs at least 4.5:1 against its background under success criterion 1.4.3, and a button outline at least 3:1 under 1.4.11. Light grey on white fails here regularly, and of the two buttons it tends to be the refuse one. Chrome shows the contrast ratio right in the colour picker.
- Screen reader: the banner needs a name, otherwise it is announced as an unnamed region. The Adjet template brings
role="dialog"with anaria-labelfor that.
Where both requirements meet is the point from earlier: refusing has to be as easy to reach as agreeing, one click on the same layer. That is privacy and operability at once. Why an extra puzzle in front of a form has the same effect in a different place is in the post on captchas and accessibility.
Keeping performance in view
Ads and loading time are a balancing act. A few simple rules help:
- Less is more: three considered placements often work better than eight that clutter the page and drive readers away.
- Use lazy loading: anything below the first visible area does not have to load immediately.
- Test positions: use the statistic to remove ads with weak effect instead of constantly adding new ones.
If you are tidying up your WordPress site anyway, clean URLs and redirects are worth a look at the same time. How to solve that properly is in the post on setting up 301 redirects in WordPress, because there too the point is to handle a technical matter centrally instead of scattered.
Steer your ads instead of scattering code
Adjet brings automatic placement, an AdSense wizard, consent gating, lazy loading and local statistics into one cockpit, with no external calls. Its own cookie banner with Google Consent Mode v2 is built in.
Conclusion
Putting ads and AdSense into WordPress is not a question of quickly copied code, it is a question of clean control. Manage ads centrally, serve them only after consent, load them late and label them as advertising, and you are on solid ground technically, legally and on loading time. And because you measure what works, you end up earning more with fewer ads. Adjet is open source under GPLv2, brings its own capability (manage_adjet) and a REST API (adjet/v1), and hands none of your data to the outside. What every plugin costs, Adjet included, is on our pricing page. And to repeat the sentence that matters: this is an orientation, not legal advice, so check the fine print of your own market with someone qualified there.
Common questions about AdSense and ads in WordPress
Do I need a consent banner for AdSense in WordPress?
In the EU, usually yes. Personalised ads like AdSense normally set cookies or read similar identifiers, and that requires an active agreement. For this you need a consent banner or a consent management platform through which visitors can agree or refuse. The rule comes from the national ePrivacy law of your market, for example the TDDDG in Germany or PECR in the UK, and not from the GDPR alone. This is an orientation, not legal advice; check the details for your own market with someone qualified.
When may an ad be loaded?
Advertising that relies on consent based technologies should load only after the agreement, not already at page load. Adjet offers two routes for that: the built in consent banner, which collects the decision itself and stores it in a cookie called adjet_consent, or consent cookie gating, where you enter the cookie name of your existing consent tool and Adjet waits for that signal. In both cases the ad code sits in a template element until then, which the browser does not execute, so the slot really is empty. Lazy loading via IntersectionObserver adds a second layer, so even consented ads load when the reader scrolls them into view.
What is Google Consent Mode v2 and what happens without it?
Consent Mode v2 is how Google wants the visitor decision reported to its own tags. The free edition reports four signals on granted or denied: ad_storage, ad_user_data, ad_personalization and analytics_storage; the two preference signals functionality_storage and personalization_storage are emitted only by Adjet Pro. Since March 2024 Google expects it for visitors from the EEA whenever AdSense, Google Ads or Analytics are running. Without the report you get incomplete measurement and weaker remarketing, in the worst case restrictions on the account. Order is what matters: before the first Google tag there has to be a consent default with everything on denied, and only then may a consent update report the decision.
Is the built in Adjet banner enough or do I need a separate consent plugin?
For a site that serves ads and embeds few other third party services, the built in banner is enough: it collects the decision, stores it in the adjet_consent cookie, emits Google Consent Mode v2 and holds the ads back until someone agrees. As soon as you run many further consent based services, video embeds, map services, chat widgets and several analytics tools, a specialised consent plugin is the better choice. You then enter its cookie name in the Adjet settings and Adjet waits for that signal instead of showing a second banner. Running both at once is the worst of the options.
Do I have to label ads as advertising?
Labelling is the safe route. Ads are advertising, and European consumer law asks that commercial intent be identifiable to the reader. How that is enforced differs by country: Germany treats a clear label as good practice, the UK works through the CAP Code and the ASA. Adjet ships an optional advertising label you can simply switch on, without touching any code, so readers can always tell which content is paid and which is not.
What does lazy loading do for ads?
Lazy loading makes sure an ad is only loaded once it actually moves into the visible part of the screen. Technically an IntersectionObserver watches whether the ad block is about to become visible. That saves loading time, especially for an ad further down the page, and it protects your Core Web Vitals. In Adjet lazy loading can be switched on together with consent gating, so both mechanisms work into each other.
Is Adjet free and how do I set up AdSense with it?
Adjet is open source under GPLv2. In the official WordPress directory the free edition is listed as Hafenstudios Ads, so that is the name to search for under Plugins, Add New. You create an ad through the AdSense wizard, enter your publisher ID and slot ID and get a responsive ad that adapts to the space; optionally you also enable the Google Auto ads loader. Placement happens automatically, before or after the content for example, or manually via a shortcode like [adjet_ad id="1"], a Gutenberg block or a widget. For automation there is a REST API at adjet/v1 and a dedicated capability called manage_adjet.