Skip to content

I made a whole Discord server kit from one HTML file

My Discord server's whole kit, from the animated icon to the role icons, lives in one HTML file. A Playwright script renders every board at 2x, shrinks it to Discord's exact size and builds the GIF. Here's the pipeline, Discord's real limits and boost levels, and the three bugs I hit.

Aatman (AJ) Jain9 min readUpdated 4 Oct 2026
A ruled cream sheet with paper cut-outs spilling from it: a chef's hat, round badges, smiley stickers, a star and a banner strip, with orange scissors, on blush paper.
A ruled cream sheet with paper cut-outs spilling from it: a chef's hat, round badges, smiley stickers, a star and a banner strip, with orange scissors, on blush paper.

You can build a whole Discord server kit from one HTML file: draw every asset as an HTML or SVG board at the exact size Discord wants, then have a Playwright script screenshot each board at 2x and shrink it back down. That's how I made the kit for my server, "Cooking up stuff!": an icon, an animated icon, 10 emojis, 4 stickers, 5 role icons, a banner, an invite background and a welcome header, every one of them still editable text.

Why one HTML file?

A Discord kit sounds small until you count it. Mine is 26 uploads plus a preview, at seven different sizes, from a 64 px role icon to a 1920 x 1080 invite background.

I wanted the server to feel like this site: lime, ink and papaya, Newsreader, Geist and Caveat, stickers and tape. And I wanted to change my mind freely. If the lime looks off next week, I'm not re-exporting 26 files.

So the source is text. Every asset is a <div class="board"> in kit.html, sized in CSS to Discord's exact pixels. Shared drawings, like the chef's hat, live once as an SVG <symbol> and get reused with <use>. A script turns the boards into files.

That's the one idea I'd take from this post: treat brand assets like code. Source you can read and diff, outputs a script rebuilds. The kit came out of a Claude Code session, same as this site.

The whole kit is one page. Every square here is a board the script screenshots.

The mark: a chef's hat that says AJ

The server is called "Cooking up stuff!", so the mark is a chef's toque with my initials on the band, lime on ink, with the papaya swoosh from the site's favicon underneath. No stock art. Every shape is SVG.

Two SVG filters do most of the work. feTurbulence and feDisplacementMap wobble the outlines just enough to look hand-drawn, and the noise has a fixed seed, so the wobble is the same on every run (that matters later). The white die-cut border on the stickers is another filter: feMorphology grows the artwork's own shape by 9 px, floods it white and puts the art back on top. New sticker, free border.

Picking all that myself was the point: skip those decisions and you get the same app as everyone else. A wobbly chef's hat isn't anyone's default.

The main icon and two alternatives.

The animated icon is 40 screenshots

Animation is where HTML pays off. The animated board carries data-frames="40", and the page has one function that poses it for any frame:

js
// kit.html: render.cjs calls this once per frame
window.setFrame = function (id, i, n) {
  const t = i / n, TAU = Math.PI * 2;
  if (id === 'icon-anim') {
    const bob = Math.sin(t * TAU);
    const hop = Math.max(0, Math.sin(t * TAU * 2)); // two little hops per loop
    document.getElementById('an-hat').style.transform = `translateY(${-14 * hop}px) rotate(${4 * bob}deg)`;
    document.getElementById('an-shadow').style.transform = `scaleX(${1 - 0.18 * hop})`;
    // ...plus three sparkles that twinkle a third of a loop apart
  }
};

The script calls it 40 times and screenshots each pose. Everything runs off t, which stops one step short of 1. The hops are sines, and the sparkles spin exactly a quarter turn, which a four-pointed star hides. So frame 39 flows straight back into frame 0. No jump.

Pillow builds one 255-colour palette from the first frame and maps all 40 frames onto it with no dithering, so the flat lime stays flat instead of crawling with dither noise.

One thing surprised me. kit.html asks for 55 ms a frame, but the GIF says 50. GIF stores delays in hundredths of a second, and Pillow turns 5.5 into 5. So the loop is 2.0 seconds, not 2.2. Nobody will notice. I still enjoyed finding it.

