Free playbook · A tool in 60 minutes

Year-long lesson plans with AI: an annual plan, slides and worksheets in one hour

The promise: Within an hour you will have an annual plan in a table, a lesson plan, a slide deck and a worksheet for the first lesson.

  • Aviad Gindi

Note: general recommendations only, not professional advice. You are responsible for checking the content and the output before you use it in class. Only upload a textbook you hold a license to use this way, and never enter student names or identifying details. The full text is at the end.

Who this is for

Teachers who start the year with a textbook, a set of curriculum standards and a number of weekly hours, and need to turn all of that into a working plan. Department heads building one shared plan for a grade. And anyone teaching a subject or a grade for the first time who wants an organized starting point instead of a blank page.

You don't need to code. You use a general AI tool you already have, and paste one script into a Google Sheet, once. From then on everything runs from a menu.

What this is not: a replacement for your judgment. The AI suggests a split, a structure and questions. You know the class, and you decide what goes into the lesson.

What you'll have at the end of the hour

  • A list of units and topics taken from the curriculum standards you pasted, with no topics the AI made up.
  • A book map: for each unit, the page range in the textbook, the concepts and the exercises it already has.
  • An annual plan in a table: week, unit, topic, pages and assessment, based on your school calendar.
  • A 45-minute lesson plan for the first lesson, with goals, flow and timings.
  • A Google Slides deck of 8 to 12 slides, with speaker notes.
  • A Google Docs worksheet in three levels: basic, core and challenge.
  • A Google Sheet with "Annual plan", "Book map" and "Lessons" tabs, and a menu that creates slides and a worksheet for any lesson you pick.

What you need

  • A Google account (Sheets, Slides, Docs and Drive). A school account is better, if you have one.
  • A general AI tool that lets you upload files to a project: ChatGPT, Claude or Gemini. Free plans sometimes limit file size or the number of messages.
  • The curriculum standards for your subject and grade, as text you can copy.
  • The textbook as a PDF, only if you hold a license to use it this way. If you don't, skip the upload: type the table of contents and page ranges yourself.
  • Your school calendar and the number of weekly hours for the subject.

Two rules you don't bend:

  • Copyright: only upload a book or PDF you hold a license for, and only for your own class. Don't publish excerpts from the book outside class: not on a website, not on social media, not in an open group. The slides and worksheets point to pages in the book; they don't copy it.
  • Privacy: never put student names, ID numbers or grades into an AI tool or into the sheet. When you need to tell students or groups apart, write "Group A" or "Student 1".

A third rule, for working with the AI: it doesn't invent. A standard you didn't paste doesn't go into the plan, and a page that isn't in the file doesn't go into the map. Anything missing is marked [to fill] or [not in the book], and you fill it in. A visible gap is better than a reference to a page that doesn't exist.


The playbook, step by step (60 minutes)

Step 1 (8 minutes): The curriculum, the subject and the grade

What to do: Open a new project in your AI tool and give it the frame: subject, grade, weekly hours and the curriculum standards, word for word.

How:

  1. In ChatGPT or Claude: create a new Project named after the subject and grade, for example "Grade 8 science 2026-27". In Gemini: create a new Gem, or keep working in one fixed chat. The whole year's work lives there, so you don't have to explain everything again each time.
  2. Copy the standards from the official document you have. Don't summarize them in your own words: paste the original text.
  3. Run the first prompt.
1. From the curriculum to a list of units
You are a teaching assistant who works only from the material I give you.
Subject: [to fill]. Grade: [to fill]. Hours per week: [to fill].
Teaching weeks in the year, after holidays: [to fill, or write "unknown"].

Here are the curriculum standards, as written:
[paste here]

Return a table: unit number | unit name | topics | key concepts | suggested hours (if the standards say).
Rules:
1. Don't add a unit, topic or concept that isn't in the standards I pasted.
2. If the hours for a unit aren't stated, write [to fill] and don't guess.
3. If a standard is unclear or cut off, write [to fill] next to its row.
4. After the table, a short list of everything you marked [to fill].

