πŸͺ† Template intended to serve as a starting point if you want to bootstrap a Figma Plugin in TypeScript.

Overview

Codely logo

πŸͺ† Codely Figma Plugin Skeleton

Build status Codely Open Source CodelyTV Courses

Template intended to serve as a starting point if you want to bootstrap a Figma Plugin in TypeScript.

Take a look, play and have fun with this. Stars are welcome 😊

The purpose of this repository is to leave it with the bare minimum dependencies and tools needed to build Figma Plugins but based on software development best practices such as SOLID principles, testing, and tooling already configured 🀟

πŸš€ Running the app

  • Install the dependencies: npm install
  • Execute the tests: npm run test
  • Check linter errors: npm run lint
  • Fix linter errors: npm run lint:fix
  • Make a build unifying everything in the same dist/figmaEntrypoint.js file: npm run build
  • Run a watcher on your plugin files and make the build on every change: npm run dev

πŸ—ΊοΈ Steps to develop your own plugin

  1. Click on the "Use this template" button in order to create your own repository based on this one
  2. Clone your repository
  3. Replace the skeleton branding by your own:
  • Modify the name property of your manifest.json file, and set the id value following the next steps in order to obtain it from Figma:
    1. Generate a plugin in the Figma App: Figma menu > Plugins > Development > New Plugin…
    2. Give it a random name and choose any kind of plugin
    3. Save the Figma plugin files locally
    4. Open the saved manifest.json and copy the id property value
  • Modify the following package.json properties: name, description, repository.url, bugs.url, and homepage
  1. Install all the plugin dependencies running: npm install
  2. Develop in a continuos feedback loop with the watcher: npm run dev
  3. Install your plugin in your Figma App: Figma menu > Plugins > Development > Import plugin from manifest…
  4. Remove the unnecessary code
  5. Add your new use case Command
  6. Now you can call to the handleCommand function passing in the created command

ℹ️ And remember to star this repository in order to promote the work behind it 🌟 😊

πŸ—οΈ Software Architecture

πŸ“ Figma entrypoint

You will find the entrypoint that Figma will execute once the plugin is executed in the src/figma-entrypoint.ts file, which is intended to represent the interaction with the Figma UI, leaving the logic of your plugin to the different commands that will be executed in the Browser or in the Figma Scene Sandbox.

🎨 UI

In the src/ui folder you will find the HTML, CSS, and TS files corresponding to the plugin user interface. We have decided to split them up in order to allow better code modularization, and leaving Webpack to transpile the TypeScript code into JavaScript and inline it into the HTML due to Figma restrictions 😊

⚑ Commands

Commands are the different actions an end user can perform from the plugin UI. In the src/ui/ui.ts you will see that we are adding event listeners to the plugin UI in order to execute these Commands such as the following one:

import { executeCommand } from "./commands-setup/executeCommand";

document.addEventListener("click", function(event: MouseEvent) {
  const target = event.target as HTMLElement;

  switch (target.id) {
    case "cancel":
      executeCommand(new CancelCommand());
      break;
    // […]
  }
});

This executeCommand(new CancelCommand()); function call is needed due to how Figma Plugins run, that is, communicating ourselves between the following types of elements:

Codely Figma Plugin Skeleton Architecture

  1. The src/figma-entrypoint.ts: As described before, in general this is the file that Figma will execute once the user runs your plugin. However, there are multiple scenarios depending on the type of plugin:
  1. The Browser iframe Figma creates for us in order to run the plugin UI. This iframe is needed in order to gain access to the browser APIs in order to perform HTTP requests for instance.
  2. The Figma scene exposed in order to create elements or access to the different layers from the src/scene-commands which runs inside the Figma sandbox.
  3. The previous commands could need some information from the external world, so they must send out a command to be handled inside the iframe. You can see an example of this in the PaintCurrentUserAvatarCommandHandler . All you have to do to perform the request is executing a NetworkRequestCommand:
    executeCommand(
      new NetworkRequestCommand("https://example.com/some/api/endpoint", "text")
    );
    And listen for the response:
    return new Promise((resolve) => {
      this.figma.ui.onmessage = async (message) => {
        await this.doThingsWith(message.payload);
        resolve();
      };
    });

πŸ†• How to add new commands

If you want to add new capabilities to your plugin, we have intended to allow you to do so without having to worry about all the TypeScript stuff behind the Commands concept. It is as simple as:

  1. Create a folder giving a name to your Command. Example: src/scene-commands/cancel
  2. Create the class that will represent your Command.
  1. Create the CommandHandler that will receive your Command and will represent the business logic behind it. Following the previous examples:
  1. Link your Command to your CommandHandler adding it to the src/commands-setup/CommandsMapping.ts
  2. Send the command from one of the following places depending on your plugin type:
  • Plugins with UI: From src/ui/ui.ts with executeCommand(new CancelCommand());
  • Plugins without UI: From the src/figma-entrypoint.ts with await handleCommand(new CancelCommand());

