Contents
  1. Choose the right approach
  2. Before you start
  3. Prepare the favicon artwork
  4. Option 1: Set the Site Icon option in code
  5. Option 2: Filter the Site Icon URL from a child theme
  6. Option 3: Replace the favicon header code with your own tags
  7. Add a self-adapting SVG favicon for dark mode
  8. Confirm the result

To change a WordPress favicon programmatically, either point the built-in Site Icon feature at your own files with a filter, or remove the core favicon output and print your own link tags from a child theme's functions.php through the wp_head hook. The second route gives you full control, which you need for dark mode variants or an SVG favicon. Either way, the code only delivers the file. The result is only as good as the logo artwork, which must be simplified to read clearly at a very small size.

A favicon is the small icon a browser shows in tabs, bookmarks and history. WordPress calls its built-in version the Site Icon, managed in the Customizer and stored as a media attachment. A dark mode variant is a second version of the icon, or an icon that changes itself, shown when the visitor's system or browser prefers a dark color scheme. Browsers expose that preference as the prefers-color-scheme media feature.

Choose the right approach

Approach | What it does | Use it when
Set the site_icon option in code | Assigns an existing media attachment as the Site Icon, as if chosen in the Customizer | You deploy sites by script and want the standard core output
Filter get_site_icon_url | Keeps core markup but swaps the image URLs for files you control | You want theme-bundled PNG files without changing the markup
Remove wp_site_icon and print your own tags | Replaces core output entirely with link tags you write | You need an SVG favicon, media-query dark variants or custom sizes

For dark mode variants, use the third approach. The core Site Icon outputs one image source per size and has no option for color-scheme alternatives.

Before you start

  • Use a child theme, or a small site-specific plugin, so a parent theme update does not overwrite your code.
  • Have file access to the child theme folder through SFTP, your host's file manager or a deployment workflow.
  • Confirm you have the right to use the logo as a favicon. A file extension does not prove you have permission to reuse a mark.
  • Check whether your theme or an SEO, PWA or performance plugin already prints favicon tags. Duplicate tags make the result unpredictable.

Prepare the favicon artwork

A full logo rarely works at favicon size. Start from a reduced mark, such as a symbol, monogram or single letterform, rather than a scaled-down lockup with a wordmark.

  • Use a square artboard and center the mark with consistent padding, so it is not cropped or set off-center in tabs.
  • Simplify the paths. Remove fine detail, thin strokes and small counters that close up at small sizes. Merge overlapping shapes and delete hidden or empty layers.
  • Convert any live text to outlines. A favicon must not depend on fonts installed on the viewer's device. Check that the outlined letterforms keep their spacing and don't create stray points.
  • Expand strokes to filled shapes where possible. This keeps line weight predictable when the icon is scaled.
  • Inspect the SVG before use. An .svg file can contain an embedded raster image instead of real vector paths. Open it in a text editor or vector application and confirm it contains path data.
  • Create the dark variant as a deliberate color change, not an automatic inversion. Check that the mark still has enough contrast against a dark tab background, and that any outline or keyline still reads.

For exports, produce a clean SVG with a viewBox attribute and minimal editor metadata. Also export square PNG files for browsers and devices that do not use SVG favicons. The WordPress Customizer asks for a Site Icon that is square and at least 512 by 512 pixels, and it generates smaller sizes from that file. If you bypass the Site Icon, export each PNG size you reference, then check each one at actual size, not zoomed in. Export settings and menu names differ between vector applications and versions, so confirm them in the version you use.

Option 1: Set the Site Icon option in code

WordPress stores the Site Icon as an attachment ID in the site_icon option. If the image is already in the Media Library, you can assign it from a deployment script, a WP-CLI eval command or a one-time setup routine. Run this once, not on every page load.

<?php
// Assign an existing Media Library image as the Site Icon.
$attachment_id = 123; // Replace with the real attachment ID.
update_option( 'site_icon', $attachment_id );

The core output then works as if you had chosen the image in the Customizer. WordPress does not allow SVG uploads to the Media Library by default, so use this route for PNG artwork only.

Option 2: Filter the Site Icon URL from a child theme

The get_site_icon_url filter lets you keep the markup WordPress prints while serving image files from your child theme. The filter receives the requested pixel size, so you can map each size to a matching file.