Why this way: An AI tool knows a lot about curricula, and that is exactly the problem. Without limits it fills in topics from an older version, another region or another book. The "only what I pasted" rule keeps you inside the standards you're accountable for.

Common mistake: pasting only the unit names. Without the topics and concepts, the AI fills the gaps from general knowledge. Paste the full standards, even if they're long.

Step 2 (10 minutes): Upload the book and build a page map

What to do: Upload the textbook to the project and ask the AI to map it: for each unit from the previous step, which pages it covers, which concepts appear there and which exercises the book already has.

How:

  1. Check that you hold a license to use the file this way. A book you got from the publisher for teacher use, or a digital book your school bought, usually comes with terms of use. Read them. If they forbid uploading the file to an outside service, skip the upload and type the table of contents instead.
  2. In your AI tool's settings, check whether your content is used to train the model, and turn that off if there is an option.
  3. Upload the file to the project (Add files / Upload). Book too large? Split it into files by unit.
  4. Run the prompt, then the check prompt.

Two things to check in the file before you start:

  • Scanned books. If you can't select a word in the file and copy it, it's an image with no text layer, and the AI will read it partly or not at all. Run it through OCR (text recognition) first. A simple way: upload the PDF to Drive and open it with Google Docs, or use the OCR tool in your PDF software.
  • Page numbers. The page number in the PDF isn't always the number printed in the book, because of the cover, the preface and the table of contents. Open the file and check: if printed page 1 is page 9 in the file, say so in the prompt. Students hold the printed book, so work with the printed numbers.
2. A page map of the book
The file I uploaded is a textbook I hold a license to use in my own class.
Page numbers: printed page 1 is page [to fill] in the file. Use the printed numbers.

For each unit in the table we built in this chat, return a table:
unit | pages | concepts that appear on those pages | exercises in the book (exercise number and page)

Rules:
1. Only list pages that exist in the file. If you can't find a unit in the book, write [not in the book].
2. Don't quote long passages from the book. Page numbers and concepts are enough.
3. If part of the file is unreadable (scanned, cut off), say which pages.
4. At the end: which curriculum topics have no coverage in the book.
3. Spot check the map
Pick 5 random rows from the map you returned.
For each one: write the page number and the first sentence on that page, word for word.
If you're not sure, write "not sure" and don't fill it in.

Open the book at those pages and check. If two out of five don't match, the map isn't reliable: usually because of a page number offset or a scanned book. Fix the offset and run it again.

Why this way: the map is what turns a generic plan into yours. Thanks to it, every lesson in the plan points to pages and exercises your students already have, and you don't rewrite what the publisher already wrote.

Step 3 (8 minutes): An annual plan in a table

What to do: Ask the AI to spread the units over the weeks of the year, based on your weekly hours and your school calendar.

How: Copy your school's holiday dates and run the prompt. You don't need all 35 weeks at once: ask for the method and the first four weeks, check them, and only then ask for the rest.

4. Annual plan
Based on the units table and the book map in this chat, build an annual plan.
First day of school: [to fill]. Last day: [to fill].
Holidays and days with no classes: [paste dates].
Hours per week: [to fill]. Tests or assessments already scheduled: [to fill, or "none"].

Part 1: in five lines, explain how you split it: how many weeks per unit and why, and where you left buffer weeks for review and catch-up.
Part 2: return a table for the first four weeks only:
week | dates | unit | topic | pages in the book | assessment (quiz, task, product, or empty)

Rules:
1. Pages only from the map. If there are none, write [not in the book].
2. Don't invent tests. Assessment only if I asked for it, or as a suggestion marked "Suggestion:".
3. Leave at least one buffer week in each semester.
Once I approve, I'll ask for the remaining weeks.

Why this way: a 35-row plan generated in one go looks tidy but is hard to check. When the method is written up front ("six weeks for unit 1 because the rest builds on it"), it's easy to see where the split doesn't fit your class and fix it before you go on.

Common mistake: planning every week as a full week. In practice there are field trips, special days, standardized tests and lessons that get cancelled. Without buffer weeks, the plan falls apart by November.

