# favicons-webpack-plugin turns one logo.png into every icon a browser asks for

> This webpack plugin wraps the favicons library to generate the full set of icon files, web app manifests and HTML tags that iOS, Android, Windows Phone and desktop browsers each request, from a single source logo. The injection is what makes it useful, and the path resolution rules are what make it predictable.

**jantimon/favicons-webpack-plugin** — Let webpack generate all your favicons and icons for you

- Repository: https://github.com/jantimon/favicons-webpack-plugin
- Stars: 1,199 · Forks: 204
- Language: JavaScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/jantimon-favicons-webpack-plugin

## Forty four formats from one file

The plugin is a thin layer over the favicons library by itgalaxy, and its whole job is to remove the chore of maintaining every icon file a browser might ask for.

The default configuration automatically generates webapp manifest files along with 44 different icon formats as appropriate for iOS devices, Android devices, Windows Phone and various desktop browsers, out of a single `logo.png`. That is the pitch, and it is a real one. The set of things a browser will request is not obvious and it changes slowly, so an application that ships a hand-maintained `favicon.ico` and a couple of PNGs is almost certainly missing several of them.

The audience is a webpack developer with a logo and a build. The plugin is MIT licensed, the author is Jan Nicklas, and the package is published as `favicons-webpack-plugin` on npm.

There is a related project in the same ecosystem that is worth knowing about, because the HTML injection is the part people want and it only happens through html-webpack-plugin. The documentation states that in combination with html-webpack-plugin it will also inject the necessary HTML for you. The favicons library itself produces the files, and html-webpack-plugin is the mechanism that puts the tags into the page.

That division matters for how you configure things, and it is also the plugin's main limitation, which comes later in this article.

## Zero config means the logo goes into the webpack context

The zero configuration path is genuinely zero configuration, and the details of where the file goes are worth reading.

Installation is the plugin plus the library it wraps, both as development dependencies.

```shell
$ npm install --save-dev favicons favicons-webpack-plugin
```

Then you add your base logo as a `logo.png` file to your webpack context folder, and by default the context is the current working directory. The plugin is then added to the plugins array with no arguments at all.

```javascript
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')

plugins: [
  new FaviconsWebpackPlugin()
]
```

The basic usage passes the path explicitly, and the documentation notes that an SVG works too.

```javascript
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')

...

plugins: [
  new FaviconsWebpackPlugin('/path/to/logo.png') // svg works too!
]
```

So the fallback in the zero config case is to look for a `logo.png` in the webpack context, which by default is `process.cwd()`. That is convenient and it is also the source of a classic build failure, because the context is the directory you ran the build from and not necessarily the directory your source lives in. A CI job that runs from the repository root and a developer running from a package subdirectory will look in different places.

The tip in the documentation is that you might want to fine tune which vendors to support, and the advanced usage section is where that option lives.

## Path resolution follows webpack's own rules

The documentation has a short section titled A Note on Path Resolution, and it is the part that saves debugging time.

Under the hood, webpack resolves the path to the logo according to three rules. If the path is absolute, there is nothing to resolve and the path specified is used as is. If it is explicitly relative, starting with `./`, it is resolved with respect to webpack's context, which defaults to `process.cwd()`. If it is neither explicitly relative nor absolute, webpack attempts to resolve it according to `resolve.modules`, which defaults to `modules: ["node_modules"]`.

The third rule is the surprising one. A bare path like `logo.png`, with no `./` in front, is treated as a module request and looked for inside `node_modules`. A file called `logo.png` in your source tree is not found by that form of path, and the error that comes out is a module resolution error rather than a missing file error, which is a genuinely confusing first experience for anyone who has not read this section.

The practical rule that falls out of it is short. Use an absolute path or one that begins with `./`, and never a bare filename. The demo linked from the basic usage section shows this pattern in a CodeSandbox, which is the fastest way to see a working configuration.

There is an additional wrinkle in the advanced options, where the plugin is given both a `logo` and a separate `logoMaskable` source. Maskable icons are the version of an icon that a platform can crop to any shape, and the option is described as optional, which means you can keep the same source for both if your logo has enough padding.

## The injected HTML is the useful part

Generating the files is half the job. The other half is telling the browser where they are, and that is what the plugin contributes on top of the favicons library.

The list of injected tags is long, and reading it is the fastest way to understand what the plugin does. The apple-touch-icon links run from 57x57 all the way to 1024x1024, eleven sizes for iOS. Then come the apple-touch-startup-image links, each with a media query describing the device width, height, orientation and pixel ratio, covering portrait and landscape phones and tablets. Then the plain favicon links at 16x16 and 32x32, the yandex-tableau-widget link, and the meta tags, which include the apple-mobile-web-app-capable flag, the status bar style, the application name, the mobile-web-app-capable flag, the msapplication TileColor and TileImage, the msapplication-config pointing at a browserconfig.xml, and the theme-color.

Two of those deserve comment. The `msapplication-config` meta tag is what makes a Windows browser read a `browserconfig.xml` to produce its tile images, which is the Windows Phone and modern Windows equivalent of the manifest file. And the yandex-tableau-widget link is a niche one, a Russian browser that looks for its own manifest JSON.

The manifest itself is linked as `<link rel="manifest" href="/assets/manifest.webmanifest">`, and the shortcut icon points at the generated `favicon.ico`.

