AdMob no ads to show: publish a consent message
I swapped Google's sample AdMob app ID for the real one in a Capacitor game and no ad was requested at all. The consent check failed until I published a message in Privacy & messaging. Then, how I forced a no-fill on an emulator to test what shows instead.
Contents
ASMR Logo Craft Block is a Capacitor game: the whole thing
runs in a WebView, and ads come through @capacitor-community/admob 8.1.0 wrapped in a
shared ads package I use across my games. During development it ran on Google's sample
AdMob app ID and the sample ad units, and the test ads loaded.
Then I created the real AdMob app, pasted in the real app ID and the real ad units, and built it. Tests passed. The app started on two emulators without crashing, which is the quickest check that the manifest app ID is right, since a malformed one crashes at launch. And no ad appeared, anywhere, of any type.
Most of what comes back for admob no ads to show is about no fill: the request went out and the network had nothing to send. This was a different problem. No request went out.
The request error behind no ads to show
The plugin calls in logcat told the story by what was missing. These were the only AdMob methods the game had called (the plugin logs each call twice, hence the 2s):
2 methodName: addListener
2 methodName: requestConsentInfo
No initialize, no showBanner, no prepareInterstitial. The consent request had come back
as an error, verbatim apart from the app ID:
Publisher misconfiguration: Failed to read publisher's account configuration; no form(s)
configured for the input app ID. Verify that you have configured one or more forms for this
application and try again. Received app ID: `ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX`.
and my own package logged what it did with that:
W Capacitor/Console: ... Msg: Consent: could not find out whether consent is required
Error: Publisher misconfiguration: Failed to read publisher's account confi…
Read slowly, the error says what is wrong. "No form(s) configured" means the AdMob app has no consent message. Google's User Messaging Platform (UMP) went to fetch the one for this app ID and found none. It also prints the app ID it received, which is worth checking against the one you meant to ship.
Why nothing was requested at all
The second half of the silence is mine. My ads package follows Google's order: refresh what is known about consent, show the form if it is required and unanswered, and only then start the SDK. When the first step fails, it has to decide something. A simplified version of what it decides, not the package's own code:
// Consent state could not be found out: offline, or no consent message
// published in AdMob. A real app ID requests no ads until there is an answer.
const unknownConsent = (appId) => ({
status: 'unknown',
canRequestAds: isGoogleSampleAppId(appId),
});
With a real app ID and an unknown consent state, canRequestAds is false, and the runtime
never calls initialize. That is the behaviour I want for a player in the EU whose answer I
could not get. It is also the behaviour every player gets when the consent message does not
exist, which is how a release build would have earned nothing from anyone.
The exception for the sample app ID is why it hid the whole time. On the sample ID the package lets ads through whatever the consent check says, so development never ran into the missing message. The day the real ID went in was the first day the rule applied. I wrote that exception believing the sample ID never has a consent message. That turned out to be wrong: in a later test the sample ID showed Google's demo consent form with nothing set up in the console. The exception still did what it did here, but not for the reason I gave it.
What the Google Mobile Ads SDK would have done on its own in that state, without my gate in front of it, I did not test. My package never got that far, by design, so I have nothing to report about it.
Publishing the consent message
The fix is all in the AdMob console:
- Privacy & messaging → European regulations → Create message, for the app whose ID was in the error.
- Publish it. A message that is created but not published is still no message.
- In that message's settings, turn on consent mode.
- Optionally, Privacy & messaging → US state regulations, and publish a message there too.
Only the first two are what I tested as the fix. Consent mode and the US message were part of the same setup, and nothing below depends on them.
What changed after publishing
About forty minutes after the first failure I rebuilt for an unrelated change and launched
again. This time the log had a third method in it, showConsentForm, and the emulator was
sitting on a real consent dialog headed "ASMR Logo Craft Block asks for your consent". I
tapped Consent and read the plugin calls again:
2 methodName: requestConsentInfo
2 methodName: showConsentForm
2 methodName: initialize
2 methodName: showBanner
2 methodName: prepareInterstitial
2 methodName: prepareRewardVideoAd
2 methodName: loadAppOpen
2 bannerAdLoaded
3 interstitialAdLoaded
3 onRewardedVideoAdLoaded
3 appOpenAdLoaded
The same list also had a few FailedToLoad lines for each type. I did not chase them that
day; the question was whether requests were happening at all, and they were.
These are test ads, and that is fine. An emulator is always treated as a test device, which I wrote about in AdMob limited ad serving on the wrong app: it makes the emulator safe for checking a real app ID, and useless for checking anything about the account. The thing being checked here was the consent step, and that is the same on an emulator as on a phone.
That post is the other common reason real IDs earn little or nothing, and the two are easy to
confuse. Limited ad serving means the requests go out and come back thin. A missing consent
message, with a gate like mine, means the requests never go out. Logcat tells them apart in a
second: look for initialize.
When AdMob has no ad to show: a house banner
The same day I wanted something in the banner strip for when AdMob really does come back empty. Each of my games now carries a list of my other games, taken from my Play developer page, with the current game left out. When the banner fails to load, one of them takes the strip, styled like the game, and a tap opens its Play page. I kept it to the banner. An interstitial house ad brings pacing questions I did not want to answer yet.
The rule in the ads package is short. On every banner FailedToLoad, the backfill gets the
strip and AdMob is asked again. Normally that retry waits 15 s, doubling per failure up to five
minutes; while the house banner holds the strip the wait is a fixed minute, so it never keeps
AdMob's place for long. The first AdMob banner that loads takes the strip back.
Making an AdMob test ad unit have no ad to show
Testing that was harder than writing it. Google's test ad units always fill, so on an emulator AdMob never fails, and the backfill never shows.
I made it fail at the network instead, with an ad-blocking Private DNS on the emulator:
adb shell settings put global private_dns_mode hostname
adb shell settings put global private_dns_specifier dns.adguard-dns.com
The first check was a surprise. googleads.g.doubleclick.net, the host AdMob sends ad
requests to, still resolved to a real address, even though dumpsys connectivity said
Private DNS was on and validated. I tried Mullvad's ad-blocking resolver and the emulator gave
me the same real address. Then I asked AdGuard directly from my computer:
dig +short @94.140.14.14 googleads.g.doubleclick.net
0.0.0.0
So AdGuard did block it, and the emulator had most likely kept an answer from before the switch. Toggling airplane mode off and on cleared it:
adb shell cmd connectivity airplane-mode enable
adb shell cmd connectivity airplane-mode disable
After that the ad host resolved to 127.0.0.1 on both emulators, and play.google.com still
resolved normally. That second part matters: the house banner opens a Play page, so blocking
all of Google would have broken the thing I was testing.
Strictly, this forces a failed load, not a no-fill. I never captured the error code the
banner failed with. For this test the difference does not matter, because the backfill reacts
to any FailedToLoad, but it would matter if your code treats a network error and a no-fill
differently.
What the forced failure showed
On the phone emulator the house banner came up in the strip, rotating through my other games (Word Slot was one it logged as shown). A tap opened the Play sheet for the game on screen. Coming back from Play logged no app-open request, which is right: the game sent the player out, so the return is not a return worth an ad.
Then I turned the DNS off again. The AdMob test banner loaded ten seconds later and took the strip back.
On the tablet emulator, still blocked, I left the game open for two and a half minutes and read the banner calls:
+0:00 bannerAdFailedToLoad
+0:01 methodName: showBanner
+0:03 bannerAdFailedToLoad
+1:03 methodName: showBanner
+1:03 bannerAdFailedToLoad
+2:03 methodName: showBanner
+2:03 bannerAdFailedToLoad
One retry a minute, to the second, with the house banner in the strip the whole time.
What I have not done is make this repeatable. It is a hand check on two Android emulators, and
the DNS has to be put back afterwards with private_dns_mode off. A proper device test would
need a no-fill switch in the test build, and that does not exist yet. Nothing here touched
iOS; all my games are Android-only.
Takeaway
Publish the consent message before the real AdMob app ID goes into a build, and check for it
the first time you run with real IDs: if logcat shows requestConsentInfo and nothing after
it, nothing is being requested. If your consent code has a fallback for the sample ID, as mine
does, remember it is hiding exactly this failure until the day you switch.
And when you need AdMob to come back empty on an emulator, block its ad host with Private DNS, check from outside the device that the resolver really blocks it, then toggle airplane mode so the emulator stops answering from memory.