How to Make a Game Website Like Poki (HTML5 Games and AdSense)
Build a free online games website: where the games come from, who earns from the ads inside them, what AdSense checks on a game site, the pages that make it rank, and the setup step by step.
Sites like Poki, CrazyGames and Y8 look simple: a grid of games, you click one, it plays in the browser. Behind that grid are three decisions most new game sites get wrong: where the games come from, who earns from the ads inside them, and what is on each page apart from the game. Get those right and a game site can rank and earn. Get them wrong and AdSense refuses it as “low value content”.
This guide covers all three, then the setup itself: the hosting you need, the pages that bring search traffic, the speed rules, and the problems that come up.
How a game website makes money
| Source | How it works |
|---|---|
| Display ads on the page | Normal AdSense units around the game and in the lists, never on top of the game |
| Ads inside the game | Full-screen ads between levels and rewarded ads (watch an ad for an extra life). Google's version is H5 Games Ads. |
| A revenue share from a game network | When you embed a game hosted by a distribution network, it shows its own ads in the game and shares the income under its publisher terms |
| An Android app | The same games in an app with AdMob |
One rule from Google's own documentation decides most of this: whoever hosts the game controls the ads inside it. If the game file is on your server, you choose the ads and the income is yours. If you embed a game that a network hosts, the network's ads play inside it and you get the share its terms give you.
Try it: a one-page game website you can run now
Before the business side, here is a complete game page with a real game in it. Copy the code into a file called index.html, double-click it, and it plays in your browser. Upload the same file to your hosting and it is a live game website.
The game is Star Catcher: stars fall, you move a basket to catch them, and three misses end the game. It works with the arrow keys on a computer and with your finger on a phone, keeps a best score, and gets faster as you score.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Star Catcher – Play Free Online | My Games</title>
<meta name="description" content="Play Star Catcher free in your browser. Catch the falling stars and do not miss three. Works on phone and computer, no download.">
<style>
body { margin: 0; font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; }
header { padding: 14px 20px; background: #1e293b; font-size: 20px; font-weight: 800; }
main { max-width: 760px; margin: 0 auto; padding: 20px; }
h1 { margin: 0 0 6px; }
.hud { display: flex; justify-content: space-between; margin: 14px 0 8px; font-weight: 700; }
.stage { position: relative; border-radius: 14px; overflow: hidden; background: #020617; }
canvas { display: block; width: 100%; height: auto; touch-action: none; }
.overlay { position: absolute; inset: 0; display: grid; place-items: center; text-align: center;
background: rgba(2, 6, 23, .75); }
button { font: inherit; font-weight: 800; padding: 12px 30px; border: 0; border-radius: 999px;
background: #f59e0b; color: #111827; cursor: pointer; }
</style>
</head>
<body>
<header>My Games</header>
<main>
<h1>Star Catcher</h1>
<p>Catch the falling stars with your basket. Miss three and the game is over.</p>
<div class="hud">
<span id="score">Score: 0</span><span id="best">Best: 0</span><span id="lives">Lives: 3</span>
</div>
<div class="stage">
<canvas id="game" width="640" height="400"></canvas>
<div class="overlay" id="overlay">
<div><p id="msg">Ready?</p><button id="play">Play</button></div>
</div>
</div>
<h2>How to play</h2>
<p>On a computer, move the basket with the left and right arrow keys. On a phone, drag your
finger across the game. Every star is one point, and the stars fall faster as your score grows.</p>
</main>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const keys = {};
let basket, stars, score, lives, running = false, lastSpawn = 0, best = 0;
try { best = Number(localStorage.getItem('starBest')) || 0; } catch (e) {}
document.getElementById('best').textContent = 'Best: ' + best;
function reset() {
basket = { x: W / 2 - 45, y: H - 30, w: 90, h: 16 };
stars = []; score = 0; lives = 3; lastSpawn = 0;
showScore();
}
function showScore() {
document.getElementById('score').textContent = 'Score: ' + score;
document.getElementById('lives').textContent = 'Lives: ' + lives;
}
function update(time) {
if (keys.ArrowLeft) basket.x -= 7;
if (keys.ArrowRight) basket.x += 7;
basket.x = Math.max(0, Math.min(W - basket.w, basket.x));
// a new star every 0.9 s at first, more often as the score grows
if (time - lastSpawn > Math.max(350, 900 - score * 15)) {
stars.push({ x: 15 + Math.random() * (W - 30), y: -10, r: 10 });
lastSpawn = time;
}
const speed = 2 + score * 0.08;
for (const s of stars) {
s.y += speed;
const inBasket = s.y + s.r >= basket.y && s.y - s.r <= basket.y + basket.h &&
s.x >= basket.x && s.x <= basket.x + basket.w;
if (inBasket) { s.done = true; score++; showScore(); }
else if (s.y > H + s.r) { s.done = true; lives--; showScore(); }
}
stars = stars.filter(s => !s.done);
if (lives <= 0) gameOver();
}
function draw() {
ctx.fillStyle = '#020617';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fbbf24';
for (const s of stars) {
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.fill();
}
ctx.fillStyle = '#38bdf8';
ctx.fillRect(basket.x, basket.y, basket.w, basket.h);
}
function loop(time) {
if (!running) return;
update(time);
draw();
requestAnimationFrame(loop);
}
function start() {
reset();
running = true;
document.getElementById('overlay').style.display = 'none';
requestAnimationFrame(loop);
}
function gameOver() {
running = false;
if (score > best) {
best = score;
try { localStorage.setItem('starBest', best); } catch (e) {}
}
document.getElementById('best').textContent = 'Best: ' + best;
document.getElementById('msg').textContent = 'Game over! Your score: ' + score;
document.getElementById('play').textContent = 'Play again';
document.getElementById('overlay').style.display = 'grid';
}
document.getElementById('play').onclick = start;
addEventListener('keydown', e => {
keys[e.key] = true;
if (running && e.key.startsWith('Arrow')) e.preventDefault(); // no page scrolling while playing
});
addEventListener('keyup', e => { keys[e.key] = false; });
canvas.addEventListener('pointermove', e => { // mouse and finger
const box = canvas.getBoundingClientRect();
basket.x = (e.clientX - box.left) * (W / box.width) - basket.w / 2;
});
reset();
draw();
</script>
</body>
</html>
| Part | What it does |
|---|---|
<title> and description | The game's name and a one-line summary: what Google shows in search results |
<canvas> | The game is drawn here, 60 times a second, by requestAnimationFrame |
| The Play button | Nothing runs until the player presses it, so the page opens fast |
update() | Moves the basket and the stars, counts catches and misses |
draw() | Paints the background, the stars and the basket |
pointermove and keydown | Finger or mouse, and the arrow keys |
localStorage | Remembers the best score on that phone or computer |
| “How to play” | Real text on the page: it helps the player, and it is what Google and AdSense need to see |
A game website is many pages like this one: one per game, a home page that lists them, and category pages. The rest of this guide is about filling those pages with games and making them earn.
Where the games come from
| Option | Ads inside the game | Good for |
|---|---|---|
| Your own games | Yours | The most control and the most income per player |
| Games you buy or license | Yours, if the licence allows hosting and ads | Growing the catalogue fast with games that are only on your site |
| Games embedded from a distribution network | The network's, with a revenue share | Hundreds of games on day one, without hosting them |
Most sites mix them: a large catalogue from a network, plus a few hosted games of their own on the front page. Whatever you use, only publish games you have the right to publish. Copied games are the fastest way to lose an AdSense account, because copyright is the first policy Google lists for game sites.
What AdSense checks on a game site
Google says any publisher with an HTML5 games site can apply, as long as the site follows the AdSense program policies. In practice game sites are refused for the same few reasons:
- Thin pages. A page with only a game and a title is “low value content”. Every game page needs its own text: what the game is, how to play, the controls, tips.
- The same text as every other site. Network games come with a description, and hundreds of sites paste the same one. Write your own.
- More ads than content, or ads placed over or right next to the game where players click them by accident.
- Missing basics: About, Contact and a Privacy Policy page, and a site that works on phones.
Apply with 20 to 30 games that each have a real description, not 500 with none. In-game ads through H5 Games Ads are a separate application that you make after your site is approved.
The pages that bring search traffic
People search for a game by its name (“neon racer online”), by type (“car games”, “2 player games”) and by situation (“games to play at school”). Your site needs a page for each:
- A page per game with the name in the title, a short intro, a “how to play” section and the controls. This is the page that ranks for the game's name.
- A page per category with a real introduction at the top, not only a grid. Category pages rank for the type searches.
- New, popular and featured lists on the home page, so search engines find new games quickly.
- A sitemap with every game and category, sent to Google Search Console.
Keep it fast: load the game only on Play
A game is often several megabytes. If it starts loading with the page, the page is slow, Google scores it badly, and the text that should rank is hidden behind a loading bar. Show the text first and load the game when the player presses Play:
<!-- the game page: text on the page, the game itself loads on Play -->
<h1>Neon Racer</h1>
<p>Race through a neon city and dodge the traffic. Free, no download.</p>
<button id="play">Play</button>
<div id="stage"></div>
<h2>How to play</h2>
<p>Arrow keys or swipe to change lanes. Collect boosts, avoid the cars.</p>
<script>
document.getElementById('play').onclick = function () {
var f = document.createElement('iframe');
f.src = '/games/neon-racer/index.html';
f.allow = 'fullscreen; autoplay';
f.style.cssText = 'width:100%;aspect-ratio:16/9;border:0';
document.getElementById('stage').appendChild(f);
this.remove();
};
</script>
The same page is also lighter for players who only came to read the controls, and it keeps the ad units on the page from competing with the game for bandwidth.
Ads inside your own games
For games you host yourself, H5 Games Ads use two functions from Google's Ad Placement API: adConfig() once when the game starts, and adBreak() at natural pauses, such as between levels, or for a rewarded ad. While you build, the data-adbreak-test="on" setting shows test ads:
<script async
data-adbreak-test="on"
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-0000000000000000"
crossorigin="anonymous"></script>
<script>
window.adsbygoogle = window.adsbygoogle || [];
var adBreak = adConfig = function (o) { adsbygoogle.push(o); };
adConfig({ preloadAdBreaks: 'on' });
// between two levels: pause the game and its sound while the ad shows
adBreak({
type: 'next',
name: 'level-complete',
beforeAd: function () { game.pause(); },
afterAd: function () { game.resume(); }
});
</script>
Remove the test setting only after Google has approved your site for H5 Games Ads. Never call an ad in the middle of play or on a timer: that breaks the placement policy.
Step by step
- Pick a niche or a clear identity, for example puzzle games, or games that run well on school computers. A focused site ranks faster than one more general portal.
- Get a domain and hosting. Games are static files, so normal shared hosting with PHP and MySQL is enough to start. Turn on HTTPS: browsers block many game features on plain HTTP.
- Install the site: WordPress with a games theme, your own code, or a ready game portal script.
- Add 20 to 30 games, each with your own description, how to play, controls and a clear thumbnail.
- Write the category introductions and the About, Contact and Privacy Policy pages.
- Submit the sitemap in Google Search Console.
- Apply for AdSense, then for H5 Games Ads once the site is approved.
- Add a few games a week and update the home page lists. Fresh games bring search visits back.
Common problems
| Problem | Usual cause |
|---|---|
| The game shows a blank box | The game is on http:// while your site is on HTTPS, or the game's host does not allow other sites to frame it |
| AdSense says “low value content” | Game pages with no text of their own, or the same description every other site uses |
| Pages are slow on phones | The game loads with the page. Load it on Play instead. |
| Games do not work on phones | Keyboard-only games. Mark them as desktop games, or choose games with touch controls. |
| Search Console shows duplicate pages | The same game under several addresses, for example once per category. Give each game one address. |
What it costs
| Item | Cost |
|---|---|
| Domain | About $10 a year |
| Hosting | From about $3 a month; more bandwidth as players grow |
| Games | Free from networks, or the price of the licence for games only you host |
| Your time | Mostly writing: a few hundred words for each game and category |
A ready game website
Our HTML5 game portal PHP script is built around everything above: a page per game with a description, how to play and an FAQ; category pages with your own introduction; Hot, New and Most played lists; games that load only when Play is pressed; a sitemap, structured data and an llms.txt for AI search; and an admin panel where you add games, upload thumbnails (saved as WebP) and see scores. Four sample games are included.
The same backend also runs our mini games Android app, so one admin panel serves both your website and your app.