Build your first plugin: 4. Building Your Plugin

My Dear Friends of Figma in Hi again sophie here from figma’s product education team and in the previous video we set up our environment and installed the dependencies needed so we can start building our plugin by the end of this video we’ll have a ui with some input elements and a button for our plugin before we do that we should gather the requirements for our.

Plugin in order to figure out what we need to create you can get a copy of the design file with pre-made components by searching figma for beginners in the community from the desktop app or by clicking the link in the description with a community page open click here to create your own copy of the file if you do this from the web app be sure.

To switch back to the desktop app since this is where we’ll be doing our plugin development so what are we building we’re building a plugin with a user interface that contains a form that a user can fill out the plugin will take the user input create a component instance for a post in our social media app and populate the.

Component with the information the user entered you might be wondering why are we building this plugin this example will teach us fundamental concepts like how to build a ui with the figma plugin api create an instance of a variant from a component set and take user input to create new components in a figma file.

This beginner series focuses on these important skills you’ll need a good foundation in order to build more complex plugins in the future once you have a solid grasp of some basic programming concepts and how the figma plugin api works we’ll be able to explore more advanced ways of using the api.

This means we can eventually create features that will save us time during the design process the assets in the figma for beginners design file are similar to what you might find on other social media platforms like twitter or instagram one thing these apps have in common are card components that serve different purposes.

You could use the same skills learned in this video to build a plugin that creates cards like travel website cards with an image destination name price and description ecommerce shop items with an item name price and description or podcast listings in a music app with a name and number of episodes.

These are just a few examples of how you can apply what we’re learning in this series once you build your first plugin you might start to think about creating more complex plugins that use third-party apis third-party apis are apis outside of figma that provide tons of information there are apis for almost.

Anything you can think of when we implement other apis we can build plugins that automatically pull data instead of manually filling in this information think of something like a weather app plugin that generates cards with weather information to do this we need to pull data from an external weather api.

Weather data is constantly changing and varies day to day so this saves us the trouble of manually entering all this information now that you have a better idea of how we can apply the skills from the series let’s get back to building our plugin let’s gather the project requirements before we set out to write any code.

This gives us an overall idea of where the project is headed and a way to track our progress as we build each piece with the figma for beginners file open in the desktop app go to the components page and take a look at the cards here you’ll see a few variants of a petma post component.

Our plugin will be responsible for creating an instance of one of these post components to do this we’ll need a window with a form for the user to fill out we should also give our plugin a name we can do this by adding a heading to the form then.

We’ll need input fields and labels for username and name as well as a text area and label for the description of the post remember that an input field is generally a one-line field so for multiple line entries we want to use text area we could limit our petma posts to a specific character count or have no.

Limit at all either way we want to make sure we use an input type like text area that can handle large text entries you’ll notice that there are several variants of the post component we also want to provide our user with ways to choose between light mode and dark mode and specify whether they want no image a single image or multiple.

Images on their post these features could be implemented a number of ways with components like drop downs radio buttons or input fields tom lowry’s lightweight ui library provides useful pre-built components we can add to our plugin for example the switch component can be.

Used to toggle between light mode and dark mode and the radio buttons would allow a user to select an option for the number of images in a post to save us time with creating these components we are going to include tom’s ui library in our project later finally we’ll add a button to submit all of the information from the form.

Now that we know the requirements for our user interface let’s think about some of the functional requirements of our plugin using the figma api we’ll learn how to get our plugin to open a modal in order to display our user interface once the user clicks submit we need to send the form information to our plugin.

Logic in the typescript file in order to use it in our new card component we need to find the component set and its variants from our figma for beginners file this also means that we should write some logic to check which variant of the card component the user has selected based on the form to create a new card.

We’ll first want to load the same fonts used in the existing card components once we have the information from the plugin form and the correct fonts loaded we’ll use a method from the figma api to create a new post component what we are actually doing is creating a copy of the specified variant then overriding its information in the new.

Component later on we’ll learn how to overwrite text and use other tools like the math random function to change the content in a post component once our new post component is generated we can add it to the figma file to make it easier to see where the new component has been added.

We’ll include some code that will tell figma to zoom in to the newly created post now that we’ve gathered our requirements drumroll please we are finally ready to write some code let’s open up the project that we started in the last video the first thing we need to do is delete.