When the table is ready, copy it (select the table in the answer and copy) and paste it into the "Annual plan" tab of the sheet you'll set up in Step 7. You can also wait until the end of the hour and paste everything together.

Step 4 (8 minutes): A 45-minute lesson plan

What to do: Pick the first lesson in the plan and ask for a full lesson plan. The answer comes in two parts: a readable plan for you, and a line of JSON the sheet will read in Step 7.

5. A 45-minute lesson plan
Build a lesson plan for lesson [number] in the annual plan: [topic], pages [to fill].
Length: 45 minutes. Students in the class: [to fill]. What the class already knows: [to fill].

Part 1, a readable plan:
1. 2 to 3 goals, each starting with a verb you can check ("Students will explain...", "will identify...").
2. The lesson flow with timings that add up to 45 minutes: opening, instruction, practice, check for understanding, wrap-up.
3. Next to each part: what the teacher does, what the students do, and which page or exercise in the book.
4. One exit question at the end that can be answered in a minute.
5. One adjustment for students who need more support, and one for students who finish early. No names: "Group A", "Student 1".

Part 2, one line of JSON, on a single line, with no text before or after it:
{"lesson":{"title":"","goals":[],"pages":""}}

Rules: pages and exercises only from the map. Anything missing: [to fill].

Why this way: a goal like "students will understand cells" is hard to check. "Students will identify three parts of a cell in a diagram" can be checked in a minute. When the goals are written that way, the exit question and the worksheet are built around them.

Common mistake: accepting a plan whose parts add up to 60 minutes. Check the total. If it isn't 45, tell it "the timings add up to X, cut it to 45".

Step 5 (8 minutes): A deck of 8 to 12 slides

What to do: In the same chat, ask for the slides for the lesson. The AI doesn't design the deck: it writes titles, bullets and speaker notes, and the script in Step 7 builds a Google Slides file from them.

6. Slides for the lesson
Based on the lesson plan you built, write 8 to 12 slides.
For each slide: a title of up to 7 words, 2 to 4 short bullets (up to 12 words each), and a speaker note: what to say or ask on this slide.
Slide 1: the lesson goals. Last slide: the exit question.
Don't copy paragraphs from the book. Point to the page: "See page 14".
Don't write any student names.

Return the full JSON so far, on a single line, with no text before or after it:
{"lesson":{"title":"","goals":[],"pages":""},"slides":[{"title":"","bullets":[],"notes":""}]}

Why this way: a slide with a whole paragraph moves the class's attention from you to the wall. Short bullets plus a speaker note split the work: the slide reminds, you explain. Pointing to a page instead of copying it also respects the book's copyright.

Step 6 (8 minutes): A worksheet in three levels

What to do: Ask for a worksheet for the same lesson, in three levels, and get the final JSON that goes into the sheet.

7. A worksheet in three levels
Write a worksheet for this lesson, in three levels:
basic: 4 questions. Identify, complete, match. At least one question points to a page in the book.
core: 4 questions. Explain in your own words, compare, give a real-life example.
challenge: 2 questions. Apply it to a new situation, or an open question with reasoning.
Each question ties to one of the lesson goals. No student names, no personal details.
If an exercise in the book fits, point to it ("exercise 3 on page 15") instead of copying it.

Return the full, final JSON, on a single line, with no text before or after it:
{"lesson":{"title":"","goals":[],"pages":""},"slides":[{"title":"","bullets":[],"notes":""}],"worksheet":{"basic":[],"core":[],"challenge":[]}}

Why this way: three levels on one sheet give every student a place to start, without preparing three different sheets. Whoever finishes the basic part moves on to core, and whoever finishes fast has a challenge. AI is good at writing similar questions at different levels, and that's exactly the part that takes the longest when you do it alone.

Common mistake: pasting the JSON from the previous step instead of this one. The Step 6 JSON is the full one: it has lesson, slides and worksheet. Without slides or worksheet the script stops and tells you what's missing.

Step 7 (10 minutes): The sheet and the script that create the files

