🚀 Apps Script + Gemini Mastery — Issue #6
https://github.com/lsvekis/AI-Slide-Creator-for-Google-Slides
AI Slide Creator for Google Slides
Generate full slide decks (titles, bullets, structure, and notes) from a single topic — right inside Google Slides.
⭐ What You’ll Build in This Issue
In this lesson, you’ll build an AI Slide Creator that lives inside Google Slides and can:
- 🧠 Generate a complete slide deck from a topic
- 🎯 Tailor content to a specific audience (e.g., executives, students, beginners)
- 📄 Control slide count (e.g., 5, 10, 15 slides)
- 📝 Add speaker notes generated by Gemini
- 🔁 Regenerate or extend content easily
You’ll trigger it from a custom menu and a sidebar UI directly in Slides.
🧠 Learning Objectives
By the end of this issue, you’ll know how to:
- Use SlidesApp to create and modify slides programmatically
- Build a custom sidebar UI in Google Slides
- Prompt Gemini to return structured JSON for slide content
- Parse that JSON & convert it into real Slides (titles, bullets, notes)
- Reuse your existing
callGeminihelper in a Slides-bound project
🧩 EXERCISE — Build the AI Slide Creator
You’ll create:
- A custom menu in Slides
- A sidebar with topic / audience / slide count
- A Gemini-powered generator that returns slide structure as JSON
- A slide builder that converts JSON to real slides
1️⃣ Create a Slides-bound Apps Script Project
- Open Google Slides.
- Go to Extensions → Apps Script.
- Delete any default code.
We’ll add these files:
Code.gsGeminiHelpers.gsSlideBuilder.gsSidebar.html
2️⃣ Add the Menu + Sidebar Entry Point
Code.gs
function onOpen() {
SlidesApp.getUi()
.createMenu("AI Slides")
.addItem("Open AI Slide Creator", "showSlideCreator")
.addToUi();
}
function showSlideCreator() {
const html = HtmlService.createHtmlOutputFromFile("Sidebar")
.setTitle("AI Slide Creator");
SlidesApp.getUi().showSidebar(html);
}
3️⃣ Sidebar UI for Topic, Audience, Slide Count
Sidebar.html
<div style="font-family: Arial, sans-serif; padding: 14px;">
<h2>AI Slide Creator</h2>
<p>Enter a topic and let Gemini build the deck for you.</p>
<label><b>Topic</b></label><br>
<input id="topic" style="width:100%; margin-bottom:8px;"
placeholder="e.g., Introduction to Google Apps Script" />
<label><b>Audience</b> (optional)</label><br>
<input id="audience" style="width:100%; margin-bottom:8px;"
placeholder="e.g., non-technical managers, beginners" />
<label><b>Number of Slides</b> (optional)</label><br>
<input id="slideCount" type="number" min="3" max="30"
style="width:100%; margin-bottom:8px;"
placeholder="e.g., 8" />
<button onclick="generate()" style="margin-top:10px;">Generate Deck</button>
<pre id="output"
style="white-space:pre-wrap; margin-top:14px; max-height:260px; overflow:auto;"></pre>
<script>
function generate() {
const topic = document.getElementById("topic").value;
const audience = document.getElementById("audience").value;
const slideCount = document.getElementById("slideCount").value;
document.getElementById("output").textContent = "Generating slides with Gemini...";
google.script.run
.withSuccessHandler(function(msg) {
document.getElementById("output").textContent = msg;
})
.generateDeckFromPrompt(topic, audience, slideCount);
}
</script>
</div>
4️⃣ Gemini Helper (Reuse from Previous Issues)
GeminiHelpers.gs
🔐 Don’t commit your real API key to GitHub. Use a placeholder in repos.
// === Gemini Config ===
const GEMINI_API_KEY = "YOUR_API_KEY_HERE";
const GEMINI_MODEL = "gemini-2.5-flash"; // from your ListModels response
/**
* Generic helper to call Gemini generateContent.
* @param {string} prompt - instruction for the model
* @param {string} text - optional additional text
* @returns {string} - response text or error message
*/
function callGemini(prompt, text) {
if (!GEMINI_API_KEY || GEMINI_API_KEY === "YOUR_API_KEY_HERE") {
throw new Error("GEMINI_API_KEY is not configured.");
}
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;
}
/**
* Quick test you can run from the Script Editor.
*/
function testGeminiSlides() {
const result = callGemini(
"Write a title and 3 bullet points about using AI in Google Workspace.",
""
);
Logger.log(result);
}
5️⃣ Generate Slide Structure with Gemini
We’ll ask Gemini to return strict JSON describing the slide deck:
title– global deck titleslides[]– each withtitle,bullets[],notes
SlideBuilder.gs
/**
* Main entry called from Sidebar.html.
*/
function generateDeckFromPrompt(topic, audience, slideCount) {
if (!topic || topic.trim() === "") {
return "Please enter a topic.";
}
const count = slideCount ? parseInt(slideCount, 10) : 8;
const safeCount = isNaN(count) ? 8 : Math.max(3, Math.min(count, 30));
const audienceText = audience && audience.trim()
? `Target audience: ${audience}.`
: "Target audience: general beginners.";
const prompt = `
You are an expert instructional designer.
Create a slide deck outline in pure JSON ONLY (no markdown, no commentary).
Follow this structure exactly:
{
"title": "Deck title here",
"slides": [
{
"title": "Slide title",
"bullets": [
"Bullet 1",
"Bullet 2"
],
"notes": "Speaker notes for this slide."
}
]
}
Requirements:
- Topic: ${topic}
- ${audienceText}
- Number of slides (excluding title slide if needed): ${safeCount}
- Use clear, concise bullets (no more than 5 per slide).
- Keep speaker notes short but helpful (1–3 sentences).
- JSON must be valid and parseable.
Return ONLY the JSON object, no explanation before or after.
`;
let jsonText;
try {
jsonText = callGemini(prompt, "");
} catch (err) {
Logger.log("Error calling Gemini: " + err);
return "Error calling Gemini: " + err;
}
let deck;
try {
// Some models wrap JSON in ```json ... ``` – strip if needed
jsonText = jsonText.trim();
if (jsonText.startsWith("```")) {
jsonText = jsonText.replace(/```json/i, "")
.replace(/```/g, "")
.trim();
}
deck = JSON.parse(jsonText);
} catch (e) {
Logger.log("JSON parse error: " + e + "\nRaw:\n" + jsonText);
return "Could not parse Gemini JSON. Check logs for details.";
}
try {
buildSlidesFromDeck_(deck);
} catch (e) {
Logger.log("Slide build error: " + e);
return "Error while creating slides: " + e;
}
return `Slide deck generated for topic: "${topic}" with ~${safeCount} slides.`;
}
6️⃣ Build the Actual Slides
SlideBuilder.gs (append below previous code)
/**
* Build slides in the current presentation from the JSON deck definition.
*/
function buildSlidesFromDeck_(deck) {
const presentation = SlidesApp.getActivePresentation();
// Option: clear existing slides, or append to the end.
// Here we append after the existing slides.
const slides = deck.slides || [];
const deckTitle = deck.title || "AI Generated Deck";
// Create a title slide
const titleSlide = presentation.appendSlide(
presentation.getSlides()[0].getLayout()
);
titleSlide.getPageElements().forEach(pe => {
try {
pe.asShape().getText().setText("");
} catch (e) {}
});
const shapes = titleSlide.getShapes();
if (shapes.length > 0) {
shapes[0].getText().setText(deckTitle);
}
// Create content slides
slides.forEach(slideData => {
const slide = presentation.appendSlide(
presentation.getSlides()[0].getLayout()
);
const title = slideData.title || "";
const bullets = slideData.bullets || [];
const notes = slideData.notes || "";
// Clear existing text
slide.getPageElements().forEach(pe => {
try {
pe.asShape().getText().setText("");
} catch (e) {}
});
const shapes = slide.getShapes();
let titleShape, bodyShape;
if (shapes.length >= 2) {
titleShape = shapes[0];
bodyShape = shapes[1];
} else if (shapes.length === 1) {
titleShape = shapes[0];
}
if (titleShape) {
titleShape.getText().setText(title);
}
if (bodyShape) {
const bodyText = bodyShape.getText();
bodyText.setText("");
bullets.forEach((b, i) => {
if (i === 0) {
bodyText.setText(b);
} else {
bodyText.appendParagraph(b);
}
});
}
// Speaker notes
if (notes && notes.trim()) {
const notesPage = slide.getNotesPage();
const notesShape = notesPage.getSpeakerNotesShape();
notesShape.getText().setText(notes);
}
});
}
🔬 Testing the Lesson
- Open the Slides file bound to this script.
- Go to Extensions → Apps Script, set your
GEMINI_API_KEY. - From the editor, run
testGeminiSlides()once to authorize & confirm connectivity. - Back in Slides, reload the presentation.
- Use AI Slides → Open AI Slide Creator.
- Try:
- Topic:
Getting started with Google Apps Script - Audience:
beginner web developers - Number of slides:
7
- Topic:
- Click Generate Deck and watch slides appear.
🎉 What You Built in Issue #6
You now have a working AI Slide Creator that:
- Takes a topic & audience
- Uses Gemini to design a structured deck
- Converts that structure into real Slides
- Adds speaker notes for presenters
- Lives entirely inside Google Slides
This is the backbone of:
- Auto-generated training decks
- Lesson plans for educators
- Sales / pitch decks
- Internal onboarding & policy decks