Getting DRY with Figma plugins
There's a concept called DRY in development. It means Don't Repeat Yourself and is all about code reduction through declaring and reusing shared resources. Say for example that I have a simple conditional statement that checks for all the correct node types in a Figma plugin to determine if it can make adjustments. I could do the following to do it:
if (node.type === "FRAME") { const frame = figma.createRectangle() frame.width = node.width frame.height = node.height } if (node.type === "GROUP") { const frame = figma.createRectangle() frame.width = node.width frame.height = node.height } if (node.type === "ELLIPSE") { const frame = figma.createRectangle() frame.width = node.width frame.height = node.height }
But what I'm doing there is redeclaring the same information multiple times, what a waste of compute power. So instead I can bundle these into one. It's difficult to use the !==
declaration because so many things can need exclusion, so the best way is to use our friend .includes()
.
const nodeTypes = ["FRAME", "GROUP", "ELLIPSE"] if (nodeTypes.includes(node.type)) { const frame = figma.createRectangle() frame.width = node.width frame.height = node.height }
In design we can do a similar thing using atomic components, building up reusable assets as Lego blocks to avoid having to keep creating new assets. However, we can also take it a step further into the productivity side of design using Figma plugins.
Figma Plugins, what's the deal?
Well, you've had a sneak peek at a bit of Figma plugin code above. Figma plugins use Typescript under the hood to allow you to create, modify and elevate your Figma project.
As you probably know, I'm an evangelist for the "Designers should Code" philosophy, at least, I feel Designers should understand some code. And I've taken that further to believe that knowing how to write enough Javascript to build your own Figma plugins is a superpower all Designers should try to harness.
Figma plugins enable Designers to perform at optimum operational capacity without the burden of loads of admin overhead. Now there are tonnes of available plugins in the Figma Community but that doesn't mean you can find one to solve your problem, some issues are just too unique to your use case.
At HomeHero, we're using a bunch of custom Figma plugins that I've written (including the ones I've actually released) to better maintain our files. I'll cover some of these below.
Ultimately, Figma plugins stop us from repeating ourselves with common admin tasks to clean up our files, they allow us to focus on designing the best experience instead of organising our contents.
The 'Gist' of how plugins work
A couple of these Gists use the Scripter plugin to work as it has its own API to simplify some bits, grab it here. Of course, you can use the standard Figma API to perform any of this too, it just might be a little more verbose.
HomeHero TextLint
One of the first plugins I wrote for us was aimed at simplifying text linting. We have a bunch of phrases we write in our designs regularly thanks to our industry, and there are many times that we mistype them and this sometimes makes its way to the product (oh dear!). To avoid that, I've written a super simple linter that parses the document and updates all text characters that match my arrays. It's not the cleanest or best code, but it saves us a bunch of time.
const find = ["Move in", "move in", "Homehero", "wifi", "Wifi", "WiFi"]; const replace = ["Move-in", "move-in", "HomeHero", "Wi-Fi"]; const layers = figma.currentPage.findAll((node) => node.type === "TEXT"); figma.currentPage.selection = layers; const { selection } = figma.currentPage; async function lintTextNodes(): Promise { figma.root.children.flatMap((pageNode) => pageNode.selection.forEach(async (node) => { if (layers.length > 0 && node.type === "TEXT") { await figma.loadFontAsync(node.fontName as FontName); node.characters = node.characters.replaceAll(find[0], replace[0]); node.characters = node.characters.replaceAll(find[1], replace[1]); node.characters = node.characters.replaceAll(find[2], replace[2]); node.characters = node.characters.replaceAll(find[3], replace[3]); node.characters = node.characters.replaceAll(find[4], replace[3]); node.characters = node.characters.replaceAll(find[5], replace[3]); } return Promise.resolve("Done!"); }) ); if (layers.length === 0) { return Promise.reject("No layers selected"); }} lintTextNodes(); figma.notify("All linted"); figma.closePlugin();
HomeHero CropAll
The second was a quick way to set all images to "FILL"
mode and apply a resize constraint on them. It's not full-proof, but it gets all your images to the same basic starting point. We needed to do this because we have a tonne of images we use for our Picks feature and they all need to be set to the same size to reduce bandwidth when served to the app.
When you have over 400 images, you want this!
const layers = figma.currentPage.findAll(isImage); figma.currentPage.selection = layers for (let shape of await find(selection(), n => isImage(n) && n)) { shape.fills = shape.fills.map(p => isImage(p) ? {...p, scaleMode: "FILL",} : p) shape.resize(375, 375)}
HomeHero Slide Export
Finally, we work in Figma to create our investor slide decks and other slides to share our design progress or lunch and learns. It's great but it's annoying when you need to then turn these into an exported set of slides.
Luckily, we can use Figma plugins to apply some simple settings to make this happen and then export those Frames for others to use (you can even use my Lazy PDF app to compress it into a lossless PDF afterward).
const layers = figma.currentPage.findAll(); const frames = layers.filter(isFrame); async function renameFrames() { let items = frames; if (items.length > 0) { for (const node of items) { if (node.type === "FRAME") { node.name = ((node.name).substr(0,2) != 'Slide/') ? node.name = "Slide" + "/" + node.name : node.name; } } }} renameFrames(); figma.currentPage.selection = layers.filter((n) => n.name.startsWith("Slide/")); const { selection } = figma.currentPage; function hasValidSelection(nodes) { return !(!nodes || nodes.length === 0); } const settings = [ { format: "PNG", suffix: "@1x", constraint: { type: "SCALE", value: 1 } }, { format: "PNG", suffix: "@2x",constraint: { type: "SCALE", value: 2 } }, { format: "PNG", suffix: "@3x", constraint: { type: "SCALE", value: 3 } }, { format: "SVG", suffix: "" },]; async function main(nodes): Promise { if (!hasValidSelection(nodes)) return Promise.reject("No valid selection"); for (let node of nodes) { node.exportSettings = settings; } return Promise.resolve("Done!"); } main(selection).then((res) => figma.closePlugin(res));
Published plugins
Beyond these, I've written a bunch of small utility plugins to make designer life easier. You'll see them all on my Plugins.run site, but you can find them all here also.
In the end, it's all about saving time
Ultimately, we all want to save time and not do consistent, repetitive tasks each day. Figma plugins, and having the knowledge to write them, can be that solution for you. I can't count how many countless hours I've saved myself by writing the plugins I have and using plugins others have written too.
If you want to get started with Figma plugins, try out my Gists, or, follow this awesome tutorialfrom Dan Hollick who shares a lot of my feelings about designers and code.
If you do end up creating anything awesome for your team, please send me a note and share some Gists! I love seeing what people are up to and seeing how I can adapt and utilise what they've done like I hope people do with my shares.