Like many of you out there, here at Utah State University, we are struggling to move all of our classes online in an absurdly short period of time in response to COVID-19.
To help speed up the process, I have created some JavaScript to facilitate rapidly pulling a template course into an empty course shell and I am sharing it in the hopes that it can alleviate some of the headaches for other institutions.
I am providing the code for two contexts, uploading JavaScript to Canvas using the Canvas Theme Editor and creating a browser bookmarklet for those who do not have access to account-level JavaScript in Canvas.
A Note About Permissions
The code that follows will run as the logged-in user and will only work if the user has permission to import course content in Canvas.
The code is also scoped to only show on the front-page of an empty course so it should not be visible to students.
Account Level JavaScript
Canvas allows for adding CSS and JavaScript at an account level using the Canvas Theme Editor. For institutions that want to provide this type of functionality for an entire account, here are two options:
Single Template Course
If you have a single template course that you would like to provide an easy way to copy, the following code will create an Insert Base Template button in the right Canvas sidebar:

Here is the code that you would upload to Canvas. Update the templateCourse id on line 36 to your template course:
function checkImportProgress(progressUrl) {
$.get(progressUrl, function(data) {
$('.kl-import-template').html('Checking Progress');
let completed = false;
switch(data.workflow_state) {
case 'completed':
completed = true;
break;
case 'failed':
alert('Import failed');
break;
default:
setTimeout(function(){ checkImportProgress(progressUrl); }, 5000);
}
if (completed) {
location.reload();
} else {
setTimeout(function(){ checkImportProgress(progressUrl); }, 5000);
}
setTimeout(function(){
$('.kl-import-template').html('Template Progress: ' + data.workflow_state);
}, 1000);
});
}
$(document).ready(function() {
let templateCourse = '593';
if (ENV.COURSE !== undefined && ENV.COURSE.id !== null && window.location.pathname === '/courses/' + ENV.COURSE.id && $('.ic-EmptyStateList:visible').length > 0) {
let importButton = '<button class="btn btn-primary button-sidebar-wide kl-import-template"><i class="icon-download"></i> Import Base Template</button>';
$('.kl-import-template').remove();
$('.course-options').prepend(importButton);
$('.kl-import-template').unbind('click').on('click', function () {
let confirmMessage = confirm("This will copy content into your course. Click OK to proceed.");
if (confirmMessage == true) {
$(this).html('Importing Base Template');
$.post('/api/v1/courses/' + ENV.COURSE.id + '/content_migrations', {'migration_type': 'course_copy_importer', 'settings[source_course_id]': templateCourse}, function(data, textStatus, xhr) {
checkImportProgress(data.progress_url);
});
}
});
}
});
Multiple Template Courses
This next variation is for institutions with multiple templates. Instead of a single button, this will add a list of courses with the option to preview and a brief description:

