AI Form Builder for Google Forms

https://github.com/lsvekis/AI-Form-Builder-for-Google-Forms

πŸš€ Apps Script + Gemini Mastery β€” Issue #7

AI Form Builder for Google Forms

Generate complete Google Forms β€” questions, sections, validation, and descriptions β€” automatically using Gemini.


⭐ What You Will Build in This Issue

In this lesson, you’ll create an AI-powered form builder that:

πŸ“‹ Generates an entire Google Form from a topic
❓ Defines question types (MCQ, short answer, checkbox, scale, etc.)
πŸ” Adds form descriptions, hints, and validation
πŸ“‘ Creates multi-section forms when needed
🧠 Uses Gemini to propose high-quality questions
⚑ Builds the real Google Form using Apps Script

This tool lives in a Google Doc or Sheet with a simple menu + sidebar, and produces a full, production-ready Google Form.


🧠 Learning Objectives

By the end of this issue, you will understand how to:

βœ” Use the Forms service in Apps Script
βœ” Prompt Gemini to create structured JSON describing form contents
βœ” Parse that JSON safely
βœ” Build Forms dynamically (questions, choices, rules)
βœ” Add branching options and section logic
βœ” Build a UI for generating forms instantly

This is the foundation of question generators, onboarding workflows, surveys, quizzes, feedback forms, and more.


🧩 EXERCISE β€” Build the AI Form Builder

You will create:

  1. A menu entry in Docs or Sheets
  2. A sidebar UI to gather form topic + details
  3. A Gemini-powered JSON form generator
  4. A dynamic Form builder that constructs a real Google Form

1️⃣ Add the Menu + Sidebar

Code.gs

function onOpen() {
  DocumentApp.getUi()
    .createMenu("AI Tools")
    .addItem("AI Form Builder", "showFormBuilder")
    .addToUi();
}

function showFormBuilder() {
  const html = HtmlService.createHtmlOutputFromFile("Sidebar")
    .setTitle("AI Form Builder");
  DocumentApp.getUi().showSidebar(html);
}

2️⃣ Sidebar for Topic + Question Count

Sidebar.html

<div style="font-family: Arial; padding: 14px;">
  <h2>AI Form Builder</h2>

  <label>Form Topic</label>
  <input id="topic" style="width: 100%; margin-bottom: 8px;" placeholder="e.g., Employee Onboarding Survey" />

  <label>Number of Questions</label>
  <input id="count" type="number" min="3" max="50" style="width: 100%; margin-bottom: 8px;" placeholder="10" />

  <button onclick="generate()">Generate Form</button>

  <pre id="output" style="white-space: pre-wrap; margin-top: 12px;"></pre>

  <script>
    function generate() {
      document.getElementById("output").textContent = "Generating...";
      google.script.run
        .withSuccessHandler(text => document.getElementById("output").textContent = text)
        .generateForm(
          document.getElementById("topic").value,
          document.getElementById("count").value
        );
    }
  </script>
</div>

3️⃣ Gemini Prompt + Form JSON

FormGenerator.gs

function generateForm(topic, count) {
  const num = count ? parseInt(count, 10) : 10;

  const prompt = `
Create a Google Form definition in pure JSON (no markdown, no comments).

Form must follow this structure:

{
  "title": "Form Title",
  "description": "Short description",
  "questions": [
    {
      "type": "multiple_choice" | "checkbox" | "short_answer" | "paragraph" | "scale",
      "title": "Question text",
      "choices": ["Option 1", "Option 2"],  // only for MCQ/checkbox
      "required": true
    }
  ]
}

Requirements:
- Topic: ${topic}
- Number of questions: ${num}
- Mix question types
- Keep questions clear and useful
Return ONLY valid JSON.
`;

  let jsonText;
  try {
    jsonText = callGemini(prompt, "");
  } catch (err) {
    return "Gemini error: " + err;
  }

  // Clean JSON if wrapped
  jsonText = jsonText.trim();
  if (jsonText.startsWith("```")) {
    jsonText = jsonText.replace(/```json/i, "").replace(/```/g, "").trim();
  }

  let formDef;
  try {
    formDef = JSON.parse(jsonText);
  } catch (err) {
    Logger.log("Raw JSON:\n" + jsonText);
    return "Could not parse Gemini JSON. Check logs.";
  }

  const url = buildGoogleForm_(formDef);
  return "Form created:\n" + url;
}