What to do: Open a Google Sheet, paste the code from the appendix, and get a menu that creates slides and a worksheet from any row in the "Lessons" tab.

How:

  1. Open a new Google Sheet and give it a name, for example "Lesson plans: Grade 8 science".
  2. In the sheet: Extensions > Apps Script. A Code.gs file opens.
  3. Delete what's there, paste all the code from the appendix at the end of this playbook, and save. The first line is var LANG = 'he';: change it to var LANG = 'en'; for an English menu, tabs and headers.
  4. At the top, choose the setup function and click Run. Google asks for permission for the sheet, Slides and Docs. Approve. If you see "Google hasn't verified this app", click Advanced and then Go to the project. It's your code, in your account.
  5. Go back to the sheet and refresh. Three tabs appear, "Annual plan", "Book map" and "Lessons", plus a new menu called Lesson plans.
  6. Paste the annual plan from Step 3 and the book map from Step 2 into their tabs, from cell A2.
  7. In the "Lessons" tab, row 2: week, topic and pages. In column D (JSON): double-click the cell first, then paste the JSON from Step 6. That keeps it in one cell.
  8. Select the row, and in the menu: Lesson plans > Slides and worksheet for this row.
  9. After a few seconds a message appears, and columns E and F get links to the slides and the worksheet. The files are saved in your Drive, in the top folder. With var LANG = 'he'; the slide text is right-aligned; if the bullets still appear on the left, full right-to-left direction in Slides is one click: select the text and use the right-to-left button in the toolbar (or Format > Align & indent).
Lessons tab, for example (tab separated)
Week	Topic	Pages	JSON	Slides	Worksheet	Created
1	[topic]	[pages]	[the JSON from Step 6]			
2	[topic]	[pages]	[the JSON from Step 6]			

Why this way: the script sends no email and shares no files with anyone. It only creates two files in your folder and writes the links to the row. You open them, review, edit, and decide what reaches the class. If you want to share the deck with a colleague or the class, do it by hand from Drive, like any other file.

The next lesson: from here, each lesson takes a few minutes. In the same project chat, run this prompt, paste the JSON into a new row, and use the menu.

8. The next lesson in the plan
Now lesson [number] in the annual plan: [topic], pages [to fill].
Same structure as the previous lesson: goals, 8 to 12 slides with speaker notes, and a worksheet in three levels.
Start with a slide that links back to the previous lesson with one question.
Pages and exercises only from the map. No student names.
Return only the full JSON, on a single line:
{"lesson":{"title":"","goals":[],"pages":""},"slides":[{"title":"","bullets":[],"notes":""}],"worksheet":{"basic":[],"core":[],"challenge":[]}}

Copyright and privacy, briefly

  • The book belongs to the publisher. A classroom license doesn't always include uploading to an outside service. Read the license terms before you upload, and ask the publisher or your school leadership if it's unclear.
  • The output points, it doesn't copy. A slide or worksheet that quotes a whole page of the book is a copy. Write "See page 14", and use the book's exercises by reference.
  • Nothing is published outside class. A deck built on a specific book stays in your class, or in your school's closed environment. Not on an open website, not on social media.
  • No student names. Not in the prompts, not in the sheet, not in the worksheets it creates. The worksheet comes with a "Group: ___ Date: ___" line, and students fill it in by hand.
  • No grades and no personal data. This sheet is for planning only. Tracking students stays in the systems your school has approved.
  • The sheet is yours. Share it only with people who teach with you. Anyone with edit access can run the menu.

When something doesn't work

What you see What to do
"The JSON is not valid" The AI added text, or the JSON got cut off. Ask it: "Return only the full JSON, on a single line", and paste again.
The JSON spread over several rows in the sheet You pasted without double-clicking the cell. Delete it, double-click a cell in column D, and paste.
A message about missing slides or worksheet You pasted the JSON from Step 4 or 5. Paste the full JSON from Step 6.
"Select a lesson row" The cursor isn't in the "Lessons" tab, or it's on the header row.
No "Lesson plans" menu Refresh the sheet. If it's still missing, run onOpen from Apps Script.
Wrong pages on the slides The page numbers in the file differ from the printed ones. Fix the offset in the Step 2 prompt and rebuild the map.
The AI finds nothing in the book The file is scanned with no text. Run OCR (Step 2) and upload again.
The menu and tabs are in Hebrew The first line of the code is still var LANG = 'he';. Change it to 'en', save, and run setup again.