🌈 Features

✨ Illustrative working examples

In order to show the potential Figma Plugins have, we have developed several use cases:

Plugin menu with the 3 use cases

πŸ‘€ Shapes Creator Form

Shapes Creator Form Shapes Creator Form Result

Demonstrative purposes:

  • Render a UI allowing it to be modular and scalable (Webpack bundling working in Figma thanks to JS inline)
  • How to communicate from the Figma Browser iframe where the UI lives to the Figma Scene Sandbox in order to execute commands like the createShapes one which require to modify the viewport, create and select objects, and so on
  • Work with the Figma Plugins API randomizing multiple variables to make it a little more playful:
    • The shapes to create (rectangles and ellipses)
    • The rotation of each shape
    • The color of the shapes

⌨️ Shapes Creator Parametrized

You can launch parametrized menu commands from the Figma Quick Actions search bar:

Shapes Creator Parametrized in the Quick Actions search bar

It even allows you to configure optional parameters and suggestions for them:

Filtering our the type of shapes parameter value

Demonstrative purposes:

  • Take advantage of the Parametrized Figma Plugins in order to offer a simple UI integrated with the Figma ecosystem without having to implement any HTML or CSS
  • Reuse the very same use case (CreateShapesCommandHandler) from multiple entry-points. That, is we are using that very same business logic class:
  • Configure optional parameters and how they map to nullable TypeScript arguments
  • Specify suggestions for some parameter values that can be programmatically set. Example in the figma-entrypoint for the typeOfShapes parameter.

🎨 Paint current user avatar

How the use case paint out the avatar and its user name

Demonstrative purposes:

  • Communicate back from the Figma Scene Sandbox to the Figma Browser iframe in order to perform the HTTP request in order to get the actual user avatar image based on its URL due to not having access to browser APIs inside the src/scene-commands world
  • Define the architecture in order to have that HTTP request response handler defined in a cohesive way inside the actual use case which fires it. Example in the PaintCurrentUserAvatarCommandHandler .
  • Paint an image inside the Figma scene based on its binary information
  • Declare a more complex menu structure containing separators and sub-menu items
  • Loading the text font needed in order to create a text layer and position it relative to the image size

🫡 Simplified communication

If you take a look at the official documentation on how Figma Plugins run, you will see that there is a postMessage function in order to communicate between the two Figma Plugin worlds previously described:

Original Figma Plugins Architecture

However, that postMessage function is different depending on where you are executing it:

  • From the Figma Scene sandbox to the UI iframe: figma.ui.postMessage(message)
  • From the UI iframe to the Figma Scene sandbox: window.parent.postMessage({ pluginMessage: command }, "*")

We have simplified this with an abstraction that also provides semantics and type constraints making it easier to use. You only have to use the executeCommand function without worrying about anything else:

import { executeCommand } from "./commands-setup/executeCommand";

executeCommand(new CancelCommand());

This is why you will see it on the Codely Figma Plugin Architecture diagram while communicating on both ways:

Codely Figma Plugin Skeleton Architecture

βœ… Software development best practices

Focus of all the decisions made in the development of this skeleton: Let you, the developer of the plugin that end users will install, focus on implementing your actual use cases instead of all the surrounding boilerplate ⚑

We have followed an approach for developing this Codely Figma Plugin Skeleton based on the SOLID Software Principles, specially the Open/Closed Principle in order to make it easy for you to extend the capabilities of your plugin with just adding little pieces of code in a very structured way 😊

✨ Developer and end user experience

This skeleton already provides a friendly way to handle error produced by the plugins built with it.

If your plugin makes use of the executeCommand method in order to execute commands, we already have you covered in case you have not registered them yet. It would be visible in the actual Figma interface, and specify all the details in the JavaScript console, ‘even suggesting a fix! 🌈 :

Error seen if you do not add your new command.

In case you already registered your command, but it throws an unhandled by you error for whatever reason, we propagate it to the end user in a very friendly way πŸ˜‡ :

Error seen if you do not handle it.

🧰 Tooling already configured

🀏 Decisions made to promote code quality and structure consistency

  • Specify proper dependencies version restriction (no wild wildcards *)
  • Encapsulate all the transpiled code into the dist folder
  • Encapsulate all the Plugin source code into the src folder
  • Configure TypeScript through the tsconfig.json in order to promote safety and robust contracts (no more any paradise)
  • Add code style checker with Prettier and ESLint
  • Add test suite runner with Jest
  • Add Continuous Integration Workflow with GitHub Actions

🧽 Remove unnecessary code

Depending on your plugin type you will find unnecessary code in this template. However, here you have the instructions on how to delete it with a few commands 😊

