Fix GA Site Search With Google Tag Manager

How to fix Google Analytics site search tracking on your website, if it has features that make it difficult to track internal site search via Google Tag Manager.

Analyzing what people write in your site search field is pretty much one of the smartest things you can do for your website tracking. If certain terms pop up over and over again in internal search reports, it means that your site is not providing the answers people are looking for, meaning you have an excellent opportunity to provide supply for the demand!

However, not all site search applications are trackable out-of-the-box. In this post, I go over three scenarios, and I provide three solutions for tackling internal site search problems in Google Tag Manager.

1) No query parameter in URL

Google Analytics’ site search settings require a query parameter in the URL, e.g. https://www.simoahava.com/search?q=analytics. Often, however, the site search tool does not apply a query parameter to the URL. Rather, the keyword might be as a typical URL folder, like https://www.simoahava.com/search/analytics.

This is pretty commonplace. You might have seen this phenomenon in Drupal websites. It’s easy to fix with a URL macro and the page field in your pageview tag.

2) No search term in URL

This is a bit more difficult, since you can’t use the URL anymore. This means that the URL stays the same (or has some generic search path), and you need to look into on-page elements for information on what the user searched for. Some DOM scraping and the page field should do the trick.

3) Nothing on the page to use

This is the most difficult scenario to handle, since there’s nothing on the page for you to latch on to. Perhaps the search term isn’t iterated (stupid), or it’s not contained in a DOM element (stupid, stupid). This time, we’ll try to get the value of the search field in the form submit event, and pass that as a virtual pageview.

1) No query parameter in URL

I see this pretty often. You have a search tool but it doesn’t utilize a query parameter for the search term. Rather, it displays the keyword in a virtual folder in the URI. Nothing wrong with that, though every CMS should let you choose how the search terms are displayed in the URL.

This is what you’re going to do:

  1. In your ordinary pageview tag, you click open the Fields to set setting, and you add a new field with page as the field name. Field value would be the reference to a Custom JavaScript Macro {{search path}}.

  2. This macro will look for the search term in the URL

  3. If it finds a search term, it will return the modified document path with the search term as a query parameter

  4. If no search term is found, the macro returns false, meaning the document path will not be modified, and the pageview will fire normally

That’s the beauty of GTM, again. Since the macro returns false for non-search pageviews, you’ll just need the one, single, normal, ordinary pageview tag to cover all the searches as well. No need for extra tags, woohoo!

So, let’s say that the site search term is displayed like it’s displayed on www.drupal.org: /search/site/keyword. The {{search path}} macro should first check if the URI starts with /search/site/, and if it does, it should return whatever comes after, properly modified into a proper path.

Here’s the code for the {{search path}} macro:

function() {
  var regex = /^\/search\/site\/(.*)/;
  if(regex.test({{url path}})) {
    return "/search/site?q=" + regex.exec({{url path}})[1];
  }
  return;
}

The regular expression looks at the {{url path}} macro (which stores the current URI of the page), and if the path starts with /search/site/, it returns a modified version of the URI with the search term appended as the value of the query parameter q. Change this parameter to reflect whatever you want to have in your Google Analytics view’s site search settings.

Finally, just edit your pageview tag, add a new field to Fields to set, set page as the field name, and add your macro as its value. That’s all there is to it!

Don’t forget to do some Preview & Debugging, and also check with a debugger like WASP to see if the path gets sent correctly for search terms (and correctly for all other pages as well!).

2) No search term in URL

OK, this one is a bit trickier. In this case, there’s no search term in the URL for you to use. This solution requires that the search term is explicitly presented in an HTML element we can grab onto. The best way is to have it in a span or div with a unique ID attribute. Since it’s the best way, it’s the one I’ll be using in this example, with a <span> element as the wrapper. Feel free to get creative with the DOM if your search term is hidden deeper in the page.

For this to work, this is what happens:

  1. In your ordinary pageview tag, you click open the Fields to set setting, and you add a new field with page as the field name. Field value would be the reference to a Custom JavaScript Macro {{search path}}

  2. This macro will look for the search term in the HTML element with the ID you specify, using a new DOM Element Macro {{search term}}

  3. If it finds a search term, the {{search path}} macro will return the modified document path with the search term as a query parameter

  4. If no search term is found, the macro returns false, meaning the Document Path will not be modified, and the pageview will fire normally

First, let’s create the DOM Element Macro {{search term}}.

Replace the Element ID field with whatever your page is using.

Next, we’ll need to edit the Custom JavaScript Macro {{search path}}:

function() {
  if({{search term}} && {{search term}} !== 'null') {
    return "/search/site?q=" + {{search term}};
  }
  return;
}

So first we check if an element with the ID you specify exists. If it does, the macro returns "/search/site?q=<keyword>" as the document path, same as in the previous example. If no such element is found, the macro returns false, and the original document path (i.e. the URI of the page the visitor is on) is sent.

The final step is the same as before: edit your pageview tag, add a new field to the Fields to set, set the field name to page, and add this {{search path}} macro as the field value.

