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:
- A menu entry in Docs or Sheets
- A sidebar UI to gather form topic + details
- A Gemini-powered JSON form generator
- 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
- Run
testGeminiText()to verify the API key works. - Reload your Google Doc.
- Open AI Tools β AI Form Builder.
- Try generating a form with:
- Topic: New Employee Orientation
- Questions: 12
- 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.gsand 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
- Make sure you have:
callGemini(...)(or use the one above)generateForm(topic, count)from Issue #7buildGoogleForm_(def)from Issue #7
- Add this Setup & Testing code into a new file like
SetupAndTests.gs. - 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"));
}