A Discord bot without an avatar looks unfinished. The default gray Discord logo signals “the developer did not bother,” and in a server list full of bots with polished icons, yours will blend into the background. But commissioning custom art takes time and money, and finding a free icon that is not already used by a dozen other bots is surprisingly difficult.
Pixel art solves this elegantly. A pixel-art avatar is immediately recognizable in a server list. It reads clearly at small sizes. And it signals exactly the right thing: this is a bot, not a person, and it has character. This guide walks through generating and setting pixel-art bot avatars programmatically using MechGen and Discord.js.
Discord’s Avatar Requirements
Before generating anything, you need to know what Discord expects:
- Recommended size: 512x512 pixels (displayed at 128x128 in most contexts)
- Formats: PNG, JPG, or GIF (animated avatars require Nitro for users, but bots can use static images without restrictions)
- Max file size: 8MB
- Aspect ratio: Square (1:1). Non-square images will be cropped.
- Minimum size: 128x128, but higher resolution is recommended for clarity on high-DPI screens
For bot avatars specifically, 512x512 PNG is the sweet spot. It is large enough to look crisp on any screen, small enough to stay well under the 8MB limit, and PNG preserves the hard pixel edges that make pixel art look sharp.
Avoid JPG for pixel art — the lossy compression smudges the clean pixel boundaries that give the style its charm.
Why Pixel Art Works for Bots
Pixel art has a few properties that make it ideal for bot avatars specifically:
- Reads at small sizes. Discord renders avatars at 32x32 or 40x40 in message lists. Complex illustrations turn to mush at these sizes. Pixel art, designed for low resolution, stays legible.
- Instantly signals “not a human.” A pixel robot avatar makes it immediately obvious that the user is interacting with a bot. This sets expectations correctly.
- Scales without blurring. With
image-rendering: pixelated(or nearest-neighbor scaling), pixel art scales up cleanly. A 64x64 source image becomes a crisp 512x512 export with no interpolation artifacts. - Deterministic generation is possible. Unlike AI-generated portraits or hand-drawn illustrations, pixel art from modular parts can be generated from a seed string. The same bot name always produces the same avatar.
Generating a Bot Avatar with MechGen
MechGen is a client-side pixel-art mech avatar generator. Every mech is assembled from 7 part categories with 10 variants each, colored by one of 12 curated palettes. The key feature for bot developers is MechGen.fromSeed(string) — pass in any string and get a deterministic, unique robot avatar.
Quick Manual Approach
If you just need a one-time avatar for your bot:
- Open the MechGen generator
- Find the Bot Mode card and enter your bot’s name as the seed
- Click Generate (or press Enter)
- Click the Discord export button in the social export card
- Upload the downloaded 512x512 PNG to the Discord Developer Portal under your bot’s settings
This takes about 30 seconds and gives you a unique avatar tied to your bot’s identity.
Programmatic Approach with Puppeteer
For bots that need to generate avatars at runtime — or if you want to automate avatar generation as part of your CI/CD pipeline — you can drive MechGen from a headless browser.
const puppeteer = require('puppeteer');
async function generateBotAvatar(seed) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Load MechGen (use the live site or a local copy)
await page.goto('https://mechgen.app');
// Generate a deterministic mech from the seed
const dataUrl = await page.evaluate((s) => {
MechGen.fromSeed(s);
return MechGen.getImageDataURL(512);
}, seed);
await browser.close();
// Convert data URL to Buffer
const base64 = dataUrl.replace(/^data:image\/png;base64,/, '');
return Buffer.from(base64, 'base64');
}
This function takes a seed string, loads MechGen in a headless Chromium instance, generates the mech, and returns a PNG buffer. The same seed always produces the same image.
Setting the Avatar with Discord.js
Once you have the PNG buffer, setting it as your bot’s avatar with Discord.js is straightforward:
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [GatewayIntentBits.Guilds]
});
client.once('ready', async () => {
console.log(`Logged in as ${client.user.tag}`);
// Generate avatar from bot's username
const avatarBuffer = await generateBotAvatar(client.user.username);
// Set the avatar
await client.user.setAvatar(avatarBuffer);
console.log('Avatar updated successfully');
});
client.login(process.env.DISCORD_TOKEN);
A few important notes about setAvatar():
- Rate limits: Discord rate-limits avatar changes. You can only change a bot’s avatar twice per hour. Do not call this on every startup — check if an update is needed first.
- Error handling: Wrap the
setAvatar()call in a try/catch. If the rate limit is hit, Discord throws an error with aretryAftervalue. - One-time setup: For most bots, you generate the avatar once and never change it. Run the avatar generation as a separate script or a one-time setup command, not as part of your bot’s normal startup.
Caching the Avatar Locally
To avoid regenerating the avatar on every deployment, save it to disk:
const fs = require('fs');
const path = require('path');
async function ensureAvatar(seed) {
const avatarPath = path.join(__dirname, 'avatar.png');
// Use cached version if it exists
if (fs.existsSync(avatarPath)) {
return fs.readFileSync(avatarPath);
}
// Generate and cache
const buffer = await generateBotAvatar(seed);
fs.writeFileSync(avatarPath, buffer);
return buffer;
}
This way, Puppeteer only runs once. Subsequent deployments read the cached PNG.
Using a Local Copy of MechGen
Since MechGen is a single HTML file with no external dependencies (other than Google Fonts, which are not required for generation), you can bundle a local copy with your bot:
// Point Puppeteer at a local file instead of the live site
await page.goto(`file://${path.join(__dirname, 'mechgen.html')}`);
This eliminates the network dependency entirely. Your avatar generation works offline, in Docker containers, in CI environments — anywhere Puppeteer can run.
Generating Unique Avatars per Server
Some bots have different “personalities” per server. You can generate server-specific avatars by combining the bot name with the guild ID:
const seed = `${client.user.username}-${guild.id}`;
const avatarBuffer = await generateBotAvatar(seed);
While Discord does not support per-server bot avatars natively, you can use this approach to generate unique images for documentation, dashboards, or embed thumbnails that vary by server context.
Platform Export Sizes
MechGen includes built-in platform-aware export. If you are generating avatars for contexts beyond Discord, here are the supported platforms and their recommended sizes:
| Platform | Size | MechGen Method |
|---|---|---|
| Discord | 512px | exportForPlatform('discord') |
| X (Twitter) | 400px | exportForPlatform('x') |
| GitHub | 460px | exportForPlatform('github') |
| Steam | 184px | exportForPlatform('steam') |
| YouTube | 800px | exportForPlatform('youtube') |
| Twitch | 256px | exportForPlatform('twitch') |
In the Puppeteer context, you can call MechGen.getImageDataURL(512) with any custom size, or use the platform helpers for standard dimensions.
Putting It All Together
Here is a minimal but complete script that generates a MechGen avatar and sets it on a Discord bot:
const puppeteer = require('puppeteer');
const { Client, GatewayIntentBits } = require('discord.js');
const fs = require('fs');
async function generateAvatar(seed, size = 512) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://mechgen.app');
const dataUrl = await page.evaluate((s, sz) => {
MechGen.fromSeed(s);
return MechGen.getImageDataURL(sz);
}, seed, size);
await browser.close();
const base64 = dataUrl.replace(/^data:image\/png;base64,/, '');
return Buffer.from(base64, 'base64');
}
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once('ready', async () => {
try {
const avatar = await generateAvatar(client.user.username);
await client.user.setAvatar(avatar);
console.log('Bot avatar set successfully');
} catch (err) {
console.error('Failed to set avatar:', err.message);
}
});
client.login(process.env.DISCORD_TOKEN);
That is roughly 25 lines of code to go from “bot with no avatar” to “bot with a unique, deterministic pixel-art mech avatar.” No design skills required, no API keys, no image hosting. Just a seed string and a few function calls.
Your bot deserves better than the default gray icon. Give it a mech.