Skip navigation
All Places > Canvas Admins > Blog > Author: James Jones

Canvas Admins

3 Posts authored by: James Jones Expert

In the question User Creation Report , Lecia Sims asked

Is there a report I can run that will show when a user was added and how? Ex. I want to know who was added by SIS and who was added manually. In addition, if manually, which individual with admin privileges. I bet there is - but I have searched until my head hurts! :-(

I started writing the response there, but then realized that it might be better as a blog so that people could find it more easily. If anyone has a better solution, please share. I've documented what doesn't work, as well as what does.


There is no built in report that will do this, but depending on how they were added, you may have some ability to determine this. I think a question very similar to this was asked recently, but I can't find it now. Oh well, I like reinventing the wheel. Hopefully someone will have a faster way.


Telling whether it was a SIS import or a manual creation is relatively easy. Telling who created the account is a tad tougher.


SIS Imports


First, there is the SIS Export report from the Admin > account > Settings > Reports screen. Ideally, your SIS would already know which accounts it had created and possibly even when it created them. Unfortunately, the user.csv file does not contain the date at which the account was created, but you will be able to see which user accounts were created through the SIS import process.


There may? be a way to get the dates that someone was added through the SIS.


You can Show user details using the Users API.

GET /api/v1/users/:id

For example, this is what it says for me, but notice that there is no date when my account was created.

However, there is a sis_import_id. You can take ID and run the Get SIS import status endpoint of the SIS Import API.

GET /api/v1/accounts/:account_id/sis_imports/:id


Replace the :id at the end with the sis_import_id from the user record. There's a lot of information returned (I cropped it below), but you want the created_at time to see when the request was made.

There -- now you know that my account was created by a SIS import on January 22, 2015, at 5:25 pm CST.


Except that's not true. I've had my account since 2012. That was the last time a SIS import sent my information, not the first time. We used to send full dumps once a day to Canvas and that must have been when I implemented a flag to say this information is definitely in the system, stop sending it.



However, if you go into our SIS, there's a date of when I got my account. That's not true for me since I've been around longer than our SIS has maintained that information, but it's more accurate than what's in Canvas.


The user ID of the account used to generate the SIS import is not available, so you won't be able to tell who created the account using a SIS import through the API.


However, you can tell if the account wasn't created using a SIS import if it's not in that SIS Export report. You can probably also use the Provisioning Report, which includes a "created_by_sis" column that will be true if the user account was created through a SIS import. Still no dates, though.


Manually Added Accounts

Remember that the user details did not return a date that the account was created. The only way I was able to get a date was to use the SIS Import status, but there will be SIS import for manually added reports.


If you are lucky enough to know when the account was created and you have a list of potential admins who might have created it, then you try go through the Page Views in Canvas for each of the admins and look to see if anyone created a user during that time.


This information is also available through the List user page views endpoint of the Users API and you can put restrictions on the dates to make the search go more effectively.

GET /api/v1/users/:user_id/page_views


For example, let's say that the Canvas user ID I'm trying to track down is 123456 and I know they were created around March 10, 2017. A suspect admin for creating the account has a Canvas User ID of 321. You can add a per_page=100 to the query to get 100 page requests at a time, but you will almost undoubtedly have to deal with pagination when getting the data since admins are likely to make more than 100 requests during that time.


I made this call (the blurred ID is the 321)

GET /api/v1/users/321/page_views?start_time=2017-03-09&end_time=2017-03-11


You're looking for a POST to /accounts/:account_id/users.


A slight (ok, major) problem is it isn't there!


While this looks promising, I was testing it with a user account where I knew the exact second when the account was created and I knew who created it, but it didn't show up in the page requests. To verify that I wasn't missing something, I went in and created a dummy user and then looked at my page views and sure enough, it didn't show.


So, you're out of luck through the page views. But the good news is that I need to stop recommending this way to people because it doesn't work.


Canvas Data to the Rescue

If you have access to Canvas data, then you can track down which admin created a user account through the web UI.


I'm going to provide some MySQL statements but they should be adaptable. Also realize that I've fixed the misspelled field name on my end in the requests table.


When was the account created?

The user_dim table contains a column called created_at, which is the timestamp when the account was created.

SELECT created_at FROM user_dim WHERE canvas_id = 123456;

This gets you this output.


Of course, replace the 123456 with the Canvas user ID of the account in question.


If you run it with my ID, you get:

June 13, 2012 sounds legit as I know I was teaching in the Fall 2012 semester. I will not be able to find out who created my account as I think it was a SIS import and our requests table don't go back that far.


Who created the account?

Now for the fun part. The admin ID that created the user isn't stored there. It isn't stored anywhere in Canvas Data directly, but it can be found in the requests table.


Now, the issue before was that the page views didn't show the creation process and some may think that the requests table is just the page views, but it's actually more. This time, it does contain the account creation process for manually created user accounts.


What we need is for the web_application_controller = 'users' and the web_application_action='create'. Note that web_application_action is misspelled in the schema as "web_applicaiton_action" with a note that it will be fixed at some point. I took it upon myself to fix it, but if you haven't, you'll need to use the misspelled name.

This query will return the list of all manual user creations from the data available in the requests table.

SELECT * FROM requests 
WHERE web_application_controller = 'users' AND web_application_action = 'create';

Be prepared to wait a long time depending on your indexes on that table. You may also want to qualify the WHERE with some timestamps. For instance, if I know it was around March 10, I might want to limit them to a few days before and after.

SELECT * FROM requests 
WHERE web_application_controller = 'users' AND web_application_action = 'create'
AND timestamp_day > '2017-03-01' AND timestamp_day < '2017-03-20';


Here's what that returns on our site (I cut off the user_id for anonymity)

Sounds awesome, right? We see that there were five accounts created on March 10.


The user_id is NOT the user_id of the person being created, it's the user_id of the person making the request. That is, the admin who created the account. The next wrinkle is that the user_id isn't the Canvas User ID, that's the Canvas Data User ID. We can JOIN up with the user_dim table to convert that to a real name.


Now my query looks like this

SELECT r.timestamp, ud.canvas_id, 
FROM requests r
JOIN user_dim ud ON (r.user_id =
WHERE web_application_controller = 'users' AND web_application_action = 'create'
AND timestamp_day > '2017-03-01' AND timestamp_day < '2017-03-20';

and the response looks like this


Now we're getting somewhere, you think.


But wait, there are five accounts there. How do we know which one we want? Technically, it doesn't matter since all five were created by the same person. But that may not be the case for you.


Go back to the section on when the account was created. Remember that our user 123456 had this created_at value.


Now look at your response and you'll see that row 4 matches that exactly.


We now know that Canvas User ID 123456 was created on March 10 at 19:19:55 UTC (1:19:55 CST) by "Suspect Admin"


A Report

The original request asked for a report and this is more of a series of clues. Can't we put it all together into one thing I can use?

    u1.canvas_id AS user_canvas_id, AS user_name,
    u2.canvas_id AS admin_canvas_id, AS admin_name
FROM requests r
JOIN user_dim u2 ON (r.user_id =
JOIN user_dim u1 ON (r.timestamp = u1.created_at)
WHERE web_application_controller = 'users' AND web_application_action = 'create';

This may need reworked depending on how your database allows joining the same table twice, but it works for MySQL. I left off the date restrictions so I could get all of the data from the requests table. I also could have asked for r.timestamp_day instead of r.timestamp if I just wanted the day.


It comes out looking like this.

The astute eye will notice the dates are not in the proper order. You can tack an ORDER BY r.timestamp onto the end of the query.


You could also order by the user's name. This report uses just the day (labeled as created_at) and returns and sorts by the user's name.

    r.timestamp_day AS created_at,
    u1.canvas_id AS user_canvas_id,
    u1.sortable_name AS user_name,
    u2.canvas_id AS admin_canvas_id, AS admin_name
FROM requests r
JOIN user_dim u2 ON (r.user_id =
JOIN user_dim u1 ON (r.timestamp = u1.created_at)
WHERE web_application_controller = 'users' AND web_application_action = 'create'
ORDER BY u1.sortable_name;

This is what I get now.

Possible issues

It might have been possible that the timestamp from the user's created_at timestamp does not match the timestamp in the requests table. If this worries you or you fail to see the desired results, you could check to if they were within a few seconds of each other. You don't want to be too lenient, though, as our Suspect Admin created two accounts within 30 seconds of each other.


MySQL has the TIMESTAMPDIFF() function that computes the difference in timestamps between two values. I decided to find the difference in timestamps and make sure they were within 5 seconds of each other using the absolute value function.


Here's a script that allows up to 5 seconds difference between the request and the account creation.

    r.timestamp_day AS created_at,
    u1.canvas_id AS user_canvas_id,
    u1.sortable_name AS user_name,
    u2.canvas_id AS admin_canvas_id, AS admin_name
FROM requests r
JOIN user_dim u2 ON (r.user_id =
JOIN user_dim u1 ON (ABS(TIMESTAMPDIFF(SECOND,r.timestamp,u1.created_at)) < 5)
WHERE web_application_controller = 'users' AND web_application_action = 'create'
ORDER BY u1.sortable_name;


Strange things happened when I did this and I had to run it twice to confirm the results. It was taking about 3 minutes and 18 seconds to run it by directly comparing r.timestamp to u1.created_at. When I looked at the timestamp difference, it ran in 1 minute 18 seconds. Yes, the timestamp difference route was much faster.  Depending on your database and what your indices are, you may get different results, but I wanted to mention that. Even if you want the times to match exactly, you could look for a difference of 0 (no absolute value should be needed)


An even faster approach for me was to create a separate table of just the requests that were for user creation. This took about 1 minute 12 seconds, but then all of the other queries were almost instantaneous because there were only 8 rows in the one table.


SELECT timestamp, user_id FROM requests
WHERE web_application_controller = 'users' AND web_application_action = 'create';

    DATE(r.timestamp) AS created_at,
    u1.canvas_id AS user_canvas_id,
    u1.sortable_name AS user_name,
    u2.canvas_id AS admin_canvas_id, AS admin_name
FROM reqlist r
JOIN user_dim u2 ON (r.user_id =
JOIN user_dim u1 ON (r.timestamp = u1.created_at)
ORDER BY u1.sortable_name;


Again, your database results may vary. I don't have any of the fields in my requests table indexed as I don't use it very often.  I also haven't done any optimizations to the fields beyond what the schema describes, although I have indexed columns in the user_dim table so the lookups there were quick. I have almost 22 million rows in our requests table, only 8 of which were user account creation. Adding an index on web_application_controller and web_application_action took 9 minutes and I don't know what affect it will have on adding new data, but then the search results were nearly instantaneous.


You may notice that this report only lists accounts that were manually created. You can get accounts that have SIS IDs from Canvas Data, but it's in the pseudonym tables, not in the user_dim table. The focus of the question seemed to be on manually added user accounts, but I might have read that wrong.



A final issue that may arise is with people who use the API to create their user accounts. We don't do that at our institution, we use SIS imports instead, so I can't address what would happen. None of our requests had the /api/v1 in front of them for the create user call, but we don't that, so it could be missing because it's not recorded or it could simply be that we don't have any.  I suppose that I could add one and see what happened, but I'd have to wait for the add to hit Canvas Data before I could finish the blog post. If someone knows, they could add a comment, but I don't expect that people being added through the API is the issue.

Update: November 18, 2017

Canvas has provided their own solution!



The solution for Canvas is from the list of all pages, click on the admin cog next to the course that is currently the front page and choose "Remove as Front Page".


The solution I offered in 2016 was to go to the page itself and then deselect it as the front page. The solution described in this blog post still works, so I'm going to leave it out there in case people can learn from it, but you probably should disable it and just use the Canvas solution.


Original Post: August 28, 2016

This is going to get lengthy - for those who are impatient, there are installation instructions and a video at the bottom.


Every now and then someone wants to remove the front page designation from their course. In fact, another request just came in a couple of days ago: Delete Homepage. They don't actually want to delete the home page, they just want to tell Canvas that there shouldn't be a home page, which in Canvas parlance is called the front page, which may or may not be what a user gets when they click on the Home navigation link.


The Canvas guides tell you How do I set a Front Page in a course?  but they don't tell you how to unset a front page for a course, so asking how to do this is a reasonable question.


Typically when this request is made, there is a back-and-forth discussion about how it's not really as big of deal as it seems. The point is made that it is easy to change the default view of the page from within the web interface so that the page is not shown when people hit the Home button. The return conversations points out that changing the default view still keeps a page with the "Front Page" designation and that's what is shown when users click on the Pages navigation item, which means that the list of all pages is no longer shown, but requires an additional click to see the list. The rebuttal is that the list of Pages is worthless and so you shouldn't be using it. The original poster then asks why do we have the Pages page then, and the conversation goes on like this until someone gets tired and gives up.


But the problem still exists that once you've assigned a front page, there is nothing in the web interface that allows you to unassign it and get back to the list of pages as the default. There may actually be a use for having a list of pages, I named all of my notes for my summer course last year in a way that alphabetical ordering made sense, so I can see the point.


There was a lengthy discussion about it a year ago: Allow deselection of Front page status . Many people there said it was impossible, some offered hacks like recreating the course or rolling back the history on the page to a time when it wasn't the front page. And Matthew Libera presented a solution on September 9, 2015, that went mostly unheeded and definitely not understood. In fact, the only response to it was "??".


Fast forward a year and on August 12, 2016, Matthew made another post, with the same information, but this time included screen shots and instructions, and became the hero.


The problem is his solution required people to obtain an access token, load the Live API interface, and enter the access token and the specific information about the course. The approach is certainly not impossible, and Matthew does a good job explaining how to do it, but it not easy. Unfortunately, that means that many won't attempt it.


The trick he points out is to use the Update/Create Page endpoint of the Pages API. This inability to deselect a front page had thrown me for over a year until I ran into Matthew's post last week. The reason is because, to me, the Update/Create Front Page endpoint was the obvious place where you would set the status of the front page.


Except that the obvious place is not the correct place.The trick is to take the page that is the front page and tell it that it is no longer the front page. Then, since there is no more front page, Canvas displays the list of all pages instead and the problem is solved. You can't tell the front page it's not the front page by updating the front page. Like I said, it's not obvious.


Removing the front page status involves the PUT method with a payload of "wiki_page[front_page]=0" to the API. The URL used is exactly the same as the page you would see while you're viewing that page from within Canvas, except there is an /api/v1 squeezed in before the /courses.


This can be accomplished using the API or using REST clients that are available. If you already have an access token and do other things with the API, it's a fairly easy task to remove the front page status.


The problem comes when you don't use the API and don't already have an access token and don't have the technical skills to figure it out.


The good news is that you don't have to have an access token to access the API. You can access the API through the browser if you are logged into your Canvas instance. In fact, if your API call is a GET, you can just put the API endpoint into your browser's location window and get the information. There will be a while(1); at the beginning, but after that will be the results.


The first problem with that is that this removal process uses the PUT method and not the GET method, so that approach is out. Otherwise we would have a really simple solution that anyone who had permissions and was logged into Canvas through a browser could do.


The JavaScript code that loads with Canvas often makes calls to the API using all of the basic methods (GET, PUT, POST, and DELETE). The way it gets around this without an access token is by sending an X-CSRF-Token header with the request. Oversimplified, this token verifies that you are a legitimate user who should have access to the API. It's kind of like an access token, except it automatically comes with information sent from Canvas and changes frequently to make sure the request isn't a fake because someone was able to guess it.


I've written some user scripts before that make AJAX calls through the browser to obtain information. I used jQuery and it just kind of magically took care of the what needed taken care of, so I had never really given it much thought until today. Today, I tried writing a script without jQuery, using only JavaScript, and so I had to figure out all that stuff out by hand. I probably missed something and it's definitely not pretty, but in the end it works.


Here's what it does.

  • Checks to see that you're on a page that begins with /courses/*/pages/*. Those * are wildcards and require that there be something after the /pages in the URL, so it won't activate on the list of pages. It also won't activate on the page that you get when you click the Pages navigation link, which is called /wiki and not the name of the page itself.
  • Checks to make sure that the current page is set as the front page and that the user has the ability to manage the page. According to the Canvas Course Role Permissions document, managing a page is what's needed to set something as a front page, so I figured that was a good permission to use for deselecting a front page.
  • Waits for the page to display. Canvas has gone to including the contents of wiki pages completely within the ENV variable and then using JavaScript to display it on the page. That means that the page is not displayed as soon as the Document Object Model (DOM) is ready. You can tell this by viewing the source of the page in the browser and seeing there's very little there in the wiki_page_show portion. In lay terms, I can't add a link to remove the front page status because the place where I need to put the link isn't even there when my script starts running. So I have to wait until the information is displayed on the page. I use a MutationObserver to do that. It turns out that the first set of mutations to the element with id="wiki_page_show" is good enough, so I disconnect the mutation observer so it's not continuing to listen for changes to the page and then add the link to remove the front page status.
  • There is a link added to the bottom of the Admin Cog that says "Deselect Front Page" and it has the little Home icon before it so that it looks like the other links of Delete, which is disabled for the front page, and View Page History. This won't show up unless someone clicks on the Admin Cog for the page.
  • When the Deselect Front Page link is clicked, I obtain the URL of the page, insert the /api/v1 before the /course, find the _csrf_token from the list of cookies, and then make an AJAX call to the Create/update page endpoint. Once that call returns, I remove the little notice from the top of the page that says this page is the "Front Page" and the script is done.


Here is what right side normally looks like when you click on the Admin Cog.

Here is what the right side of the front page looks like when my script is running and you click on the Admin Cog:


You've decided that you would like to remove the front page status without having to obtain an access token, so what do you need to do? The steps may still be beyond the technical level of some users, but this is a place where Canvas Admins could load the script in their browser and then help out their faculty how want the front page removed. Here are the steps.

  1. Install a browser add-on: Greasemonkey for Firefox or Tampermonkey for Chrome/Safari. I've tested the script in Firefox, but I don't think there's anything special I did that would keep it from running in the other browsers. If there is, let me know.
  2. Install the Deselect Front Page user script.
  3. Click on the Pages navigation link and View All Pages. Note that the script will not run on the page that you get when you click on Pages. This is to keep it from running when it doesn't need to and also to get the URL of the page from the browser.
  4. Find the page that is indicated as "Front Page" in the list of pages and click on it.
  5. Click on the Admin Cog and choose "Deselect Front Page"


Here is a video showing you what you can expect.

I've written a user script that will add the Login ID and SIS User ID (if you're using them) to the User Search from the Admin page in Canvas.


Quick Install

For those power users who are impatient, here are the quick install steps.

  1. Install a browser add-on: Greasemonkey for Firefox or Tampermonkey for Chrome/Safari
  2. Install the Admin User Search Details user script.

When you are searching for a User from the Account page, it will automatically display the extra information.

It has been tested with Firefox, Chrome, and Safari.



I had a discussion with my Canvas Admin a few weeks ago about the Admin User Search. I was working on something, did a user search, noticed there was a duplicate name entry with no way to tell them apart and wondered what information would be useful. I was aware that the search functionality for users has long been a sore spot Advanced Admin Search & Sort , but I don't use it on a regular basis, so I hadn't devoted much time to it.


I have recently made some advancements in my JavaScript knowledge and was ready to make an attempt to do something about this. I whipped something up one night, coming up with different ways that it could be enhanced. I showed Kona Jones the working model the next day and asked her what she thought. She initially said it was wonderful, then before I ease the pain from patting myself on the back, she came back with a laundry list of things that it should do.


I looked into her list, which included the ability to filter users by term and decided that with the current API, there was little that I could do to speed up the process short of writing code that would query our local copy of the SIS database instead of the one maintained by Canvas'. While most of that information is available locally, there's something nice to be said about having it directly available within Canvas and part of the user interface. Besides, I already have a lookup tool that our local admins could use to find a student SIS ID and then search by that to get the results quickly within Canvas.


The bottleneck is the Canvas search. At our institution, with only about 13,500 users, when you search for "Jones" or "Smith", you get about a 25 second delay while Canvas searches for that information and returns a paginated list of 118 users (Jones) or 169 users (Smith). Subsequent reloads within a short time or moves to the next or previous pages are much faster. But it's that initial delay that is the killer and there's nothing I can do to speed that part up.


So, deciding I wouldn't be able to help Kona with her dream implementation, I focused on what was available.


Sometimes, Canvas returns information on the page but just hides it from the user. I held out hope, because the API call to search for users returns lots of information like the full name, sortable name, SIS user ID, and login ID. In some lists of users, you can get the email address, the enrollments, and the last login. Unfortunately, no matter what was being fetched to generate the page, the only thing being stored on the page was the name of the user and the URL to the user's page.


Considering some of the incredible markup and nested divs that Canvas uses in a lot of places like the modules page, it was actually very boring markup: a simple unordered list that was returned as part of the page itself, rather than an AJAX call as becoming more frequent for loading information.


After the 9+ To Do fiasco of February 20, I had written Restoring the Needs Graded count  (by the way, if you installed that script, you should disable it) and now knew how to fetch information about each item on a page. So, I took what I had learned there and fetched the user information for all 30 users that was listed on the page. I then appended the Login ID and SIS User ID to the user's name and I formatted it to look more like a table than a list.


Kona influenced the design, so if you don't like it ... Anyway, I had three ideas for displaying it. 1) look for duplicate names and only fetch the information for those names, 2) wait until a user mouses over a name and then fetch it, displaying the information as a tool-tip if they hovered over the name, and 3) load the information for all users on the page, possibly with a delay of a few seconds in case the name was easily located.


Kona said she wanted the information for all of the users, even if there wasn't a duplicate name, she didn't like the tool-tip, it was extra work on her part, and she wanted the information immediately. So that gives us what I'm announcing today.


Canvas is Doing Something

Kona told me that Canvas was working on something and that it would hopefully be coming soon. Renee Carney wrote on March 7, 2016, that it is currently in code review and that hopefully it would be appearing in one of the next few releases. It didn't show up in the March 12 release and after code review, it has to go through Beta before making it into Production, so it's at least a few weeks off. I asked Kona whether I should go ahead and release this or just wait for Canvas to do something. She said to go ahead and release it.


As with much of what I write, I hope that it is temporary and that Canvas will do something much better and this can be retired. As mentioned earlier, they are the only ones that can speed up the search.


Why Not List the Courses?

When developing this, I thought about adding currently enrolled courses to the list. The enrollments API will filter enrollments by state, but we don't conclude our courses at the end of the term and so the "active" courses included all of the previous ones as well. Then the names of the courses weren't included in the enrollments, just the Canvas and SIS IDs for them. You can get a list of courses for a user (if you have admin privileges, which shouldn't be a problem here, but it might be specific to masquerade permissions), but that might not return the dates of the course if your school puts them on the section instead and it doesn't include the user information. For the same reason, the email addresses were an additional API call when it wasn't already their Login ID. Basically, adding anything extra boiled down to a lot more complexity and time (network API calls) involved to get all of that information and I wanted something that would be reasonably quick. Canvas displays just 30 names at a time, so it makes 30 calls to the API to fetch the user information and it's pretty quick about it -- although you'll be able to see it update the screen as it fetches the information.


Stop Babbling and Show Me!

Fair enough.

I did a search for Michael Smith since he is our most popular name (having a local copy of the database made that easy to find).


Native Search Results

This is what it currently looks like:

Enhanced Search Results

After installing the user script, you get this:


Note: Your screen won't actually contain the blur, that's done for FERPA reasons.



The script should run as-is on any site that doesn't use a custom domain name. If your Canvas instance doesn't end in, then you'll need to edit the \\ @include on line 5.


The source code contains a configuration variable with a columns array inside of it. This contains the information to display, the order to display it in, and any extra CSS classes you would like to apply to that column.


Currently, it displays the login_id first and then the sis_user_id. Our sis_user_id values are numeric, so I applied the text-right class to that column so they are right-aligned. If you have alphanumeric SIS codes, then you could take out the class value (and the comma on the line before it).


Other values besides login_id and sis_user_id that you might use include:

  • id: The Canvas User ID. You can get this now by mousing over the URL
  • sis_login_id: We use the same thing for our Canvas Login ID, so I didn't include it, but others might have them be separate values
  • avatar_url: If you wanted to display the location of their Avatar, you could include this. You probably don't want the location, though, you would want the actual avatar to show up. That's extra functionality that would have to be programmed into the system.
  • permissions: This is a really incomplete list of what the user can do. For example, my user record has can_update_name: false and can_update_avatar: true.

There are different variations of the name. name, sortable_name, and short_name.



This script is a Canvancement and designed to improve the user's experience with Canvas. It is up to the user to decide whether or not to use it. The source code is available on the Canvancement website.


When Canvas releases their improved user search, you will probably want to disable this one. In the meantime, I hope someone can benefit from it.

Filter Blog

By date: By tag: