Skip navigation
All Places > Canvas Developers > Blog
1 2 3 4 Previous Next

Canvas Developers

58 posts

James Jones suggested that I should file a blog post about my recent findings/results.


I just recently started with Canvas because Uppsala University has decided to use it as its upcoming LMS platform after a failed attempt with another product. Therefore I had already spent some time with Blackboard and was quite fond of the calculated questions type in quizzes. I quickly found out that Canvas offers essentially the same functionality but a bit less comfortable.



A calculated question or Formula Question as it is called in the interface of Canvas is based on a table of pre-generated variable values and corresponding results. In the general case the variables are defined and the target function is entered using the web interface, then Canvas calculates random number values for the variables and the resulting answer value. However, as the designer you have no possibility to influence the variable values afterwards (unlike in Blackboard where you have a spreadsheet-like interface). Also, in Canvas, the equation cannot be altered once it has been entered - and the supported syntax is not very convenient for more complex problems.
I was also missing the ability to give a relative tolerance for the correct answers in a question, however, I found out that entering a percentage-sign exactly gives this behavior even though it does not seem documented anywhere.


Solution or problems?

My hope was then for the API, since it seemed to support the creation of questions. But even though there is a Python library for the purpose of controlling Canvas, many of the functions are not very well documented. My first tries failed miserably but finally I was on the right track.


The cause of my problems was that the Canvas API uses different field identifiers and structures when creating a calculated question as when you retrieve the contents of an already existing question, as I of course did in my attempts to reverse-engineer the interface.


Working solution

Here is now an example for a working solution to give you full control over the generation of Formula Qeustions using Python and the canvasapi library. The example is in Python 3 and creates a question from the field of electronics - the voltage in a voltage divider. The Python script generates the variables, fills the variables with random numbers from a set of predefined, commonly used values. I tried to write the script more for readability than any pythonic optimization.

from canvasapi import Canvas
import itertools
import random

API_URL = ""
API_KEY = <your api key here>

canvas = Canvas(API_URL, API_KEY)

# create a calculated_question
# example of a potential divider
#  U2 = U0 * R2 / ( R1 + R2 )

E3  = [1, 2, 5]
E6  = [1.0, 1.5, 2.2, 3.3, 4.7, 6.8]
E12 = [1.0, 1.2, 1.5, 1.8, 2.2, 2.7, 3.3, 3.9, 4.7, 5.6, 6.8, 8.2]

coursename = 'test'
quizname   = 'test'

# define the input variable names
#   each variable has its own range, format and scale
variables = \
        'name':   'U0',
        'unit':   'V',
        'format': '{:.1f}',
        'scale':  '1',
        'range':  [1.2, 1.5, 4.5, 9, 12, 24, 48, 110, 220]
        'name':   'R1',
        'unit':   'ohm',
        'format': '{:.1f}',
        'scale':  '1',
        'range':  [ i*j for i, j in itertools.product([10, 100, 1000], E12)]
        'name':   'R2',
        'unit':   'ohm',
        'format': '{:.1f}',
        'scale':  '1',
        'range':  [ i*j for i, j in itertools.product([10, 100, 1000], E12)]

# how many sets of answers
rows = 30

# create an empty list of lists (array) for the values
values = [ [ i for i in range(len(variables))] for _ in range(rows)]

# create an empty list for the calculated results
results = [i for i in range(rows)]

# fill the array of input values with random choices from the given ranges
for i in range(rows):
    for j in range(len(variables)):
        values[i][j] = random.choice(variables[j].get('range'))

    # and calculate the result value   
    results[i] = values[i][0] * values[i][2] / (values[i][1]+values[i][2])

# format the text field for the question
#   an HTML table is created which presents the variables and their values
question_text = '<p><table border="1"><tr><th></th><th>value</th><th>unit</th></tr>';
for j in range(len(variables)):
    question_text += '<tr>'
    question_text += '<td style="text-align:center;">' + variables[j].get('name') + '</td>'
    question_text += '<td style="text-align:right;">[' + variables[j].get('name') + ']</td>'
    question_text += '<td style="text-align:center;">' + variables[j].get('unit') + '</td>'
    question_text += '</tr>'
question_text += '</table></p>'

# format the central block of values and results
answers = []
for i in range(rows):
          'weight': '100',
              'name': variables[j].get('name'),
              'value': variables[j].get('format').format(values[i][j])
            } for j in range(len(variables))
          'answer_text': '{:.5g}'.format(results[i])

# format the block of variables,
#   'min' and 'max' do not matter since the values are created inside the script
#   'scale' determines the decimal places during output 
variables_block = []
for j in range(len(variables)):
          'name':  variables[j].get('name'),
          'min':   '1.0',
          'max':   '10.0',
          'scale': variables[j].get('scale')

# put together the structure of the question
new_question = \
      'question_name':           'Question 6',
      'question_type':           'calculated_question',
      'question_text':           question_text,
      'points_possible':         '1.0',
      'correct_comments':        '',
      'incorrect_comments':      '',
      'neutral_comments':        '',
      'correct_comments_html':   '',
      'incorrect_comments_html': '',
      'neutral_comments_html':   '',
      'answers':                 answers,
      'variables':               variables_block,
      'formulas':                ['automated by python'],
      'answer_tolerance':        '5%',
      'formula_decimal_places':  '1',
      'matches':                 None,
      'matching_answer_incorrect_matches': None,

courses  = canvas.get_courses()
for course in courses:
    if == coursename.lower():
        print('found course')
        quizzes = course.get_quizzes()
        for quiz in quizzes:
            if quiz.title.lower() == quizname.lower():
                print('found quiz')

                question = quiz.create_question(question = new_question)      

Since this is mostly the result of successful reverse engineering and not based on the actual source code of Canvas the above example should perhaps be used with care, but for me it is what I needed to create usable questions for my students. Perhaps this could also serve the developers as an example on how the interface for calculated questions could be improved in the future.


How does it work?

