Skip to content
PaperJSX

[FIELD NOTE]

Generate PPTX from Google Sheets data with PaperJSX

Automate Google Sheets to PowerPoint: read sheet data via the Sheets API, transform it into a PaperJSX JSON schema, generate a PPTX with editable charts.

2026-07-217 min read

This is a technical decision aid. Check current package and hosted-access details before implementation.

Read data from a Google Sheet, transform it into a PaperJSX JSON schema, and generate a PowerPoint deck with native editable charts — all in a single Node.js script. No copy-paste, no Google Slides, no manual formatting. The output is a real .pptx file that opens in Microsoft PowerPoint. Run it once manually, schedule it with cron, or trigger it from a webhook. Total setup: 15 minutes.

What problem does this solve?

Sales teams maintain pipeline data in Google Sheets. Every week, a manager needs a PowerPoint deck summarizing the numbers. The current workflow: open the sheet, copy cells, paste into PowerPoint, manually create charts, fix formatting, save, email. This takes 30–60 minutes every week and produces inconsistent results.

The automated workflow: a Node.js script reads the Google Sheet, builds a PaperJSX JSON schema with charts and tables, generates a PPTX, and saves it to a shared drive or emails it. The script runs in under 5 seconds. The output is consistent every time.

Google Sheets → PPTX pipeline Google Sheet API Node.js transform JSON PaperJSX .pptx with charts Runs in <5 seconds. Schedule with cron or trigger via webhook. paperjsx.com — fig 1

How do you set up Google Sheets API credentials?

For server-side automation, use a Google Cloud service account — no OAuth consent screen, no user login, no browser interaction.

According to the Google Sheets API Node.js quickstart, the setup requires three steps: enable the Google Sheets API in your Google Cloud project, create a service account with a JSON key, and share the target spreadsheet with the service account's email address (e.g., sheets-reader@your-project.iam.gserviceaccount.com).

Save the downloaded JSON key as credentials.json in your project directory. Do not commit this file to version control — add it to .gitignore.

2. Install dependencies

npm install google-spreadsheet google-auth-library @paperjsx/json-to-pptx

Three packages: google-spreadsheet (clean wrapper around the Google Sheets API), google-auth-library (service account authentication), and @paperjsx/json-to-pptx (PPTX generation from JSON).

How do you read data from the sheet?

Assume a Google Sheet called "Q3 Sales" with this structure:

Region | Q1 | Q2 | Q3 | Growth

NA | 3200 | 3800 | 4200 | 10.5%

EMEA | 2400 | 2900 | 3100 | 6.9%

APAC | 1600 | 2200 | 2800 | 27.3%

import { GoogleSpreadsheet } from "google-spreadsheet";
import { JWT } from "google-auth-library";

const SHEET_ID = "1abc...your-sheet-id";

const auth = new JWT({
  email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
  key: process.env.GOOGLE_PRIVATE_KEY,
  scopes: ["https://www.googleapis.com/auth/spreadsheets.readonly"],
});

export async function readSalesData() {
  const doc = new GoogleSpreadsheet(SHEET_ID, auth);
  await doc.loadInfo();

  const sheet = doc.sheetsByTitle["Q3 Sales"];
  const rows = await sheet.getRows();

  return rows.map(row => ({
    region: row.get("Region"),
    q1: Number(row.get("Q1")),
    q2: Number(row.get("Q2")),
    q3: Number(row.get("Q3")),
    growth: row.get("Growth"),
  }));
}

The google-spreadsheet package returns rows as objects with column headers as keys. We extract the data we need and convert numeric strings to numbers with Number().

4. Transform into PaperJSX schema

export function buildDeckSchema(data) {
  return {
    slides: [
      // Slide 1: title
      {
        elements: [
          { type: "text", value: "Q3 2026 sales report",
            style: { fontSize: 36, bold: true } },
          { type: "text",
            value: `Generated ${new Date().toISOString().slice(0, 10)} from Google Sheets`,
            style: { fontSize: 14, color: "#6B6960" } }
        ]
      },

      // Slide 2: bar chart — revenue by region
      {
        elements: [
          { type: "text", value: "Revenue by region",
            style: { fontSize: 24, bold: true } },
          {
            type: "chart",
            chartType: "bar",
            data: {
              categories: data.map(d => d.region),
              series: [
                { name: "Q2", values: data.map(d => d.q2) },
                { name: "Q3", values: data.map(d => d.q3) }
              ]
            }
          }
        ]
      },

      // Slide 3: data table
      {
        elements: [
          { type: "text", value: "Regional breakdown",
            style: { fontSize: 24, bold: true } },
          {
            type: "table",
            headers: ["Region", "Q1", "Q2", "Q3", "Growth"],
            rows: data.map(d => [
              d.region,
              `$${d.q1.toLocaleString()}`,
              `$${d.q2.toLocaleString()}`,
              `$${d.q3.toLocaleString()}`,
              d.growth
            ]),
            style: {
              headerBackground: "#1A1A18",
              headerColor: "#FFFFFF"
            }
          }
        ]
      }
    ]
  };
}