All code from code.ts and ui.html we won’t be using the sample plugin code since we’re building ours from scratch make sure that you add editor type figma to the manifest.json file in order for the plugin to work in the figma desktop app taking a look at our checklist we want a modal to pop up when we run our plugin.

In ui.html let’s give our plugin a title use an h1 heading tag for this this is the message that will display in the modal once it pops up the sigma api has a show ui function that displays a plugin’s interface this function creates a modal that contains the html markup we pass into it.

For example figma.showui hello world would result in something like this now replace that line of code with figma.showui html by passing in html the modal is able to display the contents of your html file in your manifest.json file you’ll see.

That the ui property is set to ui.html this is how we specify which file the html variable is referring to so far we’ve added a heading to our html file added the show ui function to code.ts and confirmed that our manifest is pointing to the right file this gives us enough code to run our plugin for the.

First time remember we can use the command command shift b then select the watch script since the file is now watching for changes we don’t need to worry about updating the build manually while the project is open in visual studio going back to the figma for beginners.

File in the desktop app we can select our plugin to run it if the build was successful you should see a modal pop up with the message we wrote in our html file you can pause here to make sure that you’re getting the same results just hit play when you’re ready to continue awesome now that you see how changes in.

Our code affect the plugin in the desktop app we can continue constructing our ui right now all our plugin does is display a heading inside of a modal which isn’t very useful we need to add a form that will allow the user to interact with their plugin we want the input fields on this form to.

Match the fields on the component we’re making this includes inputs for username name and the description of the post finally we’ll provide a way for them to choose between light mode or dark mode and to select the number of images in a post back in ui.html let’s add a div tag to create a section.

For our text inputs div tags are a great way to divide up content in a page we can use it to group other html tags in a way that is easier to visualize in this div section we’re going to use the input tag to create inputs for the username name and description don’t forget to add a corresponding.

Label to each input while the input types for username and name are text remember that description will be a text area because it needs to hold a larger amount of characters since we’re still in watch mode in visual studio we should be able to save our file and.

View the form when we run the plugin again we can add some basic styling to make sure that our inputs are being displayed as block elements and not wrapping to do this add a set of style tags at the top of your html file and place this snippet of code in between.

So far we’ve got our form with a heading as well as input fields for username and name and a text area for description next we need to decide how we’re going to represent the choice between dark mode and the default styling which we’ll refer to as light mode while there are several ways for us to.

Approach this something that immediately comes to mind is a switch that toggles between the two options creating a custom element like this would require more css on our part and additional programming logic that we don’t want to worry about at this point to make things easier we are going to.

Use thomas lowry’s figma plugin ui library by including this in our project we can access ready-to-use elements without having to build them from scratch to include the ui library in our plugin project check out the description below for the line of code that we’re going to add to.

The top of ui.html taking a look at the ui libraries readme on github you’ll see that we need to include specific classes to style our elements let’s add some classes to our inputs and see what happens great our input fields should look a little.

Bit different than they did before this is thanks to the css already included in the ui library with these built-in styles we can even do things like specify the number of rows that a text area takes up now we can add the switch that allows our user to turn dark mode on and off we can find the code for a switch in the.

Readme and it’ll look like this once we include it in ui.html we highly recommend typing out this code yourself while learning how to build plugins but if you need to reference this code in the future check out the link to the github repo in the description with the dark mode switch added we need.

To give the user an option between no image a single image or a carousel in their post since they will only have to choose one of these options for every post created radio buttons are a great way to represent this we’ll create a div with a class of radio to make sure the radio buttons are part.

Of the same group then we’ll add inputs and labels for each option like this note that while each radio button has a different id they all have the class radio button in the name variant group make sure that the labels correspond to the correct input by using the input id before running the plugin again.

Let’s add a submit button at the end of the form and give it the id attribute submit post we’ve just completed our last two ui requirements let’s run the plugin awesome our form is built out and ready to start taking user input before moving on to the next section we.

Can add some default values for our text inputs using the value attribute now is also a great time for us to make sure that all of our inputs have an id attribute next we can resize the plugin modal by going to code.ts and adding the figma.resize method this method takes in numbers to.

Represent the width and the height of the modal let’s try 500 by 500 finally let’s give our form a bit more style feel free to pause here and if you’d like to learn more about how to style elements with css check out the link in the description.

