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"));
}