The transform function maps spreadsheet rows to PaperJSX elements. Charts use data.map() to extract arrays from the row objects — categories from the region column, values from the quarterly columns. This is the same data structure used in the JSON-to-PPTX quickstart.

5. Generate the PPTX

import { generate } from "@paperjsx/json-to-pptx";
import { writeFileSync } from "node:fs";
import { readSalesData } from "./read-sheet.mjs";
import { buildDeckSchema } from "./build-schema.mjs";

const data = await readSalesData();
console.log(`Read ${data.length} regions from Google Sheets`);

const schema = buildDeckSchema(data);
const buffer = await generate(schema);

const filename = `q3-sales-${new Date().toISOString().slice(0, 10)}.pptx`;
writeFileSync(filename, buffer);
console.log(`Generated: ${filename}`);
export GOOGLE_SERVICE_ACCOUNT_EMAIL=sheets-reader@your-project.iam.gserviceaccount.com
export GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."

node generate.mjs
# Read 3 regions from Google Sheets
# Generated: q3-sales-2026-04-12.pptx

Open q3-sales-2026-04-12.pptx in PowerPoint. It contains three slides: a title slide with the generation date, a bar chart comparing Q2 and Q3 revenue by region (native and editable), and a styled data table with all quarterly figures and growth rates. The chart is a real PowerPoint chart — click it to see the data table.

Complete script

Here is the entire pipeline in one file for easy copy-paste.

import { GoogleSpreadsheet } from "google-spreadsheet";
import { JWT } from "google-auth-library";
import { generate } from "@paperjsx/json-to-pptx";
import { writeFileSync } from "node:fs";

const SHEET_ID = process.env.GOOGLE_SHEET_ID;

// 1. Authenticate
const auth = new JWT({
  email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
  key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n"),
  scopes: ["https://www.googleapis.com/auth/spreadsheets.readonly"],
});

// 2. Read Google Sheet
const doc = new GoogleSpreadsheet(SHEET_ID, auth);
await doc.loadInfo();
const sheet = doc.sheetsByIndex[0];
const rows = await sheet.getRows();
const data = rows.map(r => ({
  region: r.get("Region"),
  q1: Number(r.get("Q1")),
  q2: Number(r.get("Q2")),
  q3: Number(r.get("Q3")),
  growth: r.get("Growth"),
}));

// 3. Build PaperJSX schema
const schema = {
  slides: [
    { elements: [
      { type: "text", value: `${doc.title}`,
        style: { fontSize: 36, bold: true } },
    ]},
    { elements: [
      { type: "text", value: "Revenue by region",
        style: { fontSize: 24, bold: true } },
      { type: "chart", chartType: "bar",
        data: {
          categories: data.map(d => d.region),
          series: [
            { name: "Q2", values: data.map(d => d.q2) },
            { name: "Q3", values: data.map(d => d.q3) },
          ]
        }},
    ]},
    { elements: [
      { type: "text", value: "Breakdown",
        style: { fontSize: 24, bold: true } },
      { type: "table",
        headers: ["Region", "Q1", "Q2", "Q3", "Growth"],
        rows: data.map(d => [d.region, `$${d.q1}`, `$${d.q2}`, `$${d.q3}`, d.growth]) },
    ]}
  ]
};

// 4. Generate PPTX
const buffer = await generate(schema);
const filename = `report-${new Date().toISOString().slice(0, 10)}.pptx`;
writeFileSync(filename, buffer);
console.log(`✓ ${filename} (${data.length} regions, ${schema.slides.length} slides)`);

How do you schedule it with cron or webhooks?

Run the script every Monday morning:

# Every Monday at 8:00 AM
0 8 * * 1 cd /path/to/project && node sheets-to-pptx.mjs

Or trigger it from a Google Apps Script when the sheet changes — add a webhook that calls your API endpoint, which runs the generation script and saves the output to Google Drive or sends it via email. For the API route pattern, see the Express tutorial or the Next.js tutorial.

The same pipeline works with other data sources. Replace the Google Sheets read with a database query (see the invoice tutorial), an API response, or a CSV file. PaperJSX does not care where the data comes from — it only cares about the JSON schema.

Keep the workflow in your product.

Use local engines or local MCP to prove the artifact. When release controls matter, discuss an approved hosted evaluation for a real delivery.