How to display a default image for post thumbnail using functions.php?

The relevant code is in wp-includes/post-thumbnail-template.php. The thumbnail ID is stored in post metadata as _thumbnail_id.

You could either

  1. upload the default image to WordPress and make its ID the default thumbnail ID for any post that doesn’t have one:

    function default_post_metadata__thumbnail_id( $value, $object_id, $meta_key,
                                                  $single, $meta_type )
    {
        if ( '_thumbnail_id' == $meta_key )
        {
            $value = 123; // the ID for the default image
        }
        return $value;
    }
    add_filter( 'default_post_metadata', 'default_post_metadata__thumbnail_id', 10, 5 );
    

    This means that has_post_thumbnail() and get_the_post_thumbnail() will now find a thumbnail for the post even if one isn’t configured, and will just work as normal with the given ID, i.e. find the correct image size and generate the image tag for you etc.

    However I’d guess there’s a risk this ID would be saved into a post when you edit it,
    so you possibly want to only register the filter if if (! is_admin() ) {, i.e. on the front-end site only. And this does mean you’ll need to add the image to the media library, although it might be possible to fake that too e.g. by using ID -123 and hooking image_downsize_default_thumbnail to return an image for -123.

  2. override the has_post_thumbnail filter to always return true, and override the post_thumbnail_html filter to fill in missing HTML if it returns an empty string.

    function default_post_thumbnail_html( $html ) {
        if ( $html == '' ) {
            return sprintf("<img src=\"%s\" alt=\"%s\" />",
                esc_url( bloginfo('template_directory') . '/images/default-image.jpg' ),
                esc_url( get_the_title() ) );
        }
        return $html;
    }
    add_filter( 'post_thumbnail_html', 'default_post_thumbnail_html', 10, 1 );
    add_filter( 'has_post_thumbnail', '__return_true' );
    

    This way you have to generate the <img> completely yourself, but this will let you use an image that doesn’t have an ID.