Adding likes to your blog
This page is the canonical, always up to date guide. Two ways to integrate: the drop-in widget (two lines of HTML) or the direct API if you prefer to build your own button. Either way, start with the setup below.
1. Get set up
- Register an account — create one here, it is free.
- Create a project — on the dashboard, give it a name like “My blog”. A project groups the domains that belong to the same site, and analytics are aggregated per project.
-
Add and verify your domain — add your domain to the project.
You will get a verification token that looks like
likes-<32 hex chars>. Prove the domain is yours with either method, then add the domain again:Option A — DNS TXT record (checked first)
likes-verification=likes-a1b2c3...32charhashAdd it on the domain itself or on its root domain — subdomains fall back to the root, and multi-part TLDs like
.com.esand.co.ukare handled correctly.Option B — HTML meta tag (fallback, checked on your homepage)
<meta name="likes-verification" content="likes-a1b2c3...32charhash"> - Integrate — paste the widget below, or call the API directly.
2. The widget recommended
Drop these two lines where you want the like button to appear:
<div id="likes"></div>
<script src="https://likeslikes.net/likes.js" defer></script>
The widget renders a heart with the like count for the current page and lets each visitor like it once. A canonical URL is used when present. If its domain is not registered with Likeslikes, the widget tries the URL being visited instead.
Options
-
data-selector— attribute on the script tag; CSS selector of the container(s), default#likes. -
data-url— like a different URL than the current page. Set it on the script tag, or per container — useful on list pages with one button per post. -
data-ignore-canonical— presence-only attribute on the script tag; ignore the page's canonical URL and use the URL being visited. -
--likeslikes-color— CSS variable that sets the heart color:
#likes { --likeslikes-color: #7c3aed; }
How the page URL is chosen
The widget uses the first available URL in this order: a container's data-url,
the script's data-url, then the canonical URL (unless ignored). If a canonical on
another origin returns Domain not found, the widget retries with the current
origin and path. Explicit data-url values do not fall back automatically.
<div id="likes"></div>
<script src="https://likeslikes.net/likes.js" data-ignore-canonical defer></script>
Hugo and republished posts
Set Hugo's baseURL to the public domain visitors use so generated links,
metadata, and sitemaps use the verified domain. To make every hosting alias count likes for
that public domain, set a fixed per-page data-url in your layout or partial:
<div id="likes" data-url="{{ .Permalink }}"></div>
<script src="https://likeslikes.net/likes.js" defer></script>
Troubleshooting "Domain not found"
When neither the canonical domain nor the current domain is registered, the widget remains
visible as a disabled counter showing 0 and logs the error in the browser
console. Register the fallback hostname or use data-url to select a verified URL.
3. The direct API
Building your own button (a static site, a Hugo template, anything)? There are only two endpoints, and you always pass the full URL of the post — the domain is extracted automatically, no IDs or keys needed.
Get the like count for a URL
curl "https://likeslikes.net/api/v1/likes?url=https://myblog.com/my-post"
Returns {"likes": 5, "is_liked": false, ...} — is_liked tells
whether the calling IP has already liked this URL.
Add a like
curl -X POST https://likeslikes.net/api/v1/likes \
-H "Content-Type: application/json" \
-d '{"url": "https://myblog.com/my-post"}'
One like per visitor per URL; duplicates return an error.
A minimal JavaScript button
<button id="like-btn">❤ <span id="like-count">…</span></button>
<script>
const API = 'https://likeslikes.net';
const url = window.location.href;
const btn = document.getElementById('like-btn');
const count = document.getElementById('like-count');
fetch(`${API}/api/v1/likes?url=${encodeURIComponent(url)}`)
.then(r => r.json())
.then(({ data }) => {
count.textContent = data.likes;
btn.disabled = data.is_liked;
});
btn.addEventListener('click', () => {
fetch(`${API}/api/v1/likes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url })
})
.then(r => r.json())
.then(({ data }) => {
if (data.success) {
count.textContent = data.likes;
btn.disabled = true;
}
});
});
</script>
Good to know
- The API only works for domains verified in a project.
- One like per visitor (IP) per URL. Raw IPs are never stored — only salted hashes.
- Rate limits: 30 requests/minute, 300/hour per IP.
- URLs on
localhostalways return 999 likes, so you can develop without verifying anything. - Queries and fragments are discarded. Host case, duplicate or trailing slashes, dot segments, and percent encoding are normalized so equivalent URLs share one counter.
- All responses are wrapped in an envelope:
{"statusCode": 200, "data": {...}}.