Articles on: Overlay
This article is also available in:

How to Create Custom Overlay


Want your stream to look cooler with your own custom overlay? A custom overlay is like a blank canvas you build with HTML. Make special messages, fun animations, or branding that makes people instantly remember you!


What Overlay Types Can Be Customized?


  1. Alert (Doesn't include media like Voice Note or GIF)
  2. Media Share (Only the message part — media/video and progress bar can't be customized)
  3. Leaderboard
  4. Timer (Re-rendered every time the time changes)
  5. Soundboard
  6. Milestone
  7. Polling
  8. Running Text
  9. VIP Queue
  10. Song Share (Song info, queue, and visual player — progress bar, play/pause, cover art — fully customizable with variables. The audio engine itself is still managed by Tako, can't be swapped/controlled from the overlay.)



HTML Writing Tips


  1. Use standard HTML structure: Always start with <html>, <head>, and <body>.
  2. Don't use position: absolute on the body CSS: Make sure the body has a definite height — Tako's overlay system detects the body's size, and if it can't detect it, the overlay won't show (the system treats the body height as 0px when it uses absolute positioning).
  3. Use flexible sizes: Use px, rem, or em for fonts/elements. Don't use vh or vw.
  4. Prefer rem — it scales properly with the font-size setting in the overlay's dashboard settings.
  5. Always test in the dashboard so it looks as expected.
  6. Don't use // comments inside <script>: Tako's overlay system strips every newline from your code before rendering. A // comment only stops at a newline — once the newline is gone, that comment can "swallow" the rest of the script, breaking logic below it with no visible error. If you need a comment, use /* ... */, or safer — skip comments entirely.
  7. Write JSON data (arrays/objects) literally, don't wrap it in quotes: Write const data = {{variableName}}; — NOT const data = JSON.parse('{{variableName}}'). The JSON content already uses double quotes ("), and wrapping it in quotes again will cut your JS string in the middle, breaking the script.
  8. Don't set a fixed max-width on the main container: The overlay box size (width/height) is already set by you via OBS Browser Source or the dashboard preview. If your code uses a fixed max-width (e.g. 26rem), the box gets capped there and leaves empty space if the real box is wider. Use width: 100% to fill the box size you've configured.



Variables for Each Overlay


Every overlay has variables you can use to make the display dynamic without manual edits. Full list below, including which overlay supports what and usage examples:


⚠️ Make sure there's no space between the curly braces and the variable name!


1. Alert & Media Share


Variable Name

Example Value

Description

{{amount}}

15000

Donation amount (raw number).

{{formattedAmount}}

IDR 15.000

Formatted donation amount.

{{message}}

Keep it up!

Message from the donor.

{{gifterName}}

BudiGaming

Donor's name.

{{gifterPicture}}

https://example.com/profile.jpg

Donor's profile picture URL.

{{gifterBadges}}

VIP,TopDonator

Donor's badge names, comma-separated.

{{isGifterVerified}}

true or false

Donor's verification status (true if verified).

{{mediaDuration}}

10 (seconds)

Media duration on alert/mediashare; if there's no media, it's the alert's duration instead.


⚠️ Tako doesn’t send badge icon URLs. If you want the badge to appear as an image (not text/a tag), manually fill in the BADGE_ICON_URLS in the script below: key = the exact badge name as it appears in {{gifterBadges}}, value = the URL of that badge icon. Badges not on that list will automatically fall back to a regular text tag.


The code example below has been pre-filled with 6 official Tako system badges (name + URL):


Badge Name

Icon URL

Tako Verified Partner

https://tako.id/images/badges/partnered-creator.png

Tako Verified Creator

https://tako.id/images/badges/verified-creator.png

Rich Rich Club

https://tako.id/images/badges/king.png

Charity

https://tako.id/images/badges/charity.png

VTuber

https://tako.id/images/badges/vtuber.png

Tako Team

https://tako.id/images/badges/admin.png


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; }
.ticket {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
background: #f2e9d3;
border: 2px solid #1c1712;
box-shadow: 0.2rem 0.25rem 0 0 #1c1712;
padding: 0.9rem 1rem 0.9rem 1.5rem;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 0.85rem; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, 0); z-index: 2; }
.gifter-row { display: flex; align-items: center; gap: 0.6rem; }
.avatar { width: 2.6rem; height: 2.6rem; border-radius: 50%; border: 2px solid #1c1712; object-fit: cover; flex-shrink: 0; display: none; }
.avatar-fallback { width: 2.6rem; height: 2.6rem; border-radius: 50%; border: 2px solid #1c1712; background: #e8dab8; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
.gifter-info { flex: 1; min-width: 0; }
.gifter-name { font-weight: 700; font-size: 1.0625rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.badges { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; margin-top: 0.2rem; }
.badge-tag { font-size: 0.75rem; border: 1px solid #7a7160; color: #7a7160; padding: 0.05rem 0.35rem; }
.badge-icon { height: 1rem; width: auto; display: block; }
.amount { font-weight: 700; font-size: 1.125rem; color: #b6842c; flex-shrink: 0; }
.perf { border-bottom: 1px dashed #7a7160; margin: 0.6rem 0; }
.message { font-size: 1rem; color: #4a4335; }
.media-tag { display: inline-block; margin-top: 0.4rem; font-size: 0.75rem; color: #7a7160; border: 1px solid #7a7160; padding: 0.05rem 0.35rem; }
</style>
</head>
<body>
<div class="ticket">
<div class="tab"></div>
<div class="grommet"></div>

<div class="gifter-row">
<img class="avatar" id="avatar" alt="">
<span class="avatar-fallback" id="avatar-fallback">🎁</span>
<div class="gifter-info">
<div class="gifter-name" id="gifter-name">{{gifterName}}</div>
<div class="badges" id="badges"></div>
</div>
<div class="amount">{{formattedAmount}}</div>
</div>
<div class="perf"></div>
<div class="message" id="message">{{message}}</div>
<div class="media-tag" id="media-tag"></div>
</div>

<script>
const picture = "{{gifterPicture}}".trim();
const badgesRaw = "{{gifterBadges}}".trim();
const isVerified = "{{isGifterVerified}}" === "true";
const mediaDuration = "{{mediaDuration}}";

const BADGE_ICON_URLS = {
"Tako Verified Partner": "https://tako.id/images/badges/partnered-creator.png",
"Tako Verified Creator": "https://tako.id/images/badges/verified-creator.png",
"Rich Rich Club": "https://tako.id/images/badges/king.png",
"Charity": "https://tako.id/images/badges/charity.png",
"VTuber": "https://tako.id/images/badges/vtuber.png",
"Tako Team": "https://tako.id/images/badges/admin.png"
};

const avatar = document.getElementById("avatar");
const avatarFallback = document.getElementById("avatar-fallback");
const gifterName = document.getElementById("gifter-name");
const badgesEl = document.getElementById("badges");
const messageEl = document.getElementById("message");
const mediaTag = document.getElementById("media-tag");

if (picture && !picture.includes("{{")) {
avatar.src = picture;
avatar.style.display = "block";
avatarFallback.style.display = "none";
avatar.onerror = () => {
avatar.style.display = "none";
avatarFallback.style.display = "flex";
};
}

if (isVerified) gifterName.appendChild(document.createTextNode(" ✓"));

if (badgesRaw && !badgesRaw.includes("{{")) {
badgesRaw.split(",").filter(Boolean).forEach((name) => {
const iconUrl = BADGE_ICON_URLS[name];

if (iconUrl) {
const icon = document.createElement("img");
icon.className = "badge-icon";
icon.src = iconUrl;
icon.alt = name;
icon.title = name;
badgesEl.appendChild(icon);
} else {
const tag = document.createElement("span");
tag.className = "badge-tag";
tag.textContent = name;
badgesEl.appendChild(tag);
}
});
}

if (!messageEl.textContent.trim() || messageEl.textContent.includes("{{")) {
messageEl.style.display = "none";
}

if (mediaDuration && !mediaDuration.includes("{{") && Number(mediaDuration) > 0) {
mediaTag.textContent = "⏱ " + mediaDuration + "s";
} else {
mediaTag.style.display = "none";
}
</script>
</body>
</html>



2. Leaderboard


Variable Name

Example Value

Description

{{title}}

Top Donors This Month

Leaderboard title from settings.

{{rankings}}

[{"name":"BudiGaming","picture":"https://example.com/profile.jpg","badges":"VIP,TopDonator","amount":15000,"formattedAmount":"Rp15.000"}, ...]

Ranking list (JSON): name, picture, badges (names only, comma-separated — no icon URL), donation amount, formatted amount.


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; }
.ticket {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
background: #f2e9d3;
border: 2px solid #1c1712;
box-shadow: 0.2rem 0.25rem 0 0 #1c1712;
padding: 0.9rem 1rem 0.9rem 1.5rem;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 0.85rem; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, 0); z-index: 2; }
.title { font-size: 1.0625rem; font-weight: 700; }
.perf { border-bottom: 1px dashed #7a7160; margin: 0.6rem 0; }
.rank-row { display: flex; align-items: center; gap: 0.6rem; font-size: 1rem; padding: 0.35rem 0; border-bottom: 1px dashed #e8dab8; }
.rank-row:last-child { border-bottom: none; }
.rank-num { width: 1.6rem; font-weight: 700; color: #b6842c; flex-shrink: 0; }
.rank-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rank-badges { display: flex; align-items: center; gap: 0.2rem; flex-shrink: 0; }
.rank-badge-tag { font-size: 0.75rem; border: 1px solid #7a7160; color: #7a7160; padding: 0.05rem 0.35rem; }
.rank-badge-icon { height: 0.9375rem; width: auto; display: block; }
.rank-amount { font-weight: 700; color: #7a7160; flex-shrink: 0; }
</style>
</head>
<body>
<div class="ticket">
<div class="tab"></div>
<div class="grommet"></div>

<div class="title" id="lb-title">{{title}}</div>
<div class="perf"></div>
<div id="lb-list"></div>
</div>

<script>
const rankings = {{rankings}};
const list = document.getElementById("lb-list");

const BADGE_ICON_URLS = {
"Tako Verified Partner": "https://tako.id/images/badges/partnered-creator.png",
"Tako Verified Creator": "https://tako.id/images/badges/verified-creator.png",
"Rich Rich Club": "https://tako.id/images/badges/king.png",
"Charity": "https://tako.id/images/badges/charity.png",
"VTuber": "https://tako.id/images/badges/vtuber.png",
"Tako Team": "https://tako.id/images/badges/admin.png"
};

rankings.forEach((r, i) => {
const row = document.createElement("div");
row.className = "rank-row";

const num = document.createElement("span");
num.className = "rank-num";
num.textContent = "#" + (i + 1);

const name = document.createElement("span");
name.className = "rank-name";
name.textContent = r.name;

const badges = document.createElement("div");
badges.className = "rank-badges";
(r.badges || "").split(",").filter(Boolean).forEach((badgeName) => {
const iconUrl = BADGE_ICON_URLS[badgeName];

if (iconUrl) {
const icon = document.createElement("img");
icon.className = "rank-badge-icon";
icon.src = iconUrl;
icon.alt = badgeName;
icon.title = badgeName;
badges.appendChild(icon);
} else {
const tag = document.createElement("span");
tag.className = "rank-badge-tag";
tag.textContent = badgeName;
badges.appendChild(tag);
}
});

const amount = document.createElement("span");
amount.className = "rank-amount";
amount.textContent = r.formattedAmount;

row.appendChild(num);
row.appendChild(name);
row.appendChild(badges);
row.appendChild(amount);
list.appendChild(row);
});
</script>
</body>
</html>



3. Milestone


Variable Name

Example Value

Description

{{title}}

Around the Archipelago

Milestone title from settings.

{{currentAmount}}

75000

Total raised across all steps (raw number).

{{formattedCurrentAmount}}

IDR 75.000

Total raised, formatted.

{{targetAmount}}

100000

Total target across all steps (raw number).

{{formattedTargetAmount}}

IDR 100.000

Total target, formatted.

{{overallPercentage}}

75

Overall progress percentage (0-100, can exceed 100 if overshot).

{{stepProgress}}

25000

Progress within the currently active step only (raw number).

{{formattedStepProgress}}

IDR 25.000

Active step's progress, formatted.

{{stepTarget}}

50000

Currently active step's target (raw number).

{{formattedStepTarget}}

IDR 50.000

Active step's target, formatted.

{{percentage}}

50

Active step's progress percentage (0-100).

{{stepIndex}}

2

Active step's order number (starts at 1).

{{stepCount}}

4

Total number of steps.

{{stepTitle}}

Step 2: Bali

Active step's title (empty if not set).

{{steps}} / {{milestoneSteps}}

[{"title":"Step 1","target":25000,"currentAmount":25000,"percentage":100,"isActive":false}, ...]

List of all steps (JSON), each item has formattedTarget/formattedCurrentAmount.


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; }
.ticket {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
background: #f2e9d3;
border: 2px solid #1c1712;
box-shadow: 0.2rem 0.25rem 0 0 #1c1712;
padding: 0.9rem 1rem 0.9rem 1.5rem;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 0.85rem; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, 0); z-index: 2; }
.title { font-size: 1.0625rem; font-weight: 700; }
.perf { border-bottom: 1px dashed #7a7160; margin: 0.6rem 0; }
.bar-bg { height: 0.7rem; background: #e8dab8; border: 1px solid #1c1712; }
.bar-fill { height: 100%; background: #b6842c; }
.amounts { display: flex; justify-content: space-between; font-size: 1rem; font-weight: 700; margin-top: 0.45rem; }
.meta { font-size: 0.8125rem; color: #7a7160; margin-top: 0.45rem; }
</style>
</head>
<body>
<div class="ticket">
<div class="tab"></div>
<div class="grommet"></div>

<div class="title" id="ms-title">{{title}}</div>
<div class="perf"></div>
<div class="bar-bg">
<div class="bar-fill" id="bar-fill" style="width: {{percentage}}%"></div>
</div>
<div class="amounts">
<span id="current">{{formattedStepProgress}}</span>
<span id="target">{{formattedStepTarget}}</span>
</div>
<div class="meta">Step {{stepIndex}} / {{stepCount}}</div>
</div>
</body>
</html>



4. Polling


Variable Name

Example Value

Description

{{title}}

Pick the Next Game!

Poll title from settings.

{{pollingOptions}}

[{"id":1,"name":"Game A","totalAmount":5000,"formattedTotalAmount":"Rp5.000"}, ...]

Poll options list (JSON): ID, name, donation amount, formatted amount.

{{startAt}}

2025-07-08

Poll start date.

{{startTime}}

09:30

Poll start time.

{{endAt}}

2025-07-08

Poll end date.

{{endTime}}

12:30

Poll end time.


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; }
.ticket {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
background: #f2e9d3;
border: 2px solid #1c1712;
box-shadow: 0.2rem 0.25rem 0 0 #1c1712;
padding: 0.9rem 1rem 0.9rem 1.5rem;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 0.85rem; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, 0); z-index: 2; }
.title { font-size: 1.0625rem; font-weight: 700; }
.perf { border-bottom: 1px dashed #7a7160; margin: 0.6rem 0; }
.poll-row { display: flex; align-items: center; gap: 0.6rem; font-size: 1rem; padding: 0.35rem 0; border-bottom: 1px dashed #e8dab8; }
.poll-row:last-child { border-bottom: none; }
.poll-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.poll-amount { font-weight: 700; color: #7a7160; flex-shrink: 0; }
.meta { font-size: 0.8125rem; color: #7a7160; margin-top: 0.55rem; }
</style>
</head>
<body>
<div class="ticket">
<div class="tab"></div>
<div class="grommet"></div>

<div class="title" id="poll-title">{{title}}</div>
<div class="perf"></div>
<div id="poll-list"></div>
<div class="meta">{{startAt}} {{startTime}} — {{endAt}} {{endTime}}</div>
</div>

<script>
const options = {{pollingOptions}};
const list = document.getElementById("poll-list");

options.forEach((o) => {
const row = document.createElement("div");
row.className = "poll-row";

const name = document.createElement("span");
name.className = "poll-name";
name.textContent = o.name;

const amount = document.createElement("span");
amount.className = "poll-amount";
amount.textContent = o.formattedTotalAmount;

row.appendChild(name);
row.appendChild(amount);
list.appendChild(row);
});
</script>
</body>
</html>



5. Running Text


Variable Name

Example Value

Description

{{texts}}

[{"title":"Welcome!","backgroundColor":"#333","textColor":"#FFF"}, ...]

Extra text list (JSON): title, background color, text color.

{{gifts}}

[{"gifterName":"BudiGaming","amount":15000,"formattedAmount":"Rp15.000"}, ...]

Donation list (JSON): donor name, amount, formatted amount.


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; overflow: hidden; }
.ticker {
position: relative;
width: 100%;
box-sizing: border-box;
background: #f2e9d3;
border-top: 2px solid #1c1712;
border-bottom: 2px solid #1c1712;
padding: 0.6rem 0 0.6rem 1.4rem;
overflow: hidden;
white-space: nowrap;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 50%; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, -50%); z-index: 2; }
.ticker-track { display: inline-flex; gap: 1.5rem; animation: scroll 18s linear infinite; }
.ticker-item { font-size: 1rem; font-weight: 700; flex-shrink: 0; }
.ticker-item.gift { color: #9c3b2e; }
@keyframes scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
</style>
</head>
<body>
<div class="ticker">
<div class="tab"></div>
<div class="grommet"></div>
<div class="ticker-track" id="track"></div>
</div>

<script>
const texts = {{texts}};
const gifts = {{gifts}};
const track = document.getElementById("track");

function buildItems() {
const frag = document.createDocumentFragment();

texts.forEach((t) => {
const item = document.createElement("span");
item.className = "ticker-item";
item.style.color = t.textColor || "#1c1712";
item.textContent = t.title;
frag.appendChild(item);
});

gifts.forEach((g) => {
const item = document.createElement("span");
item.className = "ticker-item gift";
item.textContent = g.gifterName + " — " + g.formattedAmount;
frag.appendChild(item);
});

return frag;
}

track.appendChild(buildItems());
track.appendChild(buildItems());
</script>
</body>
</html>


Appending buildItems() twice makes the scroll seamless (two copies of the content sit side by side, and translateX(-50%) stops exactly when the second copy reaches the first copy's position). This overlay has no 250ms progress-tick like Song Share, so the CSS animation loop is safe to run continuously without restarting.



6. Soundboard


Variable Name

Example Value

Description

{{amount}}

15000

Donation amount (raw number).

{{formattedAmount}}

Rp15.000

Formatted donation amount.

{{soundName}}

Explosion

Name of the chosen sound effect.

{{gifterName}}

BudiGaming

Donor's name.

{{gifterPicture}}

https://example.com/profile.jpg

Donor's profile picture URL.

{{gifterBadges}}

VIP,TopDonator

Donor's badge names, comma-separated — no icon URL from the system.

{{isGifterVerified}}

true or false

Donor's verification status (true if verified).


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; }
.ticket {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
background: #f2e9d3;
border: 2px solid #1c1712;
box-shadow: 0.2rem 0.25rem 0 0 #1c1712;
padding: 0.9rem 1rem 0.9rem 1.5rem;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 0.85rem; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, 0); z-index: 2; }
.gifter-row { display: flex; align-items: center; gap: 0.6rem; }
.avatar { width: 2.6rem; height: 2.6rem; border-radius: 50%; border: 2px solid #1c1712; object-fit: cover; flex-shrink: 0; display: none; }
.avatar-fallback { width: 2.6rem; height: 2.6rem; border-radius: 50%; border: 2px solid #1c1712; background: #e8dab8; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
.gifter-info { flex: 1; min-width: 0; }
.gifter-name { font-weight: 700; font-size: 1.0625rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.badges { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; margin-top: 0.2rem; }
.badge-tag { font-size: 0.75rem; border: 1px solid #7a7160; color: #7a7160; padding: 0.05rem 0.35rem; }
.badge-icon { height: 1rem; width: auto; display: block; }
.amount { font-weight: 700; font-size: 1.125rem; color: #b6842c; flex-shrink: 0; }
.perf { border-bottom: 1px dashed #7a7160; margin: 0.6rem 0; }
.sound-name { font-size: 1.0625rem; font-weight: 700; }
</style>
</head>
<body>
<div class="ticket">
<div class="tab"></div>
<div class="grommet"></div>

<div class="gifter-row">
<img class="avatar" id="avatar" alt="">
<span class="avatar-fallback" id="avatar-fallback">🔊</span>
<div class="gifter-info">
<div class="gifter-name" id="gifter-name">{{gifterName}}</div>
<div class="badges" id="badges"></div>
</div>
<div class="amount">{{formattedAmount}}</div>
</div>
<div class="perf"></div>
<div class="sound-name">🔈 {{soundName}}</div>
</div>

<script>
const picture = "{{gifterPicture}}".trim();
const badgesRaw = "{{gifterBadges}}".trim();
const isVerified = "{{isGifterVerified}}" === "true";

const BADGE_ICON_URLS = {
"Tako Verified Partner": "https://tako.id/images/badges/partnered-creator.png",
"Tako Verified Creator": "https://tako.id/images/badges/verified-creator.png",
"Rich Rich Club": "https://tako.id/images/badges/king.png",
"Charity": "https://tako.id/images/badges/charity.png",
"VTuber": "https://tako.id/images/badges/vtuber.png",
"Tako Team": "https://tako.id/images/badges/admin.png"
};

const avatar = document.getElementById("avatar");
const avatarFallback = document.getElementById("avatar-fallback");
const gifterName = document.getElementById("gifter-name");
const badgesEl = document.getElementById("badges");

if (picture && !picture.includes("{{")) {
avatar.src = picture;
avatar.style.display = "block";
avatarFallback.style.display = "none";
avatar.onerror = () => {
avatar.style.display = "none";
avatarFallback.style.display = "flex";
};
}

if (isVerified) gifterName.appendChild(document.createTextNode(" ✓"));

if (badgesRaw && !badgesRaw.includes("{{")) {
badgesRaw.split(",").filter(Boolean).forEach((name) => {
const iconUrl = BADGE_ICON_URLS[name];

if (iconUrl) {
const icon = document.createElement("img");
icon.className = "badge-icon";
icon.src = iconUrl;
icon.alt = name;
icon.title = name;
badgesEl.appendChild(icon);
} else {
const tag = document.createElement("span");
tag.className = "badge-tag";
tag.textContent = name;
badgesEl.appendChild(tag);
}
});
}
</script>
</body>
</html>



7. Timer


Variable Name

Example Value

Description

{{title}}

Event Starting!

Timer title from settings.

{{timeLeft}}

05:30

Time left in MM:SS format (or 00:00 when expired).

{{isEnabled}}

true or false

Timer status (true if active).

{{options}}

[{"durationAdded":300000,"minimumGiftAmount":10000}, ...]

Timer options list (JSON): added duration (ms), minimum donation amount to add time.


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; }
.ticket {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
background: #f2e9d3;
border: 2px solid #1c1712;
box-shadow: 0.2rem 0.25rem 0 0 #1c1712;
padding: 0.9rem 1rem 0.9rem 1.5rem;
text-align: center;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 0.85rem; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, 0); z-index: 2; }
.row-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; text-align: left; }
.title { flex: 1; min-width: 0; font-size: 1.0625rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.status { flex-shrink: 0; font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: #7a7160; border: 1px solid #7a7160; padding: 0.1rem 0.4rem; }
.perf { border-bottom: 1px dashed #7a7160; margin: 0.6rem 0; }
.countdown { font-size: 2.25rem; font-weight: 700; letter-spacing: 0.05em; }
</style>
</head>
<body>
<div class="ticket">
<div class="tab"></div>
<div class="grommet"></div>

<div class="row-head">
<span class="title" id="timer-title">{{title}}</span>
<span class="status" id="status-el">Active</span>
</div>
<div class="perf"></div>
<div class="countdown">{{timeLeft}}</div>
</div>

<script>
const isEnabled = "{{isEnabled}}" === "true";
document.getElementById("status-el").textContent = isEnabled ? "Active" : "Inactive";
</script>
</body>
</html>



8. VIP Queue


Variable Name

Example Value

Description

{{title}}

VIP Queue

Title from settings.

{{totalEntries}}

7

Total number of people in the queue.

{{hiddenCount}}

2

Number of entries not shown because they exceed the display limit (displayedQueueLimit in settings).

{{showFields}}

"true" or "false" (string)

Whether custom fields (e.g. address, note) are shown.

{{visibleEntries}}

[{"index":1,"id":"1","name":"BudiGaming","picture":"https://example.com/profile.jpg","badges":"VIP,TopDonator","badgeItems":[{"url":"https://tako.id/images/badges/verified-creator.png","title":"Verified Creator"}],"isVerified":true,"amount":15000,"formattedAmount":"Rp15.000","fields":[{"label":"Address","value":"123 Merdeka St"}]}, ...]

Queue list already cut down to the display limit (JSON) — used for rendering. Each entry has badgeItems: an array of real badges (icon + title), used to render badges as images, not just text.

{{entries}} / {{queue}}

same structure as visibleEntries

List of all entries (JSON), not cut down to the limit.

{{fields}}

[{"label":"Address","value":"123 Merdeka St"}, ...]

All custom fields from every entry, flattened into one array (JSON).


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; }
.ticket {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
background: #f2e9d3;
border: 2px solid #1c1712;
box-shadow: 0.2rem 0.25rem 0 0 #1c1712;
padding: 0.9rem 1rem 0.9rem 1.5rem;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 0.85rem; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, 0); z-index: 2; }
.row-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.title { font-size: 1.0625rem; font-weight: 700; }
.count-badge { font-size: 0.875rem; font-weight: 700; color: #b6842c; border: 1px solid #7a7160; padding: 0.05rem 0.45rem; border-radius: 999px; flex-shrink: 0; }
.perf { border-bottom: 1px dashed #7a7160; margin: 0.6rem 0; }
.queue-list { display: flex; flex-direction: column; gap: 0.5rem; }
.queue-row { display: flex; align-items: center; gap: 0.5rem; }
.queue-num { width: 1.5rem; height: 1.5rem; border-radius: 50%; background: #e8dab8; border: 1px solid #1c1712; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 700; }
.queue-avatar { width: 1.6rem; height: 1.6rem; border-radius: 50%; border: 1px solid #1c1712; object-fit: cover; flex-shrink: 0; display: none; }
.queue-name { flex: 1; min-width: 0; font-size: 1rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.queue-badges { display: flex; align-items: center; gap: 0.2rem; flex-shrink: 0; }
.queue-badge-icon { height: 1.0625rem; width: auto; display: block; }
.queue-amount { font-size: 0.875rem; font-weight: 700; color: #7a7160; flex-shrink: 0; }
.queue-fields { font-size: 0.75rem; color: #7a7160; padding-left: 2.1rem; }
.queue-empty { font-size: 1rem; font-style: italic; color: #7a7160; }
.hidden-note { font-size: 0.8125rem; text-align: center; color: #7a7160; font-weight: 700; margin-top: 0.3rem; }
</style>
</head>
<body>
<div class="ticket">
<div class="tab"></div>
<div class="grommet"></div>

<div class="row-head">
<div class="title" id="queue-title">{{title}}</div>
<div class="count-badge" id="count-badge">{{totalEntries}}</div>
</div>
<div class="perf"></div>
<div class="queue-list" id="queue-list"></div>
<div class="hidden-note" id="hidden-note"></div>
</div>

<script>
const entries = {{visibleEntries}};
const hiddenCount = {{hiddenCount}};
const showFields = "{{showFields}}" === "true";

const list = document.getElementById("queue-list");
const hiddenNote = document.getElementById("hidden-note");

if (entries.length === 0) {
const empty = document.createElement("p");
empty.className = "queue-empty";
empty.textContent = "No one in the queue yet.";
list.appendChild(empty);
}

entries.forEach((entry) => {
const row = document.createElement("div");
row.className = "queue-row";

const num = document.createElement("div");
num.className = "queue-num";
num.textContent = entry.index;

const avatar = document.createElement("img");
avatar.className = "queue-avatar";
if (entry.picture) {
avatar.src = entry.picture;
avatar.style.display = "block";
avatar.onerror = () => { avatar.style.display = "none"; };
}

const name = document.createElement("span");
name.className = "queue-name";
name.textContent = entry.name + (entry.isVerified ? " ✓" : "");

const badgesEl = document.createElement("div");
badgesEl.className = "queue-badges";
(entry.badgeItems || []).forEach((badge) => {
if (!badge.url) return;
const icon = document.createElement("img");
icon.className = "queue-badge-icon";
icon.src = badge.url;
icon.alt = badge.title || "";
icon.title = badge.title || "";
badgesEl.appendChild(icon);
});

const amount = document.createElement("span");
amount.className = "queue-amount";
amount.textContent = entry.formattedAmount;

row.appendChild(num);
row.appendChild(avatar);
row.appendChild(name);
row.appendChild(badgesEl);
row.appendChild(amount);
list.appendChild(row);

if (showFields && entry.fields && entry.fields.length > 0) {
const fieldsRow = document.createElement("div");
fieldsRow.className = "queue-fields";
fieldsRow.textContent = entry.fields.map((f) => f.label + ": " + (f.value || "-")).join(" • ");
list.appendChild(fieldsRow);
}
});

if (hiddenCount > 0) {
hiddenNote.textContent = "+" + hiddenCount + " more waiting";
}
</script>
</body>
</html>



9. Song Share


Variable Name

Example Value

Description

{{title}} / {{songTitle}}

Song Title

Title of the song currently playing.

{{songArtist}}

Artist Name

Artist of the song currently playing.

{{songCoverArtUrl}}

https://example.com/cover.jpg

Cover art URL of the song currently playing.

{{requesterName}}

BudiGaming

Name of the person who requested the song.

{{requesterPicture}}

https://example.com/profile.jpg

Requester's profile picture URL.

{{requesterBadges}}

VIP,TopDonator

Requester's badge names (names only, comma-separated) — no icon URL from the system for this "Now Playing" row. Fill in BADGE_ICON_URLS manually in the script if you want it shown as an image — the code below already has the 6 official Tako system badges filled in, see the table under Alert (§1).

{{isRequesterVerified}}

true or false

Requester's verification status.

{{duration}}

215 (seconds)

Song duration (raw number).

{{formattedDuration}}

03:35

Formatted song duration.

{{elapsed}}

42 (seconds)

Elapsed playback time (raw number).

{{formattedElapsed}}

00:42

Formatted elapsed playback time.

{{progressPercent}}

19

Song progress percentage (0-100) — use it to build your own progress bar.

{{isPlaying}}

true or false

Whether the song is currently playing.

{{isPaused}}

true or false

Whether the song is currently paused.

{{isInGap}}

true or false

Whether it's currently in a gap between songs.

{{gapRemaining}}

3 (seconds)

Time left in the gap between songs.

{{gapText}}

Next Song At 00:03

Status text for the gap between songs.

{{error}}

Song failed to play

Error message if any, empty if none.

{{nowPlaying}}

{"id":"1","title":"Song Title","artist":"Artist Name","coverArtUrl":"...","duration":215,"formattedDuration":"03:35","sender":{"name":"BudiGaming"},...}

Object (JSON) of the song currently playing, null if none.

{{queue}} / {{queues}}

[{"id":"2","title":"Queued Song","artist":"Another Artist","duration":180,"senderName":"Another Supporter","senderBadges":"VIP,TopDonator","senderBadgeItems":[{"url":"https://tako.id/images/badges/verified-creator.png","title":"Verified Creator"}],...}, ...]

List of upcoming songs in the queue (JSON). Each item has senderBadgeItems — an array of real badges (icon + title) for that song's requester, which genuinely can be rendered as an image.


Example Code:


<html>
<head>
<style>
body { margin: 0; background: none; font-family: "Courier New", monospace; color: #1c1712; }
.ticket {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
background: #f2e9d3;
border: 2px solid #1c1712;
box-shadow: 0.2rem 0.25rem 0 0 #1c1712;
padding: 0.9rem 1rem 0.9rem 1.5rem;
}
.tab { position: absolute; left: 0; top: 0; bottom: 0; width: 0.55rem; background: #9c3b2e; border-right: 2px solid #1c1712; }
.grommet { position: absolute; left: 0.275rem; top: 0.85rem; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: #f2e9d3; border: 1px solid #1c1712; transform: translate(-50%, 0); z-index: 2; }
.row-head { display: flex; justify-content: flex-end; margin-bottom: 0.5rem; }
.status { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: #7a7160; border: 1px solid #7a7160; padding: 0.1rem 0.4rem; }
.error-banner { font-size: 0.8125rem; background: #9c3b2e; color: #f2e9d3; padding: 0.35rem 0.5rem; margin-bottom: 0.5rem; display: none; }
.gap-banner { font-size: 0.9375rem; color: #7a7160; padding: 0.4rem 0; display: none; }
.now-playing { display: flex; gap: 0.75rem; }
.cover { position: relative; width: 3.4rem; height: 3.4rem; flex-shrink: 0; background: #e8dab8; border: 2px solid #1c1712; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; color: #7a7160; }
.cover img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: none; }
.info { flex: 1; min-width: 0; }
.title { font-size: 1.0625rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.title.idle { font-weight: 400; color: #7a7160; }
.artist { font-size: 0.9375rem; color: #7a7160; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 0.35rem; }
.requester { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; font-size: 0.8125rem; color: #7a7160; margin-bottom: 0.45rem; }
.requester img { width: 1.1rem; height: 1.1rem; border-radius: 50%; flex-shrink: 0; display: none; }
.requester .badge-tag { font-size: 0.75rem; border: 1px solid #7a7160; padding: 0.05rem 0.35rem; }
.requester .badge-icon { height: 0.9375rem; width: auto; display: block; }
.progress-row { display: flex; align-items: center; gap: 0.5rem; }
.bar-bg { flex: 1; height: 0.45rem; background: #e8dab8; border: 1px solid #1c1712; }
.bar-fill { height: 100%; background: #b6842c; }
.time { font-size: 0.8125rem; color: #7a7160; flex-shrink: 0; }
.perf { border-bottom: 1px dashed #1c1712; margin: 0.65rem 0 0.5rem; }
.setlist-label { font-size: 0.75rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #7a7160; margin-bottom: 0.4rem; }
.setlist { display: none; }
.setlist-row { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9375rem; padding: 0.25rem 0; }
.setlist-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.setlist-badges { display: flex; align-items: center; gap: 0.2rem; flex-shrink: 0; }
.setlist-badge-icon { height: 0.9375rem; width: auto; display: block; }
.setlist-time { color: #7a7160; flex-shrink: 0; }
</style>
</head>
<body>
<div class="ticket">
<div class="tab"></div>
<div class="grommet"></div>

<div class="row-head">
<span class="status" id="status-el">Now Playing</span>
</div>

<div class="error-banner" id="error-banner"></div>
<div class="gap-banner" id="gap-banner"></div>

<div id="player-body">
<div class="now-playing">
<div class="cover" id="cover">
<span id="cover-fallback"></span>
<img id="cover-img" alt="">
</div>

<div class="info">
<div class="title" id="title-el">{{songTitle}}</div>
<div class="artist" id="artist-el">{{songArtist}}</div>

<div class="requester" id="requester-row">
<img id="requester-pic" alt="">
<span id="requester-name">{{requesterName}}</span>
<span id="requester-verified"></span>
<span id="requester-badges"></span>
</div>

<div class="progress-row" id="progress-row">
<div class="bar-bg">
<div class="bar-fill" style="width: {{progressPercent}}%"></div>
</div>
<div class="time" id="time-el" data-elapsed="{{elapsed}}" data-duration="{{duration}}">{{formattedElapsed}} / {{formattedDuration}}</div>
</div>
</div>
</div>
</div>

<div id="setlist-wrap">
<div class="perf" id="setlist-perf" style="display:none"></div>
<div class="setlist" id="setlist">
<div class="setlist-label" id="setlist-label">Up Next</div>
<div id="setlist-body"></div>
</div>
</div>
</div>

<script>
const isPlaying = "{{isPlaying}}" === "true";
const isPaused = "{{isPaused}}" === "true";
const isInGap = "{{isInGap}}" === "true";
const gapText = "{{gapText}}";
const errorText = "{{error}}";
const coverUrl = "{{songCoverArtUrl}}".trim();
const requesterPicture = "{{requesterPicture}}".trim();
const requesterBadges = "{{requesterBadges}}".trim();
const isRequesterVerified = "{{isRequesterVerified}}" === "true";

const BADGE_ICON_URLS = {
"Tako Verified Partner": "https://tako.id/images/badges/partnered-creator.png",
"Tako Verified Creator": "https://tako.id/images/badges/verified-creator.png",
"Rich Rich Club": "https://tako.id/images/badges/king.png",
"Charity": "https://tako.id/images/badges/charity.png",
"VTuber": "https://tako.id/images/badges/vtuber.png",
"Tako Team": "https://tako.id/images/badges/admin.png"
};

const titleEl = document.getElementById("title-el");
const artistEl = document.getElementById("artist-el");
const coverImg = document.getElementById("cover-img");
const coverFallback = document.getElementById("cover-fallback");
const statusEl = document.getElementById("status-el");
const errorBanner = document.getElementById("error-banner");
const gapBanner = document.getElementById("gap-banner");
const playerBody = document.getElementById("player-body");
const requesterRow = document.getElementById("requester-row");
const requesterPic = document.getElementById("requester-pic");
const requesterVerified = document.getElementById("requester-verified");
const requesterBadgesEl = document.getElementById("requester-badges");
const progressRow = document.getElementById("progress-row");
const setlistPerf = document.getElementById("setlist-perf");
const setlist = document.getElementById("setlist");
const setlistLabel = document.getElementById("setlist-label");
const setlistBody = document.getElementById("setlist-body");

const titleText = titleEl.textContent.trim();
const isIdle = !titleText || titleText === "{{" + "songTitle" + "}}";

if (errorText && !errorText.includes("{{")) {
errorBanner.textContent = "⚠ " + errorText;
errorBanner.style.display = "block";
}

if (isInGap) {
playerBody.style.display = "none";
gapBanner.textContent = gapText || "Preparing next song...";
gapBanner.style.display = "block";
statusEl.textContent = "Gap";
} else if (isIdle) {
titleEl.textContent = "No song playing yet";
titleEl.classList.add("idle");
artistEl.textContent = "Send a gift to request a song";
requesterRow.style.display = "none";
progressRow.style.display = "none";
coverFallback.style.display = "flex";
statusEl.textContent = "Idle";
} else {
statusEl.textContent = isPaused ? "Paused" : isPlaying ? "Now Playing" : "Buffering";

if (coverUrl && !coverUrl.includes("{{")) {
coverImg.src = coverUrl;
coverImg.style.display = "block";
coverFallback.style.display = "none";
coverImg.onerror = () => {
coverImg.style.display = "none";
coverFallback.style.display = "flex";
};
}

if (requesterPicture && !requesterPicture.includes("{{")) {
requesterPic.src = requesterPicture;
requesterPic.style.display = "block";
}

if (isRequesterVerified) requesterVerified.textContent = "✓";

if (requesterBadges) {
requesterBadges.split(",").filter(Boolean).forEach((name) => {
const iconUrl = BADGE_ICON_URLS[name];

if (iconUrl) {
const icon = document.createElement("img");
icon.className = "badge-icon";
icon.src = iconUrl;
icon.alt = name;
icon.title = name;
requesterBadgesEl.appendChild(icon);
} else {
const tag = document.createElement("span");
tag.className = "badge-tag";
tag.textContent = name;
requesterBadgesEl.appendChild(tag);
}
});
}
}

try {
const queueData = {{queue}};

if (Array.isArray(queueData) && queueData.length > 0) {
setlistPerf.style.display = "block";
setlist.style.display = "block";
setlistLabel.textContent = "Up Next (" + queueData.length + ")";

queueData.forEach((song, i) => {
const row = document.createElement("div");
row.className = "setlist-row";

const name = document.createElement("span");
name.className = "setlist-name";
name.textContent = (i + 1) + ". " + song.title + " — " + song.artist;

const badgesEl = document.createElement("div");
badgesEl.className = "setlist-badges";
(song.senderBadgeItems || []).forEach((badge) => {
if (!badge.url) return;
const icon = document.createElement("img");
icon.className = "setlist-badge-icon";
icon.src = badge.url;
icon.alt = badge.title || "";
icon.title = badge.title || "";
badgesEl.appendChild(icon);
});

const duration = document.createElement("span");
duration.className = "setlist-time";
duration.textContent = song.formattedDuration;

row.appendChild(name);
row.appendChild(badgesEl);
row.appendChild(duration);
setlistBody.appendChild(row);
});
}
} catch (e) {}
</script>
</body>
</html>



Additional Info You Might See


  • "⚠ This is a test message": Shows when the overlay is being tested or previewed.
  • "⛔ Sent by Admin": Shows when a gift is sent by Tako's team/admin.



How to Implement/Build a Custom Overlay


  1. Log into your Creator Dashboard
  2. Find the "Overlays" menu on the left
  3. Pick an overlay (e.g. Alert)
  4. Under "Theme", switch to "Custom HTML".
  5. Copy-paste the code you built into the textarea that appears.
  6. Check the preview at the top.


Updated on: 04/08/2026

Was this article helpful?

Share your feedback

Cancel

Thank you!