The dictionary variables (lines 26-49) contains the names and ranges of the variables, as well as formatting instructions. The ranges are given as lists. In lines 61-66 the random values are generated and the results calculated from these values. Lines 70-77 create a rudimentary table to be included in the question text containing the variables and their values as well as physical units for this particular question. Lines 80-93 finally assemble the variable/answer block and lines 109-128 put everything together into the dictionary to create a new question.

The script then inserts the question into an existing quiz in an existing course in line 140.


After running the script

This screenshot shows the inserted question after running the script, obviously this would need some more cosmetics.

inserted question inside the quiz after executing the script

And when editing the question this is what you see:

editing the question

Be careful not to touch the variables or the formula section since this will reset the table values.



In order to be presentable to the students the above questions needs some cosmetics. What is to be calculated? Perhaps insert a picture or an equation? More text?

after editing, but still inside the editor

After updating the question and leaving the editor it now looks like this in the Canvas UI:

the modified question inside the quiz


Seeing and answering the question

When you now start the quiz, this is how the question looks:

the question as it is seen by the student


  • calculated_questions can be generated using the Python canvasapi library
  • answer values have to be provided with the key 'answer-text'
    'answers': [
         'weight': '100',
         'variables': [
         {'name': 'U0', 'value': '9.0'},
         {'name': 'R1', 'value': '5600.0'},
         {'name': 'R2', 'value': '5600.0'}],
         'answer_text': '4.5'},


  • when querying an existing calculated_question through the API the answer values are found with the key 'answer'
        {'weight': 100,
         'variables': [
          {'name': 'U0', 'value': '110.0'},
          {'name': 'R1', 'value': '82.0'},
          {'name': 'R2', 'value': '8200.0'}],
         'answer': 108.91,
         'id': 3863},


  • when supplying an equation for the 'formular' field this has to be done in a list, not a dictionary
     'formulas':  ['a*b'],


  • when querying an existing calculated_question through the API the equations are found in a dictionary like this:
     formulas=[{'formula': 'a*b'}],

When poking the Canvas API with curl I would often find myself copy and pasting lots of Authorization headers around to correctly authenticate against Canvas. This is error prone and also leaves your tokens accessible in your .bash_history file. To improve on this I wrote a small script for macOS that stores the tokens in Apple's keychain and then automatically adds the Authorization header to the curl request based on the URL in the command line. While this isn't perfect, it's much better and easier to use. The result is a script I call ccurl. 



Download a copy of ccurl, make it executable and set the CCURL_HOSTS environmental variable to a space separated list of hosts you wish to use ccurl against. 

$ curl -s -O$ chmod +x ccurl
$ echo 'export CCURL_HOSTS=""' >> ~/.bashrc

