angular.json assets array + src/index.html

Angular Favicon Generator

Angular is the one framework here where dropping files in a folder is not enough. The CLI only copies what the assets array in angular.json tells it to copy, and index.html resolves icon paths against a base href.

Drop an image
Drop a logo or image
PNG, JPG, WebP · square 512×512+ works best · non-square images are padded, never cropped · processed in your browser

Favicon Setup for Angular — Free, Fast & Private

Newer Angular CLI versions scaffold a public/ directory at the project root and give it a single catch-all entry in the assets array. Older projects put favicon.ico directly in src/ and everything else under src/assets, with two separate entries. Both are still perfectly valid; what matters is that whatever directory you use appears in angular.json.

Where each generated file goes in Angular

The generator above emits seven files with fixed names. Static assets in Angular live in public/ (Angular 18+) or src/assets (earlier), and the markup is written in src/index.html. This is the mapping between the two.

Generated fileGoes toNotes
favicon.icopublic/favicon.icoPre-18 layout: src/favicon.ico, with its own entry in the assets array.
favicon-16x16.pngpublic/favicon-16x16.pngPre-18: src/assets/favicon-16x16.png.
favicon-32x32.pngpublic/favicon-32x32.pngPre-18: src/assets/favicon-32x32.png.
favicon-48x48.pngpublic/favicon-48x48.pngOptional — duplicated inside the .ico.
apple-touch-icon.pngpublic/apple-touch-icon.pngiOS home screen, 180×180.
android-chrome-192x192.pngpublic/android-chrome-192x192.pngManifest icon, needed for ng add @angular/pwa.
android-chrome-512x512.pngpublic/android-chrome-512x512.pngManifest icon and splash screen.

The assets array is the whole game

Angular does not have a magic static folder. The CLI builder copies exactly the paths listed under projects.<name>.architect.build.options.assets in angular.json, and anything not listed is invisible to the build no matter how sensibly it is named. This is the number one reason an Angular favicon works with ng serve on one machine and 404s in production on another: someone added the file, someone else changed the assets array, and the two never met.

The modern shape is a single glob entry that treats public/ as a copy-everything directory. The older shape names src/favicon.ico explicitly and adds src/assets as a directory with an output path. Read your angular.json before you move any files — the layout you have is the layout the build expects, and converting between them is a deliberate change, not a cleanup.

If you run a multi-project workspace, remember that each project has its own assets array. A favicon added to the library-adjacent demo app will not appear in the real one.

angular.json — both layouts

// Angular 18+ scaffold: one glob covers the whole public/ folder
"assets": [
  { "glob": "**/*", "input": "public" }
]

// Older layout: favicon.ico named explicitly, assets copied as a directory
"assets": [
  "src/favicon.ico",
  { "glob": "**/*", "input": "src/assets", "output": "/assets" }
]

Relative hrefs and the base href

Open a scaffolded src/index.html and you will find <base href="/"> in the head and a favicon link written as href="favicon.ico" with no leading slash. That is not sloppiness. Angular applications are routinely deployed under a path prefix, and the CLI supports that with a single build flag, --base-href, which rewrites that base element. Every relative URL in the document then resolves against it automatically, favicons included.

Write href="/favicon.ico" instead and you have opted out of that machinery: the icon will resolve to the domain root regardless of where the app actually lives, which is fine at the root and broken under a prefix. Keeping the hrefs relative is the whole reason the base element is there.

The APP_BASE_HREF injection token you may have seen is a runtime concern for the router, not a build-time one for HTML. It does not affect where the browser looks for your icon files.

src/index.html — relative paths under <base>

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Your App</title>
  <base href="/">
  <link rel="icon" href="favicon.ico" sizes="16x16 32x32 48x48">
  <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
  <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
  <link rel="manifest" href="manifest.webmanifest">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body><app-root></app-root></body>
</html>

The build output moved

