Google Forms has a built-in toggle to shuffle question order, but it acts as a blunt instrument.
If you flip that native switch, your carefully placed name and email fields will scatter randomly across the page.
To lock critical demographic questions at the top while randomizing the actual assessment, you have to bypass the standard settings entirely.
Writing a custom Google Apps Script allows you to reorder specific items automatically while leaving your static fields exactly where they belong.
What are the prerequisites for scripting a custom Google Form shuffle?
Before you write any code, you need to understand how Google Forms interacts with Apps Script. You cannot run a script that changes the form dynamically in the respondent's browser while they are looking at it. Instead, you write a script that reshuffles the base form template on the server.
To set this up successfully, you need a few structural and permission-based elements in place.
- Container-bound script access: You must be the owner or an editor of the specific Google Form. The script will be bound directly to this form, not created as a standalone project in your Google Drive.
- A clear form structure: You need a strict boundary between the questions you want to lock and the questions you want to randomize. The easiest approach groups all locked questions at the top of the form (e.g., questions 1 and 2), followed by the randomized block.
- The right mental model for triggers: Because a form is served as static HTML to the user, you cannot shuffle questions on open for a respondent. You will use an
onSubmittrigger. When Student A submits their answers, the script silently shuffles the form in the background so that Student B sees a new order. - Workspace permissions: If you are operating within a strict corporate or school domain, your Google Workspace administrator might restrict Apps Script execution. You need permission to authorize scripts that interact with the
FormAppservice. - No mixed media dependencies: If your questions rely on a standalone image or video block placed directly above them, shuffling will break that relationship. Shuffled items move independently unless they are merged into a single
QuestionItem.
If your form is highly complex with branching logic, randomizing the order can break the flow. Ensure your randomized section is a flat list of independent questions.
How to open the Apps Script editor and configure your form project
To control a Google Form programmatically, you use the built-in Apps Script editor. This editor gives you direct access to the FormApp class, which handles the structure and settings of your file.
Here is how to access the environment and set up the automated trigger that will execute your shuffle logic.
- Open your Google Form in edit mode.
- Click the three-dot menu icon (
More) in the top right corner, next to theSendbutton. - Select
Script editorfrom the dropdown menu. This opens a new browser tab with an empty project. - Click on
Untitled projectat the top left and rename it to something descriptive, like Form Question Auto-Shuffle. - In the editor window, you will see a default function called
myFunction(). You will delete this and paste the custom code provided in the next section. - Once your code is saved, click the clock icon (
Triggers) in the left-hand navigation menu. - Click the blue
+ Add Triggerbutton in the bottom right corner. - Set the function to run as your new shuffle function, choose
From formas the event source, and selectOn form submitas the event type. - Click
Saveand follow the Google authorization prompts to grant the script permission to modify your form.
Expert tip: The first time you authorize the script, Google will show a stark "Google hasn't verified this app" warning. Because you wrote the code yourself, click
Advancedand thenGo to Form Question Auto-Shuffle (unsafe)to proceed.
Setting the trigger to On form submit is the crucial step. It ensures that the heavy lifting happens invisibly after a respondent finishes, keeping the form fresh for the next person without slowing down the loading time.
How to write the Apps Script code to randomize form questions
The script below isolates the questions you want to shuffle, randomizes their order, and moves them to new positions within the form.
Delete any default code in your Apps Script editor and paste the following block.
/**
* Shuffles the questions in a Google Form, keeping a set number
* of introductory questions locked in place.
*/
function shuffleAssessmentQuestions() {
const form = FormApp.getActiveForm();
const allItems = form.getItems();
// Define how many items at the top of the form should NOT move.
// For example, if Item 0 is Name and Item 1 is Email, set this to 2.
const lockedCount = 2;
// If the form has fewer items than the locked count, exit early.
if (allItems.length <= lockedCount) {
return;
}
// Extract only the items that are eligible for shuffling.
let itemsToShuffle = allItems.slice(lockedCount);
// Randomize the extracted array using the Fisher-Yates algorithm.
for (let i = itemsToShuffle.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[itemsToShuffle[i], itemsToShuffle[j]] = [itemsToShuffle[j], itemsToShuffle[i]];
}
// Move the shuffled items back into the form structure.
// We offset the new index by the lockedCount to preserve the top questions.
itemsToShuffle.forEach((item, index) => {
const targetIndex = lockedCount + index;
// Only call the moveItem API if the position actually changed.
if (item.getIndex() !== targetIndex) {
form.moveItem(item, targetIndex);
}
});
}
This code relies on a few specific programming concepts to function efficiently and safely within Google's infrastructure.
Connecting to the form: The FormApp.getActiveForm() method binds the script to the current file. form.getItems() pulls every element on the page - including questions, images, and section breaks - into a single JavaScript array.
Protecting locked items: The lockedCount variable acts as a boundary. By setting it to 2, we tell the script to ignore the elements at index 0 and index 1. The slice() method then creates a new, temporary array containing only the questions from index 2 onward.
The randomization logic: You must shuffle the array reliably before moving the form items.
- ❌ Weak: Using
itemsToShuffle.sort(() => Math.random() - 0.5)to mix the array. - ✅ Strong: Using the Fisher-Yates algorithm (
forloop with index swapping) to ensure a mathematically even distribution. Why it works: The built-in JavaScript sort function is not designed for randomization and creates predictable, biased patterns depending on the browser engine.
Moving the items: The forEach loop iterates through the newly shuffled array. It calculates the targetIndex by adding the lockedCount back in, ensuring the shuffled items start right after your locked demographic fields. The script then uses form.moveItem(item, targetIndex) to physically relocate the element.
Checking if (item.getIndex() !== targetIndex) is a vital optimization. Apps Script API calls to Google servers are slow. By skipping the move command for items that coincidentally landed in their original spot, the script executes significantly faster and avoids timing out.
How to handle advanced section-based shuffling patterns
A flat list of questions is easy to shuffle, but forms used in education often rely on page breaks and categorized sections.
If you run the basic script on a multi-page form, it will drag questions across page breaks, ruining your carefully structured sections. To handle sections, your script must identify PageBreakItems and restrict the shuffling logic to operate exclusively within those boundaries.
Here is how different structural needs map to specific programmatic logic.
| Target User Flow | Routing Logic | Technical Implementation |
|---|---|---|
| Fixed header, random body | Lock page 1, shuffle everything on page 2. | Store the index of the first PageBreakItem. Slice the array from that index + 1 to the end, shuffle, and reinsert. |
| Randomized categories | Keep questions grouped by topic, but shuffle the order of the topics themselves. | Identify all PageBreakItems. Move entire blocks of questions simultaneously by shifting the section break and its child elements as a single array chunk. |
| Question pooling | Show exactly 10 random questions from a hidden bank of 50. | Store the bank in a separate Google Sheet. On submit, delete all current form questions, randomly pull 10 from the Sheet, and rebuild the form items from scratch. |
| Section-internal shuffle | Keep the sections in a fixed order, but shuffle the questions inside each section independently. | Iterate through form.getItems(). Push items into temporary arrays based on their PageBreakItem parent. Shuffle each subarray, then reconstruct the form. |
When building transforming a quiz to a Google Form, section-internal shuffling is usually the safest advanced pattern. It allows you to maintain distinct topics - like a Math section and a History section - while still randomizing the specific questions inside them to deter cheating.
To achieve section-internal shuffling, you stop treating form.getItems() as one long list. Instead, you loop through the items, creating a new array every time you encounter a form.getItemType() === FormApp.ItemType.PAGE_BREAK. You then run the Fisher-Yates shuffle on those isolated arrays before moving the items back.
How to troubleshoot common Apps Script execution errors
Even a perfectly written script can fail when it interacts with Google's cloud environment. Triggers might fire incorrectly, permissions can expire, and structural changes to the form can break the logic.
When your script fails, Google will send an email to the form owner with the subject "Summary of failures for Google Apps Script." Inside the Apps Script editor, you can also view the exact logs by clicking Executions on the left-hand menu.
Use this table to map common error messages to their root causes and fixes.
| Error Message | Root Cause | Code Fix |
|---|---|---|
Exception: You do not have permission to call FormApp.getActiveForm() |
The script lost authorization, or you are running a standalone script not bound to a form container. | Open the editor, run the function manually once by clicking Run, and accept the OAuth permission prompts. |
TypeError: Cannot read property 'getItems' of null |
getActiveForm() returned null because the script is detached or running in an unsupported context. |
Ensure the script is bound to the form (created via the form's three-dot menu). If using a standalone script, use FormApp.openById('YOUR_FORM_ID') instead. |
Exception: Item cannot be moved to this index. |
The script attempted to move an item to an index greater than the total number of items, or a negative index. | Check your lockedCount variable. Ensure lockedCount + index does not exceed allItems.length - 1. |
Service invoked too many times for one day: form. |
You hit Google's daily quota for API calls, often caused by a form receiving thousands of submissions rapidly. | Optimize the script to skip unnecessary moves. If volume is too high, change the trigger from On form submit to a Time-driven trigger that shuffles once per hour. |
Exceeded maximum execution time |
The form has hundreds of questions, and moving them individually takes longer than the 6-minute Apps Script limit. | Reduce form length, or implement logic to batch updates. Unfortunately, moveItem is inherently slow. |
If you encounter persistent issues, temporarily add console.log(item.getTitle()) inside your loop. This allows you to check the execution logs and see exactly which question the script was trying to move when it crashed.
What are the security and platform limitations of programmatic shuffling?
While Apps Script provides more control than the native UI, it relies on a fundamentally different mechanism. Native settings change how the form is displayed to the client. Apps Script permanently alters the underlying file architecture.
This architectural difference creates specific limitations regarding concurrency and execution speed.
| Feature | Native Google Forms Shuffle | Apps Script Custom Shuffle |
|---|---|---|
| Execution timing | Renders instantly in the user's browser. | Runs on Google's servers after a previous submission. |
| Concurrency handling | 50 users clicking the link simultaneously get 50 unique orders. | 50 users clicking simultaneously will see the exact same order, because the trigger hasn't fired yet. |
| Locking specific fields | ❌ Not supported. All items are mixed. | ✅ Supported. You define exact array boundaries to protect fields. |
| Data integrity | Google Sheets backend maintains column order automatically. | Google Sheets backend maintains column order automatically. |
| Execution reliability | 100% uptime, handled by Google's core platform. | Subject to Apps Script daily quotas and 6-minute timeout limits. |
The concurrency limitation is the most critical factor for classroom settings. If a teacher tells thirty students to open the form at exactly 9:00 AM, all thirty students will load the current, static version of the form. The onSubmit trigger only helps if students are taking the assessment at staggered times.
If true real-time, per-respondent randomization is strictly required for a simultaneous exam, Google Forms is the wrong platform. You would need a dedicated assessment tool or an external web app that generates a unique payload for every session. For asynchronous testing, homework, or rolling surveys, the Apps Script approach is highly effective.
FAQ
Does Apps Script shuffle the questions in real-time for each individual respondent?
No. Apps Script cannot alter a Google Form dynamically while a user is opening it. The script modifies the underlying form file after a submission occurs, meaning the next person to open the link sees the newly shuffled layout.
Can I lock specific questions like name or email in place while shuffling the rest?
Yes. By using array manipulation methods like slice() in your script, you can ignore the first few items in the form. The script will only randomize the remaining items, keeping your demographic fields locked securely at the top.
Will shuffling questions programmatically break my linked Google Sheets response destination?
No. Google Sheets maps form responses using invisible, unique item IDs, not the visual order of the questions on the page. You can shuffle the questions as many times as you want, and the data will still flow into the correct, fixed columns in your spreadsheet.
If managing container-bound scripts, API quotas, and concurrency limits feels heavier than the problem warrants, you might prefer generating fresh forms on demand. Tools like Doc2Form can instantly convert a static document or brief into a structured Google Form in your Drive, allowing you to easily spin up distinct, randomized variations of an assessment without maintaining background code.