Full example: Grade 8 science at Horizon Middle School (a fictional school)

Everything in this example is fictional: the school, the book, the page numbers and the questions. There are no student names in it, and none are needed.

The frame: Grade 8 science, 3 hours a week, three classes. The teacher pasted the standards for the first unit and uploaded the grade's textbook, which the school holds a digital license for. Printed page 1 is page 7 in the file.

What came back in Step 1 (shortened):

Unit Topics Key concepts Hours
1. Cells What is a cell, plant and animal cells, the microscope cell, membrane, nucleus, cell wall 18
2. Body systems The digestive system, the respiratory system organ, system, enzyme [to fill]

The standards that were pasted didn't state hours for unit 2, so the AI marked [to fill] and didn't guess. The teacher checked the curriculum document and filled in 24.

The book map (Step 2, shortened):

Unit Pages Concepts Exercises in the book
1. Cells 12-41 cell, membrane, nucleus, cell wall, microscope exercises 1-4 on page 15, exercises 1-6 on page 28
2. Body systems 42-77 organ, system, enzyme exercises 1-5 on page 50
Microscope: lab work [not in the book]

In the spot check, four out of five sentences matched. The fifth was from a page with a table, and the AI wrote "not sure". That's fine: it didn't make anything up.

The annual plan, first four weeks (Step 3):

Week Unit Topic Pages Assessment
1 Cells What is a cell 12-15
2 Cells Plant and animal cells 16-22
3 Cells The microscope: how to look at a cell 23-27 Suggestion: group lab product
4 Cells Review and a short quiz 28-29 Quiz
The Step 6 JSON for the first lesson (shortened):
{"lesson":{"title":"What is a cell","goals":["Students will define a cell in one sentence","Students will identify three parts of a cell in a diagram"],"pages":"12-15"},"slides":[{"title":"Lesson goals","bullets":["What a cell is","Three parts of a cell"],"notes":"Ask: what is a leaf made of?"},{"title":"Everything is made of cells","bullets":["Every living thing is made of cells","See page 12"],"notes":"Show the picture on page 12"},{"title":"Exit question","bullets":["What is the difference between a cell and an organ?"],"notes":"One minute, written answer"}],"worksheet":{"basic":["Complete: every living thing is made of ______","Mark the membrane in the diagram on page 13"],"core":["Explain in your own words why the cell is called the unit of life"],"challenge":["If one cell is damaged, does the whole organ stop working? Explain"]}}

What happened in the sheet: in the "Lessons" tab, on the week 1 row, two links were written. The deck opened with a title slide ("What is a cell", "Pages in the book: 12-15") and three more slides, each with a speaker note. The worksheet opened with a title, a "Group and date" line, the lesson goals and three levels of questions.

Notice three things: the slides point to page 12 and don't copy from it. The microscope lab wasn't in the book, so the teacher will prepare it herself and won't get invented pages. And not a single student was named along the way.


Checklist before anything reaches the class

  • You hold a license to use the book this way, and you checked the license terms
  • No student names, ID numbers or grades in the prompts, the sheet or the worksheet
  • Every topic in the plan appears in the curriculum standards you pasted
  • You spot checked that the page numbers in the map are right
  • The timings in the lesson plan add up to 45 minutes
  • No long passages copied from the book on the slides or the worksheet
  • You reviewed and corrected the deck and the worksheet before the lesson
  • Every [to fill] and [not in the book] has been dealt with

Appendix: the full script (Code.gs)

Copy everything, from the first line to the last, into the Code.gs file. The only line to change is the first one: set var LANG = 'he'; to var LANG = 'en'; so the menu, tabs and files are in English.