Since the application builder became the default, ng build writes to dist/<project-name>/browser/ rather than straight into dist/<project-name>/. The extra directory exists to make room for a server/ sibling when SSR is enabled. Deployment scripts written against the older layout will happily upload the parent folder, and the result is a site where index.html is one level too deep and every relative icon path resolves to nothing.

When a favicon mysteriously disappears after an Angular upgrade, check the deploy target before you touch any markup. Compare the file listing under dist against what your host is actually serving; nine times out of ten the icon is present in the build and the wrong directory got shipped.

PWA support and the manifest

Running ng add @angular/pwa wires up the service worker and drops a manifest.webmanifest into the project along with a pile of Angular-branded PNGs under an icons directory. It also adds the manifest link tag to index.html and registers the icons at eight sizes, most of which no current platform requests.

Trim it. Point the manifest at android-chrome-192x192.png and android-chrome-512x512.png, delete the generated Angular logos, and confirm the paths still resolve after the assets array change. The service worker adds a second cache layer in front of everything, so after a favicon change expect to need a full reload — or a service worker update cycle — before the new icon appears, even in a fresh tab.

manifest.webmanifest — trimmed to what platforms ask for

{
  "name": "Your App",
  "short_name": "App",
  "start_url": "./",
  "display": "standalone",
  "icons": [
    { "src": "android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "android-chrome-512x512.png", "sizes": "512x512", "type": "image/png",
      "purpose": "any maskable" }
  ]
}

Whatever you paste, the artwork itself never leaves your machine. The set above is rendered with the Canvas API and the .ico container is assembled in JavaScript on your device, so an unreleased logo stays unreleased. If the 16px preview looks like mush, that is the honest signal to simplify the mark before it ships to a Angular tab near you.

How it works

  1. Generate the set: Drop a square 512px logo into the tool above and download the full set.
  2. Read angular.json first: Find the assets array for your project. Newer scaffolds glob public/; older ones name src/favicon.ico and src/assets separately.
  3. Copy files into the listed directory: Put the seven files wherever that array points, and add an entry if you are introducing a new folder.
  4. Edit src/index.html with relative hrefs: No leading slash — the <base href> element is what makes subpath deployments work.

Frequently asked questions

Where does the favicon go in an Angular project?
Wherever angular.json says. Angular 18 and newer scaffold a public/ directory covered by a single glob entry in the assets array; earlier projects keep src/favicon.ico with its own entry and everything else under src/assets. Files outside the assets array are never copied into the build.
Why is my Angular favicon missing after ng build?
Almost always the assets array. The CLI copies only the paths listed there, so a file dropped into a folder the array does not mention is silently ignored. Check angular.json, then check that you edited the array for the right project in a multi-project workspace.
Should the href start with a slash?
No. The scaffolded index.html contains <base href="/"> and writes icon paths relative, so a build made with --base-href /subpath/ still resolves them correctly. A leading slash pins the icon to the domain root and breaks any subpath deployment.
My favicon vanished after upgrading Angular.
The application builder writes to dist/<project>/browser/ rather than dist/<project>/. Deploy scripts written for the old layout ship the parent directory, leaving index.html a level too deep. Compare the build output with what your host is actually serving before editing any markup.
What about the icons ng add @angular/pwa generated?
They are Angular-branded placeholders at eight sizes, most of which nothing requests any more. Replace the manifest icons array with the two android-chrome PNGs and delete the rest. Keep the manifest link tag it added to index.html.
Why does the old icon persist with a service worker installed?
The Angular service worker caches assets in front of the browser cache, so a favicon change needs a service worker update cycle before it is visible. A hard refresh alone may not be enough — unregister the worker in DevTools if you are testing.
Do I need a separate entry for favicon.ico?
Only in the older layout, where it sits directly in src/ rather than under an asset directory. With the public/ glob entry, favicon.ico is covered like everything else in that folder.

All Image Tools

Solutions by use case