Skip navigation
All Places > Canvas Admins > Blog



Knowing where to begin as a brand new Canvas Admin can be a huge task. You probably need to move quickly, and learn a lot all at once. We've created a new free resource - Be The Hero -  for you to help you get acclimated to how Canvas works and how it can work for you!


About The Course

Be The Hero has five modules of content that will take those who will be Canvas Admins through everything they need to know about using Canvas at their institutions - no matter what level. The course uses a combination of the Canvas Video Guides and Canvas Guides to teach the content. Each concept also includes information about the corresponding Canvas Training webinar. Participants in the course will have the opportunity to check their understanding with self-check quizzes at the end of each module. 



The course is available in the Canvas Commons at this link: Be The Hero, (pro tip - make sure you're logged in to Canvas!) and is offered under a Attribution-Non-Commerical-ShareAlike licence. That means you’re free to share the course, as well adapt it to meet the needs of your institution.


If you’ve never used a resource in the Canvas Commons before, all you need is an empty course in your Canvas instance in which to copy the Growing with Canvas course. Once Be The Hero is in your instance, you’re free to edit and use the course as you wish. Check out this guide - How do I use Commons? - if you need more help.


UPDATE 10/30/2018: If you don't have the Commons, or if you are having an issue with downloading the course from the Commons, visit this link ( - Google Drive ) instead to download a copy of the course. Simply then have a blank course in your instance of Canvas to import the course to, and you'll be good to go!


Potential Uses

  • Share this course with your colleagues
  • Apply the principles in Growing with Canvas to your own courses
  • Elevate your knowledge about Canvas

The terms list on the account-level course page is sorted in ascending order. When I filter terms, I almost always want the current term or a recent term. Having to scroll to the bottom of our long list of terms is annoying, so I wrote some JavaScript (with the help of Stack Overflow) to sort the list by term ID in descending order. In case anyone else might find this useful, here it is:


if (window.location.pathname.indexOf('accounts') > -1) {
    var selectOptions = $('select optgroup[label="Active Terms"] option');
        return parseInt(b.value) > parseInt(a.value);
    $('select optgroup[label="Active Terms"]').html(selectOptions);


(I welcome tips for improvement, JavaScript aces!)

In my recent efforts to migrate data into Canvas from another reporting system one of the things that I found useful was to enroll all the users into the course and set their state as active, but for students who should not have access to the course (because they have taken a leave, quit, have an administrative hold on course participation, ...) - after adding their data to the gradebook - simply set their state to inactive (deactivate the student).

The code below shows how to do this in python:

def deactivate_user(course_id, enrollment_id):     global Verbose_Flag     #Request Method: POST     # DELETE /api/v1/courses/:course_id/enrollments/:id     # Scope: url:DELETE|/api/v1/courses/:course_id/enrollments/:id     # Conclude, deactivate, or delete an enrollment. If the task argument isn't given, the enrollment will be concluded.      url = baseUrl + '%s/enrollments/%s/?task=deactivate' % (course_id, enrollment_id)     if Verbose_Flag:        print("url: " + url)      r = requests.delete(url, headers = header)     if Verbose_Flag:            write_to_log("result of put of reactivate: " + r.text)     if r.status_code ==        page_response=r.json()        if Verbose_Flag:               print("deactivated {0} in course {1}".format(enrollment_id, course_id))        return True     else:        if r.status_code == 404: # "404 Not Found"               write_to_log("status code: " + str(r.status_code))        else:               write_to_log("status code: " + str(r.status_code))     return False 

One surprise in doing the above is that one has to use the student enrollment_id and not their user_id. One can find this enrollment_id with:

def active_user_in_course_by_user_id(course_id, user_id):        # Use the Canvas API to get the list of users enrolled in this course        #GET /api/v1/courses/:course_id/enrollments        # user_id string     Filter by user_id (only valid for course or section enrollment queries). If set to the current user's id, this is a way to determine if the user has any enrollments in the course or section, independent of whether the user has permission to view other people on the roster.         url = baseUrl + '%s/enrollments' % (course_id)        if Verbose_Flag:               print("url: " + url)         extra_parameters={'user_id': user_id,                          'enrollment_type[]': 'StudentEnrollment,TeacherEnrollment,TaEnrollment,DesignerEnrollment,ObserverEnrollment',                          'state[]': 'active'        }        print("extra_parameters={}".format(extra_parameters))        r = requests.get(url, params=extra_parameters, headers = header)        if Verbose_Flag:               write_to_log("result of getting enrollments: " + r.text)         if r.status_code ==               page_response=r.json()               return page_response        return None

In conjunction with migrating users from one source to another (such asMigrating grades from one Canvas course to another ). I found it useful to be able add users based on their SIS ID and to be able set them to inactive or reactivate them.

The results are python programs to enroll a user: course_id SISID role

and a program to inactivate or reactivate the user: course_id SISID state


Examples with pictures and code are at activating and reactivating users: Chip sandbox 


Initially I was enrolling the user with a given state, but this has the undesirable side effect of putting them in the default section and not just changing their state (if they are already enrolled). Looking at what the GUI sends when deactivating and reactivating a user let me to the solution adopted in the two programs above. The main gotcha was that you have to use the enrollment_id and not the sis_id or user_id, this means that given the user's SIS ID you first have to lookup the user to find their enrollment_id. Unfortunately, the GET /api/v1/courses/:course_id/enrollments state[] parameter does not seem to support an array of values, but rather has to be a string with one of the values; this means that one has to lookup inactive users separately from active users. It would be desirable if the state could be a vector of values or is there was synthetic state "all". Even more confusing as the parameter name is state[] , which to me suggests that it has an array value.



Now what caused me to do the above? A question came up about what to do with users that could be migrated, but whose status was that they had interrupted their studies in the course or for some other reason could not complete the course.  I would like to thank Brian Neporadny for his answer in When do you archive / remove / disable users and content?  that lead to exploring the idea of adding these users, adding their grades, and then setting their state to inactive. In this way the instructor can see the grades of the student for those assignments that did have a reported result, while not allowing the student access to the course. [For testing purposes it was useful to be able to reactivate a user after setting them inactive.]

My recent attempt to migrate users' grades for assignments from a pre-Canvas course to Canvas starts by adding the users to a Canvas course, then adding the assignments, and then finally adding each student's grade for each assignment.


Of course such a program requires testing. This testing creates lots of enrollments for users. In order to run the program again it is very convenient to remove these users. Similarly there is a need to remove the created assignments. Hence I wrote two scripts:  (1) to remove users (except for myself) and (2) to remove all assignments.


You can find the code for them at Removing other users and assignments: Chip sandbox .


One of the small gotchas that I found is that to remove a user from enrollment in a course you need to use the user's enrollment_id and not their canvas_id.

In the first several year of using Canvas I spent a lot of time moving content (especially quizzes) from the prior LMSs, but this is my first major attempt to move state information for students. Has anyone else addressed such a migration?


In this migration effort, I need to move records of students' grades from another system into Canvas. Many of these students have partially completed an earlier course round (a specific session of the course) prior to the introduction of Canvas and now need to complete the remainder of the requirements for the course (for which they will have to register for a new course round that will be in Canvas).


One difficulty was that these grades were from a number of different grading systems (more than a dozen of them). One of my colleagues suggested a grading standard that would capture all of the different grades (a unified grading standard), so that when looking at grades in the gradebook for each assignment you would see the original grades. In this way a teacher could see which parts had already been completed by the student and which parts remain, and then act accordingly.


I was pleasantly surprised at how easy it was to add the new grading standard, but do not yet know what unexpected repercussions will occur by using it. For those who are interested in examples and the program that puts the new grading standard into a course or account, see Grading standard: Chip sandbox .


As a side question is there anyone who has used an ECTS A-F and Fx grading standard or the corresponding Pass/Fail grading standard for their institution? I am a bit surprised that both of these do not come as predefined grading standards for an LMS used in Europe. 


In a subsequent blog post I will report on my migration effort via experiments in enrolling students into a Canvas course, adding each of the assignments from the earlier course round, and then populating the gradebook with grades.

Neal Shebeck

CCSD - Sharing the Love

Posted by Neal Shebeck Apr 11, 2018

I never know when I should be doing a blog post, document, discussion, etc. but this just felt like a blog ( I think ). While the Clark County School District is pretty massive (over 44,000 employees and 318,000 students) our core group of Canvas people is quite small. A majority of management, development, and support comes down to just 3-4 people. We have shared some things in the past like templates for courses and templates for "conference-style" courses in commons but we haven't shared too much code in the past. 


Over the last 2-3 years we have been expanding our hacks of Canvas to try to increase adoption and respond to teacher and student needs. While we have created some very cool things we find that getting feedback from other developers and users is what we're lacking. In an effort to expand our reach for collaboration to other districts and higher ed, we have decided to start sharing more. To that end Robert Carroll has recently posted a couple things to github that we have found helpful. Hopefully you will too.


Create a "Workroom"

We don't let our teachers create courses on their own but we found the need for our teachers to make practice courses for themselves if they were going to leverage Commons, professional development, or just to play around and experiment with Canvas. We managed to do this by:

  • Making sure every employee is a teacher in one "workroom", giving them the teacher role
  • Turning on the ability to create courses for "teachers"
  • Hiding the button to create courses from them
  • Coding a link that creates the additional workrooms for them (and adds them as the teacher)

Check out Robert's post on how we did this.


Sub Account Menu

We have a lot of sub accounts (8,090 and counting). When you're a top level account admin and have more than  even 20 sub accounts, you may find yourself frustrated with navigating to the one you need. This fixes that... big time. Yes, it only helps about 8 people, but speaking as one of those eight people, I love it! Plus, if you save account admin time, that equals quicker support for your users.

See how this was accomplished and get the code for yourself.


When we share something else, I'll be sure to update. In the meantime, hope to see you in the community and at InstructureCon!


---Update (4/26/18)---


Global Nav - Custom Tray

There were several institutions that lost the functionality of some custom "pop out" menus from their global nav after a Canvas recent update. Robert worked on that, (starting with some code he credits in his post), and posted a solution over in the developers area.

The Story

A common frustration of teachers using Canvas at an institution is when their course navigation gets messed up by an admin. A teacher may have been teaching their courses for a couple of weeks and then all of a sudden, their institution adds a new LTI that has a course navigation launch and it messes up their course navigation in all their courses. Then their students start asking about the new LTI and the teacher has to tell them all they aren't using it and hide it in their course settings


However, the story is worse for those instructors who don't know to customize their course navigation. Before you know it, they have a course navigation list that is so long that students have to scroll to see everything on it (see image to right). Core Canvas tools will hide themselves if they aren't being used (light grey items), but not with LTI's. So now those teachers have their students asking about each new tool and wondering why their teachers aren't using it...


Taking Control

Part 1 - Installing New LTIs

As the Canvas admin, you have the ability to decide, when an LTI is installed, whether you want it to be visible or not. Most LTI vendors want to encourage your teachers to use the tool and they enable it by default. A simple edit can help you avoid the above hassle for your teachers, while allowing you to roll the tool out to the teachers who need it.


When installing an LTI tool, the 3rd party provider will generally provide you a URL to configure the tool. If installing the tool from the apps tab in Canvas, the URL's for the installs can be found at the tool's detail page on These URLs should end in .xml which is the format needed to configure an LTI tool. Navigating to these URL's in your browser should take you to a page that looks like this:


<blti:title>Course Wanda Fish</blti:title>
<blti:description>This tool adds a course navigation link to a page on a fish called "Wanda"</blti:description>
<blti:extensions platform="">
  <lticm:property name="tool_id">course_navigation</lticm:property>
  <lticm:property name="privacy_level">anonymous</lticm:property>
  <lticm:options name="course_navigation">
    <lticm:property name="text">Course Wanda Fish</lticm:property>


The trick is to find the <lticm:options name="course_navigation"> part of the xml file so we can customize it a little bit. We just need to add one more line after this and then we can install the LTI by pasting in XML. So here are the instructions:

  1. Copy and paste the XML into a text editor (Notepad for Windows or textEdit for Mac)
  2. Find the <lticm:options name="course_navigation"part of the code
  3. Copy and paste the following code immediately below the course navigation line
    • <lticm:property name="default">disabled</lticm:property>
  4. Follow these instructions to install the LTI via XML
  5. Enjoy the new LTI being hidden by default


Part 2 - Editing Existing LTIs

If you already have LTIs that are visible by default that you'd like to change, you can do that by using the Canvas API. There is a lot to working with the API that I will not go into on this post, but if you have a Mac you already have "cURL" installed by default so you can copy and paste the following code to update your LTI tools. If you have a PC, you can try and follow the instructions here to install cURL on Windows.


You will need to:

  1. Copy and paste the following code snippet to a text editor
    • NOTE: If the tool has other custom navigation parameters they need to be added to avoid breaking some functionality
      • View the existing parameters by viewing the response from step 4
    • curl -X PUT 'https://<canvas>/api/v1/accounts/<account_id>/external_tools/<external_tool_id>' \
             -H "Authorization: Bearer <token>" \
             -F 'course_navigation[default]=disabled'\

             -F 'course_navigation[*CUSTOM PARAMETERS*]=VALUE"

  2. Put your Institution's Canvas URL where <canvas> placeholder is ( or vanity URL)
  3. Put the Account/Sub-Account ID where the <account_id> placeholder is
    • The can be found by navigating to the Account/Sub-account in Canvas and looking at the url. It should be https://<canvas>/accounts/123 with number 123 being the account_id
  4. External tool ID can be found by updating the <school> and <account_id> fields in the url below and then navigating to it in your browser
    • https://<school>/api/v1/accounts/<account_id>/external_tools?per_page=100

    • You may want to download this Chrome Plugin to make the text more organized (similar plugins exist for other browsers)
    • Find the LTI tool you are looking for, the LTI should be a number. eg:

  1. Update the <external_tool_id> placeholder with the LTI id number
  2. Generate an API Token
  3. Replace the <token> placeholder with your new API Token
  4. Copy and paste the edited code snippet into Terminal (on mac) or Command Prompt (on Windows after you install cURL)
  5. Hit Enter and watch the LTI be hidden by default in all courses. 


*Note, your teachers may have to re-enable the LTI tool in their courses to have access. So you may want to send out communication about this change before you do it.

I hadn't found anything in the community that aggregated the information, so I thought I'd share some details from an interaction I had with Canvas Support while researching which content applies to which quotas. The info is all out there in different places, but my hope is this will provide a summary in one place.


The Course Storage report, offers two different values, "storage used in MB" and "sum of all files in MB". The tool tips for the Report offers this basic description:

"The storage used reflects all files that go against the courses quota. The sum of all files in the course is the sum of all files that are in the course regardless of impact to the courses quota."


So, "storage used" in a course is generally:

  • All of the content that the instructor has uploaded to the course
  • This is the number counts against the course quota that has been set


While the "Sum of all files":

  • Also includes all of the student submissions, or documents uploaded in discussions, etc.
  • This is the number that counts towards your overall account quota for your Canvas instance


In case you're now wondering how this impacts individual user quotas, since these files also appear in their My Files area...

  • Files that have been submitted by students as attachments to assignments and graded discussions are not counted against their user quota. 
  • Files that are uploaded to a course by a student that are not related to assignment or graded discussion submissions will count toward their user quota.
  • Files that are uploaded to a course site by an instructor do not count towards their user quota.


If anyone has encountered other important information, or has found their experience to be contrary, please feel free to leave comments, so that we can make this a useful resource for other admins.

 ** This content is out of date and will not be updated as of 8/22/17 **


First things first: Unplag is pro-nounced "un-PLAH-g"  Took us a while to figure that out too.



Our School had been previously using Turnitin as our main Plagiarism tool and decided to move on to another for various reasons. I was neck deep in the community trying to see what everyone else uses for their Plagiarism detection. There were enough options to make my head spin. Research upon research, not much else was popping up aside from the Industry Giant we were using. I then ran across the Alternative to Turnitin? thread discussion and was immediately relieved to see other people had the same problem I did.


After sifting through the comments, there were a few options before me, so we begun to test. I created a resource Plagiarism Checker Applications, LTI's, Oh My! to be able to link back all the Plagiarism checkers I could find in one spot.


Unplag has had really great customer service and awesome response times, they also have a lot of resources. (seriously, check out the The specified item was not found. page, they have everything hyperlinked there) Considering they're based in the Ukraine and we are located in Salt Lake City (MST) this is an incredible achievement. Additionally, we had several questions and requests while we were testing this in Beta, and Unplag responded to every one of them. Often taking our feature suggestions and making changes in record turn around time.


Our institution had a checklist for what we needed:

-Must have Speed Grader Integration

-Must be allow student to Re-Submit assignments

-Must be able to retain Assignment Settings


All of these have been achieved. There are always things that can be improved, but as far as another plagiarism checker, they've quickly become our top pick and great to use. They even put more credit on our account to allow us to continue using the tool while our Finance department settled things with getting officially signed over. Essentially, anything they could to do help, they did.


Since I've been doing trainings with our staff every day this week to get them comfortable with the tool, I wanted to post some info here if you're looking at another plagiarism tool and wanted to see how it integrated into Canvas yourself.


Feel free to take a look at these documents:

Unplag Plagiarism LTI: How to Create an Unplag Assignment

Unplag Plagiarism LTI: How to Change Settings on an Unplag Assignment

Unplag Plagiarism LTI: Student Submissions in Unplag - How does it Work?

Unplag Plagiarism LTI: Student Submissions in Unplag - DRAFT CHECKS - How do they work?

Unplag Plagiarism LTI: Student Submissions in Unplag - RE-SUBMISSIONS - How do they work?


Are you using Unplag? What are your experiences?

For admins who do dozens of searches per day on Canvas, here are a couple of tips that save a few clicks (and a few seconds) each time. I use these many times a day. (This post is an adaptation of the "Customer Lightning Round" 10-minute presentation I did last week at InstructureCon 2017, called "Save 1 Minute Per Day.")


The Problem:

Searching for a user or a course takes too many clicks.


The Solution: Use Google Chrome's Custom Search feature


(This is similar to John Thomson's "Efficiency Tips" from two years ago, with screen shots added, plus some extra suggestions.)


The Chrome browser has a cool feature that lets you create custom searches, which you can use to quickly search for a user or a course on Canvas. Here are detailed instructions for using this feature:

  1. Using Chrome, log in to Canvas and do a search (as you normally would) for user Waldo
  2. From the search results page, copy the link from the address bar, which will look like something like this: 
  3. Next create custom search by clicking on the three dots "Customize" link on the top-right of the browser, and choose "Settings"
    Screen shot - Google Settings
  4. Then open the "Manage Search Engines" by clicking on the little triangle to the right, then on the next page click "ADD" on the "Other Search Engines" line:
    Screen shot - manage search engines and add
  5. Fill in a name (like "Canvas Users") and a keyword (just "u" in this example), then paste the link from step #2 above, and replace Waldo with %s
    Screen shot - custom Google search
  6. Click "Save" and you're finished.


To use this new search, type this sequence of keys:
  <CTRL> L u Bob Smith <Enter> 
(or <COMMAND> L on Macs)
<CTRL> L puts your cursor in the address bar, where you can begin typing a search or URL. Next, typing u then the space bar switches to this new search. Type the name Bob Smith and then Enter, and you will be taken directly to the search results (no clicks, just keys!)


You can re-do the above steps to make another customer search for courses (or really, for any common search you do). I use one to search for a class in the current term by first searching for a course, filtering for the term, then copying / pasting that search URL.


Another Tip / Feature Idea: Make the Enrollments search result larger

Screen shot - Enrollments Window too small


This is more complicated, and I've created a Feature Idea (go vote!). See the comments in that post for a work-around using a TamperMonkey script to fix this on your own, until Canvas gets updated.


Another Tip / Feature Idea: Add More Links to Courses Search

Screen shot - add course links example

This also can be added with a TamperMonkey script. I've created a second Feature Idea (vote here too!). Again, see the comments in that post for the script that can add a "Users" link to the list of links.


admin admin tips ui improved ui

24 Jan 2019 Update: This guide is still relevant, but you should also learn more about some new features that have been released since this was posted. Thanks for all the comments and feedback that helped me understand where the knowledge gaps and confusion are surrounding our new developer key features!

When working with an integration provider (i.e. vendor, internal development, etc.), they will often ask for a developer key. Many integrations require Canvas API access to function correctly, and developer keys are currently the most secure way to allow integrations to access the API. This documentation covers some of my recommendations for how to decide whether issuing a developer key to a vendor is secure and safe. There is no general “best practice” for addressing this challenge since every institution has it’s own sets of requirements and procedures for security and privacy.

Developer keys can be issued by a Canvas Account Admin in order to allow an integration to request access on behalf of the user to the API. The user is prompted to approve access to the application and upon approving an API token is granted to the integration.

     An example of the UI shown when a developer key to requests access to a user's Canvas API

After authorization, Canvas generates an access code that is later exchanged for an API token by the external application. The integration can then make API requests as if they were that user. In other words, they are able to make API requests to all of the same endpoints that the authorizing user has permission to.


Considering API tokens inherit the permissions of the granting user therefore granting that same level of access to the vendor, you might consider doing the following before issuing a developer key:


  • Ensure you have a formal written contract with the vendor and that your relationship with the vendor is in good standing.
  • Ask the vendor to explicitly document all of the API endpoints their integration will be using (this should be a list of all the data that is being read and written) and, optionally, why.
  • Reserve the right, in writing, to take any actions your team deems appropriate should the vendor make API calls outside of the scope that they defined without notifying your team.
    • NOTE: Developer keys can be disabled via the Admin UI which will invalidate all issued API tokens to the integration.
  • Ask the vendor to clearly state how it complies with FERPA and any other laws that apply to your students (e.g. COPPA, accessibility laws etc...).
  • Ensure you understand the vendor’s security policy surrounding how they store API tokens and developer keys (ex. they should never be exposing the developer key or API tokens in any kind of UI, including error reports; only their core engineering team should have access).
  • If available, ensure that your institution's security team has reviewed the application’s security.


Again, whether you follow some, all, or even more of these guidelines is up to your team to decide. Hopefully this discussion can help you make the best choice for your institution.


Discuss: What are some of the policies that your institution has taken to determine when to issue developer keys?

For the last several years I’ve been developing a library of Python code that makes my life as Samuel Merritt University’s Canvas admin easier and enables me to turn institutional ideas into reality. Though I’ve always intended to share this code with the Canvas Community in case someone else might find it helpful, it's always been a work in progress, and it’s been all-too-easy to fall into believing it’s not complete enough, not polished enough, not professional enough, not documented enough....


But now, in a mighty effort to stop letting the perfect being the enemy of the good, I’m sharing it just as it is, with these few words of introduction.


I began developing this library in response to an institutional need to import learning outcomes into Canvas courses from CMI, our homebrew, stand-alone assessment tool. I accomplished that through the use of Python, the Requests module, and the Canvas API documentation, as well as PyCharm, the Python IDE I can’t say enough good things about! (Python coders in academic institutions can get a free academic license).


Over time, the library of API call wrappers and utility functions has grown in the process of writing scripts to automate new tasks. Among the tasks I’ve automated using this library are:


  • Sync subaccount-level learning outcomes with outcomes in an external repository.
  • Sync course-level learning outcomes with subaccount-level outcomes.
  • Import outcomes into a course from a formatted Word document.
  • Generate a syllabus for a course by wrangling data from Canvas, our SIS, and CMI into a Word template.
  • Download all syllabus files from courses.
  • List assignments that use the Turnitin API.
  • Retrieve an SIS report.
  • Do an SIS import of enrollments by uploading a CSV file created by running a SQL file against the SIS.
  • Assist in assessing Canvas course design best practices by generating an inventory of specific Canvas features that courses use.
  • Find and replace text in Canvas pages.
  • List all cross-listed courses.
  • List admins at the account and subaccount level.


You can find the code for the core API functions, the utility functions, and the scripts that use them at my GitHub repository. I welcome any and all questions about the code or how to use it, and I’d be supremely grateful to receive recommendations for improving it.


Many thanks to Colin Murtaugh, Tobias Murray, and James Jones--sharing your work has inspired me to share mine.

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.


We use publicly visible courses for three primary reasons: 

  1. To allow us to use Canvas for training and/or professional learning purposes with a variety of audiences while not requiring a user account or login in our Canvas instance.
  2. To host publicly-visible resources for stakeholders like parents.
  3. To assure pre-kindergarten and kindergarten teachers that they can use Canvas with students who may not be able to quickly and/or successfully log into the system.


For the past few months, I've been dogged with emails and help tickets from external users who have frequently struggled to access information that I place in publicly-visible courses like this one (CanvasCon @ SMU). These messages typically describe how the user, provided with the URL to the course home page) was unable to access Modules (containing content pages) despite Modules being revealed in the course navigation menu AND being set as the course home page. Instead of seeing Modules, users received a 'Page Error' warning.


Befuddled by the issue, I turned to Canvas Support multiple times and received the initial advice to 're-save the course settings to clear out the settings cache'. This step performed for all of our publicly-visible courses, I still heard from users unable to access the course modules. With some persistence, I was eventually informed of the following:


Users who access the module's page of a public course (home page in this case) that also has the "Mastery Paths" feature option enabled will see intermittent page errors when trying to access it.


If Mastery Paths is enabled, unauthenticated users receive a page error when they try to access Modules page (in this case, Modules is set as the Home Page) of a public course.


This actually makes perfect sense when considering that we turned on Mastery Paths at the end of Semester One and these problems first occurred early in Semester Two.


So, it's a bug that the engineering team is aware of. The suggested troubleshooting:


To work around this issue I recommend to go to the root-account feature options and change the "Mastery Path" option from "On" to "Allow," as this would allow instructors who do not use it in "Public Courses" to disable the mastery path feature option on their individual courses to prevent the page errors.

Next Steps

While it's slightly annoying that I need to go and adjust the courses' feature options, I'm happy to know that the issue is finally known and hopefully being worked on sooner than later. In fact, because most of these courses exist in the same sub-account in our instance, I was able to very quickly adjust some feature options at the account and sub-account level to make the desired change that will hopefully do the trick until a permanent solution is in place.



Filter Blog

By date: By tag: