●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Building an AI Presentation Pipeline with Gemini API and Google Slides Apps Script — A
Learn how to build a complete presentation pipeline using Gemini API and Google Apps Script that auto-generates slide outlines, content, and designs from a single topic input.
Setup and context — Why Automate Presentation Creation?
In the business world, presentation creation consumes a disproportionate amount of productive time. According to McKinsey, knowledge workers spend roughly 28% of their work week on document creation and information organization. The combination of Gemini API and Google Apps Script offers a way to fundamentally reduce this burden through an AI-powered presentation pipeline.
This guide walks you through building a system that automates the entire workflow — from slide structure design and content generation to design template application and multilingual expansion — all from a single topic input. Every code example is production-ready and fully functional.
For basic AI-powered features in Google Slides, check out our Google Slides × Gemini AI Practical Guide. This article goes beyond that foundation into programmatic pipeline construction.
This article is designed for the following readers:
Developers familiar with Google Apps Script basics
Anyone with Gemini API experience (or who has already obtained an API key)
Engineers and managers looking to automate presentation workflows within their organization
Pipeline Architecture — A 5-Stage Design
The AI presentation pipeline consists of five distinct stages:
Stage 1: Topic Input and Requirements Definition
Users specify the theme, target audience, slide count, and tone. A Google Forms or Sheets sidebar interface works well for this purpose.
Stage 2: Outline Generation (Gemini API)
Using Gemini API's Structured Output, the system generates a JSON structure containing per-slide titles, key points, and layout types.
Stage 3: Content Generation (Gemini API)
For each slide in the outline, the system generates body text, bullet points, and chart descriptions.
Stage 4: Slide Construction (Google Slides API)
Apps Script's SlidesApp builds the actual slides based on design templates.
Stage 5: Post-Processing and Distribution
PDF export, multilingual version generation, Google Drive storage, and Slack/email notifications.
A key advantage of this architecture is that each stage operates independently. This naturally supports a human-in-the-loop workflow where someone can review and adjust the outline before proceeding to content generation.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Build a pipeline that generates slide structure, content, and design from a single topic input
✦Master Structured Output patterns for generating slide JSON schemas with Gemini API
✦Learn production-ready techniques including template management, multilingual support, and batch processing
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
// config.gs — Pipeline configurationconst CONFIG = { GEMINI_API_KEY: PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY'), GEMINI_MODEL: 'gemini-2.5-pro', GEMINI_ENDPOINT: 'https://generativelanguage.googleapis.com/v1beta/models/', TEMPLATE_FOLDER_ID: 'YOUR_TEMPLATE_FOLDER_ID', OUTPUT_FOLDER_ID: 'YOUR_OUTPUT_FOLDER_ID', MAX_SLIDES: 20, DEFAULT_SLIDES: 10,};// input.gs — Receive input from the sidebarfunction showInputSidebar() { const html = HtmlService.createHtmlOutputFromFile('Sidebar') .setTitle('AI Presentation Generator') .setWidth(350); SpreadsheetApp.getUi().showSidebar(html);}// Called from the sidebar UIfunction generatePresentation(params) { const { theme, audience, slideCount, tone, language } = params; // Validation if (!theme || theme.trim().length < 5) { throw new Error('Topic must be at least 5 characters long'); } const count = Math.min( Math.max(parseInt(slideCount) || CONFIG.DEFAULT_SLIDES, 3), CONFIG.MAX_SLIDES ); // Execute the pipeline const outline = generateOutline(theme, audience, count, tone, language); const contents = generateSlideContents(outline, tone, language); const presentation = buildPresentation(contents, language); const result = postProcess(presentation, language); return { presentationUrl: result.url, pdfUrl: result.pdfUrl, slideCount: contents.slides.length, };}
A critical detail here is using PropertiesService to securely store API keys in script properties. Never hardcode keys directly in your source code.
Stage 2: Generating Outlines as JSON with Gemini API
The core of the pipeline is outline generation using Gemini API's Structured Output:
// outline.gs — Outline generationfunction generateOutline(theme, audience, slideCount, tone, language) { const systemPrompt = `You are an expert presentation architect.Design a professional slide deck outline based on the given theme.Constraints:- Slide count: ${slideCount} slides (including title and closing slides)- Target audience: ${audience}- Tone: ${tone}- Assign an appropriate layout type to each slide- Layout types: title_slide, section_header, content_with_bullets, content_with_image, two_column, chart_suggestion, quote, closing`; const userPrompt = `Theme: ${theme}Output in the following JSON format:{ "title": "Main presentation title", "subtitle": "Subtitle", "slides": [ { "slideNumber": 1, "layout": "title_slide", "title": "Slide title", "keyPoints": ["Point 1", "Point 2"], "speakerNotes": "Speaker notes summary" } ]}`; const response = callGeminiAPI(systemPrompt, userPrompt, { responseMimeType: 'application/json', }); const outline = JSON.parse(response); // Validation: check slide count if (!outline.slides || outline.slides.length < 3) { throw new Error('Outline generation failed. Please retry.'); } // Log for debugging and auditing logToSheet('Outline', JSON.stringify(outline, null, 2)); return outline;}// gemini-api.gs — Shared Gemini API callerfunction callGeminiAPI(systemPrompt, userPrompt, options = {}) { const url = `${CONFIG.GEMINI_ENDPOINT}${CONFIG.GEMINI_MODEL}:generateContent?key=${CONFIG.GEMINI_API_KEY}`; const payload = { system_instruction: { parts: [{ text: systemPrompt }], }, contents: [ { role: 'user', parts: [{ text: userPrompt }], }, ], generationConfig: { temperature: options.temperature || 0.7, topP: 0.95, maxOutputTokens: options.maxTokens || 8192, }, }; // Enable Structured Output (JSON Mode) if (options.responseMimeType) { payload.generationConfig.responseMimeType = options.responseMimeType; } const fetchOptions = { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true, }; // Retry logic with exponential backoff let lastError; for (let attempt = 0; attempt < 3; attempt++) { try { const res = UrlFetchApp.fetch(url, fetchOptions); const status = res.getResponseCode(); if (status === 200) { const json = JSON.parse(res.getContentText()); return json.candidates[0].content.parts[0].text; } if (status === 429) { // Rate limited: exponential backoff const waitMs = Math.pow(2, attempt) * 1000 + Math.random() * 1000; Utilities.sleep(waitMs); continue; } throw new Error(`Gemini API error: ${status} - ${res.getContentText()}`); } catch (e) { lastError = e; if (attempt < 2) { Utilities.sleep(2000 * (attempt + 1)); } } } throw lastError;}
By specifying responseMimeType: 'application/json', Gemini API guarantees valid JSON output. Compared to schema-in-prompt approaches, this dramatically reduces the risk of parse errors — a critical consideration for production systems.
Once the outline is finalized, we generate detailed content for each slide. This stage uses a batch processing pattern to optimize API calls:
// content-gen.gs — Slide content generationfunction generateSlideContents(outline, tone, language) { const batchSize = 3; // Process 3 slides per batch const allSlides = []; for (let i = 0; i < outline.slides.length; i += batchSize) { const batch = outline.slides.slice(i, i + batchSize); const batchContents = generateBatchContents(batch, outline.title, tone, language); allSlides.push(...batchContents); // Rate limit prevention: short pause between batches if (i + batchSize < outline.slides.length) { Utilities.sleep(500); } } return { title: outline.title, subtitle: outline.subtitle, slides: allSlides, };}function generateBatchContents(slides, presentationTitle, tone, language) { const systemPrompt = `You are a presentation content writer.Generate detailed content for each slide as part of the presentation titled "${presentationTitle}".Rules:- Tone: ${tone}- 3-5 bullet points per slide- Each bullet should be concise and specific (15-30 words)- Speaker notes: 100-200 words with detailed context- For chart_suggestion layouts, include chart specs with sample data`; const slidesInfo = slides.map(s => `Slide ${s.slideNumber}: layout=${s.layout}, title="${s.title}", keyPoints=${JSON.stringify(s.keyPoints)}` ).join('\n'); const userPrompt = `Generate detailed content for the following slides as a JSON array:${slidesInfo}Output format:[ { "slideNumber": 1, "title": "Slide title", "body": "Body text (used for content_with_image, etc.)", "bullets": ["Bullet 1", "Bullet 2"], "speakerNotes": "Detailed speaker notes", "chartSpec": null }]`; const response = callGeminiAPI(systemPrompt, userPrompt, { responseMimeType: 'application/json', temperature: 0.6, // Lower temperature for more consistent content }); return JSON.parse(response);}
By batching 3 slides per API call, a 10-slide presentation requires only 4 API calls. This reduces latency by roughly 60% compared to per-slide generation while also maintaining better contextual consistency across slides.
Stage 4: Building Slides with the Google Slides API
This stage transforms the generated content into actual Google Slides. We use a template-driven approach to ensure design quality:
// slide-builder.gs — Slide constructionfunction buildPresentation(contents, language) { // Copy the template to create a new presentation const templateId = getTemplateId(language); const template = DriveApp.getFileById(templateId); const newFile = template.makeCopy( `${contents.title} — ${new Date().toISOString().slice(0, 10)}`, DriveApp.getFolderById(CONFIG.OUTPUT_FOLDER_ID) ); const presentation = SlidesApp.openById(newFile.getId()); const masterSlides = presentation.getMasters()[0].getLayouts(); // Remove all existing slides (keep layout masters) const existingSlides = presentation.getSlides(); existingSlides.forEach(slide => slide.remove()); // Generate slides from content contents.slides.forEach((slideData, index) => { const layout = findLayout(masterSlides, slideData.layout); const slide = presentation.appendSlide(layout); populateSlide(slide, slideData); // Set speaker notes if (slideData.speakerNotes) { slide.getNotesPage() .getSpeakerNotesShape() .getText() .setText(slideData.speakerNotes); } }); presentation.saveAndClose(); return { id: newFile.getId(), url: newFile.getUrl(), name: newFile.getName(), };}// Map layout types to Google Slides layout namesfunction findLayout(masterLayouts, layoutType) { const layoutMap = { 'title_slide': 'TITLE', 'section_header': 'SECTION_HEADER', 'content_with_bullets': 'TITLE_AND_BODY', 'content_with_image': 'TITLE_AND_BODY', 'two_column': 'TITLE_AND_TWO_COLUMNS', 'chart_suggestion': 'TITLE_AND_BODY', 'quote': 'TITLE_ONLY', 'closing': 'TITLE', }; const targetName = layoutMap[layoutType] || 'TITLE_AND_BODY'; for (const layout of masterLayouts) { if (layout.getLayoutName() === targetName) { return layout; } } // Fallback: use the first available layout return masterLayouts[0];}// Populate a slide with contentfunction populateSlide(slide, data) { const placeholders = slide.getPlaceholders(); placeholders.forEach(placeholder => { const type = placeholder.getPlaceholderType(); switch (type) { case SlidesApp.PlaceholderType.TITLE: case SlidesApp.PlaceholderType.CENTER_TITLE: placeholder.asShape().getText().setText(data.title); break; case SlidesApp.PlaceholderType.BODY: case SlidesApp.PlaceholderType.SUBTITLE: if (data.bullets && data.bullets.length > 0) { const textRange = placeholder.asShape().getText(); textRange.clear(); data.bullets.forEach((bullet, i) => { const paragraph = i === 0 ? textRange.setText(bullet) : textRange.appendParagraph(bullet); }); } else if (data.body) { placeholder.asShape().getText().setText(data.body); } break; } });}
With the template-driven approach, fonts, colors, and layouts designed by your design team are automatically applied. Since AI only generates the content, brand consistency is maintained across every output.
Stage 5: Post-Processing — PDF Export, Translation, and Notifications
The final stage handles post-processing of the generated presentation:
// post-process.gs — Post-processing pipelinefunction postProcess(presentation, language) { const result = { url: presentation.url, pdfUrl: null, translations: [], }; // 1. PDF export result.pdfUrl = exportToPDF(presentation.id, presentation.name); // 2. Record metadata to spreadsheet logPresentationMetadata(presentation); // 3. Completion notification (optional) sendNotification(presentation, result.pdfUrl); return result;}function exportToPDF(fileId, fileName) { const url = `https://docs.google.com/presentation/d/${fileId}/export/pdf`; const token = ScriptApp.getOAuthToken(); const response = UrlFetchApp.fetch(url, { headers: { Authorization: `Bearer ${token}` }, muteHttpExceptions: true, }); if (response.getResponseCode() !== 200) { Logger.log(`PDF export failed: ${response.getResponseCode()}`); return null; } const pdfBlob = response.getBlob().setName(`${fileName}.pdf`); const outputFolder = DriveApp.getFolderById(CONFIG.OUTPUT_FOLDER_ID); const pdfFile = outputFolder.createFile(pdfBlob); return pdfFile.getUrl();}// Generate translated version automaticallyfunction generateTranslatedVersion(presentationId, targetLanguage) { const original = SlidesApp.openById(presentationId); const slides = original.getSlides(); // Extract all text from slides const texts = slides.map(slide => { const elements = slide.getPageElements(); return elements .filter(e => e.getPageElementType() === SlidesApp.PageElementType.SHAPE) .map(e => e.asShape().getText().asString()) .filter(t => t.trim().length > 0); }); // Batch translate with Gemini API const systemPrompt = `You are a professional translator.Translate the following presentation text into ${targetLanguage}.- Maintain bullet point structure- Keep technical terms in their original language- Use natural ${targetLanguage} expressions`; const userPrompt = `Translate the following slide texts.Maintain the structure of each slide and output as a JSON array:${JSON.stringify(texts, null, 2)}`; const response = callGeminiAPI(systemPrompt, userPrompt, { responseMimeType: 'application/json', maxTokens: 16384, }); const translations = JSON.parse(response); // Create a copy and apply translated text const file = DriveApp.getFileById(presentationId); const copy = file.makeCopy( file.getName().replace(/(.+)/, `$1 (${targetLanguage})`), DriveApp.getFolderById(CONFIG.OUTPUT_FOLDER_ID) ); const translatedPres = SlidesApp.openById(copy.getId()); const translatedSlides = translatedPres.getSlides(); translations.forEach((slideTexts, slideIndex) => { if (slideIndex >= translatedSlides.length) return; const elements = translatedSlides[slideIndex].getPageElements() .filter(e => e.getPageElementType() === SlidesApp.PageElementType.SHAPE); let textIndex = 0; elements.forEach(element => { const shape = element.asShape(); const currentText = shape.getText().asString().trim(); if (currentText.length > 0 && textIndex < slideTexts.length) { shape.getText().setText(slideTexts[textIndex]); textIndex++; } }); }); translatedPres.saveAndClose(); return copy.getUrl();}
When handling multilingual output, preserving the slide structure — bullet indentation, numbered lists, and text hierarchy — is essential. Having Gemini API output translations in JSON format ensures accurate text replacement.
Template Management System Design
Production environments need a system for managing multiple templates by use case:
Separating templates by language allows for per-language font and sizing optimization. Using Noto Sans JP for Japanese and Inter for English in the master slides dramatically improves output quality.
Error Handling and Retry Strategy
A production pipeline requires robust error handling for API failures and timeouts:
Apps Script has a 6-minute execution time limit, making it essential to monitor progress and save intermediate results before timeout. For larger presentations (20+ slides), consider splitting the work across multiple trigger-based executions.
For recurring presentations like weekly or monthly reports, you can achieve fully automated generation using time-based triggers:
// batch.gs — Batch processingfunction setupWeeklyReportTrigger() { // Remove existing triggers ScriptApp.getProjectTriggers().forEach(trigger => { if (trigger.getHandlerFunction() === 'generateWeeklyReport') { ScriptApp.deleteTrigger(trigger); } }); // Run every Monday at 9:00 AM ScriptApp.newTrigger('generateWeeklyReport') .timeBased() .onWeekDay(ScriptApp.WeekDay.MONDAY) .atHour(9) .create();}function generateWeeklyReport() { // Fetch latest data from the spreadsheet const dataSheet = SpreadsheetApp.getActiveSpreadsheet() .getSheetByName('WeeklyData'); const data = dataSheet.getDataRange().getValues(); // Format data as context for the presentation const dataContext = formatDataForPrompt(data); const params = { theme: `Weekly Performance Report (${getWeekRange()})`, audience: 'Leadership team', slideCount: 8, tone: 'professional', language: 'en', additionalContext: dataContext, }; try { const result = executePipelineWithRetry(params); // Success: notify via Slack sendSlackNotification({ text: `✅ Weekly report generated automatically\n📊 ${result.presentationUrl}`, }); } catch (error) { sendSlackNotification({ text: `❌ Weekly report generation failed: ${error.message}`, }); }}function formatDataForPrompt(sheetData) { const headers = sheetData[0]; const rows = sheetData.slice(1); return rows.map(row => { const obj = {}; headers.forEach((h, i) => { obj[h] = row[i]; }); return obj; });}function getWeekRange() { const now = new Date(); const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const fmt = d => `${d.getMonth() + 1}/${d.getDate()}`; return `${fmt(weekAgo)}–${fmt(now)}`;}
By automatically pulling data from a spreadsheet and converting it into charts and tables on slides, you achieve zero-touch report generation. For more patterns on Apps Script automation, see our Cross-Product AI Workflow with Gemini API and Apps Script.
Cost Optimization and Performance Tuning
In production, optimizing API costs and performance directly impacts profitability.
Model selection strategy: Use gemini-2.5-pro for outline generation (Stage 2) and gemini-2.5-flash for content generation (Stage 3). This reduces costs by roughly 40% while maintaining quality where it matters most.
// cost-optimization.gs — Model selectionfunction callGeminiAPIWithModel(model, systemPrompt, userPrompt, options = {}) { const url = `${CONFIG.GEMINI_ENDPOINT}${model}:generateContent?key=${CONFIG.GEMINI_API_KEY}`; // ... (same implementation as callGeminiAPI, only the model in the URL changes)}// Stage 2: Use Pro for structural design (higher quality)function generateOutlineOptimized(theme, audience, slideCount, tone) { return callGeminiAPIWithModel('gemini-2.5-pro', /* ... */);}// Stage 3: Use Flash for content filling (faster, lower cost)function generateSlideContentsOptimized(outline, tone) { return callGeminiAPIWithModel('gemini-2.5-flash', /* ... */);}
Caching strategy: Cache the outline (Stage 2) results in a spreadsheet for repeated topics. This can reduce API calls by 50% for similar requests.
Apps Script execution time optimization: To stay within the 6-minute limit, leverage async processing patterns.
10 slides or fewer: synchronous execution (~2-3 minutes)
11-20 slides: split Stages 2-3 and Stages 4-5 across separate trigger-based executions
Summary
This guide covered the complete design and implementation of a presentation pipeline powered by Gemini API and Google Apps Script. We walked through a 5-stage pipeline architecture, Structured Output-driven outline generation, template-based slide construction, and production-ready techniques including multilingual support and batch processing.
By implementing this pipeline, you can reduce presentation creation time by over 90% while consistently maintaining brand-compliant quality. Start with templated recurring reports — those are where the automation delivers the most immediate value.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.