Below in the next portion of this video we’ll wire up the submit button so that it can send information over to our typescript file one of our functional requirements is to take form input and send that information to our plugin when the user clicks submit how exactly do we do this in javascript.

There’s something called an event listener that waits for a specific event like a mouse click or key press the event listener then handles what happens once the event occurs you’ve likely encountered event listeners on the web for example when you press the up or down keys you can scroll through a web page or when.

You click a button to play a video or sound file these actions are handled by event listeners first we’ll need to write some code that makes something happen when the user clicks the submit button we need to indicate that this event is happening in the document where our.

Button exists then we need to select the correct element by targeting its id this is the element that we’re attaching the event listener to in this case that’s our submit button with the id submit post finally we’ll use the on click method to specify that something should happen.

Once a user clicks on the submit button to finish writing this method we need to add a parentheses and some curly brackets the curly brackets will contain the logic that runs once the click event happens let’s go ahead and add this code to our plugin.

We can do this by using script tags at the bottom of ui.html and putting our javascript code in between them script tags allow us to embed javascript directly into our html file and are typically used to work with dynamic content on a page we still haven’t decided what happens.

When someone clicks the submit button for now let’s add the line console log building my first plugin save your file and make sure that visual studio code is watching for changes if you’re no longer in watch mode just hit command shift b again run the plugin then open the console by.

Hovering over development in the plugin menu and selecting open console in the plugin form click submit if we take a look at our console we should see the message building my first plugin great job our event listener noticed the click on the submit button then handled it by printing out a message to the.

Console now how do we send the form information to the rest of the plugin code that will live in our typescript file first we need to pull the values from each form input when the click event happens let’s start by creating a constant called name within our event listener.

A constant in javascript is a type of variable with a value that can’t be changed once it’s assigned in this constant we’ll store the current value of the name input by assigning document getelementbyid name.value why don’t you try doing the same thing.

With the username and description you can pause here and press play once you’re ready to continue nice at this point you should have something that looks like this we can check these values by logging each variable to the console save your changes and run the plugin.

Remember to open your console type in some random text for name username and description then click submit are you able to see the logs for the values you entered if not try double checking that your syntax is correct by comparing it to what we have on the screen.

Perfect let’s continue for the dark mode switch since it is a checkbox input type we need to determine whether or not it is in a checked state similar to our text inputs we’re going to create a constant to store this information rather than storing the value of the dark mode element.

We’ll use the checked method that returns either true or false depending on whether or not the switch is toggled if the variable stores true then we know that dark mode is selected and vice versa now let’s add some value attributes to each one of our radio inputs.

We’ll use string values 1 2 and 3. for radio buttons we need to use a query selector to target the group of buttons by their shared name rather than selecting a single element by its id then we’ll find the checked radio button and grab its value this will either be 1 2 or 3 depending.

On the option that the user submits we won’t be diving into query selectors in this series so if you’d like to learn more check out the additional resources we’ve linked in the description below write a couple of console logs for the two new variables you’ve created then run the plugin like our last test.

Type in some text for name username and description toggle the switch to turn dark mode on and select carousel from the list of options submit the form and check the console after the text inputs for name username and description you should see a log that prints true and another that prints.

Out three awesome we are successfully pulling all the values from our form inputs so we’ve captured the values from our form fields and logged them in the console but how do we put these values to use in the rest of our plugin we are going to use the parent postmessage method this will send over data from the form.

To our typescript file go ahead and add this line of code to your javascript in ui.html within the event listener you created this will go at the very end of the code in our event listener right before the closing curly brace we’ll replace the message parameter with an object that contains our form.

Variables remember that a function parameter acts as a placeholder with a default value of undefined until we pass in an argument in this case that’s the plugin message object with the values name username description dark mode state and image variant which.

Are pulled from the form once it’s submitted in code.ts we can use the figma api’s onmessage method this receives the same object that we send over from the post message in our event listener since plugin message is an object we need to use dot notation to.

Access its different properties for example to access the name property we would write pluginmessage.name or to check for the dark mode state we would write pluginmessage.dark mode state let’s validate that the plugin message from ui html is successfully received by our.

Typescript file in our onmessage method console.log all of the variables from the plugin message object once you’ve done this go ahead and delete the console logs from your event listener save your changes and re-run the plugin.

