The Instructure Community will enter a read-only state on November 22, 2025 as we prepare to migrate to our new Community platform in early December.
Read our blog post for more info about this change.
Found this content helpful? Log in or sign up to leave a like!
Hi everyone,
I'm working on a tool that adds a custom button to the Canvas interface using JavaScript injected via a Canvas Theme. The goal is to allow teachers to calculate and update rubric scores for an assignment (e.g., entering an average mastery score into a rubric criterion using the Canvas API).
So far, I'm able to:
Detect the correct page (e.g., Gradebook)
Inject the custom UI
Calculate mastery averages using GET requests (e.g., /outcome_rollups)
However, when I try to submit a PUT request to update grades using the rubric_assessment parameter (e.g., to /api/v1/courses/:course_id/assignments/:assignment_id/submissions/:user_id), the request fails with a 422 error. I assume this is because the Theme script is not authenticated with a valid access token.
Is it possible for a Theme-injected script to gain write access to Canvas APIs (e.g., grading endpoints)?
If not, is the recommended approach to offload write access to a backend service or Chrome Extension that has access to a secure token?
I want to make sure I'm not overlooking an officially supported method before I shift my architecture to use a server or browser extension.
Thanks in advance for your insights!
Solved! Go to Solution.
Hi @Julia_C,
Thanks for the additional details! It's been so long since I did anything with a put/post as part of theme javascript that I forgot authorization is needed there. Instead of using the api token method, you can get the csrfToken cookie and use that value for auth in a slightly different way. Try the following to see if it works (I just put this together from your code and some snippets out of one of my old projects):
function getCookie(name) {
const cookies = document.cookie.split(';').map(cookie => cookie.trim());
let cookieValue = null;
let i = 0;
while (i < cookies.length && cookieValue === null) {
const cookie = cookies[i].split('=', 2);
if (cookie[0] === name) {
cookieValue = decodeURIComponent(cookie[1]);
}
i++;
}
return cookieValue;
}
async function submitRubricScore(courseId, assignmentId, userId, criterionId, score) {
const csrfToken = getCookie('_csrf_token');
const payload = {
authenticity_token: csrfToken,
rubric_assessment: {
[criterionId.toString()]: {
points: score,
comments: "Auto-calculated average mastery score"
}
}
};
console.log("Submitting rubric score for user", userId, payload);
const response = await fetch(`/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`, {
method: "PUT",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorText = await response.text();
console.error("Submission failed for user", userId, "ā", errorText);
throw new Error(`Failed to update user ${userId}: ${errorText}`);
}
console.log("Score submitted successfully for user", userId);
}
Let us know if this runs successfully or not!
-Chris
Thanks Chris! That seems to work! What a relief.
Hello Julia, I'm Yolanda. I think that's awesome because every day, we all learn something new, and that's the best part about learning and creating new things today for ourselves and others. Congratulations.
Hi @Julia_C,
Welcome to the community, hopefully some of us here can provide you with some guidance on this!
Before diving in to your exact issue, I did want to somewhat ensure that this is a project you're doing for your own school/institution, and not something you intend to really sell or market to others. The reason I bring this up is that most admins are very reluctant to modify themes for 3rd party tools because of how fragile things can be, and how unfeasible it can be to always check that code will still work properly with every update Instructure releases for Canvas. If you're taking sole ownership of this for your own school, it's perhaps not as much of a concern.
With that out of the way... Your theme JavaScript is basically going to run as the user that is logged in to Canvas, unless you specify other credentials (which would generally not be recommended without taking measures to hide the credentials/token. If the user has the ability to modify whatever you're doing with the API, the call should generally succeed. Depending how your script works, you may want to make an attempt at checking the user role and only running your code for teachers.
If you're getting a 422 error, my experience would indicate that you have more of a problem with the data you're sending than a permissions/access issue. Have you validated all of the data you're trying to send it correct, formatted correctly, and not missing any of the required fields? You may want to try using curl, postman, or even the Canvas Live API to replicate the API call your script is attempting just to see if you get the same error. If you're able to post some of your code here, it may help some of us to review exactly what you're doing and offer suggestions for modifications if there are any obvious issues.
Look forward to hearing back from you!
-Chris
Thanks for the quick reply, Chris! I really appreciate the guidance!
This project is not intended for resale or distribution. It was developed internally at the request of one of our school directors.
I probably wasnāt clear in my original post: Iām specifically trying to avoid implementing a full OAuth flow. The script is being loaded as a Theme script, and it runs in the context of the logged-in user. So far, Iāve been able to successfully retrieve data using fetch()calls without an authorization header, for example, I can read outcome rollups, user info, rubric definitions, etc.
The blocker Iām running into is when trying to perform PUT requests, such as updating a rubric score using the rubric_assessment parameter. These requests only succeed if I manually include an access token in the Authorization header. If I remove the authorization header, the request fails, even though the user running the script is a teacher with appropriate grading permissions in the course.
I had hoped that, since data retrieval worked without tokens, writing data might as well, but it appears PUT operations require explicit authentication even within a Theme script.
Let me know if this aligns with your understanding, or if Iām overlooking an alternate approach.
```
async function submitRubricScore(courseId, assignmentId, userId, criterionId, score) {
const payload = {
rubric_assessment: {
[criterionId.toString()]: {
points: score,
comments: "Auto-calculated average mastery score"
}
}
};
console.log("Submitting rubric score for user", userId, payload);
const response = await fetch(`/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer myToken`// works when this line is included, but fails if it is removed
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorText = await response.text();
console.error("Submission failed for user", userId, "ā", errorText);
throw new Error(`Failed to update user ${userId}: ${errorText}`);
}
console.log("Score submitted successfully for user", userId);
}
Hi @Julia_C,
Thanks for the additional details! It's been so long since I did anything with a put/post as part of theme javascript that I forgot authorization is needed there. Instead of using the api token method, you can get the csrfToken cookie and use that value for auth in a slightly different way. Try the following to see if it works (I just put this together from your code and some snippets out of one of my old projects):
function getCookie(name) {
const cookies = document.cookie.split(';').map(cookie => cookie.trim());
let cookieValue = null;
let i = 0;
while (i < cookies.length && cookieValue === null) {
const cookie = cookies[i].split('=', 2);
if (cookie[0] === name) {
cookieValue = decodeURIComponent(cookie[1]);
}
i++;
}
return cookieValue;
}
async function submitRubricScore(courseId, assignmentId, userId, criterionId, score) {
const csrfToken = getCookie('_csrf_token');
const payload = {
authenticity_token: csrfToken,
rubric_assessment: {
[criterionId.toString()]: {
points: score,
comments: "Auto-calculated average mastery score"
}
}
};
console.log("Submitting rubric score for user", userId, payload);
const response = await fetch(`/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`, {
method: "PUT",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorText = await response.text();
console.error("Submission failed for user", userId, "ā", errorText);
throw new Error(`Failed to update user ${userId}: ${errorText}`);
}
console.log("Score submitted successfully for user", userId);
}
Let us know if this runs successfully or not!
-Chris
Thank you again, Julia, for all the information.
Just wanted to share appreciation for this bit of script, the 'getCookie' routine is super obvious but a really helpful method for fetching the token for doing other stuff in the LMS... thank you!
Thanks Chris! That seems to work! What a relief.
Hello Julia, I'm Yolanda. I think that's awesome because every day, we all learn something new, and that's the best part about learning and creating new things today for ourselves and others. Congratulations.
Community helpTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign inTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign in