likeslikes

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

  1. Register an accountcreate one here, it is free.
  2. 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.
  3. 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...32charhash

    Add it on the domain itself or on its root domain — subdomains fall back to the root, and multi-part TLDs like .com.es and .co.uk are handled correctly.

    Option B — HTML meta tag (fallback, checked on your homepage)

    <meta name="likes-verification" content="likes-a1b2c3...32charhash">
  4. 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

#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