1. Add ads.txt
ads.txt is a plain text file at the root of your domain that lists who is allowed to sell ads on it. It stops other people selling fake inventory under your name, and AdSense warns that your earnings are "at risk" until it finds one.
For AdSense it's a single line:
google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0
The four parts are:
- google.com, the ad system.
- Your publisher ID, found in AdSense under Account, Settings. It's the number from your
ca-pub-code withpub-in front. - DIRECT, meaning you have a direct relationship with Google rather than going through a reseller.
- f08c47fec0942fa0, Google's certification authority ID. It's the same for every AdSense publisher.
Save it as ads.txt in the root of the site, next to your homepage, so it's served at https://yourdomain.com/ads.txt. Then check that it really is there and served as text:
curl -sI https://yourdomain.com/ads.txt | grep -iE "^HTTP|content-type"
# HTTP/2 200
# content-type: text/plain
AdSense re-checks on its own schedule. It can take a few days for the status to change from "Not found" to "Authorized".

2. Create a manual ad unit
AdSense offers two approaches: Auto ads, where Google decides where ads go, and ad units, where you place each ad yourself.
On a blog, Auto ads can be fine. On anything interactive I use manual units, because Auto ads will happily drop an ad into the middle of content your JavaScript is about to redraw. My Website QA tool rebuilds its report several times as results arrive, so an injected ad would either be wiped out or land in the middle of a list of issues.
In AdSense, go to Ads, By ad unit, Display ads, give it a name, choose Responsive, and create it. The only thing you need from the code it shows you is the data-ad-slot number.
3. Place it on the page
This is the standard snippet. The script loads once per page; the <ins> element is the ad slot:
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-0000000000000000"
crossorigin="anonymous"></script>
<div class="ad-frame">
<p class="ad-label">Advertisement</p>
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-0000000000000000"
data-ad-slot="1234567890"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
</div>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
A few rules I follow when choosing where it goes:
- Outside anything JavaScript re-renders. If your page rebuilds a section with
innerHTML, the ad must sit outside that section. - Not next to buttons or navigation. AdSense policy prohibits placements that encourage accidental clicks.
- Labelled. "Advertisement" or "Sponsored links" is allowed and makes it clear what it is.
- Not in the print version. I hide ad frames in the print stylesheet.
4. Hide empty slots
Ads don't always fill. When the site isn't approved yet, or there's no ad to show, you're left with an empty labeled box, which looks broken.
AdSense sets a data-ad-status attribute on the slot once it decides: filled or unfilled. You can hide empty slots with one CSS rule:
ins.adsbygoogle[data-ad-status="unfilled"] { display: none !important; }
I go one step further and keep the whole frame, label included, invisible until Google reports the slot as filled. The trick is that AdSense needs to measure the slot's width, so it can't be display: none while it waits:
/* Laid out (AdSense needs the width), but invisible until filled */
.ad-frame.is-pending { visibility: hidden; height: 0; padding: 0; margin-block: 0; overflow: hidden; }const ins = frame.querySelector('ins.adsbygoogle');
frame.classList.add('is-pending');
new MutationObserver(() => {
const status = ins.getAttribute('data-ad-status');
if (status === 'filled') frame.classList.remove('is-pending');
if (status === 'unfilled') frame.hidden = true;
}).observe(ins, { attributes: true, attributeFilter: ['data-ad-status'] });
5. Consent and privacy
If any of your visitors are in the European Economic Area, the UK or Switzerland, Google requires a certified consent message before it serves personalized ads. You don't need a third-party cookie banner for this: AdSense has one built in under Privacy and messaging, European regulations. Create the message there, publish it, and the ad script shows it automatically.
Your privacy policy also needs to say that you use AdSense, that Google and its partners use cookies to serve ads based on previous visits, and how visitors can opt out through Google's Ads Settings. Mine is on the privacy page if you want a reference.
6. Getting approved
None of the above shows a single ad until Google approves the site. Approval is a review of the whole site, not one page, and the most common rejection is "Low value content".
I'll be honest: that's the status this site had the first time I checked. A portfolio with a few pages, some tools and three blog posts didn't give a reviewer much to read.
What the review is looking for, as far as I can tell from Google's own guidance:
- Original, useful content that someone would come back to, not pages written for ad space.
- Enough of it. Tool pages and a handful of posts usually aren't enough on their own.
- Clear navigation, plus an about page, contact details and a privacy policy.
- No thin or duplicated pages dragging the average down.
Once you've made real improvements, request a review from the Sites page in AdSense. Reviews can take from a few days to a few weeks, and if you're rejected again, add more substance before reapplying rather than resubmitting straight away.
Wrapping up
On a static site, AdSense is three files' worth of work: ads.txt at the root, one script tag, and an ad slot placed somewhere it won't get in the way. The parts that take thought are where the slot goes, what happens when it's empty, and having a site worth approving in the first place.
Keep reading
Core Web Vitals Explained for Non-Developers (and What to Fix First)
What LCP, INP and CLS measure, what good looks like, lab vs field data, and the fixes that matter most, explained in plain English with a real example.
Read the post GuideHow I QA a Website Before Handover (My Actual Checklist)
The pre-launch QA checklist I run on every website before handover, from content and forms to SEO, speed and go-live, and the order I run it in.
Read the post Free toolWebsite QA Audit
Paste a URL and get a full QA pass: speed, SEO, accessibility, security, broken links and content slips, with a fix for each issue.
Run an audit