Code.gs: the full code to copy
var LANG = 'he'; // 'he' or 'en': the menu, tab names, headers and file texts

/**
 * Lesson plans -> Google Slides + a Google Docs worksheet (free kit, gindiinnovation.com)
 *
 * Bound to a Google Sheet with three tabs (created by setup):
 *   Annual plan: week | unit | topic | pages | assessment | notes
 *   Book map:    unit | pages | concepts | exercises in the book
 *   Lessons:     week | topic | pages | JSON | slides | worksheet | created
 *
 * No AI key here: you run the prompts in your own AI tool and paste its JSON
 * into column D of the Lessons tab. The script only checks the JSON and creates
 * two files in your own Drive. It does not send anything and does not share anything.
 */

var TEXT = {
  he: {
    menu: 'מערכי שיעור',
    menuSetup: 'הכנת הגיליון',
    menuBuild: 'מצגת ודף עבודה לשורה',
    plan: 'תוכנית שנתית',
    map: 'מפת ספר',
    lessons: 'שיעורים',
    planHeaders: ['שבוע', 'יחידה', 'נושא', 'עמודים', 'הערכה', 'הערות'],
    mapHeaders: ['יחידה', 'עמודים', 'מושגים', 'תרגילים בספר'],
    lessonHeaders: ['שבוע', 'נושא', 'עמודים', 'JSON', 'מצגת', 'דף עבודה', 'נוצר'],
    levels: { basic: 'רמה בסיסית', core: 'רמת ליבה', challenge: 'רמת אתגר' },
    week: 'שבוע ',
    goals: 'מטרות השיעור',
    pages: 'עמודים בספר: ',
    worksheet: 'דף עבודה: ',
    group: 'קבוצה: ________   תאריך: ________',
    untitled: 'שיעור',
    setupOk: 'הלשוניות מוכנות. מלאו את התוכנית השנתית, והדביקו JSON בעמודה D בלשונית "שיעורים".',
    pickRow: 'עמדו על שורה של שיעור בלשונית "שיעורים" (לא על שורת הכותרות), ונסו שוב.',
    noJson: 'בשורה הזאת אין JSON בעמודה D.',
    badJson: 'ה-JSON לא תקין: ',
    done: 'נוצרו מצגת ודף עבודה ב-Drive שלכם. הקישורים נכתבו בשורה.',
  },
  en: {
    menu: 'Lesson plans',
    menuSetup: 'Set up the sheet',
    menuBuild: 'Slides and worksheet for this row',
    plan: 'Annual plan',
    map: 'Book map',
    lessons: 'Lessons',
    planHeaders: ['Week', 'Unit', 'Topic', 'Pages', 'Assessment', 'Notes'],
    mapHeaders: ['Unit', 'Pages', 'Concepts', 'Exercises in the book'],
    lessonHeaders: ['Week', 'Topic', 'Pages', 'JSON', 'Slides', 'Worksheet', 'Created'],
    levels: { basic: 'Basic', core: 'Core', challenge: 'Challenge' },
    week: 'Week ',
    goals: 'Lesson goals',
    pages: 'Pages in the book: ',
    worksheet: 'Worksheet: ',
    group: 'Group: ________   Date: ________',
    untitled: 'Lesson',
    setupOk: 'The tabs are ready. Fill in the annual plan, and paste JSON into column D of the "Lessons" tab.',
    pickRow: 'Select a lesson row in the "Lessons" tab (not the header row) and try again.',
    noJson: 'This row has no JSON in column D.',
    badJson: 'The JSON is not valid: ',
    done: 'Slides and a worksheet were created in your Drive. The links are in the row.',
  },
};

function t_() { return TEXT[LANG === 'en' ? 'en' : 'he']; }

function onOpen() {
  var t = t_();
  SpreadsheetApp.getUi().createMenu(t.menu)
    .addItem(t.menuSetup, 'setup')
    .addItem(t.menuBuild, 'buildLesson')
    .addToUi();
}

