A couple weeks ago, I wrote about hiding Google doodles in Chrome using a bit of CSS. I talked about several ways to hide Google Doodles using other extensions you might already have installed, and then about my own Chrome extension called Hide Doodles. I really wanted to just use CSS for some reason, but it wasn't enough to do the job the way I wanted to do it. If I had just hidden the logo, it would've worked easily - but I didn't want to leave a blank area.

I thought I was pretty close using content:url() but it really only replaced the doodle image while leaving everything around it intact - the title that appeared on mouseover, the hyperlink to a google search of whatever theme the doodle pertained to, etc. And on days with no doodle, it caused the default logo to appear directly behind the search bar. Hmm. So I turned to JavaScript instead.

Replacing the doodle as it appears on the page

In JavaScript you can grab any element on the page by its ID, and it happens that the logo has an ID assigned to it. There are two logos actually - a large one on the main search page, and a small one on the search results page. Once you grab an element, you can replace it with Node.replaceChild().

If I waited until the entire page (aka DOM) loaded to replace the element, however, it could create a choppy effect as the element was replaced. So I opted for the arrive.js library, which uses mutation observers to monitor for the arrival of an element. The moment the element (the doodle) arrives, I replace it. So far, the effect seems to be unnoticeable.

Here's the concept so far:

// monitor for the arrival of the doodle element, which has an ID of "lga"
document.arrive('#lga', {onceOnly: true}, function(element) {

    // build up a new_element (default google logo)
    //   to replace the current element that "arrived" (doodle)

    // replace the doodle element that "arrived" with new_element
    element.parentNode.replaceChild(new_element, element);

But replacing the doodle with what?

I had a way to replace the doodle, but what should it be replaced with? I waited for a day with no doodle, then inspected the HTML markup and copied the element assigned the id of "lga" (on the main search page), and the id of "logocont" (on the search results page). Then it was a matter of rebuilding the elements with JavaScript and replacing the current elements with mine.

Here's the snippet I wrote to handle the main search page.

document.arrive('#lga', {onceOnly: true}, function(element) {

    // rebuild the 'default logo' element
    let logo = document.createElement('img');
    logo.id = 'hplogo';
    logo.height = '92';
    logo.width = '272';
    logo.src = '/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png';
    logo.srcset = '/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png 1x, /images/branding/googlelogo/2x/googlelogo_color_272x92dp.png 2x';
    logo.style.paddingTop = '109px';
    logo.style.width = '272';
    logo.alt = 'Google';

    // rebuild the 'lga' div container and insert the default logo into it
    let lga = document.createElement('div');
    lga.id = 'lga';
    lga.style.marginTop = '89px';
    lga.style.height = '233px';

    // replace the doodle element that "arrived" with the rebuilt logo element
    element.parentNode.replaceChild(lga, element);

And here's the other snippet, to handle the search results page:

document.arrive('#logocont', {onceOnly: true}, function(element) {
    let logo = document.createElement('img');
    logo.src = '/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png';
    logo.alt = 'Google';
    logo.height = '44';
    logo.width = '120';

    let params = (new URL(location.href)).searchParams;
    let hlValue = params.get('hl');
    var link = document.createElement('a');
    link.href = '/webhp' + (hlValue == null ? '' : '?hl=' + hlValue);
    link.title = 'Google';
    link.id = 'logo';

    let hdr = document.createElement('h1');

    let logocont = document.createElement('div');
    logocont.id = 'logocont';
    logocont.classList = 'nojsv logocont';

    element.parentNode.replaceChild(logocont, element);

A very thorough manifest.json file

There's no reason to run both of the above scripts on any single page though, as each element ("lga" and "logocont") occurs on a separate page. Enter the manifest.json.

When you create an extension/app - in Chrome, Firefox, a mobile phone, etc - there's nearly always a file where you can request how and where your extension should run. For Chrome, it's the manifest.json file. Not enough extensions use it properly, so an extension that only needs to run on one site might end up prompting you to give it access to all sites.

I created an exhaustive list that matched on certain patterns for every google domain available (there's about 200 of them).

  • The main search page is matched in the first "matches" block below (anything on a google domain without a path, or ending in "webhp", which occurs when you click the small logo on the search results page).
  • The search results page is matched in the second block (anything on a google domain ending with "search").

I told it to run the scripts at "document_start", which injects the files before any other DOM is constructed or any other script is run, to make sure that arrive.js is monitoring for the element before it arrives.

"content_scripts": [
        "matches": [
        "js": [
        "run_at" : "document_start"
        "matches": [
        "js": [
        "run_at" : "document_start"

And that's pretty much it!

You can check out the rest of source code on GitHub or grab the chrome extension from the store if you'd like. Feel free to let me know if you see any strange behavior, but so far it's been working like a charm.

And if you'd like to write your own Chrome extension, I wrote a brief introduction.