How to Set Up Google Analytics for Canvas (Archived)

jperkins
Instructure
Instructure
119
62718

🛑 Please Read the Following before using this guide

This document is for a legacy implementation of Google Analytics on a deprecated version of Google Analytics no longer offered by Google. Do not attempt to use it. Instructure no longer uses Google Analytics in Canvas, and therefore updated documentation on using Google Analytics 4 (or similar) will not be provided by Instructure.  

An alternative option for Canvas usage data supported by Instructure is our Impact by Instructure product. Please reach out to your Instructure CSM or Sales representative if you'd like to explore this option.

If you still want to use Google Analytics, you should look into some of the below community resources if you are configuring Google Analytics for the first time. 

__________________________________________________________________________________

Using Google Analytics means that your institution is subject to Google's terms of service. Please verify with your institution's legal team before installing Google Analytics in your Canvas instance to ensure that you are in compliance with relevant laws and regulations.

New Custom Dimensions & Updated Script - May 31, 2019

A core code change to Canvas on Aug 28, 2019 may have broken your implementation of Google Analytics. Please install the updated code to restore functionality.

 

The steps below will help you get set up to use Google Analytics for Canvas. If you'd like to know more about why you might want to do this, please watch this Canvas Live session.

 

Create a Google Analytics Account

  1. Sign into Google
  2. Navigate to https://analytics.google.com  and click Sign Up
  3. Fill out the Form as desired, using your Canvas url in the website url field (eg: https://example.instructure.com) (or vanity url if you have one)
  4. Click "Get Tracking ID" button
  5. Accept Terms of Service
  6. Copy down the "Tracking ID" (eg: UA-12345678-1)

 

Set Up Tracking By User-ID New

Allows for more accurate session unification based on the user's Canvas ID, granting better user overtime metrics

  1. Navigate to the Admin Portal (If you just created your account you are already there)

  2. Click on "Tracking Info" to expand the menu item

  3. Click on "User-ID" option

  4. Read the agreements and follow the prompts

 

Add Custom Dimensions to Google Analytics Updated

This will let you pass custom variables from Canvas to Google Analytics such as User ID's, User Names, Course ID's and more.

  1. Navigate to the Admin Portal (If you just created your account you are already there)
  2. Click on "Custom Definitions" menu item
  3. Click on "Custom Dimensions menu item
  4. Click "+ NEW CUSTOM DIMENSION" button
  5. Add the following dimensions in order (If your index number is incorrect shared dashboards may not work)314355_pastedImage_7.png

 

Add Custom Javascript to Your Institution's Theme Editor

** Custom Javascript has been known to break things in Canvas. As of writing this post, May 31, 2019, it is working, but be aware that could change in the future.

  1. Download the attached Javascript File (If you cannot download for browser security reasons, you may copy and paste the code into a .js file from github. Minified Script. Non-minified Script.)
  2. Edit the File in a text editor
    • You'll need to customize line 3 (if using the minified version), or line 168 (non-minimized) with your tracking id number. Updated
    • //Working as of Aug 28, 2019
      function removeStorage(e){try{localStorage.removeItem(e),localStorage.removeItem(e+"_expiresIn")}catch(t){return console.log("removeStorage: Error removing key ["+e+"] from localStorage: "+JSON.stringify(t)),!1}return!0}function getStorage(e){var t=Date.now(),o=localStorage.getItem(e+"_expiresIn");if(null==o&&(o=0),o<t)return removeStorage(e),null;try{return localStorage.getItem(e)}catch(t){return console.log("getStorage: Error reading key ["+e+"] from localStorage: "+JSON.stringify(t)),null}}function setStorage(e,t,o){o=null==o?86400:Math.abs(o);var s=Date.now()+1e3*o;try{localStorage.setItem(e,t),localStorage.setItem(e+"_expiresIn",s)}catch(t){return console.log("setStorage: Error setting key ["+e+"] in localStorage: "+JSON.stringify(t)),!1}return!0}async function coursesRequest(e){let t=await fetch("/api/v1/users/self/courses?per_page=100"),o=await t.text();o=o.substr(9),o=JSON.parse(o);var s=JSON.stringify(o);return setStorage("ga_enrollments",s,null),parseCourses(e,s)}function parseCourses(e,t){if(null!=t){let s=JSON.parse(t);for(var o=0;o<s.length;o++)if(s[o].id==e)return s[o]}return null}function gaCourseDimensions(e){custom_ga("set","dimension4",e.id),custom_ga("set","dimension5",e.name),custom_ga("set","dimension6",e.account_id),custom_ga("set","dimension7",e.enrollment_term_id),custom_ga("set","dimension8",e.enrollments[0].type),custom_ga("send","pageview")}function googleAnalyticsCode(e){var t,o,s,n;if(custom_ga("create",e,"auto"),t=ENV.current_user_id,o=ENV.current_user_roles,custom_ga("set","userId",t),custom_ga("set","dimension1",t),custom_ga("set","dimension3",o),n=window.location.pathname.match(/\/courses\/(\d+)/)){n=n[1],s=0;try{let e=getStorage("ga_enrollments");if(null!=e){var r=parseCourses(n,e);null===r?coursesRequest(n).then(e=>{null===e?(custom_ga("set","dimension4",n),custom_ga("send","pageview")):gaCourseDimensions(e)}):gaCourseDimensions(r)}else coursesRequest(n).then(e=>{null===e?(custom_ga("set","dimension4",n),custom_ga("send","pageview")):gaCourseDimensions(e)})}catch(e){if((s+=1)>5)return custom_ga("set","dimension4",n),void custom_ga("send","pageview")}}else custom_ga("send","pageview")}!function(e,t,o,s,n,r,a){e.GoogleAnalyticsObject=n,e[n]=e[n]||function(){(e[n].q=e[n].q||[]).push(arguments)},e[n].l=1*new Date,r=t.createElement(o),a=t.getElementsByTagName(o)[0],r.async=1,r.src="https://www.google-analytics.com/analytics.js",a.parentNode.insertBefore(r,a)}(window,document,"script",0,"custom_ga");
      googleAnalyticsCode("UA-12345678-1"); // customize google analytics tracking number here‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
  3. Save the javascript file after making changes
  4. Upload the javascript file into your institution's Theme Editor
  5. Navigate back to Google Analytics and view real-time data to see if it is tracking real-time users on your account. Know that you may have to wait for 15 minutes or so to be able to see custom dimension data.

 

Build Dashboards & Reports in Google Analytics

  1. You may wish to get started by downloading the following dashboards:
  2. Build your own and once you get it the way you want, share the template URL in the comments on this document!

 

Embedded Custom Javascript File

There were issues with people copying the embedded Javascript. Please view the scripts on Github.

 

Changes Made (May 31, 2019) New

The updated script has undergone significant change. Below are some highlights of what the updated script does. The updated script is mostly backwards compatible with the previous version.

 

General Changes

  • Updated to remove any jQuery dependencies. Modern javascript is being used (may not work on Internet explorer). This means that API calls are now asynchronous and non-blocking. Improves performance.
  • Fixed a bug that meant some pageviews were never sent.
  • Added a timeout if API requests fail to avoid server spamming
  • Added support for Google Analytics User-ID tracking
  • Altered API Endpoint & Data Caching
    • Instead of pulling the course information (/api/v1/course/:course_id), the script pulls a user's courses ("/api/v1/users/self/courses?per_page=100") which includes an enrollment object and caches the returned value into local storage. The associated enrollment object allows us to set the "Canvas Course Role" dimension. Caching the data should also result in significantly better performance due to reduced API calls. If a course does not appear in the user's course list just the course_id and user dimensions will be sent (should only be for Admins and public courses).  Also it will only pull the first 100 enrollments for a user (let me know if you need support for pagination with the revised endpoint).

Dimension Changes

  • Canvas User RoleUpdated
    • Instead of filtering for a best match of a user's roles and only returning 1 value this is now a comma separated array of all of a user's roles. When filtering on this value, look use something like "contains" & "student" to get any users with a role of student. The data is still pulled from environmental variables. You should be able to build better Audience Segments from this data. (ie: Students should only have user, student, and maybe ta roles).
  • Canvas Term ID New
    • This can be a valuable filtering tool as much of Canvas built in reports filter on terms
  • Canvas Course Role New
    • By altering the way the script queries the course data, we are able to now capture the exact role the user has in a course. If a user has multiple user roles in a course, the value assigned to the pageview will always be the first role in the array. This should be a very useful dimension for building reports and analyzing course usage. If you think capturing all user roles will be helpful, let me know in the comments.
119 Comments
martin_kabat
Community Participant

Hi all, I have been asked to track data for 6 courses. Using Google analytics, is there a possibility to track data for a single course instead of all courses on our platform? Or courses in one sub-account?

Thank you

jperkins
Instructure
Instructure
Author

You could install the above script on a sub-account (with only those 6 courses in it) if your instance allows sub-account branding.

Enrique_Mtz2
Community Member

Good day
Has there been any change in the process or in GA? Suddenly it stopped registering the information in GA in the Overview section, in the Real-Time section information is recorded at the moment, but the history does not record information for today, the information from before yesterday is displayed correctly.
Greetings

mwike
Community Member

Has anyone set up Google Analytics with Canvas using the new Google Analytics 4 (G-) over the Universal Analytics (UA-) property? Wondering if this has created any issues on the Canvas side. (https://support.google.com/analytics/answer/10089681)

hollietaylr
Community Member

You can look at your keyword google analytics to see what times of the day or days of the week you are most likely to get clicks and conversions. Then, schedule your ads, so they only appear during the times you have lower costs per click or higher conversions.

This helps by reducing the amount of money you spend on ads that people are unlikely to click on. Managing  ppc campaigns at the same time, you are unlikely to see a significant reduction in the number of clicks, as you specifically chose those times without many clicks.

The result is that this method of scheduling your ads makes helpful changes to both important parts of the cost per click formula: it reduces the costs without reducing the clicks.

saidayouris
Community Member

hi 
thank you for your post but this guide not working for me
i think the script not supported for canvas lms
can anyone still work with Google Analytics in canvas lms to give me the right javascript file Please 
or the correct guide
thankyou 

escull
Community Explorer

I noticed yesterday (not exactly sure when it started), that the custom JavaScript suggested in this post was resulting in a JSON parsing error in the browser console (...Unexpected token , in JSON at position 5). Stems from the courseRequest() function...It looks like the while(1); statement that is normally prepended to JSON responses to prevent JSON hijacking is not there for some reason? 

async function coursesRequest(courseId) {
    let response = await fetch('/api/v1/users/self/courses?per_page=100');
    let data = await response.text();
    data = data.substr(9);
    data = JSON.parse(data)
    var stringData = JSON.stringify(data)
    setStorage('ga_enrollments', stringData, null)
    var course = parseCourses(courseId, stringData)
    return course
};

Changing the line:

data = data.substr(9);

to

data = data.replace('while(1);', '');

seems to have resolved the JSON parsing error while allowing it to continue to work if the while(1); statement starts appearing again. Not sure why it's not present in the response at the moment...

 

martin_kabat
Community Participant

Hi,

Is this article up to date? We have recently (16/04/2021) lost tracking by Canvas on our courses and can not find any notifications from Google or Canvas about any updates that would have caused this.

Fix above has solved our issue. Thank you to escull

martin_kabat
Community Participant

@escull your fix works, great job and thank you.  I wasn't able to see parsing issue in position 5 on our courses. Did you have a chance to find out why this issue occurred? This would help us to mitigate any future issues.

Thanks

korina_figueroa
Community Participant

If this has already been addressed and I missed it, apologies for bringing it up again!

In this comment, there's a discussion of how the Analytics Terms of Service prohibit sending identifiable information. I just noticed that the URLs for the Canvas inbox can include course id and both the user id and the full name of the person you're sending a message to. Since that URL would get passed to analytics during a pageview, does that also count as information that violates the terms of service?

Jul_Cro
Community Participant

@korina_figueroa I actually don't see that any URLs that contain that information. Where do you see those URLs? 

korina_figueroa
Community Participant

@Jul_Cro Under Behavior > Site Content > All Pages, if you search for "conversations", it'll list all the pages that match that, which are formatted like "/conversations?context_id=course_XXXXXXX&user_id=XXXXXXX&user_name=First Last"

On the Canvas side, the inbox doesn't always have that URL. I think I remember that I recreated it by starting a message from inside a course instead of starting in the Inbox from the lefthand nav.

berryjr
Community Explorer

Hi, it looks like google stopped tracking page views in our Canvas instance on 16th April 2021 (although some activity is still being tracked, such as menu clicks). I have seen this date mentioned a couple of times so something somewhere has changed!

Am I right in saying the post by @escull will fix this? If so, I will point our IT guys at this page!

c_murphy
Community Participant

Thank you escull! Fix has solved our issues. Phew.

martin_kabat
Community Participant

Hi all,

There are some changes in google analytics could you please share the new settings? In my old settings I have property setting tracking id matching description above, view tab where I could set up filters, etc. now I can only see Property tab, but no option to create view. Is there a major change in the setting?

tita
Community Participant

There doesn't seem to be an index for custom dimensions in Google Analytics 4. Is there an appropriate substitution? There is an event parameter but you can't enter a number. Is GA4 not compatible? Or does entering custom dimensions in order set the index (if so it's not displayed)?

JoanaBergman
Instructure
Instructure

Hello! I would like to know if anyone here had an experience of transitioning to GA4 that would like to share (special challenges/recommendations). Thank you so much in advance!

AndresCespedes
Community Member

Hi all,

Has anyone managed to setup Google Analytics 4?

JedKeenan
Community Participant

Hello @AndresCespedes et alia

We built this js below and it works fine, https://lookerstudio.google.com/reporting/a1edcff7-926d-4810-8253-9ed15a3eaa23, even with the UA tracking code:

function g() {
window.addEventListener("load", function () {
ENV.current_user_roles.indexOf("teacher") >= 1 &&
[...document.querySelectorAll(".teacher-only")].forEach((e) => {
(function (e, o, t, a, n, s, r) {
(e.GoogleAnalyticsObject = n),
(e[n] =
e[n] ||
function () {
(e[n].q = e[n].q || []).push(arguments);
}),
(e[n].l = 1 * new Date()),
(s = o.createElement(t)),
(r = o.getElementsByTagName(t)[0]),
(s.async = 1),
(s.src=a),
r.parentNode.insertBefore(s, r);
})(window, document, "script", "https://www.google-analytics.com/analytics.js", "gtag");
function v(e) {
try {
localStorage.removeItem(e), localStorage.removeItem(e + "_expiresIn");
} catch (o) {
return console.log("removeStorage: Error removing key [" + e + "] from localStorage: " + JSON.stringify(o)), !1;
}
return !0;
}
function h(e) {
var o = Date.now(),
t = localStorage.getItem(e + "_expiresIn");
if ((t == null && (t = 0), t < o)) return v(e), null;
try {
var a = localStorage.getItem(e);
return a;
} catch (n) {
return console.log("getStorage: Error reading key [" + e + "] from localStorage: " + JSON.stringify(n)), null;
}
}
function w(e, o, t) {
t == null ? (t = 24 * 60 * 60) : (t = Math.abs(t));
var a = Date.now(),
n = a + t * 1e3;
try {
localStorage.setItem(e, o), localStorage.setItem(e + "_expiresIn", n);
} catch (s) {
return console.log("setStorage: Error setting key [" + e + "] in localStorage: " + JSON.stringify(s)), !1;
}
return !0;
}
async function _(e) {
let t = await (await fetch("/api/v1/users/self/courses?per_page=100")).text();
(t = t.replace("while(1);", "")), (t = JSON.parse(t));
var a = JSON.stringify(t);
w("ga_enrollments", a, null);
var n = f(e, a);
return n;
}
function f(e, o) {
if (o != null) {
let a = JSON.parse(o);
for (var t = 0; t < a.length; t++) if (a[t].id == e) return a[t];
}
return null;
}
function u(e) {
gtag("set", "dimension4", e.id),
gtag("set", "dimension5", e.name),
gtag("set", "dimension6", e.account_id),
gtag("set", "dimension7", e.enrollment_term_id),
gtag("set", "dimension8", e.enrollments[0].type),
gtag("send", "pageview");
}
function m(e) {
var o, t, a, n;
if (
(gtag("create", e, "auto"),
(o = ENV.current_user_id),
(t = ENV.current_user_roles),
gtag("set", "userId", o),
gtag("set", "dimension2", o),
gtag("set", "dimension3", t),
(n = window.location.pathname.match(/\/courses\/(\d+)/)),
n)
) {
(n = n[1]), (a = 0);
try {
let r = h("ga_enrollments");
if (r != null) {
var s = f(n, r);
s === null
? _(n).then((l) => {
l === null ? (gtag("set", "dimension4", n), gtag("send", "pageview")) : u(l);
})
: u(s);
} else
_(n).then((l) => {
l === null ? (gtag("set", "dimension4", n), gtag("send", "pageview")) : u(l);
});
} catch {
if (((a += 1), a > 5)) {
gtag("set", "dimension4", n), gtag("send", "pageview");
return;
}
}
} else gtag("send", "pageview");
}
i();
g();
d();
globalThis.DT_variables = c;
m("UA-XXXXXXXXX-1");
})();