8 of the 40 frames. Two hops per loop, and the sparkles take turns.

Emojis, stickers and role icons

The 10 emojis are the server's in-jokes: :letHimCook:, :worksOnMyMachine:, :rls: for whenever someone forgets row-level security, and an :lgtm: stamp. They're 128 px, transparent, and 9 to 28 KB each.

All ten emojis at true size, with real file sizes.

Stickers are bigger, with words on. Role icons are tiny: a kitchen brigade from Head Chef down to Fresh Ingredient for new members, each a 64 px coloured circle.

Stickers on top, role icons underneath at their real 64 px.

And a welcome header for the top of #welcome, with my illustrated portrait wearing the hat.

The welcome header, 1600 x 600. It's a normal image post, so no boosts needed.

Where each file goes, and what Discord allows

I checked every limit below on Discord's help centre on 4 October 2026. Where Discord publishes no number, the table says so.

FileSizeWhere it goes in DiscordDiscord's ruleBoosts needed
icon.png (and 2 alternatives)512 x 512, 153 KBServer Settings, Server Profile, Change Server IconAt least 128 x 128, in multiples of 128. No file size limit listedNone
icon-animated.gif512 x 512, 40 frames, 605 KBThe server iconA level 1 perk. No file size limit listedLevel 1 (2 boosts)
banner.png960 x 540, 342 KBServer Settings, Overview, Server Banner BackgroundAt least 960 x 540, 16:9. Keep the top 48 px simpleLevel 2 (7 boosts)
splash.png1920 x 1080, 808 KBServer Settings, Overview, Server Invite BackgroundMust be 1920 x 1080, JPG or PNG, never a GIFLevel 1 (2 boosts)
welcome.png1600 x 600, 469 KBPosted at the top of #welcomeA normal upload: 20 MB on a free accountNone
10 emojis (e- files)128 x 128, 9 to 28 KBServer Settings, EmojiUnder 256 KB. JPEG, PNG, GIF or WEBPNone
4 stickers (s- files)320 x 320, about 50 to 58 KBServer Settings, StickersExactly 320 x 320, PNG or APNG, up to 512 KBNone
5 role icons (r- files)64 x 64, 5 to 6 KBServer Settings, Roles, a role, Display, Role Icon64 x 64, up to 256 KB, no animationLevel 2 (7 boosts)

Sources: Discord's boosting FAQ (opens in a new tab), emojis (opens in a new tab), stickers (opens in a new tab), sticker specs (opens in a new tab), role icons (opens in a new tab), banners (opens in a new tab), invite background (opens in a new tab), icon size (opens in a new tab), server profile (opens in a new tab) and attachments (opens in a new tab).

The 10 emojis fit easily in the 50 slots every server gets, and the 4 stickers fit in the 5 free sticker slots. So a server with no boosts can take the icon, emojis, stickers and welcome header today.

The one gap: Discord's help centre gives no file size limit for server icons, banners or invite backgrounds. I looked. So I can't promise a number there.

The render pipeline

render.cjs is 73 lines. Here's everything it does:

  1. Draw a board

    One <div class="board"> per asset in kit.html, sized in CSS to Discord's exact pixels. Add the clear class if it needs a transparent background.

  2. Open it at 2x

    Playwright loads kit.html in Chromium with deviceScaleFactor: 2, then waits for the fonts and images.

  3. Screenshot each board

    el.screenshot() captures just that board. On clear boards, omitBackground: true keeps it transparent.

  4. Shrink it to size

    Pillow resizes the 2x capture with LANCZOS, so a 512 px board becomes a sharp 512 px PNG.

  5. Pose the animation

    For a board with data-frames, window.setFrame(id, i, n) poses it for each frame and every pose gets screenshotted.

  6. Make the GIF

    One 255-colour palette from the first frame, no dithering, looped forever, saved as icon-animated.gif.

The whole pipeline. Change a colour, run it again, and every file updates.

