How to Change a Favicon with JavaScript
Change a favicon with JavaScript: swap the icon link, draw badge icons on canvas, build a favicon rotator, and prepare vector artwork that stays legible.
Contents
- The short answer
- Terms used in this guide
- Step 1: Declare a default favicon in the HTML
- Step 2: Write a reusable function that swaps the icon
- Step 3 (optional): Generate a dynamic favicon on a canvas
- Step 4 (optional): Build a favicon rotator
- Choosing the right approach
- Preparing logo artwork for a changing favicon
- Step 5: Verify the result
- Gaps in the available evidence
The short answer
Browsers read the favicon from a link element in the page head whose rel attribute contains "icon". To change the favicon with JavaScript, find that element and set its href to a different image. If the element does not exist, create it and append it to the head. You can point the new href at another image file, or at a data URL generated in the browser, for example from a canvas drawing. Every dynamic favicon technique, including notification badges, status colors and favicon rotators, builds on this single step.
The script only controls which image appears. Whether the result is legible in a browser tab depends on the artwork: a favicon is displayed at a very small pixel size, so the mark you swap in must be designed for that scale.
Terms used in this guide
- Favicon: the small icon a browser shows in tabs, bookmarks and history entries for a page.
- Vector artwork: an image defined by mathematical shapes rather than a fixed grid of pixels, so it can be scaled without losing edge quality.
- Path: a single vector shape made of points and curve segments; logos are built from paths.
- Artboard or viewBox: the canvas boundary of a vector file; in SVG, the viewBox attribute defines the coordinate area that is scaled to fit the display size.
- Rasterize: convert vector shapes into pixels at a specific size, such as when exporting a PNG.
- Data URL: an image encoded directly into a text string, so it can be used as an href without a separate file.
Step 1: Declare a default favicon in the HTML
Start with a normal static declaration. It gives every visitor a working icon before any script runs, gives your script an element to modify, and is the state you return to when a temporary change ends.
<link rel="icon" href="favicon.png">
Step 2: Write a reusable function that swaps the icon
The selector rel~="icon" matches any link whose rel list includes the word icon, which covers both rel="icon" and the older rel="shortcut icon". The function creates the element if the page lacks one.
function setFavicon(href) {
let link = document.querySelector('link[rel~="icon"]');
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.head.appendChild(link);
}
link.href = href;
}
const originalFavicon = document.querySelector('link[rel~="icon"]')?.href;
setFavicon('favicon-alert.png');
// Later, restore the default:
// setFavicon(originalFavicon);
If a browser does not repaint the tab after only the href changes, a common fallback is to remove the existing link element and append a new one with the new href. Storing the original href, as shown above, makes restoring the default icon straightforward.
Step 3 (optional): Generate a dynamic favicon on a canvas
When the icon must reflect live data, such as an unread count, you can draw the base logo onto a canvas element, add an overlay, and pass the canvas output to the same function. The example below draws a numeric badge in the top-right corner.
function setBadgeFavicon(baseSrc, count) {
const size = 32;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, size, size);
if (count > 0) {
ctx.fillStyle = '#d93025';
ctx.beginPath();
ctx.arc(size - 9, 9, 9, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 12px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(count > 9 ? '9+' : String(count), size - 9, 9);
}
setFavicon(canvas.toDataURL('image/png'));
};
img.src = baseSrc;
}
setBadgeFavicon('favicon.png', 3);
Serve the base image from the same origin as the page. Drawing a cross-origin image without the correct CORS headers taints the canvas, and the browser then blocks toDataURL with a security error. The canvas size and badge coordinates in the example are illustrative values, not a documented requirement; adjust them to your artwork.
Step 4 (optional): Build a favicon rotator
A favicon rotator cycles through a set of pre-made icons on a timer, for example to draw attention to a finished upload or an incoming message. Keep the frames few and stop the rotation once the user returns to the tab.
const frames = ['favicon.png', 'favicon-alert.png'];
let frameIndex = 0;
let rotator = null;
function startRotator(intervalMs) {
stopRotator();
rotator = setInterval(() => {
frameIndex = (frameIndex + 1) % frames.length;
setFavicon(frames[frameIndex]);
}, intervalMs);
}
function stopRotator() {
clearInterval(rotator);
rotator = null;
setFavicon(originalFavicon);
}
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
stopRotator();
}
});
Browsers commonly throttle timers in background tabs, so a rotator may run slower or less evenly than the interval you set. Constant flashing can also be distracting, so reserve animation for events the user actually needs to notice.
Choosing the right approach
| Approach | Best for | Main caveat |
| Swap to another image file | A small, fixed set of states such as normal, alert or offline | Every state needs its own exported file |
| Canvas-generated data URL | Live values such as counts or progress | Base image must be same-origin or CORS-enabled |
| Timed rotator | Briefly drawing attention to a background tab | Timer throttling and potential distraction |
Preparing logo artwork for a changing favicon
Symbol selection matters more for a dynamic favicon than for a static one, because each state has to be recognizable as the same brand while signaling something different. These are design recommendations rather than documented browser requirements.
- Use a simplified mark, such as a monogram or the logo's symbol alone, rather than a full logotype; long wordmarks become illegible at tab size.
- Build every state from one vector master file so shape, alignment and color stay consistent across variants.
- Keep the artboard square and center the symbol with consistent padding, so the icon does not appear to jump when the script swaps states.
- If you plan to overlay a badge, leave the corner where it will sit visually quiet so the badge does not cover an essential part of the mark.
- Prefer solid shapes and strong contrast over thin strokes and fine detail, which tend to disappear or blur when rasterized to small sizes.
- Convert any text in the symbol to outlined paths before export so the result does not depend on installed fonts.
- Export and preview each state at the actual pixel size it will display at, not only enlarged in the design application.
An SVG or PNG extension alone does not confirm that a file is clean vector artwork, that it is editable, or that you have permission to modify and reuse the logo. Check the source file's contents and the usage rights before building favicon variants from someone else's mark.
Step 5: Verify the result
- Open the browser's developer tools, inspect the head element, and confirm the icon link's href changed after your script ran.
- Watch the tab itself, not only the code, since a correct href does not guarantee the tab repainted.
- Test in every browser you need to support; behavior for script-driven favicon changes is not uniform, and the remove-and-reappend fallback may be needed in some.
- Check the console for canvas security errors if you use the badge approach.
- Switch to another tab and back to confirm a rotator stops and the default icon is restored.
- If an old icon persists, a cached file may be the cause; changing the file name or appending a version query string to the href helps rule this out.
Gaps in the available evidence
No confirmed source data was supplied for this guide, so it does not state which browser versions support dynamic favicon updates, SVG favicons or data URL icons, and it does not specify required icon sizes. The pixel values in the code samples are working examples only. Confirm current browser support and any platform-specific icon requirements against official browser documentation before shipping.