You may also wish to put ccurl somewhere on your PATH. Then to set a token for a host use (history -c flushes bash history so the token doesn't get save in plain sight):

$ security add-generic-password -a $USER -s -w 7~1K9WJ3xobQp5RX8DUbbSdigxn2WD8yMOfUlCHbH9FIPlyL7E9E5QWSWN4CCVfqAEHC
$ history -c


Then to use it just do a curl command but add the extra c, it passes all command line options through to curl so it should support all examples you see for the standard curl tool (jq is a tool to transform json, but here it just formats it to make it more readable):

$ ccurl -s | jq .
  "id": 4539009,
  "name": "Matthew Buckett",
  "created_at": "2015-05-31T19:49:29+01:00",
  "sortable_name": "Buckett, Matthew",
  "short_name": "Matthew Buckett",
  "avatar_url": "",
  "locale": null,
  "effective_locale": "en-GB",
  "permissions": {
    "can_update_name": true,
    "can_update_avatar": true

Links - How to create a token for your account.

Two commonly used grading schemes in Sweden are A-F (i.e., A, B, C, D, E, and F) and P/F (i.e., Pass and Fail) together with Fx (an incomplete - this is not a final grade). To install these grading schemes into a Canvas course or account I wrote a simple python program: (available at


Note that the numeric values associated with the letter grades do not matter, as it is only the letter grade that will be recorded in the central grade register (called Ladok).


While we have been using Canvas as an LMS for several years, we still did not have such a grading scale available at the account level, so I made a program to make it easy to insert these schemes for a course.


 I wrote a script that creates a Catalog Session and then downloads all Users' Transcripts. There are some subtle authentication tricks happening so if the code does not make sense refer to the following text. 


Why Did I want to download all Catalog User Transcripts?

Recently we have been looking into institution-branded storefront services other than Catalog. In this process, we have had to look into how to download every transcript before possibly sunsetting our Catalog instance. The simplest way to preserve the user data is to download a user's transcript. Unfortunately, the transcript PDFs are not accessible by the API. However, I have found a workaround for this and I wanted to share it with others who are interested in creating a local store of transcript PDFs. 


What do I need before I do this?

 You will need to have admin rights to both the Catalog instance and the Canvas instance that is linked to the Catalog instance. 


Why Can't I Use My API Token to Authenticate?

To access transcripts seems relatively trivial; one could simply iterate through the Catalog User_Ids and make GET requests to '<CATALOG_DOMAIN>/transcripts/transcript.pdf?user_id=<USER_ID>'. Since this is not accessible with the API, you must simulate a 'login' or create a session ( python documentation ). Without creating a session any requests will be rerouted to the /login page.  This login page contains information that is passed to the login POST request that is hidden to the user. To obtain that info I use the lxml ( ) package to parse the HTML for these hidden values and then add my username and password to the form before sending it in a POST request to log in.


Why Can't I Make a Basic Request Now That I am 'Logged In'?

After simulating a login, I found that making a GET request to /transcripts/transcript.pdf?user_id=<USER_ID> would redirect me to /transcripts/transcript.pdf which is my own transcript. When I looked at the history of the redirect ( see the function history() ) I noticed that the parameter user_id was lost in the first redirect which was to /login?force_login=0&target_uri=%2Ftranscripts%2Ftranscript.pdf . 

However, if I requested the page /login with params force_login=0 and target_uri=

%2Ftranscripts%2Ftranscript.pdf%3Fuser_id%3D<USER_ID> , the request would ultimately redirect to the desired page! I am not fully sure why this worked and if anyone has any idea why I would love to know.




Python Script

  some information has been omitted for privacy and clarity. Please post a comment if there is anything unclear 

# Fill in your details here to be posted to the login form.
username = config.get('catalog','username')
password = config.get('catalog','password')
canvas_catalog_domain = config.get('instance','canvas_catalog')
catalog_domain = config.get('instance', 'catalog')
catalog_headers = {
    'Authorization': 'Token token="%s"' % (config.get('auth_token','catalog')) ,
catalog_ids = ## A LIST OF CATALOG USER IDS ##

# Use 'with' to ensure the session context is closed after use.
with requests.Session() as s:
    login = s.get(catalog_domain+'/login/canvas')
    # print the html returned or something more intelligent to see if it's a successful login page.
    login_html = lxml.html.fromstring(login.text)
    hidden_inputs = login_html.xpath(r'//form//input[@type="hidden"]')
    form = {x.attrib["name"]: x.attrib["value"] for x in hidden_inputs}
    #print("form: ",form)

    form['pseudonym_session[unique_id]']= username
    form['pseudonym_session[password]']= password
    response ='/login/canvas',data=form)
    #print(response.url, response.status_code) # gets <domain>?login_success=1 200
    # An authorised request.
    if int(response.code) != 200:
        raise Exception("Login failed with :", response.code )

    for user_id in catalog_ids:
        #print('user_id: ',user_id)
        # getting transcript pdf
        r = s.get(catalog_domain+'/login?force_login=0&target_uri=%2Ftranscripts%2Ftranscript.pdf%3Fuser_id%3D' + user_id, headers=catalog_headers)
        if int(r.status_code) != 200:
            # possible error
            error_log.write('%s -- %s ERROR \n' % (r.url, r.status_code))
        else:  # lets continue getting info
            filename = 'pdfs/%s_catalog_transcript.pdf' % (user_id)
            with open(filename, 'wb') as f:

def history(r):
if r.history:
print("Request was redirected")
for resp in r.history:
print('\t',resp.status_code, resp.url)
print ("Final destination:")
print('\t',r.status_code, r.url)
print("Request was not redirected")



I hope this post helps other Canvas Developers out there! Feel free to contact me if you are trying to troubleshoot running this script or a variation of it. 


Maddy Hodges 

Courseware Developer
University of Pennsylvania

In conjunction with experiments concerning using an LTI interface to realize a dynamic quiz, I built a version of Canvas that could run in a Virtual Machine (specifically an image that could be run by VirtualBox).

Advantages of this approach include:

  • easy to try things without any risk to the real Canvas infrastructures
  • easy to give an OVA image of the VM to students

The image was built using an ubuntu version of linux by following the Quick Start instructions at

Details of the operation of the different containers that comprise this system will be described in a forthcoming Bachelor's thesis.

I should note that when I do the docker-compose to bring up the Canvas instance, it takes a very long time.

In order to do some experiments with the dynamic quiz, I created some fake users and enrolled them in a course. Additionally, since one of the things that I would like the dynamic quiz to exploit is knowledge of what program of study these users are in I augments the user's custom data with details of what program they are in.

The initial version of the program ( can be found at .

The result is a course with a set of fake users as can be seen in the list of user's in this course:

List of users in the course

An example of fake program data is:

custom data for user Ann FakeStudent is {'data': {'programs': [{'code': 'CINTE', 'name': 'Degree Programme in Information and Communication Technology', 'start': 2016}]}}


Some further details about the above can be found at Creating fake users and enrolling them into a course: Chip sandbox 

Mike Cowen

Templating Courses Tool

Posted by Mike Cowen Employee Nov 13, 2018

Templates are Awesome!  They help create a solid launching point for creating quality courses.

There are a couple of different ways to put a template in a new course shell.

  1. Importing a template course from Commons
  2. Using the Blueprint feature
  3. Copying another course which you have designated as a 'Template'.


Depending on the needs and workflow of your institution any one of these can work very well. This blog will focus on the third method listed "Copying another course which you have designated as a Template."



As a Remote Canvas Admin, I have been asked to apply a course template to all newly created courses for an institution that I serve. This was a daily task, so I decided to automate it using Google Sheets / App Script.  Word got out around the office about my tool and I've had a couple of requests for it, so I decided to make it generic for any institution and share it here in the Community. I chose Google Sheets because it is easily shareable with anyone, uses mostly JavaScript and offers a UI that makes manipulating data more human readable.



This tool is a Google Sheet that uses the App Script language which is mostly JavaScript.  Once it is configured, just click the 'Canvas' menu, then 'Apply Template to Unchanged & New Courses' and all courses that are newly created will be updated with your template course. BAM!

The logic of the tool is essentially this:

  1. Configure your domain and token.
  2. Tell the script the canvas course ID of the most recently created course. Any courses that are created after this one will have the template applied to it, with one exception.
    1.  Exception: The script will check each course to look for existing content.  If content exists in the course, the template will not be applied. There were a couple of requests for this feature. If you prefer not to use it, you can remove the
      if (getCourseChanges(check_course_id) == 0)
      from within the processCSV function.
  3. Tell the script what is the course that you want to use as a template
  4. The script then creates and downloads a courses.csv provisioning report and compares each course to see if it is newer than the  course mentioned in step 2. If a course is newer, then apply the template course using a content_migration API endpoint.


For a better user experience, the script then gets the status of the migration and updates a cell when the migration is complete. I also made the name of each course a link to the course to make it easy to verify content.  


How to Use the Tool:

These instructions are in the 'Instructions' tab in the spreadsheet.

Configuration Instructions - This only needs to be done once

  1. In the 'Canvas Menu' select 'Configure API Settings'
  2. When prompted, Authorize the script by clicking on 'Authorize.'
  3. Select an email address to run the script.
  4. Select Allow
  5. In the 'Canvas Hostname' box, enter the URL of your Canvas instance. For example
  6. In the 'Access Token' box, enter your token. (For help creating the token, click on the ' How do I obtain an API access token for an account?' link right below the box.)
  7. Click 'Submit'
  8. If the Notice box says 'Using <> for your Canvas Hostname.' then click OK. Otherwise click 'Cancel' and repeat steps 5-7.
  9. In the 'Canvas' Menu select 'Don't Apply Template to Courses Created Before This Course ID'. Enter the Canvas course ID of the most recently created course in your instance and click OK. If you want to look at a list of all your courses, select 'Show All My Courses' from the 'Canvas' menu and wait a few minutes for the courses.csv report to create & populate the 'CurrentCourses' tab.
  10. In the 'Canvas' Menu select 'Set Template Course' and enter the Canvas course ID of your template course and click OK. 


Applying the Template Course

  1. In the Canvas menu, select 'Apply Template to Unchanged & New Courses'
    1. The Google sheet will now get a Courses Provisioning report and work it's magic. In a few minutes, you will see list of your courses in the 'Current Courses' tab.
    2. After the courses display on the CurrentCourses Tab, the new courses will appear on the NewCourses tab and automatically apply the template to the new courses.
    3. After another minute or so, the 'Status' and date/time' columns will update when the template course is applied
    4. There are links on the courses for your convenience
    5. Note: Google Scripts have a time limit. If you have a larger institution with hundreds of thousands of courses, this script may time-out. If it does, you can re-start the 'status update' by selecting 'Update Status of Template Copying' from the Canvas menu.
  2. If you wish to automate this and run it on a daily timer, select Tools > Script Editor > Edit > Current project's triggers. Then create a trigger to run updateNewCourses'.



As mentioned above Google App Script have limitations. Depending on the size of your institution, the script may time out. If that happens you're either a HUGE institution or making a LOT of changes. You may want to remove the 'if (getCourseChanges(check_course_id) == 0)' as mentioned above.

Just as a point of reference, I have worked with one institution that has almost 600K courses. That report takes about 12 minutes to create, so I estimate around 50K courses per minute to create the users.csv file. Your milage may vary.

The time required to apply the template course will also vary based on the quantity of material in the template.


What's Next:

  • Frankly, I'm not thrilled with the getMigrationStatus() function. If you feel the desire to help, please see the comments in the code.
  • I know my code isn't always optimized as good as it could be. If/when you find ways to improve it, please let me know. I love to learn and appreciate any help.
  • If you add more feature & functionality to this, please share back to the community. 



James Jones - The Configure API Settings and Forget API Settings come straight from his super work. If you haven't seen his Canvancements, prepare to be amazed!

Jeremy Perkins - Thanks for showing me how to pull reports and put them into sheets! 


Link to Template

I almost forgot... Here is a link to the template

You'll need to make a copy of it in your own drive first.


Video Demo


Sep 19, 2018 > Sep 21, 2018 12:28 PM

Jun 6, 2018

May 31, 2018



If you're a system administrator of a Canvas LMS instance with a deep organization of sub accounts you have inevitably found yourself in one of Dante's 9 Circles of Hell, repetitively clicking Sub Accounts at each and every level as you drill down to where you need to go. Here at CCSD we have over 8,000 sub accounts, and yet this only affects 5 people. So we will share this to help anyone else who is trapped.


The JavaScript and CSS files here add a directory style recursive HTML menu to the Canvas global navigation admin tray...


In Pictures

Admin Tray CollapsedAdmin Tray ExpandedAdmin Tray Search

Configuration & Setup

Zero to little configuration is needed for admintray-subaccmenu.js, so I won't even bother explaining subacctray.cfg


Option 1

  1. Host the file admintray-subaccmenu.js on a secure server (https) or somewhere like Amazon S3
  2. Copy/append the contents of to your Canvas Theme JavaScript file, point the URL to the hosted file.

Option 2 (Quick Start)

  1. Just copy the example snippet below that uses this repo's version and RawGit   to your Canvas Theme JavaScript file. However, please note that the RawGit URL points to the repo source and any changes to it may affect your usage later. I recommend hosting your own for stability, but this can get you started.


Once loaded the script will initialize an API call (to /api/v1/accounts/self/sub_accounts) and loop through x/100, collecting every sub account in your instance, compile a recursive HTML unordered list and store it your browser's localStorage.

Depending on the number of your sub accounts and internet speed, this will take a moment, be patient for the first load, open the tray and wait for it to show up. This takes us about 45-60 seconds on production. You will see a loading indicator in the Admin Tray while it compiles.


Features & Use


Instance Independent

There are some users (like Instructure Canvas Remote Admins) who may login to multiple Canvas Instances/institutions, so each menu will be stored in localStorage based on the uniqueness of, including,, and

For most users and institutions this will go mostly unnoticed unless you have different sub accounts between your Production, Test and Beta environments. I made this update to help those users it will affect. I also personally hate copying, pasting or replicating files just to change 1 line. This update allows 1 file to be hosted on a CDN like Amazon S3 and simply change the var show_results_parent value in or leave it blank.

Therefore an already minified file has been provided in the repo.


Alphabetical Sorting

Yup! As far as I know, the Canvas API does not allow sorting API calls alphabetically during pagination. The entire stack of sub accounts is sorted prior to building the menu.

Localized Search

Using JavaScript/jQuery to search within the stored HTML, preventing further API calls. You can search the entire sub account menu by name.

Search Result - Prefix Parent Account (Skip-to-Depth Display)

I don't know what else to call it, so here is an explanation.

Suppose your directory structure looks something like the following, where (#) is the depth of the account.

  • High Schools (1)
    • George Washington HS (2)
      • Course Shells (3)
      • SIS Courses (3)
        • English (4)
        • Math (4)
        • Science (4)
  • Middle Schools (1)
    • Betsy Ross MS (2)
      • Course Shells (3)
      • SIS Courses (3)
        • English (4)
        • Math (4)
        • Science (4)


When we search for Science and get multiple sub accounts of 'Science', we can't identify which one we want to choose.

  1. Science
  2. Science

So if we map the results depth of 4 to it's parent depth at 2 (instead of 3, SIS Courses) we can get a result like:

  1. George Washington HS > Science
  2. Betsy Ross MS > Science


And both are links to their respective account.



// one skip
show_results_parent = { 4:2 }
// expected result:
// George Washington HS > Science

// or multiple skips, must be unique
show_results_parent = { 4:2, 3:2 }
// expected results:
// (4:2) George Washington HS > Science
// (3:2) George Washington HS > SIS Courses

Note: If you don't define the skip, it will not display a parent


The ↻ button at the bottom right of the menu will clear the menu from localStorage and recompile it for the current Canvas instance. Use this when you or other admins have added or removed sub accounts.


User Impact

As mentioned above, CCSD has 5 system admins, with 40,000 Employees and over 300,000 Students. Be kind to your users, use the snippet below to reduce the impact of your admin-only tools. This is included in the repo as

if (ccsd.util.hasAnyRole('admin','root_admin')) {
// used for search results, result:parent, see documentation for more details
var show_results_parent = {}
// async load the sub account menu script
url: '',
dataType: 'script',
cache: true,
data: {skipd: JSON.stringify(show_results_parent)}


User Script

At the suggestion and some coaching by James Jones, I have created a User Script version. This is useful for those admins that can't or don't want to install this script in their Canvas Theme/Global JavaScript. This script is identical to the global JavaScript file, except that the CSS will be added to the DOM by the script and has the various userscript requirements at the top for Tampermonkey.

  1. You will need to install and enable the Tampermonkey browser extension
  2. Install the UserScript version of admintray-subaccmenu.user.js


Known Issues

If you are not a root admin of your Canvas Instance, you will need to set (1) sub account in the root setting. At this time you cannot add multiple sub accounts. I am planning to fix this.


Code repo here...

ccsd-canvas/admintray-subaccmenu at master · robert-carroll/ccsd-canvas · GitHub



Credits for Blog Post Banner Image

Public domain

Chart of Hell
File:Sandro Botticelli - La Carte de l'Enfer.jpg - Wikimedia Commons

Botticelli : de Laurent le Magnifique à Savonarole : catalogue de l'exposition à Paris, Musée du Luxembourg, du 1er octobre 2003 au 22 février 2004 et à Florence, Palazzo Strozzi, du 10 mars au 11 juillet 2004. Milan : Skira editore, Paris : Musée du Luxembourg, 2003. ISBN 9788884915641


This media file is in the public domain in the United States. This applies to U.S. works where the copyright has expired, often because its first publication occurred prior to January 1, 1923. See this page for further explanation.

When faced with having to assign peer reviewing for multiple assignments, I realized that I needed a tool to copy peer reviewing assignments from one assignment to another. Then in a course with multiple assignments and multiple instructors (not all of whom were synchronized in making their peer reviewing assignment), I modified this program to allow the copying on a section basis (i.e., one can specify that only the peer reviewing assignment for a user in a given section is to be copied).

The results and the programs to do the above are at Copying peer reviewing assignments: Chip sandbox 


This is my second blog post to the Canvas Community, but I'm going to stick with my tl;dr format.


The Situation

Who doesn't like their code to be easier to read!? Oh, you use the Rich Content Editor? Well, why are you in this blog? I see, you thought this was to add syntax highlighting to the content of an activity. Yeah...about


I know there have been a few idea requests to add a syntax highlighter to the core of Canvas for supplying syntax highlighted code within the content of activities. While I have no doubt I could do this, I'm not going to.


So what's this blog all about? The HTML Editor is SO boring! We load ~95% of our content via the HTML Editor, having written it in third-party applications (i.e., Notepad++), because the HTML Editor in Canvas isn't particularly inspiring, nor is it easy to read.


Reason for Creating

Simply put, I did this to allow our developers to more easily read the code from within Canvas. There really isn't anything more to it.


The Solution

A userscript the "replaces" the HTML Editor with an Ace Editor ( So, without further ado, the Q&A:


Q: What does this userscript do?
A: Simply put, it "replaces" the HTML Editor provided by Canvas with an Ace Editor.


Q: Why did you put "replaces" in quotation marks?
A: Ah, the long answer.


The userscript doesn't actually change any of the Canvas functionality. What it actually does is to hide the HTML Editor and add the Ace Editor to display in its place. When changes are made via the Ace Editor and the user clicks out of it (i.e., to see the Rich Content Editor or save), the HTML Editor is updated with the code from the Ace Editor and Canvas processes it normally.


Q: Will the syntax highlighter always be enabled?
A: It can be. The userscript adds a toggle anchor to the line immediately above the editors where the toggle between editors is. This will allow you to enable/disable the syntax highlighter.


Q: Will it remember if I had it enabled or do I have to enable it every time?
A: It will remember. The userscript uses cookies to remember the state of the toggle. This means you could enable it while working in one assignment and it will already be enabled when you open a discussion in another course.


Q: What about multiple Canvas instances? I want it to be enabled in one, but not another.
A: It will! Rather, it should! I configured the userscript to use the very first part of the domain name it's toggled on to give it a unique name for the instance. I haven't tested it on different Canvas instances, but if I did everything correctly it will create unique toggle cookies for each unique Canvas production instance.


Q: You just said it was for "production" Canvas. Why can't I use it for "Test" or "Beta" instances?
A: You can. Taking the entire previous answer to give context, I said that there would be unique cookies based upon the first part of a domain name. So, if your production URL is with a test URL of, your cookie will be the same between the two: abc123_Canvas_syntaxHighlighter.


Q: Can I integrate this into my global JavaScript?
A: Knock yourself out, but I won't be supporting that.


Q: Can I use this in German? Japanese? Italian?
A: Yes? There are a few settings that can be adjusted at the top of the script (keep an eye out for the "DO NOT EDIT BELOW" line). Included in this area are variables for adjusting to different languages. These will need to be manually adjusted if your Canvas is not in English.


Q: Can I use this with a self-hosted instance?
A: Yes? I won't commit that it'll work, but if you update the userscript @include and @exclude values to match your actual domain, I don't see why it wouldn't. Having said that, I will not support use of this code for self-hosted instances.


Q: Will it work with the #editor_tabs for inserting links, files, and images?
A: No. To be blunt, the functionality of the #editor_tabs has always been a bit "wonky" for me, so I'm not even going to try to make this new editor work with it. You can, of course, toggle the editor on/off or switch back to the Rich Content Editor, make your inserts, then toggle back.


Q: So, it adds this to ALL editors in Canvas?
A: No. For simplicity only the first editor on a page will have the editor applied (so it won't be available to quiz questions). Furthermore, there are some editors (i.e., global announcements) that lack the HTML Editor, which the script is dependent upon.


Having said that, the userscript was designed to be as inclusive as possible. If, however, you do find pages where the editor doesn't work correctly, please let me know so I can either fix it or prevent it from loading there.




How It Works

  1. Load the userscript to your Userscript Manager of choice
  2. Enable the userscript
  3. Access the "HTML Editor" of an activity edit page
  4. Click the "Enable Syntax Highlighter" anchor right next to the toggle that allowed you to access the HTML Editor.






Releated Ideas/Discussion

HTML Formatter

Drew Royster

File Sync

Posted by Drew Royster Jul 31, 2018

Having become incredibly frustrated at the frequency my professors added and updated their course files requiring me to download and move files to wherever I had organized them before, I built an app that handles syncing Canvas files to student's computers. 


The app is open source using electron js GitHub - drew-royster/canvasFileSync: syncs files from canvas to your local machine to ensure you're always up to date 


Any thoughts on how to improve the app or if you could just spread the word I'd appreciate it.

I've been working on an easier way for teachers to upload Quiz Items and Outcomes from a Google Sheet. The template sheet uses Google Apps Script and a user's personal API key to interact with their courses.



Teachers frequently write tests and quizzes together. This mechanism allows teachers to write and upload dozens of questions easily from a Google Sheet.

Screenshot of the template spreadsheet

The Canvas API only allows questions to be uploaded to an existing Quiz resource. Teachers can select the appropriate course and quiz dynamically using the popup.


Quiz uploader sidebar with dynamic course and quiz selection boxesOnce a quiz resource has been selected, users can define the number of items to upload. The spreadsheet marks an item as successful and updates the next row to be uploaded so questions are not duplicated on subsequent runs.


  • All questions uploaded are stored in the "Unnamed bank" question bank.
  • The "Topic" and "Primary Standard Indicator" fields are used to title questions for easy bulk sorting in the Canvas UI.

Updates to come

  • Error messages are...opaque. Often a failure results in "the resource does not exist," meaning the user hasn't selected a course or quiz.
  • Questions are not batched right now, so uploading too many questions can lead to a script timeout.



The Outcomes API is difficult to use. I know there are CSV imports now, but it is sometimes helpful to upload Outcomes as they're written rather than generating a CSV over and over.


Outcomes have to go into an Outcome Group. If there are no groups defined in the course, the course name is used as a top-level group.


The template spreadsheet is similar to the quiz template, with specific fields being defined for the upload to run successfully. There is a helper sheet where users can define different rubric scoring ranges for each upload.

Outcome upload template in a Google Sheet

Rubric template for uploading Outcomes

The Outcome upload sidebar is similar to the Quiz Item sidebar. Because new outcomes must be attached to a group, a dynamic list of codes and group titles are generated when the user selects their course.

Outcome group codes displayed dynamically on course selection



  • Outcome uploads are done to the course, not to the user's account. This allows them to bulk-modify outcomes semester to semester as they update their courses.


Updates to come

  • Improve error messaging
  • Pull all existing outcomes and their status into a sheet for proofing and updating.


I have a template project you can copy. You can also see the entire source on GitHub if you'd like to take a look or contribute. My plans are to eventually convert this to an AddOn for easier distribution and use.

Developer Tools for the Canvas User > Tutorial #3


For this tutorial we are going to combine what we learned in tutorial's 1 and 2. The goal will be to make an AJAX request with JavaScript to the Canvas API, and then when the request is successful update the DOM with an updated value. The Document Object Model or DOM, is the webpage as your browser sees it. I'll get you through this tutorial without any more details, but if you're curious here are some resources:

W3 Schools - JavaScript DOM Methods

MDN - Document Object Model (DOM) - Web APIs


As part of this rapidly escalating tutorial I am no longer going to use pictures. Anything you need should be referenced from the previous tutorials.


  1. Grab the snippet from the last tutorial and add the following code (lines 10-14) to the end.
    var payload = {
         "nickname":"My W○RKR∞M ⸚ Test"     // what we are changing the nickname to

         url : '/api/v1/users/self/course_nicknames/1214595',
         type: 'PUT',
         data: payload
    // when the request is resolved
    .done(function(response, status) {
    • The jQuery done() method allows us to do something when the AJAX request is complete. For now, copy or type this code into your Console tab, and press Enter
    • The console.log() method allows us to display messages to the console. This is generally used for debugging or displaying other messages while developing. The user will never see these messages unless they have the Console tab open.
    • The response should look just like the one you saw in the Preview pane of the Network tab from Tutorial #2
  2. Now, you will update the name of the Course Card...
    Note: We could have done this with the payload value, but this is a better example of making an AJAX request and doing something when the request is finished and successful.
  3. First we have to find a Selector we can use to identify the element we want to change. 
    Right click on the Course Card where you see the course nickname (the text with color), and Inspect Element
    Hopefully, you will land on a element that looks something like this
    <span style="color:#FDC010;" data-reactid=".1.$1266609.">W○RKR∞M⸚CARROLL</span>
    Note the style="" attribute with value color:#FDC010;
    We will use this as our selector for this tutorial.[1][2]

  4. Let's update our code and use jQuery to update the element after the AJAX request completes, see line 12
    We will use the response.nickname value
    var payload = {
         "nickname":"My W○RKR∞M ⸚ Test"     // what we are changing the nickname to

         url : '/api/v1/users/self/course_nicknames/1214595',
         type: 'PUT',
         data: payload
    }).done(function(response, status) {
    Press Enter, after updating the code in the Console tab
  5. You should see the nickname of your course card update
    If not, try refreshing the page and reset or adjust some of the values you are passing with the code.


Congrats, AGAIN! 

This is the basic workflow of a Canvas end-user modification. These are some of the fundamental practices used in making interactive web pages all across the web. You can expand these skills to make your own custom Canvas modifications. Remember to review the Canvas API Documentation and thoroughly plan out the objective and goals your script is trying to achieve.


[1] There a plenty of resources on the web for CSS and HTML selectors for interacting with DOM elements via JavaScript, check with Google.

[2] This is because Canvas uses React - A JavaScript library for building user interfaces, which allows elements to load or update after the Primary DOM loads. This is noticeable via the attribute data-reactid="". Usually unique elements will have an id="" attribute if the element is unique and a class="" attribute if there are similar elements. This creates problems when jQuery tries to interact with these elements, because they are not part of the page when jQuery loads. We would prefer to use predictable Selectors whenever possible. This is a more advanced topic and may be covered in a later tutorial.


 Rapid Escalation

Repeat Tutorial 3 and update the color of the Course Card, after sending a new color to the Canvas API.

Developer Tools for the Canvas User > Tutorial #2


Before getting started with APIs, read through paragraphs 1-5 of the linked thread by Stuart Ryan

Canvas APIs: Getting started, the practical ins and outs, gotchas, tips, and tricks

Stuart confirmed there is a working beta environment on the Canvas Free For Teachers accounts at

To save on lengthy screenshots, I've combined some panels, please let me know if this causes confusion.


  1. Now that you have the console open, click on the Network tab, and the ⃠ Clear button on the left
    This will clear all the requests that happened during page load
    Drag out some space with the border between the ViewPort (on the left) and the Developer Tools
  2. Now click on the edit button at the top right of any Course Card, change the nickname and the color, click Apply
    You should see two new requests with the course number
  3. Click on the request prefixed with course_ and then the Network Headers tab
    Take a quick peak at the following:
    Request URL - this is the URL (or endpoint) of the request we want to send to the API to change the color, it contains your user and course id's
    MethodPUT - HTTP, and all the Request Methods - HTTP | MDN 
    Status Code200 OK, and all the Response Status Codes - HTTP | MDN 
    Content Type
  4. Now click on the Network Preview tab
    This is JSON, I hope you clicked that last link.
    I'll walk you through with Colors first, it's a simple place to start.
    // this is a simple JSON object, with the hex value of the color you chose
    // you'll usually see JSON like you see in the console, with all the white space removed
    {hexcode: "#F0592B"}

    // but it's often useful to nest it with tabs while you're working and learning
         hexcode: "#F0592B" // this is the data that was sent to Canvas to save the color

    // it has one (1) value, simple right?

    Canvas Developer Tools | Change Color

  5. That's 5 pieces of information we need to change the color with the API
    URL - API endpoint/api/v1/users/:id/colors/:asset_string
    MethodPUT - replaces all current representations of the target resource with the request payload.
    ID - your user idself - use self when you can't remember your user_id [1]
    Asset Stringcourse_# - get the course number from the course URL
    Parameters{hexcode: "#F0592B"} // the payload you will PUT to the URL
  6. Let's keep it simple and use jQuery to make this API request
    Type or copy the 10 lines below into the Console tab, and edit the course number in the URL
    After you get this snippet into the console, hit Enter
    var payload = { hexcode: '#FDC010' } // yellow

        url  : '/api/v1/users/self/colors/course_1214595',
         type: 'PUT',
         data: payload

    // #8BC34A - a green hex code

    {readyState: 1, setRequestHeader: ƒ, getAllResponseHeaders: ƒ, getResponseHeader: ƒ, overrideMimeType: ƒ, …}

  7. You should immediately see a response that looks something like the one above, we'll cover it in tutorial #3, click on the Network tab
    Review the Network Headers and the most recent request for course_#
    Hopefully you'll see Status Code of 200 OK
    If not, compare your code to the snippet above // comments can be ignored, or take a peak at tutorial 3
  8. Click on the Network Preview tab
    You should see a JSON object with the hexcode you sent with the request in the Console tab
  9. Refresh the page
    Unlike tutorial 1, this time you sent the data to the Canvas API where it was saved. When you refreshed the page, you got another copy of the Dashboard with the new color.
  10. Try It Again!
    Go back to the Console tab. To re-execute the last command/code you ran in the console, press the up arrow on your keyboard
    Copy the green hex code from the snippet, and replace the payload value, press Enter
    Pick a different Hex Code with W3 - HTML Color Picker



You've just used javascriptjqueryajax, and the Canvas api to Update custom color - Users - Canvas LMS REST API Documentation 

Review the documentation anytime you want to interact with Canvas programmatically. The documentation is a great starting point to any project, then planning how you will use it in your script or program.




  1. Let's continue to the Course Nickname.
    Click on the Network request without the course_ prefix, in the screenshot below it's 1214595
    Review the Headers and the Preview tabs as you did above
    Canvas Developer Tools | Set Course Nickname
  2. Some more JSON, this time with a few more parameters, let's take a look
    // you see a string like this
    {"course_id":1214595,"name":"W○RKR∞M⸚CARROLL","nickname":"My W○RKR∞M ⸚"}

    // we can indent and format, making it easier to read
         "course_id":1214595,          // the id of the course we want to rename
         "name":"W○RKR∞M⸚CARROLL",     // the current nickname of the course
         "nickname":"My W○RKR∞M ⸚"     // what we are changing the nickname to
  3. Three values were returned in response to setting the Course Nickname using the Dashboard Card. However, we only need to send 1 value to change the nickname.
    Take a look at Set course nickname - Users - Canvas LMS REST API Documentation
    Note the Endpoint and the Method, it looks like this

     PUT /api/v1/users/self/course_nicknames/:course_id


  4. Let's update the snippet of code we used in the first section of Tutorial #2

    We need to update the payload with the nickname, and change the URL

    // update the payload with the values we need to set
    // indent them to be easier to read
    var payload = {
         "nickname":"My W○RKR∞M ⸚ Test"     // what we are changing the nickname to

         url : '/api/v1/users/self/course_nicknames/1214595',
         type: 'PUT',
         data: payload
  5. Copy and paste your code into the Console tab (unless you edited it there) and press Enter
  6. You should immediately see a response that looks something like the one above, we'll cover it in tutorial #3, click on the Network tab
    Review the Network Headers and the most recent request for course_#, hopefully you'll see Status Code of 200 OK
    If not, compare your code to the snippet above // comments can be ignored, or take a peak at tutorial 3
  7. Click on the Network Preview tab
    You should see a JSON object with the hexcode you sent with the request in the Console tab
  8. Refresh the page
    You should see your course change names
  9. Try It Again!
    Edit the snippet, change the nickname and press Enter


[1] Profile - Users - Canvas LMS REST API Documentation

      or in the browser console, try ENV.current_user_id


 Rapid Escalation

Check out Developer Tools #3 - Update the DOM with an AJAX Response  and we'll update the Dashboard without Refreshing the page

Developer Tools for the Canvas User > Tutorial #1


  1. On your Canvas Dashboard, Right Click on a Course Card and choose Inspect
    If you miss and the highlighted element is not exactly as shown, hover over the elements until you have the whole Course Card
    Look for the <div> elements with a class of ic-DashboardCard
  2. Choose Delete Element
    Canvas Browser Tools - Delete Element
    It's Gone!
    Canvas Browser Tools - Element Deleted
    Not Really.
    And you didn't break anything either. You deleted the element from the browser's local copy of the page, nothing on the server has changed.
    To get a new copy from Canvas, continue to step 3
  3. Refresh the page


All Done! After the refreshed page downloads, your Canvas Dashboard is back to normal.


There's no real purpose for doing this other than to walk you through finding elements to edit on the page, or to make this tutorial. I'm enrolled in a few more than the three courses seen here. For the average Canvas user this may be helpful for writing tutorials and other documentation, or maybe removing and modifying personally identifiable information to take and transfer screenshots safely.




  1. To modify an element on the page, right click on it or find it in the Elements tab, and choose Copy Copy Selector
    Canvas Developer Tools | Change HTML with JavaScript
  2. Next, click on the Console tab and type in // and paste the selector, press Enter (that is 2 forward slash's)
    This is a JavaScript comment (code that isn't executed) with an identifier we can use to change the content of the <h1> that displays the Dashboard page title
    You should also see undefined on the line below, this is because nothing was executed and nothing was returned
  3. Using either of the 1 line jQuery* snippets below, you can change the title of the Dashboard page or remove the last Dashboard Card from the page
    // change the page title/header
    $('#dashboard_header_container > div > h1').html('My Dashboard')
     // remove the last dashboard card

    Each line should edit the page immediately and the console will respond with an updated DOM object

    Canvas Developer Tools | Change and Delete
  4. Refresh the page to clear your changes


* The usage of jQuery here is for demonstration purposes. Canvas currently provides jQuery, however it may be deprecated in the future. The following snippet shows how to change the page Title with vanilla JavaScript.

// change the page title/header
document.querySelector("#dashboard_header_container > div > h1").textContent = "My Dashboard";




 Rapid Escalation

If you want to make changes to your Canvas Dashboard more permanently, check out this thread and Github repository.

Sorting Dashboard Course Cards 

canvancement/dashboard at master · jamesjonesmath/canvancement · GitHub 


For a deeper look at modifying Canvas pages with Javascript, look at Hiding Content from Certain Roles

While collaborating recently, I’ve found myself referring to the Javascript Console, or is it Developer Tools, maybe Web Inspector; what about adding Browser before, do I provide a link? Sometimes, I don’t know whether to provide a link to an article that discusses the particulars of opening, inspecting or getting to the tab I want to reference; or if I’m insulting someone. So, I've decided to create a series of short tutorials that will hopefully walk new developers through some basic tooling around on Canvas with a Browser and JavaScript. I will also try to use these as references in future posts and discussions. I hope they are helpful, feedback is welcome. Enjoy!


Before we get started, let me point out that I won’t be getting lengthy on how to open the tools in each browser since everyone can do that with Google, and the easiest way is to right click the web page and select Inspect Element or Inspect, if you're using Chrome.


The following is a brief, but detailed, and rapidly escalating tutorial of things any Canvas User can do with Developer Tools. Along the way, I will share some community resources that will help guide you with additional information.


tutoriallevelfocus (tab in developer tools)
#1 - Delete and modify elements, and Refreshfreshmanelements
#2 - Update a course nickname with the APIsophomorenetwork, console
#3 - Updating the DOM and Handling Responsesjuniorconsole, elements
#4 - JavaScript XHR for Canvasseniorconsole, network
#5 - Mutation Observers & Reactmutant


// indexing a few related resources

API Testing: Postman by Garth Egbertadvancedapi requests, json


 While searching around the community for this series, I stumbled upon this comment by James Jones.


The problem with JavaScript resources is that, for the basic stuff, you're not trying to learn JavaScript, you're trying to accomplish a specific task. There's no book or website that I want to sit through the whole thing to learn how to program in JavaScript. I don't want to start with "Hello world." I don't have time to start with "Hello world." I want to solve the problem that needs solved and get back to life.


My primary resources are the encyclopedia (Mozilla Developer Network documentation) and the commentary on it (Stack Overflow), using Google to get me to the content I need. I often start with a solution from Stack Overflow and then go look up the right way to do it using MDN.

I'll also add that W3 and W3Schools are great resources for what we're doing.


If you have questions or need assistance, please ask in a comment, it will help others.

If you work through these tutorials and find any issues (or Canvas breaking changes) please let me know and I will get it fixed right away.

Please let me know if you would like to contribute, I would be happy to add new tutorials and information. Contribute new tutorials with API endpoints that work for user/student roles, no advanced permissions.