When you check your console you should see logs for all of your form variables these logs are now coming from your typescript file you’ve successfully sent information from the plugin form back into your program logic amazing job an important thing to keep in mind as you build plugins we should always close our plugin once it’s done running.

To do this we’ll add the line figma dot close plugin at the end of our on message method this method ensures that the plugin ui is closed once we’re done using it and that access to the current page is disabled this way we can avoid getting any unexpected behavior from our plugin now that we are returning information.

From the form we can use these variables in our plugin logic for example if the user selects dark mode let’s print out a message that says welcome to the dark side otherwise we’ll print out a message saying i’m mr lightside this block of code is.

Called a conditional a conditional is a statement that tells a program what action to take depending on whether a condition is true or false in this case we are telling our program that if the dark mode state from the form is set to true print out welcome to the dark side.

Otherwise print out the other message the else statement in a conditional acts as a default path in case the if condition isn’t met this concept is something that we’ll return to as we develop our plugin now that we are successfully using form information in our plugin logic how do we use information from components that.

Already exist on the canvas once again we’ll need to reference the figma plugin api documentation in the figma api there are different node types that represent layers on the canvas each of these nodes correspond to different figma objects and layers and.

Have their own set of properties for example we have the rectangle node which is a basic shape node that represents a rectangle on the canvas some of its properties include width height and corner radius there’s also a text node that allows us to reference text components in figma in.

This node we’ll find properties like characters that allow us to read or write the characters within a text component there are also methods for finding text properties like font size font name letter spacing and more let’s continue exploring nodes go back to the figma for beginners file.

And open up the components page this group of post components can be referred to as a component set you guessed it there is a matching component set node type that we can use to reference component sets on the canvas to get information from this particular component set there is a find one method.

That will return a single matching node from the canvas for example we can write figma dot root dot find one where the node type is component set and the node name is post figma dot root tells the method where to look for the node root here refers to the entire figma document we want to use this method as opposed to.

Figma.currentpage because we want to be able to find this node no matter what page we’re currently on in the document here we’re telling the find one method to locate a node whose node type is component set and has the name post let’s take this line of code and test it.

Out in the console to see what it returns you should see a similar component set node returned with an id click on the arrow to the left to expand the object what do you notice these are all the properties of our selected component set let’s take a look at the name property.

To make sure that we have the right node there it is this component set is named post so we know that our find one method found the correct node successfully go ahead and explore some of the other properties what do you see when you expand the children property.

It looks like this component set has six child components where do you think those are coming from exactly those six components are the six variants of our post component let’s move this line of code over to our typescript file and store the component set in a.

Variable named post component set we’ll add as component set node to the end of this line to make sure that when our typescript is compiled the variable will still be seen as a component set just in case javascript interprets it as a different node type test the new variable by logging post component set.

While we’re here we may as well log some of the component set’s properties like postcomponentset.children and postcomponentset.name we’ll rerun the plugin and open up the console you should see that we’re getting the same object as we were earlier we can verify this with the type.

Component set and the object name which is post now to take this one step further according to the plugin docs the default variant in a component set is the top leftmost variant that’s this component here luckily there’s already a nifty component set property that will return.

The default variant we’ll test this in our console and there we go we get a component node take a look at the default variant’s name property what do you notice this name exactly matches the name of the component on the canvas.

Let’s find this component and you’ll see that this is in fact the default variant in the top left position of the component set before we go back to the rest of our code let’s run one more test in the developer console let’s try creating an instance of the.

Default variant the component node type has a create instance method that does exactly this add create instance to your previous line of code we get this instance node object what just happened if you don’t see anything try zooming out notice anything different.

What is this that’s the default variant that we just created an instance of nice now we can work on moving this into our typescript file first we’ll store the default variant as a separate variable from our component set.

Since we’ve already got our component set in a variable we can simplify this by saying post component set dot default variant don’t forget to add as component node at the end of this line to remind our compiler to read this as a component node finally.

Create an instance of this default variable using the create instance method and run the plugin again now when we submit the form no matter what the input is plugin creates an instance of the default variant although we are successfully able to find and create an instance of the.

Default variant we can’t forget about the others the great news is we can use the same find one method to select the other variants from the component set create a new variable called default dark and using find one we’ll store the variant with the name image none dark mode true.

Which should be this component here note that the name we put in our find one method needs to exactly match the name of the component on the canvas remember the conditional we wrote earlier that checks whether or not the user selected dark mode let’s put it to use delete the console logs within the if.

