// Forms · Apps Script

Grade a quiz with a script in Google Forms.

How to auto-grade a Google Forms quiz with Apps Script using setScore(), setFeedback(), and the required submitGrades() call that actually persists the results.

I want to write an Apps Script that reads quiz responses and sets scores or feedback automatically, but nothing seems to save.

The script

copy · paste · trigger
gradeQuiz.gs
Apps Script
// Grade all responses for a Quiz-mode Google Form
// Run manually or wire to an onFormSubmit trigger
function gradeAllResponses() {
  var form = FormApp.getActiveForm();
  var responses = form.getResponses();

  for (var i = 0; i < responses.length; i++) {
    var response = responses[i];
    var itemResponses = response.getGradableItemResponses();

    for (var j = 0; j < itemResponses.length; j++) {
      var item = itemResponses[j];
      var correct = item.getItem().asMultipleChoiceItem().getChoices();
      var points = item.getResponse() === correct[0].getValue() ? 10 : 0;
      item.setScore(points);
      item.setFeedback(
        FormApp.createFeedback().setText(
          points > 0 ? 'Correct.' : 'Review section 3.'
        ).build()
      );
    }

    form.submitGrades([response]);
  }
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

Quiz mode is a prerequisite, not an assumption

Before any grading script can write scores, the form itself must be in Quiz mode. Open the form in the editor, go to Settings, and confirm the 'Make this a quiz' toggle is on. Without it, getGradableItemResponses() returns an empty array on every response and your script exits silently with nothing to show for it.

With Quiz mode on, each item that carries a point value becomes a 'gradable' item. The script calls getGradableItemResponses() rather than getItemResponses() because only the gradable variant exposes setScore() and setFeedback(). The two methods look similar in the docs; the wrong one compiles fine and does nothing.

Why setScore() alone is not enough

This is the part that bites almost everyone the first time. Calling item.setScore(10) and item.setFeedback(...) mutates the in-memory ItemResponse object. It does not write anything to Forms' backend. You can loop through every response, set every score, log 'done', and the respondent's score panel will be completely blank.

The write happens when you call form.submitGrades([response]) with the modified FormResponse passed in as an array. That single call is what flushes all the setScore() and setFeedback() changes you accumulated on that response. I kept this in a utils file comment for months after I first hit it, because the name submitGrades implies a submit action rather than a save action and nothing in the error output tells you the data was silently dropped.

One practical consequence: call submitGrades once per response, after you have finished mutating all its item responses. Calling it inside the inner loop (once per item) works but burns quota faster and is unnecessary.

Wiring to a trigger for real-time grading

Running gradeAllResponses() manually is fine for a one-off batch, but for a live quiz you want the function to fire each time a new response arrives. In the Apps Script editor, go to Triggers, add a new trigger, set the event source to 'From form' and the event type to 'On form submit'.

The trigger passes an event object with a response property, so you can skip the getResponses() loop entirely and grade only the single new submission. Replace the function signature with gradeAllResponses(e), grab the response via e.response, and call submitGrades([e.response]) after scoring. This keeps execution time well under the 30-second Apps Script trigger limit even on long quizzes.

One gotcha: the trigger runs under the account that installed it. If that account loses edit access to the form, grading stops silently. Worth noting in your team's runbook.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
My script runs without errors but scores are still blank in the response view. What am I missing?
You skipped form.submitGrades([response]). Calling setScore() and setFeedback() only modifies the in-memory object; submitGrades() is the call that actually persists the changes to Forms' backend. Add it once per response, after all item mutations.
Can I use getItemResponses() instead of getGradableItemResponses()?
No. getItemResponses() returns ItemResponse objects, which do not have setScore() or setFeedback() methods. You need getGradableItemResponses() to get GradableItemResponse objects. Both methods exist on FormResponse, which is why the confusion is easy.
Does submitGrades() send an email to the respondent?
Only if the form is configured to release scores via email and the respondent chose to receive them. submitGrades() persists the grade data; the email release is a separate form-level setting under Quiz settings > Release grade.
Can I grade open-ended or paragraph questions with a script?
Yes. getParagraphTextItem() questions are gradable if the form is in Quiz mode; you call the same setScore() and setFeedback() methods on their responses. The script has to implement its own comparison logic since there is no built-in correct-answer check for free-text items.