DatafetchPro
    google-forms-auto-filler-script
    Apr 26, 20265 min read64 views

    Google Forms Auto Filler Script with Tampermonkey

    I built a Google Forms auto filler script in Tampermonkey that fills text, radio, and checkbox questions, then submits the form for you.

    You open a form. You type your name, your email, your phone number. Then you realise you have filled out this same kind of form twenty times already.

    It is not hard work. It is just annoying.

    So instead of doing it by hand every time, I built a Google Forms auto filler script. It is a Tampermonkey userscript — Tampermonkey being a browser extension that runs your own JavaScript on the sites you choose — and it reads the questions on the page, then fills them using answers you set once in a list at the top of the file. You open the form, click a button, and watch it work. The full script is at the bottom of this post, free to copy.


    What the Google Forms auto filler script actually does

    Once the script is active on a form, a small floating button appears in the bottom-right corner of the page. Clicking it opens a panel listing every question the script knows how to answer, in order.

    The floating panel

    The panel gives you three controls:

    • Next Step fills one question and stops, so you can watch what it does before trusting it with the rest.
    • Complete This Page works through every remaining question on the current page.
    • Submit Form finds the submit button and clicks it, after a confirmation prompt.


    image.png


    The step list highlights whichever question is next, so you always know where it has got to. I use Next Step the first time I point the script at an unfamiliar form and Complete This Page every time after that.

    The three question types it handles

    Forms are rarely just text boxes, so the script recognises three shapes of answer.

    Short text and long answers

    For type: 'input', it finds the first input[type="text"], input[type="email"], input[type="tel"], or textarea inside the question and sets its value. Paragraph questions work the same way as one-line ones.

    Multiple choice

    For type: 'select', it looks through the radio options and clicks the first one whose visible text contains your value. You do not have to type the option out in full — a distinctive fragment is enough.

    Checkboxes

    For type: 'multiple', the value is an array. The script ticks every box whose label matches an item in that array, and it checks aria-checked first so it never accidentally unticks something already selected.


    Installing the Tampermonkey userscript

    There is no heavy software and no build step.

    1. Install the official Tampermonkey browser extension for your browser.
    2. Open the extension's dashboard and create a new script.
    3. Paste in the code from the end of this post and save.
    4. Open any form on docs.google.com/forms and look for the floating button.

    The @match and @include lines in the header restrict it to Google Forms addresses, so it stays dorment everywhere else. The @run-at document-start line makes it load before the page finishes rendering, which matters because the script then polls for the form to appear rather than assuming it is already there.


    Editing FORM_ACTIONS to autofill Google Forms with your answers

    This is the only part you have to change, and it is the part most people skip.

    Near the top of the file is an array called FORM_ACTIONS. Every entry describes one question and the answer you want given. The version I have published is filled with sample data from a conference registration form — John Doe, Green University, Austin TX — because that was the form I built it against. Replace those values with yours.

    The four fields in every entry

    • label — text the script searches for inside the question. "First Name" matches a question containing those words.
    • questionText — an alternative search string, used when the label is too generic to be unique. On a form with several questions containing "experience", questionText: "experience level" narrows it down.
    • type — one of input, select, or multiple, matching the three shapes described above.
    • value — what to type, or which option to click. For multiple, an array.

    Matching is case-insensitive and based on partial text, so short distinctive fragments beat long exact copies. If two questions both contain your search string, the script takes the first one on the page.


    Why a browser automation script needs delays and real input events

    The script does not rush. It pauses 400 milliseconds after each question and another 300 between steps.

    That is not decoration. Google Forms is built on a framework that listens for events rather than reading field values directly, so assigning field.value = 'John' on its own changes what you see and nothing else — the form still considers the field empty when you submit. The script fixes this by firing input and change events afterwards with bubbles: true, so the framework's listeners higher up the tree actually hear them.MDN's reference on dispatchEvent covers why bubbling matters here.

    The delays serve the same goal from a different angle: dynamic forms re-render sections after you answer, and a script that fires everything in the same millisecond will address elements that are about to be replaced. Filling at roughly human speed avoids that entirely. This is the same principle behind most of the browser automation work I take on, whether the target is a form or a full checkout flow.


    When the script stops working

    Two failures account for nearly everything.

    The "Not found" warning

    If a yellow toast reads ⚠️ Not found: followed by a question name, the script could not locate that question. Either your label no longer matches the wording on the form, or Google has changed its class names.

    The question lookup depends on the selector .Qr7Oae[role="listitem"]. That class is machine-generated and Google changes it without notice. When every question fails at once, that is the cause. Open your browser's element inspector, click a question, read the current class off the container, and swap it into the findQuestion function — the querySelectorAll documentation on MDN explains the selector syntax if you have not written one before.

    A Trusted Types error at startup

    The first line inside the wrapper calls window.trustedTypes.createPolicy('default', ...). On pages where a default policy already exists, that call throws and the script never boots — you get no button at all. Wrapping that line in a try / catch, or deleting it if your browser does not enforce the policy, clears it. The Trusted Types API on MDN explains what the policy is guarding against.


    What this script does not do

    Three question types, and no more. File uploads, date and time pickers, linear scales, and grid questions are not handled — the script will log a warning and move on. It also fills one page at a time by design; if your form has multiple sections, you click through to each one and run Complete This Page again.

    One more thing worth saying plainly: whether you are allowed to automate a particular form is a question about that form owner's terms of service, not a technical question. I can tell you how the automation works. Deciding whether to point it at a given form is yours.


    Where this is genuinely useful

    The pattern pays off wherever the same form shape comes round repeatedly:

    • Testing your own forms during development, where you need twenty submissions of plausible data
    • Internal company forms that ask the same nine fields every week
    • Repeated registrations across events or portals
    • Feeding a form as one step inside a longer automated workflow

    Set your answers once, reuse them indefinitely.


    Getting the same thing for a form that is not Google's

    This script is deliberately narrow. It knows Google Forms' structure and nothing else, and the moment you point it at a Typeform, a Salesforce page, or an internal portal behind a login, none of the selectors mean anything.

    Building the equivalent for a different site is the same job with different plumbing: find the containers, work out what events the page listens for, handle whatever it does when a field fails validation. That is what I do — as a Tampermonkey userscript, a Chrome extension, a Playwright bot, or a scheduled job that writes straight to a sheet or database. If you have a repetitive form or a data extraction problem of your own, tell me what the form looks like and I will scope it. There is more about how I work and what I build, and other write-ups on scraping and automation if you want to see the range first.


    The full script


    JavaScript
    // ==UserScript==
    // @name Google Forms Auto-Filler Pro
    // @namespace http://tampermonkey.net/
    // @version 4.1
    // @description Complete Current Page (without auto-next) + Notifications
    // @author You
    // @match https://docs.google.com/forms/*
    // @include https://docs.google.com/forms*
    // @run-at document-start
    // @grant none
    // ==/UserScript==

    (function () {
    'use strict';
    window.trustedTypes.createPolicy('default', {createHTML: (string, sink) => string})
    console.log('%c[AutoFill v4.1] Complete Current Page Ready', 'color:#1a73e8;font-weight:bold');
    /*
    https://docs.google.com/forms/d/1IvjF5l0QtPA3t6M9W-OAn0H2JMjgLnI2vaG_g2-fQoA/viewform?edit_requested=true
    sample is picked from this site
    */
    const showNotifications = true; // ← Change to false to disable popups

    const FORM_ACTIONS = [
    { label: "Which workshop", questionText: "workshop", type: 'select', value: 'June 4&5 - Sustainability Education Forum' },
    { label: "Where did you hear", questionText: "hear about", type: 'select', value: 'Colleague/friend' },
    { label: "First time attending", type: 'select', value: 'Yes' },
    { label: "First Name", type: 'input', value: 'John' },
    { label: "Last Name", type: 'input', value: 'Doe' },
    { label: "Email", type: 'input', value: '[email protected]' },
    { label: "Phone", type: 'input', value: '+1-555-0100' },
    { label: "Position Title", type: 'input', value: 'Sustainability Coordinator' },
    { label: "Department", type: 'input', value: 'Facilities' },
    { label: "Organization", type: 'input', value: 'Green University' },
    { label: "City & State", type: 'input', value: 'Austin, TX' },
    { label: "Zip Code", type: 'input', value: '78701' },
    { label: "Country", type: 'input', value: 'United States' },
    { label: "Gender identity", type: 'select', value: 'Prefer not to say' },
    { label: "Race/ethnicity", questionText: "Race/ethnicity", type: 'multiple', value: ['White'] },
    { label: "Experience level", questionText: "experience level", type: 'select', value: 'an intermediate-level professional' },
    { label: "Are you a student", type: 'select', value: 'No' },
    { label: "AASHE member", type: 'select', value: 'Yes' },
    { label: "Describe your work", questionText: "Describe your work", type: 'input', value: 'I coordinate campus sustainability initiatives including energy audits, waste reduction programs, and student engagement campaigns.' },
    { label: "How will you use the knowledge", questionText: "How will you use", type: 'input', value: 'I will apply the skills to improve our institution\'s sustainability reporting and curriculum integration.' },
    { label: "Financial support needed", questionText: "Financial support", type: 'input', value: 'I need full registration support. I have applied for departmental funding but was denied due to budget constraints.' },
    { label: "Previous AASHE support", type: 'input', value: 'None.' },
    { label: "Attendance commitment", type: 'select', value: 'Yes, I am committed to attending.' },
    ];

    const sleep = (ms) => new Promise(r => setTimeout(r, ms));

    function log(msg, type = 'info') {
    const colors = { info: '#1a73e8', success: '#34a853', warn: '#fbbc05', error: '#ea4335' };
    console.log(`%c[AutoFill] ${msg}`, `color:${colors[type]}`);
    if (showNotifications) showToast(msg, type);
    }

    function showToast(message, type = 'info') {
    let toast = document.getElementById('__gf_toast');
    if (!toast) {
    toast = document.createElement('div');
    toast.id = '__gf_toast';
    toast.style.cssText = `position:fixed; bottom:100px; right:30px; z-index:2147483647; padding:12px 18px; border-radius:8px; color:white; font-size:14px; box-shadow:0 6px 20px rgba(0,0,0,0.4); max-width:320px;`;
    document.body.appendChild(toast);
    }
    const colors = { info: '#1a73e8', success: '#34a853', warn: '#fbbc05', error: '#ea4335' };
    toast.style.backgroundColor = colors[type];
    toast.textContent = message;
    toast.style.display = 'block';
    setTimeout(() => toast.style.display = 'none', 2800);
    }

    function findQuestion(action) {
    const questions = document.querySelectorAll('.Qr7Oae[role="listitem"], .freebirdFormviewerViewItemsItemItem');
    for (let q of questions) {
    const text = q.textContent.toLowerCase();
    if (action.label && text.includes(action.label.toLowerCase())) return q;
    if (action.questionText && text.includes(action.questionText.toLowerCase())) return q;
    }
    return null;
    }

    async function executeAction(action) {
    const q = findQuestion(action);
    if (!q) {
    log(`⚠️ Not found: ${action.label || action.questionText}`, 'warn');
    return false;
    }

    if (action.type === 'input') {
    const field = q.querySelector('input[type="text"], input[type="email"], input[type="tel"], textarea');
    if (field) {
    field.focus();
    field.value = action.value;
    field.dispatchEvent(new Event('input', { bubbles: true }));
    field.dispatchEvent(new Event('change', { bubbles: true }));
    }
    } else if (action.type === 'select') {
    const opts = q.querySelectorAll('[role="radio"], label');
    for (let opt of opts) {
    if (opt.textContent.includes(action.value)) { opt.click(); break; }
    }
    } else if (action.type === 'multiple') {
    const cbs = q.querySelectorAll('[role="checkbox"]');
    for (let cb of cbs) {
    const txt = cb.closest('label')?.textContent || '';
    if (action.value.some(v => txt.includes(v))) {
    if (cb.getAttribute('aria-checked') !== 'true') cb.click();
    }
    }
    }
    await sleep(400);
    return true;
    }

    // ==================== UI ====================
    function createPanel() {
    if (document.getElementById('__gf_panel')) return;

    const panel = document.createElement('div');
    panel.id = '__gf_panel';
    panel.innerHTML = `
    <style>
    #__gf_panel { position:fixed; bottom:25px; right:25px; z-index:2147483647; font-family:system-ui; width:430px; }
    #__gf_trigger { width:68px; height:68px; border-radius:50%; background:linear-gradient(135deg,#1a73e8,#0d47a1); color:white; font-size:32px; border:none; box-shadow:0 8px 25px rgba(26,115,232,0.6); cursor:pointer; }
    #__gf_card { display:none; background:#0f1923; color:#fff; border-radius:18px; box-shadow:0 20px 70px rgba(0,0,0,0.8); overflow:hidden; }
    #__gf_card.open { display:block; }
    .gf-step { padding:12px 16px; border-bottom:1px solid #334; cursor:pointer; }
    .gf-step:hover { background:#1a2a44; }
    .gf-step.active { background:#1a73e8; font-weight:600; }
    .control-btn { padding:13px 18px; margin:5px; border-radius:8px; font-size:15px; cursor:pointer; border:none; flex:1; }
    </style>

    <button id="__gf_trigger">🤖</button>
    <div id="__gf_card">
    <div style="background:linear-gradient(135deg,#1a73e8,#0d47a1); padding:16px; font-weight:bold;">AutoFiller Pro v4.1</div>
    <div id="__gf_steps" style="max-height:380px; overflow-y:auto;"></div>

    <div style="padding:12px; display:flex; flex-wrap:wrap; gap:8px; background:#0a121b;">
    <button class="control-btn" id="__gf_next" style="background:#1a73e8;">Next Step →</button>
    <button class="control-btn" id="__gf_complete" style="background:#34a853;">Complete This Page</button>
    <button class="control-btn" id="__gf_submit" style="background:#ea4335;">Submit Form</button>
    </div>
    </div>
    `;

    document.body.appendChild(panel);

    const trigger = document.getElementById('__gf_trigger');
    const card = document.getElementById('__gf_card');
    const nextBtn = document.getElementById('__gf_next');
    const completeBtn = document.getElementById('__gf_complete');
    const submitBtn = document.getElementById('__gf_submit');

    trigger.onclick = () => card.classList.toggle('open');

    let currentIndex = 0;

    function renderSteps() {
    const container = document.getElementById('__gf_steps');
    container.innerHTML = '';
    FORM_ACTIONS.forEach((act, i) => {
    const div = document.createElement('div');
    div.className = `gf-step ${i === currentIndex ? 'active' : ''}`;
    div.textContent = `#${i+1} ${act.label || act.questionText}`;
    container.appendChild(div);
    });
    }

    // Next Step (single)
    nextBtn.onclick = async () => {
    if (currentIndex >= FORM_ACTIONS.length) return;
    await executeAction(FORM_ACTIONS[currentIndex]);
    currentIndex++;
    renderSteps();
    };

    // Complete This Page — fills remaining questions on CURRENT page only
    completeBtn.onclick = async () => {
    log('Filling remaining questions on this page...', 'info');

    let filledCount = 0;
    for (let i = currentIndex; i < FORM_ACTIONS.length; i++) {
    const success = await executeAction(FORM_ACTIONS[i]);
    if (success) filledCount++;
    currentIndex = i + 1;
    renderSteps();

    // Optional: small delay so you can see progress
    await sleep(300);
    }

    log(`✅ Completed ${filledCount} questions on this page`, 'success');
    };

    submitBtn.onclick = () => {
    if (confirm('Submit the form now?')) {
    const btn = Array.from(document.querySelectorAll('[role="button"]')).find(b =>
    b.textContent.trim().toLowerCase().includes('submit')
    );
    if (btn) btn.click();
    }
    };

    renderSteps();
    log('✅ Ready! Use "Complete This Page" to finish current page.', 'success');
    }

    function boot() {
    if (document.getElementById('__gf_panel')) return;
    if (document.querySelector('.Qr7Oae')) createPanel();
    else setTimeout(boot, 700);
    }

    boot();
    setTimeout(boot, 2500);
    })();


    0

    Rather not build it yourself?

    I build this kind of thing for a living.

    Send me the site and what you need out of it — you'll get an approach, a timeline, and a fixed quote back. Scoping is free.