Here is the code that you would upload to Canvas. Update the templateList that begins on line 4 to include a name, id, and description for each of your template courses:
$(document).ready(function() {
let templateList = [
{
name: 'Basic Online Template',
id: '593',
description: 'Basic 8 week course with readings, assignments, and quizzes'
},
{
name: 'Another Template',
id: '1234',
description: 'This is a good course template'
}
];
function checkImportProgress(progressUrl, courseID) {
$.get(progressUrl, function(data) {
$('#kl-import-progress').html('Checking Progress');
let completed = false;
switch(data.workflow_state) {
case 'completed':
completed = true;
$('#kl-import-progress').attr('class', 'alert alert-success');
break;
case 'failed':
$('#kl-import-progress').attr('class', 'alert alert-error').html('Import failed');
break;
default:
setTimeout(function(){ checkImportProgress(progressUrl, courseID); }, 5000);
}
if (completed) {
let parms = {
'course[is_public_to_auth_users]' : false,
'course[is_public]' : false
};
$.ajax({
'url' : '/api/v1/courses/' + courseID,
'type' : 'PUT',
'data' : parms
});
location.reload();
} else {
setTimeout(function(){ checkImportProgress(progressUrl, courseID); }, 5000);
}
setTimeout(function(){
$('#kl-import-progress').html('Template Progress: ' + data.workflow_state);
}, 1000);
});
}
if (ENV.COURSE !== undefined && ENV.COURSE.id !== null && window.location.pathname === '/courses/' + ENV.COURSE.id && $('.ic-EmptyStateList:visible').length > 0) {
$('#kl-template-list').remove();
$('.course-options').prepend('<div id="kl-template-list"></div><div id="kl-import-progress"></div>');
$.each(templateList, function(index, val) {
let itemInfo = `<p>
<a href="/courses/${val.id}" target="_blank" data-tooltip="left" title="${val.description}" class="Button Button--secondary kl-preview-template" style="padding: 2px 6px;"><i class="icon-eye"></i><span class="screenreader-only">View ${val.name}</span></a>
<button class="Button Button--secondary kl-import-template" data-tooltip="top" title="Import ${val.name}" data-courseid="${val.id}" style="padding: 2px 6px;"><i class="icon-download"></i><span class="screenreader-only">Import ${val.name}</span></button>
<span class="kl-template-name-${val.id}">${val.name}</span>
</p>`;
$('#kl-template-list').append(itemInfo);
});
$('.kl-import-template').unbind('click').on('click', function () {
let confirmMessage = confirm("This will copy content into your course. Click OK to proceed.");
if (confirmMessage == true) {
let templateCourse = $(this).attr('data-courseid');
let templateName = $(`.kl-template-name-${templateCourse}`).text();
$('#kl-import-progress').addClass('alert alert-info').html(`Importing ${templateName}`);
$.post('/api/v1/courses/' + ENV.COURSE.id + '/content_migrations', {'migration_type': 'course_copy_importer', 'settings[source_course_id]': templateCourse}, function(data, textStatus, xhr) {
checkImportProgress(data.progress_url, ENV.COURSE.id);
});
}
});
}
});
JavaScript Bookmarklets (No access to add JavaScript to Canvas)
Next, let's take a look at some options for those who do not have access to add JavaScript to Canvas using the Theme Editor at an account level.
What is a Bookmarklet?
A bookmarklet is similar to create a bookmark in your browser to take you to a webpage. The difference is that instead of opening a webpage, a bookmarklet will run some JavaScript.
How do I create a Bookmarklet?
- Create a bookmark in your browser (the same way you would create any bookmark).
- Edit the bookmark.
- Give it a name to make it easy for you to find.
- In the URL field, you are going to add some JavaScript (keep reading to learn what this will look like).
Hard-Coded Template Course
If you would like to add a bookmarklet that will always import the same course, this is the JavaScript code we will use (replace the template_course_id on line 2 with your template course):
let template_course_id = '593';
if (ENV.COURSE !== undefined && ENV.COURSE.id !== null && window.location.pathname === '/courses/' + ENV.COURSE.id && $('.ic-EmptyStateList:visible').length > 0) {
let confirmMessage = confirm("This will copy content into your course. Click OK to proceed.");
if (confirmMessage == true) {
$('#modules_homepage_user_create').prepend('<div id="kl-import-progress" class="alert alert-info"></div>');
$.post('/api/v1/courses/' + ENV.COURSE.id + '/content_migrations', {'migration_type': 'course_copy_importer', 'settings[source_course_id]': template_course_id}, function(data, textStatus, xhr) {
$('#kl-import-progress').html('Request submitted. Course copy will take a few minutes. Reload this page periodically or <a href="https://community.canvaslms.com/courses/'+ENV.COURSE.id+'/content_migrations">view import progress in Canvas</a>');
});
}
} else {
alert('Run this from the course home page of an empty course');
}
In order for this to work as a bookmark, we have to convert it. I like to use MrColes Bookmarklet Creator to convert the code above into what we will add to a bookmark. After converting the code, it will look more like this:
javascript:(function()%7B%2F%2F%20The%20id%20of%20our%20template%20courselet%20template_course_id%20%3D%20'#####'%3B%2F%2F%20Only%20run%20on%20the%20home%20page%20of%20courses%20without%20any%20other%20content%20in%20place%20(shows%20import%20option)if%20(ENV.COURSE%20!%3D%3D%20undefined%20%26%26%20ENV.COURSE.id%20!%3D%3D%20null%20%26%26%20window.location.pathname%20%3D%3D%3D%20'%2Fcourses%2F'%20%2B%20ENV.COURSE.id%20%26%26%20%24('.ic-EmptyStateList%3Avisible').length%20%3E%200)%20%7B%2F%2F%20Give%20user%20a%20chance%20to%20cancellet%20confirmMessage%20%3D%20confirm(%22This%20will%20copy%20content%20into%20your%20course.%20Click%20OK%20to%20proceed.%22)%3Bif%20(confirmMessage%20%3D%3D%20true)%20%7B%2F%2F%20Add%20a%20div%20for%20feedback%24('%23modules_homepage_user_create').prepend('%3Cdiv%20id%3D%22kl-import-progress%22%20class%3D%22alert%20alert-info%22%3E%3C%2Fdiv%3E')%3B%2F%2F%20Send%20request%20to%20Canvas%24.post('%2Fapi%2Fv1%2Fcourses%2F'%20%2B%20ENV.COURSE.id%20%2B%20'%2Fcontent_migrations'%2C%20%7B'migration_type'%3A%20'course_copy_importer'%2C%20'settings%5Bsource_course_id%5D'%3A%20template_course_id%7D%2C%20function(data%2C%20textStatus%2C%20xhr)%20%7B%2F%2F%20Write%20a%20response%20with%20a%20link%20to%20the%20course%20migration%20page%24('%23kl-import-progress').html('Request%20submitted.%20Course%20copy%20will%20take%20a%20few%20minutes.%20Reload%20this%20page%20periodically%20or%20%3Ca%20href%3D%22%2Fcourses%2F'%2BENV.COURSE.id%2B'%2Fcontent_migrations%22%3Eview%20import%20progress%20in%20Canvas%3C%2Fa%3E')%3B%7D)%3B%7D%7D%20else%20%7B%2F%2F%20Will%20only%20work%20on%20the%20course%20front%20pagealert('Run%20this%20from%20the%20course%20home%20page%20of%20an%20empty%20course')%3B%7D%7D)()
It is a lot harder to read but it will do the job. To make things easier for you to update, find the ##### string and replace it with your template course id. Once you update the course id, this is the code that you will paste in as the URL in your bookmark.
Prompt for Course ID
If you want a little more flexibility for what course you want to copy, this option will ask the user to provide a course ID.