On the real kit, passing board ids to render only some:

Terminal
node docs/brand/discord/render.cjs icon bannerpng  out/icon.png 512x512png  out/banner.png 960x540node docs/brand/discord/render.cjs icon-animgif  out/icon-animated.gif 40 frames

Why draw at 2x and shrink? The script's own comment says it: drawn at 2x, then downsampled, for clean edges. Text and thin strokes get four times the pixels, then get averaged down to Discord's exact size.

Does it really re-render in seconds? For one board, yes. Timed in a cloud container on 4 October, one board took 7 to 9 seconds, mostly start-up. The whole kit took 137 seconds, and the GIF alone took 80. So render the board you're working on, and save the full run for the end.

files from one kit.html
27
emojis, 9 to 28 KB each
10
stickers, about 50 to 58 KB each
4
animated icon, 40 frames
605 KB

Then I ran the whole thing from a clean copy and compared every file with the repo. All 27 came back byte-identical (once the preview bug below was dealt with). That's the fixed wobble seed paying off. Same idea as proving a speed change didn't move a pixel: if the output matches, nothing broke.

Three bugs I hit on the way

1. Grey squares behind every emoji

omitBackground sounds like "transparent screenshot, please". Playwright's docs are more precise: it "hides default white background". Anything the page paints itself stays. The page body had a grey background, so every "transparent" emoji came out with a grey square baked in. The fix is one line: body { background: transparent; }.

The bug, re-created: grey body
The fix: transparent body

2. Art hiding under Discord's own UI

Discord draws on top of two of these images: the server name over the top of the banner, and the invite card over the middle of the invite background. My first layouts put things in exactly those spots.

Discord's guides say to keep the banner's top 48 px simple and key parts out from under the invite pop-up. So the banner's words sit low, and the invite background keeps everything around the edges.

The banner. Everything important sits below the striped strip.
The invite background. The dashed box is roughly where the invite card lands.

3. A preview that was always one run behind

The kit has one more board: a mock-up of everything inside a Discord window, built from <img src="out/icon.png"> and friends. Those images load once, when the page opens, before anything renders. So the preview always shows the previous run's files, and on a clean copy it's a page of broken images. That's my real first run on the left.

The fix is boring: render the preview last, on its own, with node docs/brand/discord/render.cjs preview. A cleaner fix would reload the page before the preview board. I haven't done that yet.

First run: one step behind
Second run: caught up

Both are a mock-up I drew in HTML, not a screenshot of Discord, and the member names in them are made up.

Would I do it this way again?

Yes. Every time. The kit lives next to my site's code, rebuilds the same files every run, and a change is one edit and one command, not an afternoon of exporting.

If you run a server, steal the idea: one file, one board per asset, sizes from the table above. And if you want to see the hat in the wild, come and say hi in Cooking up stuff! (opens in a new tab). It's on my community page with everywhere else I hang out.

FAQ

What size should a Discord server icon be?

Discord's minimum is 128 x 128, and it suggests cropping in multiples of 128, so 256, 384 or 512 all work. Mine is 512 x 512. An animated icon needs the server at Boost level 1, which takes 2 boosts.

What are Discord's limits for custom emojis and stickers?

Emojis must be under 256 KB, in JPEG, PNG, GIF or WEBP, with names of at least 2 characters. Stickers must be exactly 320 x 320, PNG or APNG, and up to 512 KB. Every server gets 50 emoji slots and 5 sticker slots without boosts.

Which Discord server customisations need Server Boosts?

Level 1 (2 boosts) adds an animated server icon and a server invite background. Level 2 (7 boosts) adds a static server banner and custom role icons. Level 3 (14 boosts) adds an animated banner.

Keep reading

More posts on the same things.

Newsletter

Liked this one?

Get my Friday email: new posts, what I built and the Base44 and AI news worth knowing.

No spam. Unsubscribe whenever.

Got an app in mind?

I build and fix Base44 apps, and I'm looking for a remote internship. Tell me what you're working on.