4️⃣ Build the Google Form

FormBuilder.gs

function buildGoogleForm_(def) {
  const form = FormApp.create(def.title || "AI Generated Form");

  if (def.description) {
    form.setDescription(def.description);
  }

  (def.questions || []).forEach(q => {
    let item;

    switch (q.type) {
      case "multiple_choice":
        item = form.addMultipleChoiceItem()
          .setTitle(q.title)
          .setChoices((q.choices || []).map(c => item.createChoice(c)));
        break;

      case "checkbox":
        item = form.addCheckboxItem()
          .setTitle(q.title)
          .setChoices((q.choices || []).map(c => item.createChoice(c)));
        break;

      case "short_answer":
        item = form.addTextItem().setTitle(q.title);
        break;

      case "paragraph":
        item = form.addParagraphTextItem().setTitle(q.title);
        break;

      case "scale":
        item = form.addScaleItem()
          .setTitle(q.title)
          .setBounds(1, 5);
        break;
    }

    if (item && q.required) {
      item.setRequired(true);
    }
  });

  return form.getEditUrl();
}

5️⃣ Use Your Existing Gemini Helper

(Same as in Issue #1–6)


πŸ”¬ Testing

  1. Run testGeminiText() to verify the API key works.
  2. Reload your Google Doc.
  3. Open AI Tools β†’ AI Form Builder.
  4. Try generating a form with:
  • Topic: New Employee Orientation
  • Questions: 12
  1. Click the link returned β€” your new Google Form opens.

πŸŽ‰ What You Built in Issue #7

You now have an AI Form Builder that can:

  • Generate rich, structured Google Forms
  • Create multiple question types dynamically
  • Use Gemini to design high-quality survey content
  • Build forms instantly from a topic

This unlocks workflows for:

πŸ“Š HR surveys
πŸ“š Educational assessments
πŸ“ Event registrations
πŸ“ˆ Customer feedback
πŸŽ“ Quizzes for teachers
🏒 Internal company workflows

Below is a drop-in set of helper functions you can add to your Issue #7 project.

You can either:

  • Put all of this into a new file like SetupAndTests.gs, or
  • Merge the config into your GeminiHelpers.gs and the tests wherever you like.

1️⃣ Config + Basic Gemini Test

If you don’t already have a config, add this (or align with your existing helper):

// =======================
// Gemini CONFIG
// =======================

// πŸ”‘ Replace with your real Gemini API key
const GEMINI_API_KEY = "YOUR_API_KEY_HERE";

// Use a model you know works from your ListModels call
const GEMINI_MODEL   = "gemini-2.5-flash"; 

/**
 * Core Gemini call helper (v1 generateContent).
 */
function callGemini(prompt, text) {
  if (!GEMINI_API_KEY || GEMINI_API_KEY === "YOUR_API_KEY_HERE") {
    throw new Error("GEMINI_API_KEY is not configured. Please set it in the script.");
  }

  const url = `https://generativelanguage.googleapis.com/v1/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;

  const payload = {
    contents: [{
      parts: [{
        text: `${prompt}${text ? "\n\n---\n\n" + text : ""}`
      }]
    }]
  };

  const res = UrlFetchApp.fetch(url, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  });

  const body = res.getContentText();
  const json = JSON.parse(body);

  if (json.error) {
    throw new Error(`Gemini API error: ${json.error.message} (${json.error.status})`);
  }

  const textPart = json.candidates?.[0]?.content?.parts?.[0]?.text;
  if (!textPart) {
    throw new Error("Unexpected Gemini response: " + body);
  }

  return textPart;
}

/**
 * βœ… Test 1: Simple connectivity + auth test.
 * Run this first from the Apps Script editor.
 */
function testGeminiConnection() {
  const result = callGemini(
    "Summarize this in one sentence:",
    "Google Apps Script lets you automate tasks across Google Workspace."
  );
  Logger.log("βœ… Gemini connection OK. Sample response:\n" + result);
}

2️⃣ Test: Form JSON Generation Only (No Form Yet)

This test lets you check what JSON Gemini is returning before you actually build a Form.

/**
 * βœ… Test 2: Generate JSON ONLY for a sample topic.
 * Does NOT create a Google Form. Just logs the JSON.
 */
function testGenerateFormJsonOnly() {
  const topic = "New Employee Onboarding Feedback";
  const count = 8;

  const prompt = `
Create a Google Form definition in pure JSON (no markdown, no comments).

Form must follow this structure:

{
  "title": "Form Title",
  "description": "Short description",
  "questions": [
    {
      "type": "multiple_choice" | "checkbox" | "short_answer" | "paragraph" | "scale",
      "title": "Question text",
      "choices": ["Option 1", "Option 2"],  // only for MCQ/checkbox
      "required": true
    }
  ]
}

Requirements:
- Topic: ${topic}
- Number of questions: ${count}
- Mix question types
- Keep questions clear and useful
Return ONLY valid JSON.
`;

  const jsonText = callGemini(prompt, "");
  Logger.log("Raw Gemini JSON:\n" + jsonText);
}

Run testGenerateFormJsonOnly() and inspect the Apps Script Logs to see if the JSON looks right (proper structure, no markdown fences, etc.).


3️⃣ Test: Build a Form from a Hard-Coded Definition

This test bypasses Gemini completely to verify that your buildGoogleForm_() function is correct.

/**
 * βœ… Test 3: Build a Google Form from a hard-coded JSON object.
 * This verifies that buildGoogleForm_ works even without Gemini.
 */
function testBuildSampleForm() {
  const sampleDef = {
    title: "Sample AI-Generated Form (Test Only)",
    description: "This is a test form created to verify the Apps Script builder.",
    questions: [
      {
        type: "short_answer",
        title: "What is your name?",
        required: true
      },
      {
        type: "multiple_choice",
        title: "How familiar are you with Google Apps Script?",
        choices: [
          "Never used it",
          "Beginner",
          "Intermediate",
          "Advanced"
        ],
        required: true
      },
      {
        type: "paragraph",
        title: "What would you like to build with Apps Script?",
        required: false
      },
      {
        type: "scale",
        title: "Rate your interest in automation (1 = low, 5 = high).",
        required: false
      },
      {
        type: "checkbox",
        title: "Which Google Workspace tools do you use regularly?",
        choices: [
          "Gmail",
          "Google Docs",
          "Google Sheets",
          "Google Slides",
          "Google Forms"
        ],
        required: false
      }
    ]
  };

  const url = buildGoogleForm_(sampleDef); // uses your existing builder
  Logger.log("βœ… Sample Form created. Edit URL:\n" + url);
}

Run testBuildSampleForm() β†’ you should see a new Form in Drive and an Edit URL in the logs you can click.


4️⃣ Full End-to-End Test: Gemini β†’ JSON β†’ Google Form

This wires everything together and mimics what the sidebar will do.

Assuming you already have your generateForm(topic, count) and buildGoogleForm_(def) from the lesson, add:

/**
 * βœ… Test 4: Full pipeline.
 * Calls Gemini to generate JSON, then builds the Form.
 */
function testGenerateFormEndToEnd() {
  const topic = "Course Feedback for Online JavaScript Bootcamp";
  const count = 10;

  const result = generateForm(topic, count); // reuses your Issue #7 function
  Logger.log("βœ… End-to-end result:\n" + result);
}

Expected behavior:

  • A new Google Form is created (title + questions).
  • The log shows something like:
    Form created: https://docs.google.com/forms/d/.../edit

5️⃣ (Optional) Quick Setup Checklist Helper

If you want something that just logs what to do next (nice for students), you can add this:

/**
 * 🧩 Helper: Print setup checklist for this AI Form Builder.
 */
function logAiFormBuilderSetupChecklist() {
  Logger.log([
    "AI Form Builder Setup Checklist:",
    "1) Set GEMINI_API_KEY in the script.",
    "2) Run testGeminiConnection() – confirm no errors.",
    "3) Run testBuildSampleForm() – confirm a test form is created.",
    "4) Run testGenerateFormEndToEnd() – confirm Gemini-driven form works.",
    "5) Open your Doc, use AI Tools β†’ AI Form Builder sidebar.",
    "6) Generate real forms from high-level topics."
  ].join("\n"));
}

How to Integrate

  1. Make sure you have:
    • callGemini(...) (or use the one above)
    • generateForm(topic, count) from Issue #7
    • buildGoogleForm_(def) from Issue #7
  2. Add this Setup & Testing code into a new file like SetupAndTests.gs.
  3. In order, run from editor:
    • testGeminiConnection()
    • testBuildSampleForm()
    • testGenerateFormEndToEnd()

Once those 3 are green, your AI Form Builder is solid and the sidebar flow will be much safer to demo

Everything in one file

// ===============================
// AI Form Builder for Google Forms
// Apps Script + Gemini (Single File)
// ===============================

// ---------- MENU & SIDEBAR ----------

function onOpen() {
  DocumentApp.getUi()
    .createMenu("AI Tools")
    .addItem("AI Form Builder", "showFormBuilder")
    .addToUi();
}

function showFormBuilder() {
  const html = HtmlService.createHtmlOutputFromFile("Sidebar")
    .setTitle("AI Form Builder");
  DocumentApp.getUi().showSidebar(html);
}

// ===============================
// Gemini CONFIG & Helper
// ===============================

// πŸ”‘ Replace with your real Gemini API key
const GEMINI_API_KEY = "YOUR_API_KEY_HERE";

// Use a model that appears in your ListModels response
const GEMINI_MODEL   = "gemini-2.5-flash";

/**
 * Core Gemini call helper (v1 generateContent).
 * @param {string} prompt Instruction for the model.
 * @param {string} text   Optional additional content.
 * @returns {string}      Model response text or throws on error.
 */
function callGemini(prompt, text) {
  if (!GEMINI_API_KEY || GEMINI_API_KEY === "YOUR_API_KEY_HERE") {
    throw new Error("GEMINI_API_KEY is not configured. Set it in Code.gs.");
  }

  const url = "https://generativelanguage.googleapis.com/v1/models/" +
              GEMINI_MODEL + ":generateContent?key=" + GEMINI_API_KEY;

  const payload = {
    contents: [{
      parts: [{
        text: prompt + (text ? "\n\n---\n\n" + text : "")
      }]
    }]
  };

  const res = UrlFetchApp.fetch(url, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  });

  const body = res.getContentText();
  const json = JSON.parse(body);

  if (json.error) {
    throw new Error("Gemini API error: " + json.error.message +
                    (json.error.status ? " (" + json.error.status + ")" : ""));
  }

  const textPart =
    json.candidates &&
    json.candidates[0] &&
    json.candidates[0].content &&
    json.candidates[0].content.parts &&
    json.candidates[0].content.parts[0].text;

  if (!textPart) {
    throw new Error("Unexpected Gemini response: " + body);
  }

  return textPart;
}

/**
 * βœ… Test 1: Simple connectivity + auth test.
 */
function testGeminiConnection() {
  const result = callGemini(
    "Summarize this in one sentence:",
    "Google Apps Script lets you automate tasks across Google Workspace."
  );
  Logger.log("βœ… Gemini connection OK. Sample response:\n" + result);
}

// ===============================
// Gemini β†’ JSON Form Definition
// ===============================

/**
 * Main entry used by the sidebar.
 * Calls Gemini to create a Form definition in JSON, then builds the Form.
 */
function generateForm(topic, count) {
  if (!topic || topic.trim() === "") {
    return "Please enter a form topic.";
  }

  const num = count ? parseInt(count, 10) : 10;
  const safeNum = isNaN(num) ? 10 : Math.max(3, Math.min(num, 50));

  const prompt = [
    "Create a Google Form definition in pure JSON (no markdown, no comments).",
    "",
    "Form must follow this structure:",
    "",
    "{",
    "  \"title\": \"Form Title\",",
    "  \"description\": \"Short description\",",
    "  \"questions\": [",
    "    {",
    "      \"type\": \"multiple_choice\" | \"checkbox\" | \"short_answer\" | \"paragraph\" | \"scale\",",
    "      \"title\": \"Question text\",",
    "      \"choices\": [\"Option 1\", \"Option 2\"],",
    "      \"required\": true",
    "    }",
    "  ]",
    "}",
    "",
    "Requirements:",
    "- Topic: " + topic,
    "- Number of questions: " + safeNum,
    "- Mix question types (some multiple_choice, some short_answer, etc.).",
    "- Keep questions clear, useful, and on-topic.",
    "- Use 'choices' only for multiple_choice and checkbox questions.",
    "Return ONLY valid JSON."
  ].join("\n");

  let jsonText;
  try {
    jsonText = callGemini(prompt, "");
  } catch (err) {
    Logger.log("Gemini error in generateForm: " + err);
    return "Gemini error: " + err;
  }

  // Clean possible markdown fences
  jsonText = jsonText.trim();
  if (jsonText.startsWith("```")) {
    jsonText = jsonText.replace(/```json/i, "")
                       .replace(/```/g, "")
                       .trim();
  }

  let formDef;
  try {
    formDef = JSON.parse(jsonText);
  } catch (err) {
    Logger.log("JSON parse error in generateForm: " + err + "\nRaw JSON:\n" + jsonText);
    return "Could not parse Gemini JSON. Check logs for details.";
  }

  const url = buildGoogleForm_(formDef);
  return "βœ… Form created. Edit URL:\n" + url;
}