<?php
// Serve Site Icon images from the child theme instead of the Media Library.
add_filter( 'get_site_icon_url', 'mytheme_site_icon_url', 10, 3 );

function mytheme_site_icon_url( $url, $size, $blog_id ) {
    $file = '/favicons/icon-' . (int) $size . '.png';

    if ( file_exists( get_stylesheet_directory() . $file ) ) {
        return get_stylesheet_directory_uri() . $file;
    }

    return $url;
}

To find the sizes your installation requests, view the page source and read the sizes on the generated icon tags. Then export one PNG for each size. The file_exists check falls back to the original URL if a size is missing, so a gap in your file set does not break the output.

Option 3: Replace the favicon header code with your own tags

WordPress prints the Site Icon tags through a function called wp_site_icon, attached to the wp_head hook. Remove that action, then add your own function that prints the link tags you need. This is the route for an SVG favicon and dark mode variants.

<?php
// Replace the core Site Icon tags with theme-supplied favicons.
add_action( 'init', function () {
    remove_action( 'wp_head', 'wp_site_icon', 99 );
} );

add_action( 'wp_head', 'mytheme_favicon_tags', 99 );

function mytheme_favicon_tags() {
    $dir = get_stylesheet_directory_uri() . '/favicons';
    ?>
    <link rel="icon" href="<?php echo esc_url( $dir . '/favicon.svg' ); ?>" type="image/svg+xml">
    <link rel="icon" href="<?php echo esc_url( $dir . '/favicon-light.png' ); ?>" type="image/png" media="(prefers-color-scheme: light)">
    <link rel="icon" href="<?php echo esc_url( $dir . '/favicon-dark.png' ); ?>" type="image/png" media="(prefers-color-scheme: dark)">
    <link rel="apple-touch-icon" href="<?php echo esc_url( $dir . '/apple-touch-icon.png' ); ?>">
    <?php
}

The priority in remove_action must match the priority WordPress used when it added the action. Core also prints the Site Icon on the login and admin screens through separate hooks, so those screens keep the Customizer icon unless you remove those actions too. If a theme or plugin prints its own favicon tags, disable that output in its settings or remove its hook as well.

Add a self-adapting SVG favicon for dark mode

An SVG favicon can carry its own dark mode rule, so one file switches color when the color-scheme preference changes. Add a style element inside the SVG and give the mark's paths a class. Keep the root svg element, including the namespace attribute your editor exports, and add the style block directly inside it.

<style>
  .mark { fill: #1a1a1a; }
  @media (prefers-color-scheme: dark) {
    .mark { fill: #f5f5f5; }
  }
</style>
<path class="mark" d="...your simplified logo path data..."/>

The colors shown are placeholders. Use your brand's approved values, and test the dark one against a dark tab background, not only against white. Inline style rules inside the file are more reliable than external stylesheets, because a favicon cannot load outside resources.

Support for SVG favicons, for media attributes on icon link tags and for the color-scheme preference inside favicon SVGs differs between browsers and versions. No browser support data was supplied for this guide. Keep the PNG fallbacks, and test the actual browsers your audience uses. The apple-touch-icon is a separate raster image used for home screen shortcuts, and it does not switch with the color scheme, so give it an opaque background that works in both modes.

Confirm the result

  • View the front-end page source and search for rel="icon". You should see only your intended tags, with no leftover core or plugin favicon tags.
  • Open each favicon URL from the source directly in the browser. Each should load the image, not a 404 page.
  • Check the tab in a private window. Browsers cache favicons aggressively, so a normal reload may keep showing the old icon.
  • Switch your operating system or browser between light and dark appearance, then reload, and confirm the icon changes or stays legible in both.
  • Clear any page cache, plugin cache or CDN cache, then recheck. A cached page can serve the old header markup.
  • Look at the icon at its real size in a tab and in a bookmark list. Confirm that thin details have not merged and that the outlined text, if any, is still readable.

If the icon does not change, the usual causes are cached markup, a second set of favicon tags printed by another plugin or theme, a remove_action call with the wrong priority, or a file path that points to the parent theme instead of the child theme. For child theme files, use get_stylesheet_directory_uri. The template directory functions point to the parent theme.