Oh, and don’t forget to preview, debug, and test, test, test.

3) Nothing on the page to use

This is bleak, but it happens. There’s absolutely nothing in the document object model that you can use to grab the search term. The URL is blank, there’s no search term printed on the page, no fairies whispering in your ear, NOTHING. I know, it sucks. Fire your developer. Just kidding. No, seriously, fire them. Yeah, I’m just kidding.

I’ve chosen to fix this using form tracking. It basically requires that your search field has some unique identifier you can grab hold of in the submit event. I hope you have a proper ID for the field (or at least a CLASS or NAME!). I’m using ID here, again, but feel free to modify the script to get the value of the page.

The thing is, you’ll need to send a virtual pageview with the form submission, and it will have the search term in the Document Path as in the previous examples. However, if you send a new pageview with the search, you’ll be double-counting your search views, since you’ll be sending a pageview for when the search page loads as well! You don’t want that. That’s why this case is a bit more complex, as you’ll be using a browser cookie to carry the search term into the search page pageview, so you can then use that as the document path.

Here’s what’s happening:

  1. Have a Form Submit Listener tag firing on all pages

  2. Create a new Custom HTML Tag which fires upon {{event}} equals gtm.formSubmit, i.e. when a form is submitted

  3. In this tag verify that a search was made, and retrieve the search term using a DOM Element Macro {{search field value}}

  4. In the tag, save this search term in a new browser cookie named “keyword”

  5. Create a new 1st Party Cookie Macro {{cookie search term}}, which retrieves this cookie value

  6. In the {{search path}} Custom JavaScript Macro, check if cookie exists; if it does, use that as the virtual page path

  7. Utilize {{search path}} as the value of a new field in the Fields to set settings of the Tag. Set the field name to page and the macro as the field value.

  8. Create a new Custom JavaScript Macro {{searchCallback}}, which deletes the cookie in the callback function of the pageview tag

PHEW!

First, the Form Submit Listener. This is easy, just create a new tag of type Form Submit Listener, and have it fire on all pages.

After that, create the DOM Element Macro {{search field value}}. Be sure to change the Element ID setting to the ID of the form field your webpage uses in site search.

Next, create a new Custom HTML Tag which fires upon {{event}} equals gtm.formSubmit. Have the following code within:

<script>
  if({{search field value}} && {{search field value}} !== 'null') {
    document.cookie = "keyword="+{{search field value}}+";path=/";
  }
</script>

Here we look for an HTML element with the ID “search-field”, and with a VALUE attribute. If this is found, then the content of this attribute is set into a new cookie named “keyword”. This is, in fact, whatever was typed into the search field at the time of form submission.

(NOTE! If there are multiple forms on a page, you might want to enhance this tag with a check that it was actually the search form that was submitted. It’s possible that another form is submitted with text in the search field, and this script would then falsely interpret this event as a submission of the site search form.)

Next, create a new 1st Party Cookie Macro, which retrieves the value from the “keyword” cookie. Call this {{cookie search term}}.

Next, modify the Custom JavaScript Macro {{search path}} to look like this:

function() {
  if({{cookie search term}}) {
    return "/search/site?q=" + {{cookie search term}};
  }
  return;
}

So, first we check if the cookie exists. If it does, then its value is returned as a properly formatted Document Path for the pageview tag.

Finally, create a new Custom JavaScript Macro {{searchCallback}}, which looks like this:

function() {
  if({{cookie search term}}) {
    return function() {
      document.cookie = "keyword=;path=/;expires=Thu, 01-Jan-70 00:00:01 GMT";
    }
  }
  return;
}

This looks for the “keyword” cookie, and if one is found, then the cookie is deleted. This is extremely important, since you don’t want to keep on sending the virtual pageview with every single page load! Cookies are deleted by setting their expiration date to the past.

Put these all together in your pageview tag, utilizing the Fields to set with both hitCallback and page populated accordingly.

Why have the cookie deletion in the hitCallback and not in the macro which returns the document path? Well, two reasons:

  1. A macro shouldn’t be used to set anything. The callback macro is obviously an exception, since it RETURNS a function which sets something.

  2. There’s some weird race condition in play, and if you refer to the 1st Party Cookie macro in the same script which also deletes the cookie, the macro returns an undefined. I’ll have to research this phenomenon a bit more.

This was way more difficult than the other methods, and let’s face it: If you need to resort to the form submission, you should probably flog your developer first. Make them create a site search engine that actually works with analytics, since it really is one of the best things you can measure.

Conclusions

I was long overdue a traditional tutorial post, so I hope you’ve enjoyed this one. I’m still working on the JavaScript for GTM: Part 2 (check Part 1 here), but now I’m also dreaming about the mini-vacation that starts tomorrow.

However, if this post raises any questions, if you have other ideas for tracking site search, or if you have a problem you need help with, feel free to drop a line below in the comments! Nothing like a JavaScript challenge to make summer more fun.

Have a great, warm, relaxing summer, my friends! Live life to the fullest, and dream of JavaScript and all things wonderful.