πŸ™ˆ Plugins without UI

☝️ Attention: We will not remove the ui key from the manifest.json and some JS code such as the registerUiCommandHandlers function call because we still need them even if we do not have a UI. The reason why is that this code is used as an invisible UI while communicating from the Scene Sandbox to the UI iframe in order to access browser APIs. These browser APIs are used for instance while performing network requests from our plugin. See more on the " ⚑ Commands" software architecture section.

  • Remove unneeded dependencies: npm remove style-loader css-loader figma-plugin-ds
  • webpack.config.js: Remove the css and static assets rules from module.exports.module.rules only leaving out the ts files one
  • Remove the visual parts of the UI:
    • rm src/ui/register-ui-command-handlers.ts
    • echo -n "" >| src/ui/ui.html
    • echo "import { registerUiCommandHandlers } from \"./register-ui-command-handlers\";\n\nregisterUiCommandHandlers();" >| src/ui/ui.ts

☝️ Plugins without menus (just a single use case)

  • manifest.json: Remove the menu property
  • Modify the src/figma-entrypoint.ts removing the support for menu commands and directly executing your use case command keeping the support for the invisible UI. Example for a plugin which only would execute the paintCurrentUserAvatar command:
    import { handleCommand } from "./commands-setup/handleCommand";
    import { PaintCurrentUserAvatarCommand } from "./scene-commands/paint-current-user-avatar/PaintCurrentUserAvatarCommand";
    
    createInvisibleUiForBrowserApiAccess();
    
    await handleCommand(new PaintCurrentUserAvatarCommand());
    
    function createInvisibleUiForBrowserApiAccess() {
      figma.showUI(__html__, { visible: false });
    }

πŸ–ŒοΈ Plugins without FigJam support

manifest.json: Remove the figjam value from the editorType property, leaving the property as an array but only containing the figma value.

🧊 Plugins without tests

πŸ”’ Plugins without special permissions

Remove the permissions key from your manifest.json.

πŸ‘€ Inspiration

Other Figma plugins repositories where we found inspiration to create this one:

πŸ‘Œ Codely Code Quality Standards

Publishing this package we are committing ourselves to the following code quality standards:

  • 🀝 Respect Semantic Versioning: No breaking changes in patch or minor versions
  • 🀏 No surprises in transitive dependencies: Use the bare minimum dependencies needed to meet the purpose
  • 🎯 One specific purpose to meet without having to carry a bunch of unnecessary other utilities
  • βœ… Tests as documentation and usage examples
  • πŸ“– Well documented ReadMe showing how to install and use
  • βš–οΈ License favoring Open Source and collaboration

πŸ”€ Related skeleton templates

Opinionated TypeScript skeletons ready for different purposes:

This very same basic skeleton philosophy implemented in other programming languages:

You might also like...

Read without losing the plot. Well Read helps you organize your notes about books you're reading, so you're never lost when starting a new volume.

Read without losing the plot. Well Read helps you organize your notes about books you're reading, so you're never lost when starting a new volume.

Well Read Well Read is a website for tracking your reading of long book series. I made this to track how many pages I read in a session and to better

Dec 15, 2022

A simple browser extension, intended to get you "Back To Work" when you start slacking off to one of those really addictive sites.

Back to Work A simple browser extension, intended to get you Back To Work when you start slacking off to one of those really addictive sites. What doe

Nov 19, 2022

Customise this Figma plugin template for your own design team.

Design Toolkit Customise this Figma reference plugin to provide useful resources and automations for yourself or your team. Inspect the code to see ho

Jul 7, 2022

A variety of jQuery plugin patterns for jump starting your plugin development

jQuery Plugin Patterns So, you've tried out jQuery Boilerplate or written a few of your own plugins before. They work to a degree and are readable, bu

Dec 31, 2022

A lazy plugin for printing local network when starting NodeJS server. I made this so you don't have to.

A lazy plugin for printing local network when starting NodeJS server. I made this so you don't have to.

lazy-net A simple, lightweight plugin for printing local network when starting Node.js or Express.js server. I made this so you don't have to. There a

Feb 10, 2022

Simple NextJS Project Template to write less boilerplate code when starting a new Next JS Project

Simple NextJS Project Template to write less boilerplate code when starting a new Next JS Project

Feb 12, 2022

Starting template for building a Remix site with CloudFlare Workers (ES Modules Syntax)

Starting template for building a Remix site with CloudFlare Workers (ES Modules Syntax)

May 20, 2022

A template created with the intention of making my life easier when starting a project, and the lives of other people. :

Express API A simple and improved api with ExpressJS. πŸ“š | Glossary Dependencies How to Start Routes Controllers & Models License Author πŸ—ƒ | Dependec

Sep 22, 2022