/** Creates the three tabs with their headers, if they are missing. Run once. */
function setup() {
  var t = t_();
  var ss = SpreadsheetApp.getActive();
  [[t.plan, t.planHeaders], [t.map, t.mapHeaders], [t.lessons, t.lessonHeaders]].forEach(function (s) {
    var sh = ss.getSheetByName(s[0]) || ss.insertSheet(s[0]);
    if (sh.getLastRow() === 0) {
      sh.getRange(1, 1, 1, s[1].length).setValues([s[1]]).setFontWeight('bold');
      sh.setFrozenRows(1);
    }
  });
  onOpen();
  SpreadsheetApp.getUi().alert(t.setupOk);
}

// ---------- Pure helpers (no Google services, testable anywhere) ----------

/** Accepts the model's answer even with a json code fence or text around it */
function parseLessonJson(text) {
  var s = String(text || '').trim().replace(/^`{3}[a-zA-Z]*\s*/, '').replace(/\s*`{3}$/, '');
  var start = s.indexOf('{');
  var end = s.lastIndexOf('}');
  if (start < 0 || end <= start) throw new Error('no { ... } found');
  var data = JSON.parse(s.slice(start, end + 1));
  if (!data || typeof data !== 'object') throw new Error('not an object');
  var slides = Array.isArray(data.slides) ? data.slides : [];
  slides = slides
    .filter(function (x) { return x && String(x.title || '').trim(); })
    .map(function (x) {
      return {
        title: String(x.title).trim(),
        bullets: Array.isArray(x.bullets) ? x.bullets.map(function (b) { return String(b).trim(); }).filter(String) : [],
        notes: String(x.notes || '').trim(),
      };
    });
  if (!slides.length) throw new Error('missing "slides": at least one slide with a title is needed');
  if (!data.worksheet || typeof data.worksheet !== 'object' || Array.isArray(data.worksheet)) throw new Error('missing "worksheet"');
  var lesson = data.lesson && typeof data.lesson === 'object' ? data.lesson : {};
  return {
    lesson: {
      title: String(lesson.title || '').trim(),
      goals: Array.isArray(lesson.goals) ? lesson.goals.map(function (g) { return String(g).trim(); }).filter(String) : [],
      pages: String(lesson.pages || '').trim(),
    },
    slides: slides,
    worksheet: data.worksheet,
  };
}

/** Worksheet object -> lines: a header per level, then numbered questions. Empty levels are skipped. */
function worksheetLines(worksheet, labels) {
  var l = labels || t_().levels;
  var out = [];
  ['basic', 'core', 'challenge'].forEach(function (k) {
    var items = Array.isArray(worksheet && worksheet[k]) ? worksheet[k] : [];
    items = items.map(function (q) { return String(q == null ? '' : q).trim(); }).filter(String);
    if (!items.length) return;
    if (out.length) out.push('');
    out.push(l[k]);
    items.forEach(function (q, i) { out.push((i + 1) + '. ' + q); });
  });
  return out;
}

// ---------- Google Sheets, Slides and Docs ----------

/** Right-aligns a Slides text range when LANG is not English (full RTL direction is one click in Slides) */
function alignText_(text) {
  if (LANG !== 'en' && !text.isEmpty()) text.getParagraphStyle().setParagraphAlignment(SlidesApp.ParagraphAlignment.END);
}

function setPlaceholder_(slide, type, text) {
  var ph = slide.getPlaceholder(type);
  if (!ph) return;
  var range = ph.asShape().getText();
  range.setText(text);
  alignText_(range);
}

