Instrument x Seth Akkerman
Projects Afterpay Campaign Generator

Afterpay Campaign Generator

A custom Figma plugin that generates entire campaign asset libraries from a Google Spreadsheet and Drive folder — cutting production time and cost by more than half

Campaign production at scale is a volume problem. You have dozens of ad formats, multiple markets, rotating headlines, different merchant images — and someone has to manually populate every frame. It’s the kind of work that’s hard to scope, easy to underestimate, and almost entirely mechanical. For Afterpay, we built a Figma plugin that eliminates that mechanical layer entirely.

How it works

The system is built on a simple naming convention: the template name in the spreadsheet matches the frame name in Figma, and the column names match the layer names. That’s the whole contract. You set up your Figma frames with named layers — headline, image, merchant, terms and conditions — and your spreadsheet columns mirror those names exactly. Point the plugin at a spreadsheet URL and a Google Drive folder, hit go, and it rips through every row, finds the matching frame, replaces each layer with the corresponding content, and moves on. If a template doesn’t have a particular layer, it skips it gracefully. No crashes, no manual cleanup.

The piece that required more careful engineering was the connection between Google and Figma. Rather than asking users to manage API keys or authenticate manually each session, we built a cloud service with a dedicated Google IAM service account that acts as a trusted bridge between the two platforms. You share your spreadsheet and Drive folder with that service account — viewer access is enough — and the handshake is established. The plugin inherits those permissions during the design session, so images and data pull in securely without ever leaving the client’s own Drive environment. Both the client and the agency can work from the same spreadsheet and folder, which keeps everyone aligned on the source of truth.

The numbers

The first time we ran this against a real campaign, someone on the team pulled the comparison: scoping the same job without the tool would have cost $56,620 and taken 400 hours. With the tool, it came in at $20,615 and 150 hours. That’s $36,005 saved and 250 hours returned to the team — a 63.6% cost reduction on a single production run.

That’s not a marginal improvement. That’s a fundamentally different way to staff and scope a campaign.

Applying this to your project

  1. Clone the plugin shell, decide on one backend. Copy manifest.json, code.ts, ui.html, tsconfig.json, and the root package.json — these are the actual Figma plugin. The repo ships two parallel backend implementations: netlify/functions/drive-proxy.js (OAuth2 refresh-token auth, toggled on via the “useNetlify” checkbox in ui.html) and google-sheet-function/index.js (a standalone Google Cloud Function using service-account auth). Pick one, copy only that folder, and delete the other — keeping both just recreates the confusing runtime toggle this project currently has.

  2. Set up credentials outside the repo. There’s no .env.example — secrets are configured directly in the hosting dashboard. For the Netlify path, set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REFRESH_TOKEN under your Netlify site’s Environment Variables. For the Cloud Function path, deploy with a GCP service account (see the deploy script in google-sheet-function/package.json) and share your target Google Sheet and Drive folder with that service account’s email, Editor/Viewer access respectively.

  3. Swap out everything hardcoded for your own project. ui.html hardcodes both backend URLs directly in the fetch calls (search for pluginauthtest.netlify.app and us-central1-presentationbuilder-459014.cloudfunctions.net) — replace with your own deployed function URL. manifest.json’s networkAccess.allowedDomains array must list every domain your UI actually calls, or Figma silently blocks the request. Also replace the leftover personal note in ui.html (“Kat, you rockstar, click here…”) with real UI copy before handing this to anyone else.

  4. Know where your data actually plugs in. The spreadsheet contract is fixed by the SpreadsheetRow interface in code.ts: columns must be named templatetype, headline, and image/image1/image2/image3 (case-insensitive). On the Figma canvas, findTemplates() in code.ts scans the current page for any FRAME or COMPONENT whose name contains “template”, “master”, “base”, or “layout” — it strips those keywords via extractTemplateName(), lowercases what’s left, and matches it against each row’s templatetype. Inside each matched template, analyzeTemplate() only picks up TEXT layers (matched to sheet columns by lowercase layer name) and RECTANGLE/ELLIPSE layers whose name contains “image”, “photo”, or “picture” — so your own templates must follow this exact layer-naming convention to receive content.

  5. Watch for these non-obvious gotchas. EPS images are explicitly rejected server-side in both backend files — surface that limitation to users before they paste a sheet full of EPS filenames. If no Drive folder ID is supplied, the plugin silently falls back to placeholder color fills instead of erroring, so test both paths. code.ts inserts a 500ms pause every 10 rows (createAssetsFromData) to avoid overwhelming Figma — tune this if you expect much larger sheets. Finally, code.js is gitignored and only produced by npm run build (or npm run watch) from code.ts — after cloning, nothing will run in Figma until you build it once.

Other ways to repurpose this

Product Design

Web Design

Marketing

Branding

Dependencies

googleapis 126.0.1 Google Sheets/Drive API client used by both backend functions (netlify/functions/drive-proxy.js and google-sheet-function/index.js) to read spreadsheet rows and list/download Drive images referenced in the sheet.
google-auth-library 9.15.1 Provides GoogleAuth for the service-account authentication flow in google-sheet-function/index.js (the Google Cloud Function backend variant).
typescript 5.9.2 Compiles code.ts into the code.js entry point that Figma actually loads, via the "build"/"watch" npm scripts and tsconfig.json.
@figma/plugin-typings 1.117.0 Supplies TypeScript type definitions for the Figma Plugin API (figma.*, FrameNode, TextNode, etc.) so code.ts type-checks against the real plugin sandbox.
Role
Creative Technologist
Tools
Figma Plugin API Google Sheets API Google Drive API OAuth JavaScript
Metrics
63.6% cost reduction and 250 hours saved on first campaign production run
URL
https://www.figma.com/community/plugin/1541914848472265364/generatefromspreadsheet
Video thumbnail for Afterpay Campaign Generator