Here is the JavaScript we will use for this one (you don't have to update this one):
if (ENV.COURSE !== undefined && ENV.COURSE.id !== null && window.location.pathname === '/courses/' + ENV.COURSE.id && $('.ic-EmptyStateList:visible').length > 0) {
let course = prompt("Please enter the Canvas course ID from which to pull content");
if (course != null) {
$('#modules_homepage_user_create').prepend('<div id="kl-import-progress" class="alert alert-info"></div>');
course = course.trim();
$.post('/api/v1/courses/' + ENV.COURSE.id + '/content_migrations', {'migration_type': 'course_copy_importer', 'settings[source_course_id]': course}, function(data, textStatus, xhr) {
$('#kl-import-progress').html('Request submitted. Course copy will take a few minutes. Reload this page periodically or <a href="https://community.canvaslms.com/courses/'+ENV.COURSE.id+'/content_migrations">view import progress in Canvas</a>');
});
}
} else {
alert('Run this from the course home page of an empty course');
}
And here is that code converted to use for a bookmarklet:
javascript:(function()%7B%2F%2F%20Only%20run%20on%20the%20home%20page%20of%20courses%20without%20any%20other%20content%20in%20place%20(shows%20import%20option)if%20(ENV.COURSE%20!%3D%3D%20undefined%20%26%26%20ENV.COURSE.id%20!%3D%3D%20null%20%26%26%20window.location.pathname%20%3D%3D%3D%20'%2Fcourses%2F'%20%2B%20ENV.COURSE.id%20%26%26%20%24('.ic-EmptyStateList%3Avisible').length%20%3E%200)%20%7B%2F%2F%20Prompt%20user%20for%20Canvas%20course%20IDlet%20course%20%3D%20prompt(%22Please%20enter%20the%20Canvas%20course%20ID%20from%20which%20to%20pull%20content%22)%3B%2F%2F%20If%20they%20give%20a%20response%2C%20use%20itif%20(course%20!%3D%20null)%20%7B%2F%2F%20Placeholder%20for%20feedback%24('%23modules_homepage_user_create').prepend('%3Cdiv%20id%3D%22kl-import-progress%22%20class%3D%22alert%20alert-info%22%3E%3C%2Fdiv%3E')%3B%2F%2F%20Remove%20any%20spacescourse%20%3D%20course.trim()%3B%2F%2F%20Send%20request%20to%20Canvas%24.post('%2Fapi%2Fv1%2Fcourses%2F'%20%2B%20ENV.COURSE.id%20%2B%20'%2Fcontent_migrations'%2C%20%7B'migration_type'%3A%20'course_copy_importer'%2C%20'settings%5Bsource_course_id%5D'%3A%20course%7D%2C%20function(data%2C%20textStatus%2C%20xhr)%20%7B%2F%2F%20Write%20a%20response%20with%20a%20link%20to%20the%20course%20migration%20page%24('%23kl-import-progress').html('Request%20submitted.%20Course%20copy%20will%20take%20a%20few%20minutes.%20Reload%20this%20page%20periodically%20or%20%3Ca%20href%3D%22%2Fcourses%2F'%2BENV.COURSE.id%2B'%2Fcontent_migrations%22%3Eview%20import%20progress%20in%20Canvas%3C%2Fa%3E')%3B%7D)%3B%7D%7D%20else%20%7B%2F%2F%20Will%20only%20work%20on%20the%20course%20front%20pagealert('Run%20this%20from%20the%20course%20home%20page%20of%20an%20empty%20course')%3B%7D%7D)()
Wrap Up
Anyway, I hope that this will be useful to some of you during the present chaos and any future chaos. Feel free to modify, adapt, change, or share that code.