/** Menu: reads the JSON in the selected row of Lessons, creates the slides and the worksheet, writes the links back */
function buildLesson() {
  var t = t_();
  var ui = SpreadsheetApp.getUi();
  var sheet = SpreadsheetApp.getActiveSheet();
  var row = sheet.getActiveRange().getRow();
  if (sheet.getName() !== t.lessons || row < 2) { ui.alert(t.pickRow); return; }
  var values = sheet.getRange(row, 1, 1, 7).getValues()[0];
  if (!String(values[3]).trim()) { ui.alert(t.noJson); return; }
  var data;
  try { data = parseLessonJson(values[3]); } catch (e) { ui.alert(t.badJson + e.message); return; }

  var week = String(values[0]).trim();
  var title = data.lesson.title || String(values[1]).trim() || t.untitled;
  var name = (week ? t.week + week + ': ' : '') + title;
  var pages = data.lesson.pages || String(values[2]).trim();

  // Slides: a title slide, then one slide per item, speaker notes when there are any
  var deck = SlidesApp.create(name);
  var cover = deck.getSlides()[0];
  setPlaceholder_(cover, SlidesApp.PlaceholderType.CENTERED_TITLE, title);
  setPlaceholder_(cover, SlidesApp.PlaceholderType.SUBTITLE, pages ? t.pages + pages : '');
  data.slides.forEach(function (s) {
    var slide = deck.appendSlide(SlidesApp.PredefinedLayout.TITLE_AND_BODY);
    setPlaceholder_(slide, SlidesApp.PlaceholderType.TITLE, s.title);
    setPlaceholder_(slide, SlidesApp.PlaceholderType.BODY, s.bullets.join('\n'));
    if (s.notes) {
      var notes = slide.getNotesPage().getSpeakerNotesShape().getText();
      notes.setText(s.notes);
      alignText_(notes);
    }
  });
  var deckUrl = deck.getUrl();
  deck.saveAndClose();

  // Worksheet: a title, the group and date line (no student names), the goals, then the three levels
  var doc = DocumentApp.create(t.worksheet + name);
  var body = doc.getBody();
  var first = body.getParagraphs()[0];
  first.setText(t.worksheet + title);
  first.setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(t.group).setHeading(DocumentApp.ParagraphHeading.NORMAL);
  if (pages) body.appendParagraph(t.pages + pages).setHeading(DocumentApp.ParagraphHeading.NORMAL);
  if (data.lesson.goals.length) {
    body.appendParagraph(t.goals).setHeading(DocumentApp.ParagraphHeading.HEADING2);
    data.lesson.goals.forEach(function (g) { body.appendListItem(g); });
  }
  var headers = [t.levels.basic, t.levels.core, t.levels.challenge];
  worksheetLines(data.worksheet, t.levels).forEach(function (line) {
    var p = body.appendParagraph(line);
    p.setHeading(headers.indexOf(line) >= 0 ? DocumentApp.ParagraphHeading.HEADING2 : DocumentApp.ParagraphHeading.NORMAL);
  });
  if (LANG !== 'en') body.getParagraphs().forEach(function (p) { p.setLeftToRight(false); });
  var docUrl = doc.getUrl();
  doc.saveAndClose();

  if (!String(values[1]).trim()) sheet.getRange(row, 2).setValue(title);
  if (!String(values[2]).trim() && pages) sheet.getRange(row, 3).setValue(pages);
  sheet.getRange(row, 5, 1, 3).setValues([[deckUrl, docUrl, new Date()]]);
  ui.alert(t.done);
}

// For local tests only (Node). Apps Script has no `module`, so this line does nothing there.
if (typeof module !== 'undefined') module.exports = { parseLessonJson, worksheetLines };

Disclaimer

This content is general recommendations only, not professional, legal, accounting or tax advice. Every school, subject and class is different. You are responsible for checking that it fits your situation, and for checking the plan, the prompts, the AI output and the code before you use them in class. Outside tools, prices and terms of service change, so check them at the source. For questions about copyright and licensing, check the book's license terms or consult a lawyer. When you keep any information about students, follow your school's and your local education authority's rules, and don't enter student names or identifying details into AI tools.

The content, prompts, templates and code are provided as is, with no warranty that they fit a particular purpose or are free of errors. Gindi Innovation and Aviad Gindi are not liable for any damage, direct or indirect, arising from using them or relying on them, including output from AI tools. Use is subject to the site terms.


What's next

This playbook works with one paste per lesson: you take the JSON from the AI to the sheet. The same process can be connected so every lesson in a whole unit is built at once, in your school's slide template, and waits in a folder for you to review. What reaches the class stays your decision.