google-slides-copy
v0.1.0
Published
Copy slides between Google Slides presentations via PPTX round-trip
Downloads
249
Maintainers
Readme
google-slides-copy
Copy slides between Google Slides presentations programmatically. Built for workflows that assemble a destination deck from multiple source presentations.
Google Slides has no native API for copying a slide from one presentation to another. This library works around that gap via a PPTX round-trip: it exports presentations as Office Open XML (.pptx), performs the copy as in-memory ZIP/XML manipulation, and re-imports the result — all without any Microsoft tooling.
How it works
A PPTX file is a ZIP archive. A slide is a self-contained XML file inside that archive. Copying a slide between decks is therefore a structural operation — moving XML nodes and their associated media — rather than a semantic reconstruction. This produces high-fidelity results for standard slide content (text, shapes, images, tables, animations).
Limitations: Google-specific content — Sheets-linked charts, embedded Google Drawings, linked Forms — does not survive the PPTX round-trip and will be converted to static equivalents or dropped.
Installation
npm install google-slides-copyPrerequisites
Your Google Cloud project must have the following APIs enabled:
- Google Slides API
- Google Drive API
Your OAuth2 credentials (or service account) need the following scopes:
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/presentationsAPI
The library exposes three primitives and one convenience wrapper.
exportPptx(auth, presentationId) → Promise<Buffer>
Downloads a Google Slides presentation as an in-memory PPTX buffer. This makes one Google Drive API call.
| Parameter | Type | Description |
|------------------|----------|--------------------------------------|
| auth | object | Authenticated Google OAuth2 client |
| presentationId | string | Google Drive file ID of the presentation |
copySlide(sourcePptx, sourceSlideIndex, destPptx, insertIndex) → Promise<Buffer>
Copies one slide from a source PPTX buffer into a destination PPTX buffer. Makes no Google API calls — entirely in-memory. Returns the updated destination buffer, which should be passed into subsequent calls.
| Parameter | Type | Description |
|--------------------|------------------|-----------------------------------------------------------------------------|
| sourcePptx | Buffer | Source presentation buffer (from exportPptx) |
| sourceSlideIndex | number | 0-based index of the slide to copy |
| destPptx | Buffer | Destination presentation buffer |
| insertIndex | number \| null | 0-based position to insert at. Pass null to append at the end. Values beyond the current slide count also append. |
importPptx(auth, presentationId, pptxBuffer) → Promise<void>
Uploads a PPTX buffer back to Google Drive, replacing the given presentation in-place and converting it to Google Slides format. This makes one Google Drive API call.
| Parameter | Type | Description |
|------------------|----------|--------------------------------------------------|
| auth | object | Authenticated Google OAuth2 client |
| presentationId | string | Google Drive file ID to replace |
| pptxBuffer | Buffer | Final assembled PPTX buffer (from copySlide) |
copySlideOneShot(options) → Promise<void>
Convenience wrapper that exports source and destination, copies one slide, and re-imports — all in a single call. Use this for simple one-off operations. For bulk workflows involving multiple sources or slides, use the three primitives above to avoid redundant API calls.
| Option | Type | Description |
|------------------------------|------------------|------------------------------------|
| auth | object | Authenticated Google OAuth2 client |
| sourcePresentationId | string | Source presentation file ID |
| sourceSlideIndex | number | 0-based slide index in source |
| destinationPresentationId | string | Destination presentation file ID |
| insertIndex | number \| null | Insert position (null = end) |
Usage
Basic: copy one slide
const slides = require('google-slides-copy');
const { google } = require('googleapis');
// Set up your OAuth2 client (shown here with a service account)
const auth = new google.auth.GoogleAuth({
keyFile: 'service-account.json',
scopes: [
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/presentations',
],
});
await slides.copySlideOneShot({
auth,
sourcePresentationId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms',
sourceSlideIndex: 0, // first slide (0-based)
destinationPresentationId: '1EiJ6n3a7T9X2kLvQpRsDcFgHmYo4wN8bZuAeXcPqVtI',
insertIndex: null, // append at the end
});Bulk: assemble a deck from multiple sources
This is the primary use case. Export the destination once, copy all slides in memory, then import once at the end. Only two Google API calls touch the destination regardless of how many slides you copy.
const slides = require('google-slides-copy');
const DESTINATION_ID = '1EiJ6n3a7T9X2kLvQpRsDcFgHmYo4wN8bZuAeXcPqVtI';
const sources = [
{ presentationId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms', slideIndexes: [0, 1] },
{ presentationId: '1CyJm4b8U0YB6GOeLwRteDgIjNZp5xO9cAvBfYdQrWuJ', slideIndexes: [0, 1] },
];
// Export destination once
let destPptx = await slides.exportPptx(auth, DESTINATION_ID);
// Start at position 1 to preserve an existing title slide at index 0.
// Use position 0 if the destination should be entirely replaced.
let position = 1;
for (const source of sources) {
const sourcePptx = await slides.exportPptx(auth, source.presentationId);
for (const slideIndex of source.slideIndexes) {
destPptx = await slides.copySlide(sourcePptx, slideIndex, destPptx, position);
position++;
}
}
// Import the assembled result once
await slides.importPptx(auth, DESTINATION_ID, destPptx);Append all slides from one presentation to another
const slides = require('google-slides-copy');
const SOURCE_ID = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms';
const DEST_ID = '1EiJ6n3a7T9X2kLvQpRsDcFgHmYo4wN8bZuAeXcPqVtI';
const [sourcePptx, destPptx_initial] = await Promise.all([
slides.exportPptx(auth, SOURCE_ID),
slides.exportPptx(auth, DEST_ID),
]);
// Copy slides 0, 1, and 2 from the source, appending each to the destination
let destPptx = destPptx_initial;
for (let i = 0; i < 3; i++) {
destPptx = await slides.copySlide(sourcePptx, i, destPptx, null); // null = append
}
await slides.importPptx(auth, DEST_ID, destPptx);Authentication
The library accepts any authenticated Google OAuth2 client. The two most common patterns:
Service account (server-to-server, recommended for automation):
const { google } = require('googleapis');
const auth = new google.auth.GoogleAuth({
keyFile: 'path/to/service-account.json',
scopes: [
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/presentations',
],
});The service account must be granted editor access to both the source and destination presentations in Google Drive.
OAuth2 (user-delegated access):
const { google } = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
'YOUR_CLIENT_ID',
'YOUR_CLIENT_SECRET',
'YOUR_REDIRECT_URI'
);
oauth2Client.setCredentials({ refresh_token: 'USER_REFRESH_TOKEN' });Layout matching
When a slide is copied, the library attempts to assign it the most appropriate layout in the destination deck:
- Match by name — reads the source slide's layout name (e.g.
"Title Slide","Blank") and searches the destination for a layout with the same name. - Blank fallback — if no name match is found, the slide is assigned the destination's
"Blank"layout. - Last resort — falls back to
slideLayout1.xmlif no Blank layout exists.
For slides built with primitive shapes (no layout-inherited placeholders), the Blank fallback produces clean results regardless of template differences between source and destination.
License
MIT © RingCentral, Inc.