Else blocks and instead if dark mode is selected create an instance of the dark mode component by writing default dark dot create instance otherwise if dark mode isn’t selected write default variant dot create instance we’ll run the plugin a couple of times to see if our conditional logic.

Is working for this round we won’t select dark mode right now we can choose any of the image options since we haven’t written code to handle that yet once you submit the form it should still create the default variant from earlier then.

We’ll fill out the form again this time with dark mode selected and hit submit if we wrote our conditional correctly the plugin should have created a dark mode variant with no image awesome job using conditional logic we told our plugin to create a different component based on user input.

How do we implement the same concept to create the rest of the variants in our component set in our html we can see that the way we specify which variant the plugin creates is with the image variant variable this variable checks the value of the radio button that is selected.

A returned value of 1 means no image 2 means a single image and 3 means the carousel variant we can check these values in our typescript by logging pluginmessage.imagevariant let’s run our plugin a few times and test these different values you’ll see that the value we get back.

Directly corresponds with the radio button we selected when we submitted our form we have an additional layer of decision making to consider first we ask the question do we want dark mode yes or no if yes then which dark mode variant do we want.

One two or three if no then which light mode variant do we want one two or three there are a couple of ways we can represent this in our plugin logic one way is by creating nested if-else statements for example.

If dark mode is true we can nest if-else statements that will then check for the image variant however as you can see this looks a bit cluttered and we want to make sure our code is as concise and easy to read as possible a better option would be to use a.

Different conditional logic structure called a switch case a switch case evaluates one expression and decides which case to execute depending on the resulting value for example pluginmessage.imagevariant will either evaluate to a value of 1 2 or 3.

So we can write our switch case like this you’ll notice that instead of creating a case 1 for the dark mode variant with no image we’ve gone ahead and added this variant to the default case the default case in a switch statement will run if none of the other cases.

Match the evaluated expression so if the value of image variant is not 2 or 3 then run the default case this is useful in case our plugin somehow receives a value that we aren’t expecting it’s also important to note that each case ends with the break keyword and a.

Semicolon when a switch statement finds a matching case all of the code inside the case will execute up until the break the break statement is what tells our program to stop this is a great time to pause the video and try writing a switch statement on your own.

Finish out the conditional logic by creating a switch statement within the else code block to find the light mode variant for now don’t worry about creating instances of the variants you can use comments as placeholders just like we’ve done here.

Hit play once you’re ready to continue welcome back hopefully you had a chance to write your own switch statement at this point he should have some code that looks like this we’re getting there our plugin is really starting to take shape.

Our next task is figuring out how to create instances of all these different variants as of now we are only able to create instances of the default variant and one of the dark mode variants one way we can approach this is by creating different variables for all the other variants like what we’ve done with.

Default variant and default dark instead what if we could just have one variable that gets assigned a component depending on which case is met in our switch statement to do this we’ll create a variable outside of our conditional logic called selected.

Variant notice that instead of a constant variable we are creating a let variable here unlike a constant that can’t be changed a let variable allows us to reassign the value depending on which variant we create an instance of for example.

If the user doesn’t select dark mode and select the no image option we would want to create an instance of the default variant in the case we’ll assign selected variant the default variant from the post component set we can apply the same concept to assign.

The rest of the variants within our switch statements since the other variants are not the default we can utilize the find one method similar to the way we used it to find the first dark mode variant to demonstrate under the default case for the dark mode path of the conditional your code might.

Look like this again it’s important to note that the node name matches the name of the component on the canvas exactly this is the property that will allow us to differentiate between the variants you can pause here and complete the rest of the switch case statement replace your comments by assigning the.

Correct find one method to the selected variant variable press play once you’re ready for the next part once you’re done updating the switch cases your conditional logic should look like this we can go ahead and delete our default variant and default dark variables since we don’t need them.

Anymore remember to save your work before we run our plugin to test this we can’t forget to create an instance of the selected variant we’ll do this right after the conditional and before the close plugin method great let’s open up our file and test out our.

Changes run the plugin a few times and try different combinations of dark mode with the number of images to see if you can get all the post component variants if you’re not able to create the six variants make sure to check your syntax are the names of the nodes matching are your switch statements checking for.

String values rather than numbers attention to detail is really important here spectacular job if your plugin is working properly you should be able to create all six post component variants however with the way our plugin is at the moment.

No matter what we type in for username name and description we are still creating new instances with the component’s original text we can change this by selecting the text nodes from the newly created component and overwriting them with the information submitted in the form remember that when we create an instance.

Using the create instance method what we are actually doing is creating a copy of the selected object first we need a way to reference the new post component we can do this by storing the new instance in a variable like this then we’ll create variables for name username and description to store the.

Corresponding text nodes notice that we are looking for these nodes from within the new post which stores the new instance that we are going to create we can find each node by name and by checking that they are of the text node type before we overwrite the current text.

Nodes let’s check that we are in fact returning the right text nodes we can use the text node characters property to see the text being stored in each node write a few logs to view the characters in the text variables we just created once you’ve written out these logs run.

The plugin and take a look at the console if you’re seeing the default text printed out it means you were able to find the correct text nodes great job now comes the fun part we can also use the characters property to replace the characters in our text.

Nodes with the values submitted from the form remember that those values are sent to our typescript file from the plugin message object and we can access them using dot notation with the characters reassigned ideally we should be overriding the text nodes.

However when we run the plugin you’ll likely encounter a plugin error that looks like this this is telling us that we can’t override the text node due to an unloaded font don’t worry we can fix it it’s important to know that any time we.

Are changing the contents of a text node we first need to load the font that the text node is using it looks like the font used in our post component is rubik regular we’ll load this font in at the top of our on message method by using the figma load font async function notice the await keyword at the.

Beginning of this line that’s because this function is asynchronous and returns something called a promise since the load font function only works to load fonts already accessible in the figma editor the promise will either resolve meaning the font was found and loaded.

Successfully or reject and give us an error we won’t be diving into asynchronous methods or promises in this course so check out the link in the description below if you want to learn more after loading the font we’ll have to do a small refactor of our onmessage method to take into account.

The asynchronous load font method your function definition should now look like this all we’ve done is add the async keyword and put the plug-in message parameter in parentheses once you’re done adding these changes run the plugin again if it’s successful.

You should see the text in the new component being overwritten with your form inputs wonderful take a moment to give yourself a huge pat on the back because you just built your very first figma plugin you can continue working on this plugin and even implement additional features.

For example we can add some random number generators to randomize the number of likes and comments when we generate a new post we can overwrite these text nodes by first locating the node with the find one method then assigning the random number function to change the text node’s.

Characters in this example every time we run the plugin our code will generate a random number from one to a thousand and assign it to the number of likes and comments in a post component we could also make it easier to view our new post by writing in some code that.

Tells the editor to focus on the new component once it’s created there is a scroll and zoom into view method which automatically adjusts the viewport to focus on the selected nodes to have this method focus on our new post component node we first need to add this line right.

After we load our font we just created a variable called nodes that is currently an empty array an array is typically used to store multiple elements however in this case we’re just adding the new post to the array since the scroll and zoom into view method is expecting an.

Array to be passed in although we won’t be diving into arrays in this course remember to check out the description below to learn more we can add our new post to the list of nodes using the push method this line should be added at the bottom of our code remember that new post is the name we’ve given the variable that.

Stores the new instance of our selected variant after the new post is added into the nodes variable we can tell the scroll and zoom method to focus on that component by passing in nodes run the plugin again and you’ll notice that it’s so much easier to find our new.

Post component now these are just a few ideas you can implement so have fun and get creative once again congratulations on building your very first figma plugin i hope that learning these fundamentals helped you on your journey to becoming a plug-in developer.

Keep on learning and we can’t wait to see all of the cool things you come up with in the next video we’ll show you how to publish your plugins to the community so you can share what you’ve created see you there

Figma is free to use. Sign up here: https://bit.ly/3msp0OV Written Guide: https://bit.ly/3GCs0Un The Petma Design File: …

Figma Official Channel,your,build,first,UI design, ux design, app design, FigJam tutorial, UX tutorial, Design tips, design system, what are plugins, Figma coding, typescript, making a plugin, figma plugin tutorial, how to make a figma plugin, API, API tutorial, Figma API, product:figma_design, audience:developer, language:english, format:standard, produced_by:product_education, theme:development, event:none, series:build_your_first_plugin, type:outcome_tutorial, level:beginner, primary_feature:plugins, secondary_feature:,

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *