How we built a Figma plugin in 300 lines of code
Published: 2022-11-09 06:39:51

_Figma plugins are a set of programs or applications that adds a cherry on top of Figma functionalities. Figma plugins are usually created by the community of developers. It helps to improve product designers overall experience and enhance time management. You can interact fully with the Figma boards and also modify as much as you need to._
To find out more, check the Figma plugin [documentation](https://www.figma.com/plugin-docs/). 

# Why was this Figma plugin developed?

A few months back, My friend [Sam](https://twitter.com/samomoraiyewa) who is also a professional product designer needed a way to search for layers and texts on Figma boards, then he reached out to me as a developer and friend to build one :).

Before this moment, I had no previous knowledge on how Figma plugins are developed, hence the meme below.
![A picture of us talking about creating a plugin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xunk0xhvfv6hp1objds.jpg)

# Challenges encountered

- What programming stack is used to develop Figma plugins
- What name should we give the Figma plugin.
- How do we finally publish the Figma plugin.

Figma plugins are developed using HTML and JavaScript only. You have the option of using TypeScript(recommended) and also styling your HTML elements using CSS for a better and more intuitive user interface.

> To get the most out of this article, you need a basic understanding of TypeScript and HTML

To startwith, you need [Node.js](https://nodejs.org/en/) installed on your computer. You can test the installation by displaying the version of Node.js on your computer via the command line or  terminal.

```bash
node --version
```
> We will use NPM as our package manager. You can use yarn if you prefer. Do note that NPM comes with the Node.Js installation.

**Install TypeScript on your machine**
Install TypeScript globally so you can run it anywhere, from your favorite code editors to your terminal.

```bash
npm install -g typescript
```
**Get the Figma desktop application**
To test the plugin, you need to download the desktop application. Figma needs to parse your codebase and run it locally for you to test it out on your Figma boards.
Download the Figma desktop application on their [website](https://www.figma.com/downloads/).

> If you already have the desktop application, it is recommended to upgrade to the latest version

## Create a new plugin
Login to the Desktop application and open the file editor, create a new document, or open an existing one.

Next, Go to the menu, then navigate to Plugins > Development > New plugin as depicted below:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tlpiayqcxoxryohm88pc.png)

A modal titled "Create plugin" will pop open. Give it a name, then choose "Figma design" and click "Next". In the next screen, you will select "With UI & browser APIs" and then click "save as", this will prompt you to save the plugin anywhere on disk. 

![Selecting Figma design on Figma desktop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubh1vfcra11u3vjsm3ft.jpg)

![Selecting UI & browser API on Figma desktop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5v0o774pk7svflrf3af.jpg)

> Please note that these steps might be different depending on your Figma desktop version.

## Setup the project and run the sample plugin

Open the folder created using Visual Studio Code(recommended), you can decide to use your favorite code editor.

Install the Figma plugin typings package using NPM
```
npm install --save-dev @figma/plugin-typings
```
Next, start the typescript compilation using the terminal.

Run `npm run build -- --watch`

> Make sure your terminal is in the directory of the Figma plugin

To test the plugin, switch to the Figma desktop app, go to the menu then navigate to Plugin > Development > "Name of your plugin"

> Subsequently, you can navigate to Plugin > "Run Last Plugin" if you had run the plugin previously.

Once you run the plugin you should see a popup modal showing you the sample plugin and you can interact with it.

![Select your plugin from the dropdown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6opylzy3npzg9szk7a1s.jpg) 
![The default Figma sample plugin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s32vwan7oliapcb0dqpp.png)

## Let's Build FigFinder
Yes, we named the plugin **FigFinder** and I will explain how it was developed in this article.

FigFinder helps to search for a layer or text on the Figma board, it receives a search query and layer type via a form, the form data is sent to the Figma API which in turn sends back a list of components that matches the search query. The list is displayed on a result page. When each item on the list is clicked, an event is sent to the Figma API which in turn navigates to where the component can be found on the Figma board.

This Plugin allows designers to easily search for a component by name, even if there are thousands of components on the Figma board.

### Building the plugin interface

Open `ui.html` then empty the file, create a new `div` and `script` element respectively. The div element will contain our plugin interface while the script element will contain the business logic.
Inside the div element, create two new div elements and give them an id named "search-page" and "result-page" respectively.

```html
<div>
  <div id="search-page" style="padding: 24px;">
  </div>
  <div id="result-page" class="hide" style="padding: 0px 24px">
  </div>
</div>
```
Give the div with an id of "result-page" a class of "hide", we will use this class to toggle the visibility of the search page and result page.

Next, paste the code below inside the "search-page" div element

```html
<div style="margin-bottom: 24px;">
      <label for="search">Keyword</label>
      <div class="input-icons">
        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" class="icon-1">
          <path
            d="M7.66659 14.0002C11.1644 14.0002 13.9999 11.1646 13.9999 7.66683C13.9999 4.16903 11.1644 1.3335 7.66659 1.3335C4.16878 1.3335 1.33325 4.16903 1.33325 7.66683C1.33325 11.1646 4.16878 14.0002 7.66659 14.0002Z"
            stroke="#999999" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
          <path d="M14.6666 14.6668L13.3333 13.3335" stroke="#999999" stroke-width="1.5" stroke-linecap="round"
            stroke-linejoin="round" />
        </svg>
        <input id="search" type="text" class="text-field" placeholder="Search" />
      </div>
    </div>

    <div style="margin-bottom: 16px;">
      <label for="layer-type">Layer type</label>
      <select class="select-field" id="layer-type">
        <option value="text">Text</option>
        <option value="frame">Frame</option>
      </select>
    </div>

    <div class="info-box">
      <svg width="36" height="36" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path
          d="M7.99992 14.6668C11.6666 14.6668 14.6666 11.6668 14.6666 8.00016C14.6666 4.3335 11.6666 1.3335 7.99992 1.3335C4.33325 1.3335 1.33325 4.3335 1.33325 8.00016C1.33325 11.6668 4.33325 14.6668 7.99992 14.6668Z"
          stroke="#474747" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
        <path d="M8 5.3335V8.66683" stroke="#474747" stroke-width="1.5" stroke-linecap="round"
          stroke-linejoin="round" />
        <path d="M7.99634 10.6665H8.00233" stroke="#292D32" stroke-width="2" stroke-linecap="round"
          stroke-linejoin="round" />
      </svg>

      <p>Input your keyword phrase into the input “Keyword” and select your layer type. Click on the search button to
        run plugin.👌</p>
    </div>

    <button type="button" class="search-btn" id="search-btn">Search</button>

```
- We added the input field that obtains the user search query used to filter and find components from the Figma API 
- Dropdown input field that allow users search for either a text or a layer from the Figma board. 
- Button element used to trigger a function to send the form data to the Figma API. 

Next, we populate the div element of the "result-page"

```html
<div class="back-button" id="back-button">
      <svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M6.38016 3.95312L2.3335 7.99979L6.38016 12.0465" stroke="#7D7D7D" stroke-width="1.5"
          stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
        <path d="M13.6668 8H2.44678" stroke="#7D7D7D" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"
          stroke-linejoin="round" />
      </svg>
      <p style="margin: 0;">Back to search</p>
    </div>
    <div id="empty-result" style="padding: 48px 24px; text-align:center;" class="hide">
      <svg width="110" height="102" viewBox="0 0 110 102" fill="none" xmlns="http://www.w3.org/2000/svg">
        <circle cx="49.6812" cy="49.6812" r="49.6812" fill="#F1F1F1" />
        <circle cx="49.275" cy="49.7657" r="28.7081" fill="#F1F1F1" stroke="#C4C4C4" stroke-width="2" />
        <circle cx="49.2748" cy="49.766" r="20.2201" fill="#FFFEFF" stroke="#C4C4C4" stroke-width="2" />
        <path
          d="M49.2749 34.1653C49.2749 33.8537 49.0221 33.6 48.7106 33.6109C44.8118 33.747 41.0862 35.2895 38.2287 37.964C35.3712 40.6385 33.5858 44.254 33.1923 48.1353C33.1609 48.4453 33.3973 48.7143 33.7082 48.7349C34.0191 48.7555 34.2868 48.5199 34.319 48.21C34.693 44.6145 36.3514 41.2667 38.9998 38.7879C41.6482 36.3091 45.0983 34.8756 48.7107 34.7401C49.0221 34.7284 49.2749 34.4769 49.2749 34.1653Z"
          fill="#C4C4C4" />
        <rect x="71.4574" y="81.2695" width="10.3062" height="29.6873" rx="5.15309"
          transform="rotate(-45 71.4574 81.2695)" fill="#F1F1F1" stroke="#C4C4C4" stroke-width="2" />
        <rect x="82.1211" y="83.6025" width="1.34862" height="11.3078" rx="0.674309"
          transform="rotate(-45 82.1211 83.6025)" fill="white" />
        <rect x="70.9087" y="74.4121" width="1.38436" height="5.53746" transform="rotate(-45 70.9087 74.4121)"
          fill="#C4C4C4" />
        <rect x="85.5972" y="46.2798" width="13.8436" height="1.38436" rx="0.692182" fill="#C4C4C4" />
        <rect y="57.7319" width="13.8436" height="1.38436" rx="0.692182" fill="#C4C4C4" />
        <rect x="102.209" y="46.2798" width="6.92182" height="1.38436" rx="0.692182" fill="#C4C4C4" />
        <rect x="89.0581" y="51.3589" width="6.92182" height="1.38436" rx="0.692182" fill="#C4C4C4" />
        <rect x="3.46094" y="62.811" width="6.92182" height="1.38436" rx="0.692182" fill="#C4C4C4" />
      </svg>
      <p style="color: #7D7D7D; margin:16px;">
        We could not find any result that
        matches the keyword “search”
      </p>
    </div>
    <div id="result" class="hide">

    </div>
```
- The result section has a placeholder for displaying a simple message when no result is available.
- It also has a div element with an id of "result" which is empty by default and will get populated when the Figma API returns a list of available search results.

Next, we have to style the interface. Inside the `ui.html` but outside the primary div element, create a style element which will enclose all our styles.

```css
<style>
  * {
    font-family: Arial, Helvetica, sans-serif;
  }

  #result div {
    display: flex;
    align-items: center;
    cursor: pointer;
    margin-bottom: 12px;
    background: #FAFAFA;
    border-radius: 4px;
    padding: 12px 16px;
    height: 20px;
  }

  #result div.active {
    background-color: #D6D8DB;
  }

  #result div p {
    margin: 2px 12px;
  }

  .hide {
    display: none;
  }
  .text-field {
    width: 100%;
    border-radius: 6px;
    padding: 16px 12px 16px 32px;
    border: 0.5 solid grey;
  }
  .select-field {
    width: 100%;
    border-radius: 6px;
    padding: 16px;
    margin: 10px 0px;
    border: 0.5 solid grey;
  }

  .input-icons {
    width: 100%;
    margin: 10px 0px;
  }

  .input-icons svg {
    position: absolute;
  }

  .input-icons svg {
    position: absolute;
  }

  .icon-1 {
    padding: 16px 0px 0px 12px;
  }

  .info-box {
    margin-top: 24px;
    margin-bottom: 28px;
    padding: 8px 16px 8px 16px;
    background-color: #F5F5F5;
    display: flex;
    flex-direction: row;
    align-items: center;
    border-radius: 4px;
  }

  .back-button {
    display: flex;
    flex-direction: row;
    align-items: center;
    cursor: pointer;
    width: fit-content;
    column-gap: 12px;
    margin: 20px 0 0 0;
  }

  .back-button p {
    color: #7D7D7D;
    margin-left: 12px;
  }

  .info-box p {
    font-size: 13px;
    margin-left: 16px;
    color: #555555;
  }

  .search-btn {
    height: 52px;
    background-color: #1C77C3;
    width: 100%;
    border-radius: 4px;
    font-size: 16px;
    border: none;
    color: white;
  }
</style>
```
We styled the search page, buttons, and result page elements.

The styling gives the Figma plugin its overall blazing look 🔥🔥

> The interface of this Figma plugin was actually designed on Figma by [Sam](https://twitter.com/samomoraiyewa).

Run the Figma plugin and see how it looks. It should look like what we have below.

![FigFinder plugin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfrvhdpw2qx8k9n4uesf.png)


### Writing the plugin functionality
Next, we open the `code.ts` file. We will write all the business logic for the Figma plugin in this file.

We use the Figma API to create an interface that scaffolds how the elements will be displayed `figma.showUI(__html__, { title: 'FigFinder', height: 460, width: 490 });`, next we create a listener that gets a message from the `ui.html` then we run a set of instructions based on the message received.

```typescript
figma.showUI(__html__, { title: 'FigFinder', height: 460, width: 490 });

figma.ui.onmessage = msg => {

};

```
Inside the `onmessage` listener, we create two conditional statements, one to process the search query and one to search for the selected element on the Figma board.

```typescript
  if (msg.type === 'process-search') {

    const { searchQuery, layerType } = msg.query;
    if (layerType == 'all') {
      console.log(searchQuery)

      const node = figma.currentPage.findAll(node =>
        node.name.toLowerCase().includes(searchQuery.toLowerCase()));
      console.log(node.map(n => n.name))
      const result = node.map(n => n.name);
      figma.ui.postMessage({ count: result.length, type: 'all', result })
    } else if (layerType == 'text') {
      console.log(searchQuery)
      const node = figma.currentPage.findAll(node => node.type === "TEXT"
        &&
        node.characters.toLowerCase().includes(searchQuery.toLowerCase())) as TextNode[];
      console.log(node.map(n => n.characters))
      const result = node.map(n => n.characters);
      figma.ui.postMessage({ count: result.length, type: 'text', result })
    } else if (layerType == 'frame') {
      console.log(searchQuery)
      const node = figma.currentPage.findAll(node => node.type === "FRAME"
        &&
        node.name.toLowerCase().includes(searchQuery.toLowerCase())) as TextNode[];
      console.log(node.map(n => n.name))
      const result = node.map(n => n.name);
      figma.ui.postMessage({ count: result.length, type: 'frame', result })
    } else {
      figma.ui.postMessage({ count: 0, type: layerType, result: [] })
    }
  } else if (msg.type === 'search-item') {
    const { searchQuery, layerType } = msg.query;
    let nodesFound ;
    if (layerType == 'text') {
      nodesFound = figma.currentPage.findChildren((node) => node.type === 'TEXT' && node.characters.toLowerCase().includes(searchQuery.toLowerCase()));
    }
    if (layerType == 'frame') {
      nodesFound = figma.currentPage.findChildren((node) => node.type === 'FRAME' && node.name.toLowerCase().includes(searchQuery.toLowerCase()));
    }

    if (nodesFound) {
    figma.currentPage.selection = nodesFound;
    figma.viewport.scrollAndZoomIntoView(nodesFound);
    }

  } else {
    figma.closePlugin('Command not available');
  }

```
We used `figma.currentPage` to interact with the current Figma board while `figma.postMessage` sends an event to `ui.html`.

### Make everything work together

Next, we update the script element inside `ui.html` to listen to the message coming from `code.ts`. Inside this listener, we process the messages received, then display a list of elements based on the information.

```javascript
 var layer_type = '';

  onmessage = (event) => {
    const pluginMessageResult = event.data.pluginMessage;
    if (pluginMessageResult.count && pluginMessageResult.count > 0) {
      const resultDiv = document.getElementById('result');
      resultDiv.classList.remove('hide');
      layer_type = pluginMessageResult.type;
      resultDiv.innerHTML = `<p>Search results: ${pluginMessageResult.count} item(s)</p>`
      for (let index = 0; index < pluginMessageResult.count; index++) {
        const element = pluginMessageResult.result[index];
        const parentDivNode = document.createElement("div");


        const node = document.createElement("p");
        const textnode = document.createTextNode(element);
        node.appendChild(textnode);
 
        parentDivNode.setAttribute('content', element)
        parentDivNode.onclick = handleClick;

        parentDivNode.appendChild(node)

        resultDiv.appendChild(parentDivNode)

        node.insertAdjacentHTML("beforebegin", getIcon());

      }
    } else {
      document.getElementById('empty-result').classList.remove('hide');
    }
    console.log("got this from the plugin code", event.data.pluginMessage)
  }

  const textSvg = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.78003 4.77986V3.56652C1.78003 2.79986 2.40003 2.18652 3.16003 2.18652H12.84C13.6067 2.18652 14.22 2.80652 14.22 3.56652V4.77986" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 13.8136V2.74023" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.37329 13.8135H10.6266" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`;

  const frameSvg = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="4.5" y1="1.3335" x2="4.5" y2="14.6668" stroke="black"/>
<line x1="14.6667" y1="5.1665" x2="1.33341" y2="5.1665" stroke="black"/>
<line x1="14.6667" y1="11.1665" x2="1.33341" y2="11.1665" stroke="black"/>
<line x1="11.8333" y1="1.3335" x2="11.8333" y2="14.6668" stroke="black"/>
</svg>
`;

  function getIcon() {
    if (layer_type == 'text') {
      return textSvg;
    }
    return frameSvg;
  }



  function handleClick() {
    const query = { searchQuery: this.getAttribute('content'), layerType: layer_type };
    parent.postMessage({ pluginMessage: { type: 'search-item', query } }, '*')

    const resultDiv = document.getElementById('result');
    const divElement = resultDiv.querySelector('div.active');
    if (divElement) {
      divElement.classList.remove('active');
    }
    this.classList.add('active')
  }
```
The `handleClick` function sends an event to `code.ts` so it can search for the selected element on the Figma board.

Next, we handle the search logic, which will send an event to `code.ts` to get elements that match the search query based on the user input.

We add a click event on the button with the id "search-btn" then `parent.postMessage` is used to send the form data to the `code.ts`. This data sent will be processed inside the `onmessage` listener in `code.ts`

```javascript
 document.getElementById('search-btn').onclick = (event) => {

    const inputFieldText = document.getElementById('search').value;
    const selectFieldText = document.getElementById('layer-type').value;
    console.log(inputFieldText, selectFieldText)

    const query = { searchQuery: inputFieldText, layerType: selectFieldText };
    parent.postMessage({ pluginMessage: { type: 'process-search', query } }, '*')

    document.getElementById('search-page').classList.add('hide');
    document.getElementById('result-page').classList.remove('hide');
  }
```
Finally, we add the logic to close the result page when the user returns to the search page.

```javascript
document.getElementById('back-button').onclick = () => {
    document.getElementById('search-page').classList.remove('hide');
    document.getElementById('result').innerHTML = '';
    document.getElementById('result-page').classList.add('hide');
    document.getElementById('empty-result').classList.add('hide');
  }
```
### Test the Figma plugin

Run the Figma plugin and interact with it. You can type in the input field to search for a frame or text. Then, finally click the search button. You will get a list of components on the next page. You can click on each item of the result that sends an event to the Figma API to navigate to the particular Figma component on the Figma board.

> If the response returns an empty list, the Figma plugin alerts the user that no results are available.

![A demo of the plugin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56m6p0mu6q8x9ake29ws.gif)

The Figma plugin was built with little over 300 lines of code when combining the `ui.html` and `code.ts` files.

# What I learned from this project

- I put my critical thinking to the test as there were little or no resources on building Figma plugins except the [documentation](https://www.figma.com/plugin-docs/) and the Figma community.
- I was able to refresh my knowledge of vanilla JavaScript.
- I was able to build a solid foundation for creating complex Figma plugins in the future.

# Current limitations for developing a Figma plugin
Creating Figma plugins is exciting but has few drawbacks that make it less fun. I discussed some of them below:

- Using external assets does not seem to work, so external CSS, images, and JavaScript are not allowed at the time of writing this article
- Using JavaScript frameworks for building Figma plugins are impossible currently, so you have to be well-equipped writing Vanilla JavaScript.
You can use CDN for importing libraries like jQuery though I have not personally tested it.
- Lastly, you are confined to the project structure and cannot explore or set up your preferred architecture.

If you decide to build a Figma plugin and publish it, please refer to their official [website](https://help.figma.com/hc/en-us/articles/360042293394-Publish-plugins-to-the-Figma-Community), where they explained the steps to get the Figma plugin deployed and used by the Figma creators' community.

# Conclusion

This article explained what a Figma plugin is and its usage. I also explained how I built FigFinder(A Figma plugin that finds components on the Figma board). I cannot wait to see what you will develop with it. You can also reach out to me on [Twitter](https://twitter.com/sholaphp).
The complete codebase of this Figma plugin is available on [GitHub](https://github.com/GeekyGeeky/figfinder). Please do well to give a GitHub star to the repository.

If you found this helpful, leave a reaction ❤️