Javascript Themes

Jump to solution
Julia_C
Community Member

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.

My questions:

  • 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!

Labels (1)
1 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

View solution in original post