So the plugin is producing a fairly complete answer to the question of what a modern browser wants, and the HTML is what makes it take effect. Without the injection you would have the files and no way for a browser to find them.

## The example directory is the configuration reference

The repository has an `example/` directory with six subdirectories, and their names describe the configuration surface more clearly than the option list does.

There is `basic/`, which is the zero configuration or minimal case. There is `custom/`, which is where the advanced options are exercised, and `custom-manifest/`, which is the case where the generated web app manifest needs to be adjusted. There is `dev-server/`, which matters because the plugin writes files into the output directory and a dev server with hot reloading has to cope with that. There is `multiple/`, for the case of more than one set of icons. And there is `no-inject/`, which is the one worth understanding, because it is the configuration where the plugin generates the icon files but does not inject the HTML tags.

That last one is the escape hatch, and it tells you the shape of the design. The generation and the injection are separable, which means a project that needs custom markup in the head, for instance a different set of tags for different routes, can take the files and write the tags itself. The README also carries a tip that you might want to fine tune which vendors to support, which reduces the number of files and the number of tags when you know exactly which platforms you care about.

The test setup follows the same shape. There is a `test/` directory with an `expected` fixtures tree, and the documentation links to a fixtures path listing the 44 default formats, which is effectively a specification of what the default configuration produces. The package manifest has a `test` script that runs `c8 node --test`, so the test runner is Node's own built in one with the c8 coverage tool, and there is an `update-snapshots` script using `node --test --test-update-snapshots` for when the expected output legitimately changes.

That snapshot approach is the right one for this kind of plugin. The output is a list of files and a list of tags, and asserting on the exact set of both is what catches a regression in a new favicons release.

## Where the plugin stops

The limitations are structural and follow from the design.

The first is that it is a webpack plugin. There is no Vite, Rollup or Parcel equivalent in this repository, and a project on a different bundler cannot use it. That is not a criticism of the implementation, it is the shape of the ecosystem, but it does mean the plugin's reach is bounded by webpack's reach. The npm keywords say the same thing, with webpack, plugin, html-webpack-plugin, webapp, pwa, manifest, favicon, icon, png and svg.

The second is that the injection is a single fixed set of tags. The plugin is not aware of your pages, your routes or your templates, and it cannot produce a different set of tags for a marketing site and an application shell. The `no-inject` example exists precisely for that case, and it means the person who needs the custom markup has to write it.

The third is that everything is generated from one source image, so the quality of the output is bounded by the quality and the padding of that image. A logo designed for a 16 pixel favicon will not become a good 1024 pixel touch icon, and there is no way in this plugin to supply a separate asset per size. The `logoMaskable` option covers the maskable variant, which is the one case where a separate source genuinely helps, since a maskable icon needs its own safe zone.

The fourth is the version. The package manifest declares version 6.1.0-alpha.0, and the repository has no GitHub releases, so there is no tagged history to pin against. The last push was on 2026-08-11. A project on a major version of webpack, or one that needs the plugin's output to be byte for byte stable across builds, has to read the changelog and the test fixtures before upgrading, because the alpha line is where the behaviour can still move.

The alternative to consider, and the difference in approach, is a build step that runs outside the bundler. A small script that calls the favicons library directly and writes the files, with the HTML written by hand, gives the same files with none of the bundler coupling and none of the injection assumptions. The plugin's value over that is convenience and the automatic injection, and its cost is that you have accepted a webpack plugin's opinion about your HTML.

## Conclusion

favicons-webpack-plugin suits a webpack project that has one logo and needs the whole set of vendor icons, tags and manifests maintained without anyone remembering which sizes exist. It is the wrong choice for a project that does not build with webpack, and the wrong choice if you need to hand-tune the exact markup per page, because the injection is a single fixed set of tags. Check first the version you are installing, since the manifest declares 6.1.0-alpha.0, and look at the example directory to see the configuration surface before wiring it into a production config.

## FAQ

### What does favicons-webpack-plugin do?

It wraps the favicons library to generate every icon format, the web app manifest files and the HTML tags that iOS, Android, Windows Phone and desktop browsers need, from a single source logo. The default configuration produces 44 icon formats.

### How do I install it?

With `npm install --save-dev favicons favicons-webpack-plugin`, and then adding `new FaviconsWebpackPlugin()` to the plugins array in your webpack config, with your base logo named `logo.png` in the webpack context folder, which by default is the current working directory.

### Why does webpack fail to find my logo.png?

Path resolution follows webpack's rules. A path that is neither absolute nor explicitly relative with a leading `./` is treated as a module request and looked for in `resolve.modules`, which defaults to `node_modules`. Use an absolute path or one beginning with `./`.

### Does the plugin inject the HTML tags itself?

Yes, in combination with html-webpack-plugin it injects the necessary HTML, including the apple-touch-icon and apple-touch-startup-image links, the favicon links, the manifest link and the meta tags for the mobile web app and Windows tile configuration. There is also a no-inject example for projects that want to write their own tags.

## Sources

- [Issues](https://github.com/jantimon/favicons-webpack-plugin/issues)
- [jantimon/favicons-webpack-plugin on GitHub](https://github.com/jantimon/favicons-webpack-plugin)
- [License: MIT](https://github.com/jantimon/favicons-webpack-plugin/blob/main/LICENSE)
- [README](https://github.com/jantimon/favicons-webpack-plugin/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jantimon-favicons-webpack-plugin
