Building a Figma plugin using Kotlin

Take a look at how you can leverage Kotlin/JS to build plugins for Figma

Building a Figma plugin using Kotlin
https://unsplash.com/photos/4tXfdctTcWs

Unless you're living under a rock, you might have heard of or used Figma before, whether to design or for handoff, it's a collaborative design tool built using web technologies. It's a fantastic tool!

We are not going to talk about why Figma is amazing today. Instead, we are going to focus on Figma Plugins. Plugins are how Figma allows us to extend the capabilities of Figma design or FigJam, it has a vibrant community of plugins that vary from generating text content for placeholders to linting for accessibility or generating full-fledged components or design systems.

Since Figma is built on top of web technologies, it requires plugins to be JavaScript module to run them. But, we can write the plugin in a different language though, for example when we create a new plugin from their template. It provides a TypeScript template and once you compile it, it outputs a JavaScript file that is used by Figma.

So, I wanted to try writing a plugin using Kotlin/JS. It's a similar concept, we write Kotlin code and it compiles to JavaScript. First, let's create a new Kotlin Multiplatform project in IntelliJ IDEA

That should create a simple project that just prints "Hello, world" to the console.

Let's compile the code to generate the JS module. You can run the following Gradle command to do that.

./gradlew browserDistribution

This would compile your project into a single JS module that is ready for distribution. You can find it under build/distributions

Alright, we have our JS file. Now how do we get Figma to run it? This is where we define a manifest file for Figma to import the plugin from. Let's create the following manifest.json file in the root directory of the project.

{
  "name": "Figma Plugin KT",
  "id": "737805260747778092",
  "api": "1.0.0",
  "main": "build/distributions/figma-plugin-kt.js",
  "capabilities": [],
  "enableProposedApi": false,
  "editorType": [
    "figma"
  ]
}

Then open Figma, navigate to Account > Plugins, and then select Import plugin from manifest from the In development section

That's it, now you have your plugin ready to use in Figma. You can now open or create a new Figma design file. Then press CMD/CTRL + / , and enter the plugin name to run it. Since this plugin prints a message, you have to open the console to view it. You can follow the same shortcut mentioned above and type "Open console".

But wait a minute, why is that toast message saying running plugin even after the message is finished? Well, that's because we haven't closed the plugin after performing our operation.

Let's jump back to your Kotlin code and do that. After the print statement let's add this JS code block, and then generate the distributions again.

fun main() {
  println("Hello, ${greet()}")
  js("figma.closePlugin('Operation successful ✅')")
}

Now, when you run your plugin in Figma, it will automatically close the plugin after printing the greeting and show this message.

So, that's it right? Well not exactly. As you can see even though we are building this project in Kotlin, we are still using raw JS strings to do the Figma-related operations, thus defeating any advantage we are getting by writing a plugin in Kotlin. I would like to have type safety when writing Figma plugin APIs and be able to use any Kotlin standard library functions that are compatible. Let's take a look at how we can do that.

Fortunately, Kotlin/JS offers a way other ways to run JavaScript code from Kotlin. That is via having custom bindings. Let's Figma API exposes a global object to interact with its APIs called figma.

We would have to create a new Kotlin file and add a variable with the same name as in their APIs. So, let's do that. Let's create a new Kotlin file that exposes the Figma API global object

external val figma: PluginAPI

external interface PluginAPI {

}

Here the external modifier indicates that the code implementation of it is in JavaScript, so it won't complain about not having any body in the code.

Let's add the closePlugin API we used before to the PluginAPI interface, so that we can use it in our main file.

external interface PluginAPI {
  fun closePlugin(message: String = definedExternally)
}

Here, the definedExternally indicates that the message is an optional parameter.

Now let's replace the JS code in our main file with this.

fun main() {
  println("Hello, ${greet()}")
  figma.closePlugin("Operation successful ✅")
}

When you run the plugin again in Figma, you should see everything working as expected. This time we are using type-safe Kotlin APIs rather than a raw JS string in Kotlin.

But, you might be thinking this seems tedious work to write all the external declarations manually one by one. Well, Kotlin folks think so too. That's why they have created an experimental library called Dukat, this would allow us to convert TypeScript definition files to Kotlin declarations. Let's install that

npm install -g dukat

Now we need the Figma plugin API typings. Fortunately, Figma exposes the plugin API typings via GitHub and npm, you can find them here.

Once we downloaded the typings repo, the files that are important to us are index.d.ts and plugin-api.d.ts. Let's run Dukat on that.

dukat -d \
~/Projects/figma-plugin-kt/types \
~/Downloads/figma-typings/index.d.ts \
~/Downloads/figma-typings/plugin-api.d.ts

This should generate the Kotlin declarations we need along with library declarations as well. Now, we have access to all the APIs that Figma Plugin provides. To test this, you can update your main code to this

fun main() {
  val interRegularFont = object : FontName {
    override val family get() = "Inter"
    override val style get() = "Regular"
  }
  
  figma.loadFontAsync(interRegularFont).then {
    val text = figma.createText()
    text.characters = "Hello, world"
    text.fontSize = 24

    figma.closePlugin("Operation successful ✅")
  }
}

Once we run the plugin, a text should be created in the Figma canvas.

Gotchas

  • While Dukat helps generates the Kotlin declarations, it's far from perfect. Sometimes the generated files can have a lot of errors, like not having a override modifier, or having that when it's not needed, or wrong types, etc., So, there is some manual effort involved to resolve them accordingly before we can generate a JS module for distribution.

That's it, this is how we can create Figma Plugins using Kotlin/JS. Now I wonder if I can use Jetpack Compose UI here for plugin UI. Well, that's for a different time I guess. See you later 👋🏾

You can find the source code here