I recently took up plugin development to create proper tooling for myself & my team. This was painful. Not because the API was bad (which is actually really good), but because the documentation was impossible to navigate & utilize. Trying to create a plugin took me on what I thought would be a 1 day project, into a 4 day adventure. There are many faults with documentation, but plugin development specifically has been the worst I have worked with.
Structure
I started creating my plugin by opening the source codes of other plugins I use on a daily basis to find out the common structure & how some API works, specifically because the devhub pages provided little help. According to Intro to Plugins, this article states you should utilize a script as your “framework” but provides no real structuring. Only after diving into plugin source code I found out the most common method was actually utilizing a folder structure. There should be an article for proper plugin development that specifically describes the “package” method of utilizing a folder to host all of the contents.
DataStore Editor, by @sleitnick:
Empty Script Adder, by Roblox:
ModuleScripts
Plugins cannot be used in module scripts. This is not stated anywhere among the Plugin page, the Intro to Plugins page, or even the ModuleScript page. I spent about an hour of my time trying to figure out why my module wasn’t working when I figured out that we cannot use plugin
in module scripts through a friend who also came across this once upon a time. While I don’t agree we can’t use plugin
in module scripts, this should be stated somewhere. My work around as passing plugin
as a parameter in the module script functions.
CreateDockWidgetPluginGui
CreateDockWidgetPluginGui is confusing. While I was working on making my plugin work with widgets, I was often confused on what this was properly doing until I found the Building Studio Widgets article. On the former page, there is no mention of how CreateDockWidgetPluginGui
functioned, but on the latter page states:
Which is excellent! Except, this should also be documented on the API page. I often don’t use tutorial pages & rather figure things out myself, but not adding this sort of information into the API page was definitely a quick headache to figure out. Between the latter article & the GitHub provided on that very article, I was able to figure out how CreateDockWidgetPluginGui
actually works. I shouldn’t have to follow a bunch of paths just to get to the same end goal.
Even on the DockWidgetPluginGui page, which is the instance created by CreateDockWidgetPluginGui
, they don’t mention how you can utilize this the same way as a ScreenGui
which took me more time to read & figure out if these properties are similar at all or not, which in this case they are. After reading more source code from other plugin devs, I found out this is nearly identical to ScreenGui
, and I even utilized ZIndexBehavior
to mimic ScreenGui
behavior to where I was building my plugin’s interface.
Deactivate, Activate, Deactivation, & Unloading
What exactly does plugin:Deactivate() and plugin:Activate() actually do? While I was trying to find a way to “hot reload” my plugin without having to “Save as Local Plugin”, I was lead to believe that from the plugin.Unloading API would fire upon calling Deactivate()
because the documentation shows these pages together:
Yet upon further inspection, the Unloading
page shows this:
Which completely backtracks the previously gained knowledge. Why would Deactivate()
page tell me that Unloading
fires when called? This is completely wrong with how it behaves. Even on the Activate()
page it shows this:
Consistent with Deactivate
, but wrong with Unloading
. Again, on the plugin.Deactivation page, this shows the following:
Which yet again leads me to believe that Unloading
will fire when called, which is just not true.
PluginToolbarButton is Wack
I stole source code. What code you might be wondering? This code:
local Toolbar = plugin:CreateToolbar('My Plugins')
local Toggle = Toolbar:CreateButton('Suite', 'Loader')
local Widget = plugin:CreateDockWidgetPluginGui('Main',DockWidgetPluginGuiInfo.new(
Enum.InitialDockState.Float,
true,
false,
250, 300,
200, 100);
)
Toggle:SetActive(Widget.Enabled)
Toggle.Click:Connect(function()
if Widget.Enabled then
Widget.Enabled = false
else
Widget.Enabled = true
end
Toggle:SetActive(Widget.Enabled)
end)
Let me tell you, when I say I was confused, I was confused. I originally tried to reverse engineer what was going on with :SetActive()
since SetActive() has literally no documentation. It wasn’t until I went up the chain to PluginToolbarButton where I found out, from this one line, that SetActive fires Activation for the plugin:
Why is there documentation on the specific API page linked here!!!
Still on PluginToolbarButton, while writing this thread I joked about not knowing how to set my plugin toolbar button to be visible when you have a script open. Turns out that this is also documented, but not on it’s own individual page. Reading literally one paragraph down, it states:
But now let’s enter that specificpage, ClickableWhenViewportHidden. Oh, no mention of what this properly does? This is consistent for now two pages in a row, both being documented on a greater page but not individually.
RunService & StudioService
Utilizing RunService in your plugin is excellent! Except, hard to find how. I ran into an issue where I didn’t want my plugin to work during play testing and only during edit mode. I had 0 knowledge before I asked my colleagues for help, which to my surprise one of them presented me with RunService:IsEdit(). This API isn’t touched or mentioned by any of the plugin pages I’ve linked so far, but can be extremely valuable. There’s also RunService:IsRunning(), which gives more examples of how to use the RunService API much more in depth by presenting an in-depth code sample. Since IsEdit()
is locked to command bar and plugins, this API should be demonstrated with the plugin documentation so we can use & utilize these hidden API which can make the QoL of your plugin better.
I’m gonna be completely honest, I didn’t know StudioService even existed. What an amazing service that isn’t mentioned anywhere. Within this service, I found some great API where I was able to make use of for a Session logger which allows other developers utilizing my plugin to all hook their sessions into the DataStore so we can view each others last 10 sessions using their player name. The API I am talking about is :GetUserId(), which returns the player’s UserId from who is in studio, which is completely documented by the way. Studio Service is an excellent service that doesn’t get any love because not many people know it even exists. I had to ask my colleagues if there was a way to access the current User’s ID or name, since game.Players.LocalPlayer
does not work in studio to retrieve a name or ID (makes sense). This also prompted my most recent thread, Players.LocalPlayer Documentation is outdated and broken.
Conclusion
I created this thread to raise awareness to how annoying it is to create a plugin from the great API that isn’t even found in centralized places. Developers who create plugins for people are a godsend since I could never do this normally with my lack of knowledge & me literally just asking for help or coming across the most obscure thing ever which I need to read others source code for. I hope the documentation excels & building plugins can become much easier with the proper tooling being presented.
All pages mentioned:
https://developer.roblox.com/en-us/api-reference/class/Plugin
https://developer.roblox.com/en-us/api-reference/class/ModuleScript
https://developer.roblox.com/en-us/api-reference/function/Plugin/CreateDockWidgetPluginGui
https://developer.roblox.com/en-us/api-reference/class/DockWidgetPluginGui
https://developer.roblox.com/en-us/api-reference/function/Plugin/Deactivate
https://developer.roblox.com/en-us/api-reference/function/Plugin/Activate
https://developer.roblox.com/en-us/api-reference/event/Plugin/Unloading
https://developer.roblox.com/en-us/api-reference/event/Plugin/Deactivation
https://developer.roblox.com/en-us/api-reference/class/PluginToolbarButton
https://developer.roblox.com/en-us/api-reference/function/PluginToolbarButton/SetActive
https://developer.roblox.com/en-us/api-reference/property/PluginToolbarButton/ClickableWhenViewportHidden
https://developer.roblox.com/en-us/api-reference/function/RunService/IsRunning
https://developer.roblox.com/en-us/api-reference/function/RunService/IsEdit
https://developer.roblox.com/en-us/api-reference/class/StudioService
https://developer.roblox.com/en-us/api-reference/function/StudioService/GetUserId