flexweg-favicon (must-use)

turns one uploaded square image into the full favicon + PWA manifest cluster every modern browser expects, then injects the matching tags into every published page.

flexweg-favicon turns one uploaded square image into the full favicon + PWA manifest cluster every modern browser expects, then injects the matching <link> tags into every published page.

It's must-use because every public site benefits from favicons — the cost of having it always-on is one <link> cluster per page, and the benefit is "your site looks like a real site in browser tabs and bookmark bars."

What it generates

From a single uploaded source image (PNG / JPG / WebP / SVG):

File Path on Flexweg Format
favicon-96x96.png /favicon/favicon-96x96.png 96×96 PNG (browser tab)
apple-touch-icon.png /favicon/apple-touch-icon.png 180×180 PNG with white background (iOS home screen)
web-app-manifest-192x192.png /favicon/web-app-manifest-192x192.png 192×192 PNG, purpose: maskable
web-app-manifest-512x512.png /favicon/web-app-manifest-512x512.png 512×512 PNG, purpose: maskable
favicon.ico /favicon/favicon.ico Multi-size (16/32/48) packed via pure-JS encoder
favicon.svg /favicon/favicon.svg Passthrough only when source is SVG
site.webmanifest /favicon/site.webmanifest PWA manifest (name, short_name, icons, theme_color, …)

All resizing happens client-side via createImageBitmap + canvas cover-crop. No server, no third-party API.

Settings

/settings/plugin/flexweg-favicon exposes:

Source tab

  • Upload image — square image, ideally 512×512 or larger. Drag-and-drop or click to pick.
  • Preview — shows the cropped result for each variant.
  • Generate + Upload — runs the full pipeline. ~5-10 s for a typical source.

PWA tab

  • App name — used as name in the manifest. Falls back to the site title.
  • Short nameshort_name; falls back to truncated app name.
  • Theme colour — browser chrome tint when the PWA is installed. Also injected as <meta name="theme-color">.
  • Background colour — splash screen background.
  • Display modestandalone (default), fullscreen, minimal-ui, browser.
  • Re-upload manifest — pushes only site.webmanifest (no source image required). Useful for tweaking PWA settings without re-running the resize pipeline.

How it hooks in

TS
api.addFilter<string>("page.head.extra", (current, props) => {
  const config = readConfig(props);
  const tags = buildHeadTags(config);
  return tags ? `${current}\n${tags}` : current;
});

The filter emits only the tags whose corresponding files were actually uploaded — so a partial install (e.g. raster source so no SVG variant) doesn't produce broken <link> references. Each URL is cache-busted via ?v=<uploadedAt> so changing the favicon invalidates browser caches on the next page load.

The plugin also registers a Regeneration target for Themes → Regenerate site → Favicon manifest that re-uploads site.webmanifest only. (The PNG/ICO/SVG variants would require the source image, which the plugin doesn't store — re-uploading them means going back to the Source tab and re-clicking Generate.)

Per-format flags

The plugin tracks which variants were successfully uploaded:

TS
{
  hasIco: true,
  hasSvg: false,         // source was raster
  hasPng96: true,
  hasAppleTouch: true,
  hasManifest: true,
  uploadedAt: 1735689600000,
}

Each flag gates one <link> tag in the output. So toggling SVG support doesn't require code changes — it follows from whether the source was an SVG.

Toggle

Disabling the plugin (in code, since it's MU and has no UI toggle) would stop emitting the <link> tags. Files at /favicon/ would persist on Flexweg until manually deleted.

When changes apply

  • Newly published pages include the new tags immediately (with the new ?v= query string).
  • Already-published pages keep their old <link> tags with the old ?v=. Browsers may serve cached favicons until the page is regenerated.

To roll out a favicon change site-wide: Themes → Regenerate site → All HTML pages.

Internal details

  • Source: src/mu-plugins/flexweg-favicon/
  • Hooks used: page.head.extra filter; registerRegenerationTarget
  • Storage path: /favicon/
  • Pure-JS ICO encoder: src/mu-plugins/flexweg-favicon/icoEncoder.ts
  • Translations: 7 locales

Continue