// ===============================
// Build Google Form from Definition
// ===============================

/**
 * Build a Google Form from a JSON definition.
 * Expects:
 * {
 *   "title": "...",
 *   "description": "...",
 *   "questions": [
 *     {
 *       "type": "multiple_choice" | "checkbox" | "short_answer" | "paragraph" | "scale",
 *       "title": "Question text",
 *       "choices": ["Option 1", "Option 2"],
 *       "required": true
 *     }
 *   ]
 * }
 */
function buildGoogleForm_(def) {
  const formTitle = def.title || "AI Generated Form";
  const form = FormApp.create(formTitle);

  if (def.description) {
    form.setDescription(def.description);
  }

  const questions = def.questions || [];

  questions.forEach(function(q) {
    let item;

    const qTitle   = q.title || "Untitled question";
    const qType    = q.type || "short_answer";
    const qReq     = !!q.required;
    const qChoices = q.choices || [];

    switch (qType) {
      case "multiple_choice":
        item = form.addMultipleChoiceItem().setTitle(qTitle);
        if (qChoices.length > 0) {
          item.setChoices(qChoices.map(function(c) {
            return item.createChoice(c);
          }));
        }
        break;

      case "checkbox":
        item = form.addCheckboxItem().setTitle(qTitle);
        if (qChoices.length > 0) {
          item.setChoices(qChoices.map(function(c) {
            return item.createChoice(c));
          }));
        }
        break;

      case "paragraph":
        item = form.addParagraphTextItem().setTitle(qTitle);
        break;

      case "scale":
        item = form.addScaleItem()
          .setTitle(qTitle)
          .setBounds(1, 5); // Default 1–5 scale
        break;

      case "short_answer":
      default:
        item = form.addTextItem().setTitle(qTitle);
        break;
    }

    if (item && qReq) {
      item.setRequired(true);
    }
  });

  return form.getEditUrl();
}

