Brute Forcing Canvas Catalog

Canvas Catalog is a front-end Instructure sells so that institutions can create a storefront for courses held in Canvas. In our case, we’re starting to do some paid non-credit courses. While I had a variety of interactions with Catalog back at VCU, I wasn’t deeply involved and most of our goals were fairly linear. It’s been interesting to see what Catalog can do now that I’m involved a bit more deeply and trying to push some boundaries.

Catalog does not give you much to work in terms of customizing course listings (at least via the web interface). You have a few form components on the course level (course title, cost, etc) and the course description itself in a WYSIWYG editor field. That editor does allow HTML but it also cleans various elements (like buttons for some reason). I can’t find a list of allowed elements or a list of banned elements. Either would be helpful.

Catalog does allow you to upload your own custom CSS and javascript. It also provides this helpful warning.

Please note that you take full responsibility for the continuous upkeep of custom CSS and JavaScript. Canvas Catalog does not take any responsibility for conflicts caused by custom CSS or JavaScript as updates and improvements are made to the Canvas Catalog code base.

VCU actually has a pretty standard course display. You can see below it has a title, a featured image, a description, etc. That’s pretty close to what you get out of the box.
A screenshot of a basic Catalog course description. Pretty much an image on the left. Some pricing/purchasing info on the right and a longer description underneath.

You can compare it to what we ended up using for the Middlebury course below.
A modified course view screenshot from Catalog. It has two columns with some segmentation to separate out content. There is an embedded video.

I think this version looks a lot better but it was pain to make. Time will tell whether it is even marginally sustainable. I am betting that we either start to move more towards an API version or we reduce the complexity to better match what Catalog better supports. Even so, I thought it’d be fun to breakdown how we got here.

One thing to note is that we’re dealing with Catalog’s code and then controlling the things we can control in three interrelated parts.

  1. our custom CSS
  2. our custom javascript
  3. our HTML in the course description

One of the challenges with this kind of editing is just making sure you keep copies and don’t mess stuff up. To help me deal with that, I made a GitHub repo for the various files. I’d do my best to cut/paste updates between the system and the repo but I didn’t always do it. It’s not an optimal solution. If anyone has better paths for this, I would love to hear them.

I also learned in this process that any CSS/JS you add loads in on the front-end, as you’d expect, but also loads in the admin area. That led to some modal conflicts that took me a few minutes to figure out.

I wanted to have two columns and some ways to highlight different components of the course description. We also wanted to add some modal interactions so that forms or FAQs could be launched on the page.

I did my typical pattern where I built little pieces in CodePen for experimenting. That’s more of a hassle in a scenario like this but it’s generally good enough for getting basics laid out.

See the Pen
catalog example
by Tom (@twwoodward)
on CodePen.

Nothing fancy in terms of HTML/CSS but not what we want to ask people to write by hand either.

The FAQ button may end up being a pattern we want to expand on. It’s kind of interesting. Since we had an evolving set of FAQs for the course, I made a FAQ blog and created a post for this FAQ. I used the custom JS option, to make a little modal that loads the page content from the WP API. We could do something similar for course descriptions. My only concern there is around Google indexing and javascript content. Maybe that’s not an issue any longer. Caring about Google is still pretty new to me. I just have half-formed memories from random stuff I’ve read over the last twenty years.

if (document.querySelector('#faq-button')) {//does it have a faq button?
    const faqButton = document.querySelector('#faq-button');
    faqButton.addEventListener("click", showFaq, false);
    makeFaqModal(2);//look for a page with the id = 2 . . . we can get more complex when we need to load different FAQ ID for different courses later

//get the data
function makeFaqModal(id) {
            "" + id
        .then(function(response) {
            // Convert to JSON
            return response.json();
        .then(function(data) {
            const modalClose = document.querySelector('#closeFaqModal');
            modalClose.addEventListener("click", hideFaqModal);


function makeFaq(data) {
    const destination = document.querySelector('#faq-modal');
    destination.innerHTML = ' <span id="closeFaqModal" class="close">&times;</span>' + data;//put the content in the modal

function showFaq() {
    const faqModal = document.querySelector('#faq-holder'); = "block";

function hideFaqModal() {
    const faqModal = document.querySelector('#faq-holder'); = "none";

The other stuff that’s a bit non-standard was dealing with our internal Middlebury people vs external people. There are discovery pages that Catalog/Canvas often uses to point people to different places to login (SSO vs external) but they often feel fairly awkward. I’m not sure we ended up with anything aggressively better, but I do like that we were able to make sure Middlebury emails didn’t end up signing up for duplicate accounts.

//check email on focus change
if (document.getElementsByName("email")[0]) {//does it have the email field?
    const emailField = document.getElementsByName("email")[0];
    emailField.addEventListener("blur", function() {//check when they leave the email field
        const emailValue = emailField.value.toLowerCase();
        if (emailValue.includes('')) {//if the email field string has in it . . . trigger this modal
            const redirectText = `
           <div class="modal-content"><span id="closeModal" class="close">&times;</span>
        <h1>It looks like you are a Middlebury community member.</h1> 
        <h1>Please request Catalog courses <a href="">here.</a></h1>
            const modalRedirect = document.createElement("div");
            modalRedirect.className = "modal";
   = "myModal";
            modalRedirect.innerHTML = redirectText;
   = 'display:block';
            //SET DISPLAY TO BLOCK here . . . 
            const regTitle = document.querySelector('.RegistrationHeader__Title');
            regTitle.parentNode.insertBefore(modalRedirect, regTitle.nextSibling);
            const modalClose = document.querySelector('#closeModal');
            modalClose.addEventListener("click", hideModal);

One thought on “Brute Forcing Canvas Catalog

  1. As someone who in recent years redesigned our catalog from the ground up (, I felt every word of this in my soul. Kudos to you- it looks great!! I do have a random burning question that no one seems to have tried to address that maybe you could assist with? I’m looking to update the text on the Categories button to be ‘Topics’ (specifically in the Extension sub-catalog- link in the footer). The same JS that I used for the Filter button does not work for categories (because why would it?).
    $(“#search-refine-button–refine > div >”).text(“Filter/Sort”);
    $(“#search-refine-button–categories > div >”).text(“Topics”);

    I have resorted to.. this monstrosity…
    $(document).ready(function() {
    // Wait for the document to be ready
    setTimeout(function() {
    // Hide the button initially
    $(“#search-refine-button–categories”).css(‘visibility’, ‘hidden’);

    // Change text for the “Categories” button
    $(“#search-refine-button–categories > div >”).text(“Topics”);

    // Check if the text has been successfully changed
    var newText = $(“#search-refine-button–categories .search-refine-button__text”).text();

    // Show the button only if the text is successfully changed
    if (newText === “Topics”) {
    $(“#search-refine-button–categories”).css(‘visibility’, ‘visible’);
    }, 450); // Adjust the delay (in milliseconds) as needed

    Which does work, until you refresh the page or navigate forwards and backwards…
    I know this is very unconventional, but if there’s any insight you could provide, I would be eternally grateful!

Comments are closed.