lucky.graphics

tutorials

How to Make a Multi-Size favicon.ico (16, 32, 48)

Build a favicon.ico with 16, 32 and 48 px images using ImageMagick, Pillow or a Node script, see the file structure, and verify the sizes inside.

Published
November 13, 2026
Topic
tutorials
Reading time
17 min
Review
Reviewed against the primary source

How to Make a Multi-Size favicon.ico (16, 32 and 48 px)

To create a favicon.ico with several sizes, draw or export one PNG per size (16, 32 and 48 pixels), then pack them into a single ICO: convert master.png -define icon:auto-resize=16,32,48 favicon.ico in ImageMagick, img.save("favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)]) in Python's Pillow, or the short Node script below. Then list the file's contents to confirm all three sizes are inside.

Built four ways from identical artwork, the same three-size icon came out between 1,784 and 15,086 bytes, and one documented Pillow option quietly produced a file holding three copies of the 16 pixel image. The difference comes from how each tool stores the images inside the container, which is why the file structure section matters as much as the commands.

The Test Icon and the Machine It Was Built On

Every number on this page comes from one session on a Linux machine, using ImageMagick 6.9.12-98, Pillow 12.3.0, Python 3 and Node.js 22.23.4. The artwork is deliberately simple: a dark green rounded square with a white square ring in the middle. We drew it at 256 by 256 pixels as a master (a 1,306-byte PNG), then made four small PNGs from it:

FileHow it was madeSize on disk
master-256.pngDrawn at 256 × 2561,306 bytes
scaled-16.pngMaster downscaled with Lanczos resampling440 bytes
hand-16.pngRedrawn by hand at 16 × 16, every edge on a whole pixel156 bytes
scaled-32.pngMaster downscaled676 bytes
scaled-48.pngMaster downscaled898 bytes

The hand-drawn 16 pixel file is a third the size of the scaled one, and that is not the main reason to draw it. We come back to it in the section on the smallest size. For now, keep two inputs in mind: a single large master, which every tool can resize for you, and a set of per-size PNGs, which give you control over each size. Each route below accepts one or both.

Route 1: ImageMagick, One Command

ImageMagick is free, open source and available for Windows, macOS and Linux. Its documented option for icons is a single define, icon:auto-resize, which the project describes as: "Automatically store multiple sizes when writing ICO (requires 256x256 input)."

From a 256 pixel master:

convert master-256.png -define icon:auto-resize=16,32,48 favicon.ico

On ImageMagick 7 the program is called magick, so the same line starts with magick instead of convert. We ran version 6, where convert is the command.

That produced a 15,086-byte favicon.ico holding exactly 16, 32 and 48 pixel images. Three details from the test are worth knowing before you rely on it.

Leave the size list off and you get ten sizes. Running the define with no value (-define icon:auto-resize) wrote 256, 192, 128, 96, 64, 48, 40, 32, 24 and 16 pixel images into one file of 300,463 bytes. That is roughly 20 times the three-size file, downloaded by every new visitor who requests /favicon.ico. Always pass the list.

The "requires 256x256 input" note was not enforced here. We also ran the command on the 48 pixel PNG, and ImageMagick 6.9.12-98 wrote a valid 16, 32 and 48 pixel file of the same 15,086 bytes without a warning. Other versions may behave differently, and starting from a larger master is the safer habit anyway, because downscaling from 256 gives the resampler more detail to work with than downscaling from 48.

The small entries are stored as uncompressed bitmaps. Our verification script (below) showed that ImageMagick wrote the 16, 32 and 48 pixel images as 32-bit BMP data, not PNG. In the ten-size file, only the 256 pixel entry was PNG. That one choice explains why ImageMagick's file is 7.3 to 8.5 times the size of the PNG-based files from the other routes.

Packing your own per-size PNGs with ImageMagick

If you have drawn each size yourself, list the PNGs and ImageMagick stacks them in order, one image per input:

convert hand-16.png scaled-32.png scaled-48.png favicon.ico

This also came out at 15,086 bytes, since the three images are the same pixel dimensions and stored the same way. The command does not resize anything, so check that each input is exactly the size you intend before you run it.

Route 2: Python and Pillow

Pillow is the maintained fork of the Python Imaging Library. If you have Python, pip install Pillow installs it. Its ICO writer takes a sizes option, documented as "A list of sizes included in this ico file; these are a 2-tuple, (width, height)". The default list is 16, 24, 32, 48, 64, 128 and 256, so name the three you want:

from PIL import Image

master = Image.open("master-256.png")
master.save("favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)])

Result: 2,068 bytes, three entries, every one stored as PNG. Pillow's documentation says "By default, the image data will be saved in PNG format", and that is what the verification script found. The 16 pixel entry inside the ICO was 440 bytes, identical to our standalone scaled-16.png, because both came out of the same Lanczos resize.

Two other Pillow options matter here.

bitmap_format="bmp" switches the entries to BMP storage. The same three sizes then took 14,510 bytes, in line with ImageMagick's result. Pillow added this option in version 8.3.0.

Sizes larger than the source are dropped. The documentation states that "Any sizes bigger than the original size or 256 will be ignored." Save a 32 pixel image with a 48 pixel size in the list and the file simply has no 48 entry, with no error.

The append_images trap

Pillow also lets you supply your own images for particular sizes through append_images, documented as "A list of images to replace the scaled down versions of the image." The natural first attempt is to pass the 256 master plus a hand-drawn 16:

master.save("favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)],
            append_images=[Image.open("hand-16.png")])

We ran exactly that on Pillow 12.3.0. The file was 522 bytes and contained three entries, all 16 by 16. ImageMagick's identify and our own parser both confirmed it: the 32 and 48 entries were missing, replaced by two more copies of the hand-drawn 16.

Reading the installed Pillow source shows why. For each requested size, the writer looks for a supplied image of exactly that size. If it finds none, it shrinks the last image it looked at, and after the search loop that is the last item in your list, which here was the 16 pixel image. A 16 pixel image cannot be enlarged by a thumbnail call, so the 32 and 48 slots got 16 pixel copies.

The fix is to supply an image for every size, so no scaling happens at all:

from PIL import Image

base = Image.open("scaled-48.png")
base.save("favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)],
          append_images=[Image.open("hand-16.png"), Image.open("scaled-32.png")])

That produced a 1,784-byte file with a 156-byte hand-drawn 16, a 676-byte 32 and an 898-byte 48, all PNG. The base image should be the largest one, because Pillow skips any size bigger than the image you call save() on.

Route 3: A Dependency-Free Node Script

You do not need a library to write an ICO. The container is a small header followed by the image bytes, and since Windows Vista an entry can hold a complete PNG file, so packing PNGs is mostly arithmetic. This script uses only Node's built-in modules:

// pack-ico.mjs: node pack-ico.mjs favicon.ico icon-16.png icon-32.png icon-48.png
import { readFileSync, writeFileSync } from "node:fs";

const [out, ...inputs] = process.argv.slice(2);
const pngs = inputs.map((p) => readFileSync(p));
const header = Buffer.alloc(6 + 16 * pngs.length);
header.writeUInt16LE(0, 0);           // idReserved, must be 0
header.writeUInt16LE(1, 2);           // idType, 1 = icon
header.writeUInt16LE(pngs.length, 4); // idCount
let offset = header.length;
pngs.forEach((png, i) => {
  const w = png.readUInt32BE(16);     // width from the PNG header chunk
  const h = png.readUInt32BE(20);     // height
  const e = 6 + 16 * i;
  header.writeUInt8(w >= 256 ? 0 : w, e);
  header.writeUInt8(h >= 256 ? 0 : h, e + 1);
  header.writeUInt8(0, e + 2);        // bColorCount, 0 for 8 bpp and up
  header.writeUInt8(0, e + 3);        // bReserved, must be 0
  header.writeUInt16LE(1, e + 4);     // wPlanes
  header.writeUInt16LE(32, e + 6);    // wBitCount
  header.writeUInt32LE(png.length, e + 8);
  header.writeUInt32LE(offset, e + 12);
  offset += png.length;
});
writeFileSync(out, Buffer.concat([header, ...pngs]));
console.log(out + ": " + offset + " bytes, " + pngs.length + " images");

Run against hand-16.png, scaled-32.png and scaled-48.png, it printed node-packed.ico: 1784 bytes, 3 images. That is byte for byte the same total as the corrected Pillow file, because both write a 54-byte header and then the same three PNGs unchanged (156 + 676 + 898 + 54 = 1,784). Pillow read the Node file back and reported its sizes as 16, 32 and 48, and ImageMagick's identify listed three PNG entries.

The script trusts its inputs. It does not check that they are square, that they are real PNGs, or that each is 32-bit RGBA. Raymond Chen's description of PNG entries on Microsoft's developer blog says "The image must be in 32bpp ARGB format", so a palette PNG from an aggressive optimizer is not a safe input. The Linux file command reported our three inputs as "8-bit/color RGBA", which is the 32 bits per pixel the format expects. Run that check on your own files before packing them.

Route 4: GIMP, If You Prefer a Window to a Terminal

GIMP is a free image editor, and the common way people build an icon in it is to put each size on its own layer (a 16 pixel layer, a 32 pixel layer and a 48 pixel layer), then choose File, Export As and give the file a .ico name, with each layer meant to become one image in the file.

We did not run GIMP for this article, and its published user manuals for versions 2.10 and 3.0 do not document the ICO export dialog, so we cannot give you its byte counts or confirm its default storage choice. Treat GIMP as a drawing tool for the per-size PNGs, then pack them with one of the three tested routes, or check the result with the verification step below, which works on any ICO whatever made it.

Inside the File: Header, Directory and Image Data

Microsoft's reference for the format is the "Icons" article by John Hornick, written in 1995 and still on Microsoft Learn. An ICO has three parts, in this order.

1. The ICONDIR header, 6 bytes. Three 16-bit little-endian numbers: idReserved, which "must be 0"; idType, which is "1 for icons"; and idCount, the number of images.

2. One ICONDIRENTRY per image, 16 bytes each.

Offset in entryFieldSizeWhat it holds
0bWidth1 byteWidth in pixels (0 means 256)
1bHeight1 byteHeight in pixels (0 means 256)
2bColorCount1 byteNumber of colours, "0 if >=8bpp"
3bReserved1 byteMust be 0
4wPlanes2 bytesColour planes
6wBitCount2 bytesBits per pixel
8dwBytesInRes4 bytesLength of this image's data
12dwImageOffset4 bytesWhere the image data starts, from the beginning of the file

The width and height fields are single bytes, so they top out at 255. Raymond Chen, writing on Microsoft's Old New Thing blog, explains that from Windows 95 and Windows NT 4, "the value 0 is accepted as representing a width or height of 256." That is why a 256 pixel entry shows as 0 × 0 in a raw hex dump.

For a three-image file, the header and directory together take 6 + 3 × 16 = 54 bytes, and the first image starts at offset 54. All three of our tested tools put it exactly there.

3. The image data. Each image sits at its offset and can take one of two forms.

BMP form, the original. Microsoft's article describes a DIB: a BITMAPINFOHEADER, a colour table if needed, the colour pixels (called the XOR mask) and then a 1-bit-per-pixel AND mask that marks transparent pixels. One rule surprises people writing their own parser: "The biHeight member of the icHeader structure represents the combined height of the XOR and AND masks." A 16 pixel icon's header says 32. Our parser found biHeight values of 32, 64 and 96 in ImageMagick's file, exactly double each size. Windows XP added 32-bit images with an 8-bit alpha channel, and Chen notes that even then "you are still required to provide a mask."

PNG form, from Windows Vista. Chen's description is short: "The format of a PNG-compressed image consists simply of a PNG image, starting with the PNG file signature." There is no bitmap header in front and no separate mask, because transparency comes from the PNG's alpha channel. A reader tells the forms apart by the first eight bytes: the PNG specification says a PNG datastream always starts with the hexadecimal values 89 50 4E 47 0D 0A 1A 0A.

Why the BMP entries weigh what they do

The BMP sizes can be worked out exactly, and doing so is a good check that a file is well formed. Take ImageMagick's 16 pixel entry:

  • Header: 40 bytes (a standard BITMAPINFOHEADER).
  • Colour pixels: 16 × 16 pixels × 4 bytes = 1,024 bytes.
  • AND mask: 16 bits per row is 2 bytes, padded to a 4-byte row boundary, so 4 bytes × 16 rows = 64 bytes.
  • Total: 40 + 1,024 + 64 = 1,128 bytes.

Our parser reported 1,128 bytes for that entry. The 32 pixel entry works out to 40 + 4,096 + (4 × 32) = 4,264 bytes, and the 48 pixel entry to 40 + 9,216 + (8 × 48) = 9,640 bytes, since a 48-bit row pads to 8 bytes. Both matched. Add 54 bytes of header and directory and you get 15,086, the file size on disk.

Pillow's BMP entries were 1,064, 4,136 and 9,256 bytes. Each is exactly the colour pixels plus 40 bytes, with no AND mask, so Pillow left out the mask for 32-bit images. Chen says Windows still requires one. If you need BMP entries for an old Windows consumer, ImageMagick's output follows Microsoft's description more closely. For a browser favicon, PNG entries avoid the question.

Uncompressed BMP data is a fixed cost: a 48 by 48 by 4 byte block is 9,216 bytes whether the artwork is a flat colour or a photograph. PNG compresses flat artwork heavily, and a favicon is about as flat as artwork gets.

Every Build, Measured

BuildEntriesStorageBytes on diskBytes after gzip
ImageMagick, auto-resize=16,32,4816, 32, 48BMP, 32-bit with mask15,086864
ImageMagick, auto-resize with no list10 sizes, 16 to 256BMP, PNG for 256300,463not measured
Pillow, sizes=16,32,4816, 32, 48PNG2,068not measured
Pillow, bitmap_format="bmp"16, 32, 48BMP, 32-bit, no mask14,510not measured
Pillow, append_images with only a 16three 16 × 16 copiesPNG522not measured
Pillow, append_images for every size16 (hand), 32, 48PNG1,784not measured
Node script, three PNGs16 (hand), 32, 48PNG1,7841,787

The gzip column needs a word of caution. Compressing the ImageMagick file with gzip at its default level shrank it to 864 bytes, smaller than the PNG-based file after gzip (1,787 bytes, slightly larger than the original because PNG data is already compressed). Uncompressed bitmaps of flat colour compress extremely well. So if your server or CDN applies gzip or similar compression to .ico responses, the BMP file may cost less on the wire than its size on disk suggests. If it does not, the PNG file is about one eighth the transfer. Check the response headers your own server sends for /favicon.ico (look for a Content-Encoding header) before deciding the BMP file is a problem. Either way, the ten-size default file is the one to avoid.

For broader context on how image bytes affect loading, our hero image file size and LCP budget guide works through the same kind of arithmetic for larger images.

Checking Which Sizes Are Inside an ICO

Never assume a converter did what you asked. The append_images result above looked fine in a file listing: 522 bytes, named favicon.ico, opened without errors. Only a listing of its entries showed the problem. Here are four ways to get that listing, from least to most detail.

The file command (macOS and Linux). Run file favicon.ico. On our Node-built file it reported "MS Windows icon resource - 3 icons, 16x16 with PNG image data", followed by the other entries. On ImageMagick's file it said "3 icons, 16x16, 32 bits/pixel, 32x32, 32 bits/pixel" and so on, with no mention of PNG, which is itself a hint that the entries are BMP.

ImageMagick's identify. identify favicon.ico prints one line per entry with its index, format and size:

favicon.ico[0] PNG 16x16 16x16+0+0 8-bit sRGB 156B
favicon.ico[1] PNG 32x32 32x32+0+0 8-bit sRGB 676B
favicon.ico[2] PNG 48x48 48x48+0+0 8-bit sRGB 1784B

That is our Node file, shortened. The PNG label means a PNG entry; ImageMagick labels a BMP entry as ICO. The byte figure on the last line is the whole file, not the last entry.

Pillow, in one line. python3 -c "from PIL import Image; print(sorted(Image.open('favicon.ico').info['sizes']))" printed [(16, 16), (32, 32), (48, 48)] for both our ImageMagick and Node files.

A 20-line parser that reads the directory itself. This is the most useful version, because it reads the header fields directly and catches mismatches between what the directory claims and what the image data contains:

# icoinfo.py: python3 icoinfo.py favicon.ico
import struct, sys

def info(path):
    data = open(path, "rb").read()
    reserved, kind, count = struct.unpack_from("<HHH", data, 0)
    print(f"{path}: {len(data)} bytes, reserved={reserved}, type={kind}, images={count}")
    for i in range(count):
        w, h, colors, res, planes, bpp, size, offset = struct.unpack_from(
            "<BBBBHHII", data, 6 + 16 * i)
        w, h = w or 256, h or 256
        blob = data[offset:offset + size]
        if blob[:8] == b"\x89PNG\r\n\x1a\n":
            pw, ph = struct.unpack_from(">II", blob, 16)
            form = f"PNG {pw}x{ph}"
        else:
            hdr, bw, bh, bplanes, bbits = struct.unpack_from("<IiiHH", blob, 0)
            form = f"BMP {bw}x{bh // 2} ({bbits}-bit, biHeight={bh})"
        print(f"  #{i}: dir {w}x{h}, {bpp} bpp, {size} bytes at offset {offset} -> {form}")

for p in sys.argv[1:]:
    info(p)

Its output for the ImageMagick file:

im-auto.ico: 15086 bytes, reserved=0, type=1, images=3
  #0: dir 16x16, 32 bpp, 1128 bytes at offset 54 -> BMP 16x16 (32-bit, biHeight=32)
  #1: dir 32x32, 32 bpp, 4264 bytes at offset 1182 -> BMP 32x32 (32-bit, biHeight=64)
  #2: dir 48x48, 32 bpp, 9640 bytes at offset 5446 -> BMP 48x48 (32-bit, biHeight=96)

And for the broken append_images file, where the problem is plain:

pil-hand.ico: 522 bytes, reserved=0, type=1, images=3
  #0: dir 16x16, 32 bpp, 156 bytes at offset 54 -> PNG 16x16
  #1: dir 16x16, 32 bpp, 156 bytes at offset 210 -> PNG 16x16
  #2: dir 16x16, 32 bpp, 156 bytes at offset 366 -> PNG 16x16

For PNG entries the parser reads the width and height from bytes 16 to 23 of the embedded PNG, inside its first chunk (the PNG specification requires the IHDR image header chunk to come first). For BMP entries it halves biHeight, following Microsoft's note. Comparing the directory's size with the size inside the image catches files where the two disagree.

The parser also surfaced a small difference between tools: ImageMagick and our Node script write 1 in the wPlanes field, while Pillow writes 0. Pillow, ImageMagick and the file command all read both versions without complaint, and Microsoft's article does not state a required value for that field, so we note it rather than call it a fault.

Drawing the 16 Pixel Entry

Resizing the master to 16 pixels is the easy part, and it is also where most favicons lose their shape. We counted the pixels in both 16 pixel versions of our test icon:

16 × 16 versionDistinct coloursPixels that are a blend of two colours
scaled-16.png (Lanczos from 256)26196 of 256
hand-16.png (redrawn on the grid)30 of 256

The master's white ring covers pixels 64 to 191, so at one sixteenth scale its edges land exactly on pixels 4 and 12 (64 ÷ 16 and 192 ÷ 16). Even so, Lanczos resampling draws on neighbouring pixels for each output pixel, which softens every edge, and the rounded corners add more blending. The result is soft at tab size even for a mark this simple. The hand-drawn version puts the ring at whole pixels 4 to 11, and its 156-byte PNG is 284 bytes lighter as a side effect.

A quick test before you draw: divide your smallest stroke width by your master size and multiply by 16. A 32 pixel stroke on a 256 master gives 32 ÷ 256 × 16 = 2 pixels, which survives. A 10 pixel stroke gives 0.625 pixels, which will smudge. Our icon design principles for modern interfaces cover pixel-grid alignment and optical weight in more depth.

If you design the master as an SVG, the same SVG can also serve as your scalable favicon, and our SVG optimization guide shows how to trim it. Export the PNGs for the ICO from the SVG at each exact size rather than from one large PNG, and then correct the 16 by hand.

Choosing 16, 32 and 48 (and When to Add a Size)

No standard fixes which sizes go inside a favicon.ico. MDN's note on the sizes attribute says "Microsoft's ICO format and Apple's ICNS format can store multiple icon sizes in a single file. ICO has better browser support, so you should use this format if cross-browser support is a concern." The 16, 32 and 48 set is a practical choice: 16 for a standard-density tab, 32 for the same tab on a display with two device pixels per CSS pixel, and 48 for larger contexts. Our favicon sizes and formats guide explains where each icon file in a full set is used and how browsers choose among them.

There is one reason to think about adding a size. Google Search Central says a favicon "must be a square (1:1 aspect ratio) that's at least 8x8px", and also "we recommend using a favicon that's larger than 48x48px so that it looks good on various surfaces." Google lists ICO among its supported formats (with BMP, GIF, PNG, JPEG, PPM and TIFF). A 48 pixel maximum meets the requirement but not the recommendation, which asks for larger than 48. You can meet it in two ways:

ApproachWhat changesCost measured or worked out here
Keep the ICO at 16, 32, 48; give Google a larger PNG through another icon link (an apple-touch-icon at 180, for example)Nothing in the ICOICO stays at 1,784 bytes (PNG entries)
Add a 64 pixel PNG entry to the ICOOne more directory entry and image16 bytes of directory plus the 64 pixel PNG; Pillow's 64 pixel entry for our test icon was 1,148 bytes
Add a 256 pixel PNG entrySame, larger imagePillow's 256 pixel entry for our icon was 1,306 bytes, the master PNG stored unchanged; a detailed master would be far larger

Most sites already ship a 180 pixel apple-touch-icon and 192 and 512 pixel manifest icons, which our app icon sizes guide for iOS, Android and PWAs lists. With those in place, keeping the ICO small is reasonable. A site that ships only a favicon.ico should consider the 64 pixel entry.

Decision rule for what goes in the file

  1. Start with 16, 32 and 48, each as a 32-bit RGBA PNG.
  2. Redraw the 16 by hand if any stroke would be under one pixel at that size.
  3. Add a 64 pixel entry only if the ICO is the only raster icon the site declares.
  4. Never ship a tool's default size list without reading it; our ImageMagick default produced 300,463 bytes.
  5. Run the verification step and confirm the sizes, the storage form and that the first image starts at offset 6 + 16 × the entry count.

Putting the File Live

Save the result as favicon.ico at the root of the site. MDN notes that "browsers automatically request /favicon.ico from the root of a site", and recommends explicit links anyway, since "providing explicit links protects you against changes to these conventions." A link that matches our three-entry file is:

<link rel="icon" href="/favicon.ico" sizes="16x16 32x32 48x48">

MDN says each listed size "must be contained in the resource", so the sizes value should match what the verification step printed, no more and no less. If you later add a 64 pixel entry, add 64x64 to the list.

That exact-sizes line suits a site whose only browser icon is the ICO. If you also ship an SVG favicon, a browser weighing an ICO that declares its pixel sizes against an SVG with no sizes value may pick the ICO and never show the SVG. In that case use the ICO and SVG pair from the markup section of our favicon guide, and check in each browser you support which file the tab actually requests.

If you replace an existing favicon.ico, browsers and Google may keep showing the old one for a while. The pillar guide covers caching and versioned file names; the short version is to change the file and the link together, then verify the live file by downloading it and running the parser on the downloaded copy rather than on your local build. That last step catches a stale CDN copy, which a local check cannot.

To compare the ICO with the other formats a site uses, our image formats guide covering SVG, PNG, JPEG, WebP and AVIF sets out what each one is good at.

Favicon ICO Questions

How do I make a favicon.ico with 16, 32 and 48 sizes?

With ImageMagick, run convert master.png -define icon:auto-resize=16,32,48 favicon.ico (use magick in place of convert on version 7). With Pillow, call save("favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)]) on a large master image. Then list the entries with identify favicon.ico to confirm all three are there.

Should the images inside an ICO be PNG or BMP?

Both are valid. PNG entries, supported since Windows Vista, made our three-size file 1,784 to 2,068 bytes; BMP entries made it 14,510 to 15,086 bytes. If your server compresses .ico responses, the BMP file shrinks a lot on the wire (864 bytes with gzip in our test), so check your response headers before worrying.

How can I see which sizes are inside a favicon.ico?

Run identify favicon.ico (ImageMagick) for one line per image, file favicon.ico on macOS or Linux for a summary, or the short Python parser on this page, which also shows whether each entry is PNG or BMP and where it sits in the file.

Why does my ICO only contain one size?

Common causes we reproduced or documented: a source image smaller than the sizes you asked for (Pillow silently drops larger sizes), Pillow's append_images given images for only some sizes (we got three copies of the 16), or a single-size PNG renamed to .ico, which is not an ICO at all.

Is a 48 pixel favicon.ico enough for Google Search?

It meets Google's minimum of 8 by 8 pixels, and ICO is a supported format, but Google recommends larger than 48 by 48. Either declare a larger raster icon as well, such as a 180 pixel apple-touch-icon, or add a 64 pixel entry to the ICO.

Can I make a favicon.ico without installing anything?

You need some way to run code. If Node.js is installed, the short script on this page needs no packages; it packs PNGs you export from any editor. Python's standard library could do the same with struct, as the parser shows.

Sources Checked for This Article

All file sizes, entry listings, pixel counts and gzip figures were measured in this test on November 13, 2026, with ImageMagick 6.9.12-98, Pillow 12.3.0 and Node.js 22.23.4. The Pillow append_images behaviour comes from running Pillow 12.3.0 and reading its installed source; other versions may differ. The BMP size arithmetic is our own, from Microsoft's field definitions.