// ===============================
// Setup & Testing Helpers
// ===============================

/**
 * βœ… Test 2: Generate JSON ONLY for a sample topic.
 * Does NOT create a Google Form. Just logs the JSON.
 */
function testGenerateFormJsonOnly() {
  const topic = "New Employee Onboarding Feedback";
  const count = 8;

  const prompt = [
    "Create a Google Form definition in pure JSON (no markdown, no comments).",
    "",
    "Form must follow this structure:",
    "",
    "{",
    "  \"title\": \"Form Title\",",
    "  \"description\": \"Short description\",",
    "  \"questions\": [",
    "    {",
    "      \"type\": \"multiple_choice\" | \"checkbox\" | \"short_answer\" | \"paragraph\" | \"scale\",",
    "      \"title\": \"Question text\",",
    "      \"choices\": [\"Option 1\", \"Option 2\"],",
    "      \"required\": true",
    "    }",
    "  ]",
    "}",
    "",
    "Requirements:",
    "- Topic: " + topic,
    "- Number of questions: " + count,
    "- Mix question types.",
    "- Keep questions clear and useful.",
    "Return ONLY valid JSON."
  ].join("\n");

  const jsonText = callGemini(prompt, "");
  Logger.log("Raw Gemini JSON for testGenerateFormJsonOnly:\n" + jsonText);
}

