Found this content helpful? Log in or sign up to leave a like!

Javascript Themes

Jump to solution
Julia_C
Community Explorer

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)
3 Solutions

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

Julia_C
Community Explorer
Author

Thanks Chris! That seems to work!  What a relief.

View solution in original post

SuggMullB6774
Community Explorer

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.

View solution in original post