Bootstrap Colorpicker is a modular color picker plugin for Bootstrap.

Bootstrap Colorpicker Bootstrap Colorpicker is a modular color picker plugin for Bootstrap 4. THIS PROJECT IS NOT MAINTAINED ANYMORE. After almost 10

Dec 22, 2022
Comments
  • feat: abstract `postMessage`

    feat: abstract `postMessage`

    Purpose: Do not have to take into account from where to where you want to post messages

    Current errors:

    It seems to properly work because the use cases are executed properly, but it show errors like we would be performing memory accesses out of bounds :S

    image

    Just as a note, I have tried to refactor the abstracted method as follows, and the error changed to a table index out of bounds, so it could be something related to hoisting or whatever JS behaviour that changes depending on how we declare the function:

    export const postMessage: <CommandType extends Command>(
      command: CommandType
    ) => void = () => {
      const isFromSceneSandboxToUiIframe = typeof window === "undefined";
    
      if (isFromSceneSandboxToUiIframe) {
        return function <CommandType extends Command>(command: CommandType): void {
          figma.ui.postMessage(command);
        };
      }
    
      return function <CommandType extends Command>(command: CommandType): void {
        window.parent.postMessage({ pluginMessage: command }, "*");
      };
    };
    

    image

    opened by JavierCane 3
  • feat: implement async commands. `PaintCurrentUserAvatarCommandHandler` example

    feat: implement async commands. `PaintCurrentUserAvatarCommandHandler` example

    New menu command: image

    Result: image

    We also added CancelCommandHandler tests in order to illustrate how we could do it despite the Figma SDK limitations

    See commit messages for further details

    opened by JavierCane 0
  • [no merge] refactor: implement adapter pattern for the Figma Plugin API

    [no merge] refactor: implement adapter pattern for the Figma Plugin API

    Goal: Make it easier to mock

    refactor: illustrative example of a parallel change refactoring process refactor: finish the parallel change refactoring process

    ⚠️ PR intended for illustrative purposes. It would depend on the end user (the plugin developer) if they want to go for this option (implementing the abstraction) or not.

    opened by JavierCane 0
Owner
CodelyTV
Code examples for our software development videos and courses
CodelyTV
Screeps Typescript Starter is a starting point for a Screeps AI written in Typescript.

Screeps Typescript Starter Screeps Typescript Starter is a starting point for a Screeps AI written in Typescript. It provides everything you need to s

null 3 Jan 27, 2022
This template can be used as a starting point for any minting dApp on the Elrond Network.

Minting dApp Template Made by Giants & NF-Tim by Creative Tim Live Demo This is a dApp template based on erd-next-starter by Giants & soft-ui-dashboar

Giants Labs 11 Dec 23, 2022
This repo contains configurations for webpack, webhint, stylelint and eslint, it is a boiler-plate template and a starting point for coming projects.

Project Name Description the project. Built With Major languages Frameworks Technologies used Live Demo (if available) Experience a live Demo ?? Getti

Adel Guitoun 6 Oct 20, 2022
Serve static files using Bun.serve or Bao.js

serve-static-bun Serve static files using Bun.serve or Bao.js. Currently in beta. Aiming for similar features as expressjs/serve-static. Install This

Jakob Bouchard 10 Jan 1, 2023
πŸ—οΈ Figma Plugin for speeding up and ensure consistency in the structure of your Figma projects

??️ Codely Structurer Figma Plugin Figma Plugin for speeding up and ensure consistency in the structure of your Figma projects Stars are welcome ?? ??

CodelyTV 18 Dec 14, 2022
Starting point for total web3.0 beginners.

Web3.0 for total beginners Things to begin with First read this Medium article about web3.0 How does Ethereum work, anyway? Then take a look to this v

Bozidar Zecevic 19 Aug 30, 2022
Extends Bootstrap Tooltips and Popovers by adding custom classes. Available for Bootstrap 3 and Bootstrap 4.

Bootstrap Tooltip Custom Class Extends Bootstrap Tooltips and Popovers by adding custom classes. Available for Bootstrap 3 and Bootstrap 4. Define you

Andrei Victor Bulearca 14 Feb 10, 2022
A simple Form Validation Utility for Bootstrap 3, Bootstrap 4, and Bootstrap 5 for Humans.

bootstrap-validate A simple Form Validation Utility for Bootstrap 3, Bootstrap 4, and Bootstrap 5 for Humans. ?? Support us with Developer Merchandise

null 138 Jan 2, 2023
Grayce Muthui 8 Jun 16, 2022
Template of yew project, using tailwind and webpack for css, trunk for build and serve, deployable as is for github.io

Yew Template for Github.io Yew template that deployable as is for github.io (or as a normal yew template with css/scss stuffs without github.io), with

null 17 Dec 23, 2022