/**
 * βœ… Test 3: Build a Google Form from a hard-coded JSON object.
 * Verifies that buildGoogleForm_ works even without Gemini.
 */
function testBuildSampleForm() {
  const sampleDef = {
    title: "Sample AI-Generated Form (Test Only)",
    description: "This is a test form created to verify the Apps Script builder.",
    questions: [
      {
        type: "short_answer",
        title: "What is your name?",
        required: true
      },
      {
        type: "multiple_choice",
        title: "How familiar are you with Google Apps Script?",
        choices: [
          "Never used it",
          "Beginner",
          "Intermediate",
          "Advanced"
        ],
        required: true
      },
      {
        type: "paragraph",
        title: "What would you like to build with Apps Script?",
        required: false
      },
      {
        type: "scale",
        title: "Rate your interest in automation (1 = low, 5 = high).",
        required: false
      },
      {
        type: "checkbox",
        title: "Which Google Workspace tools do you use regularly?",
        choices: [
          "Gmail",
          "Google Docs",
          "Google Sheets",
          "Google Slides",
          "Google Forms"
        ],
        required: false
      }
    ]
  };

  const url = buildGoogleForm_(sampleDef);
  Logger.log("βœ… Sample Form created. Edit URL:\n" + url);
}

/**
 * βœ… Test 4: Full pipeline.
 * Calls Gemini to generate JSON, then builds the Form.
 */
function testGenerateFormEndToEnd() {
  const topic = "Course Feedback for Online JavaScript Bootcamp";
  const count = 10;

  const result = generateForm(topic, count);
  Logger.log("βœ… End-to-end result:\n" + result);
}

/**
 * 🧩 Helper: Print setup checklist for the AI Form Builder.
 */
function logAiFormBuilderSetupChecklist() {
  Logger.log([
    "AI Form Builder Setup Checklist:",
    "1) Set GEMINI_API_KEY in Code.gs.",
    "2) Run testGeminiConnection() – confirm no errors.",
    "3) Run testBuildSampleForm() – confirm a test Form is created.",
    "4) Run testGenerateFormEndToEnd() – confirm Gemini-driven Form works.",
    "5) In your Doc, use AI Tools β†’ AI Form Builder to generate real Forms from topics."
  ].join("\n"));
}