Accessible Plugins: per-place/game & editable/executable straight from the explorer

There are three major challenges with plugins:

  1. Developing them is an arduous process because you need to publish and update your plugin whenever you want to test changes
  2. Plugins run in every place
  3. Coordinating collaborators to install plugins necessary for the development of a game (e.g. zone editor) and making sure they have up-to-date versions

To alleviate this, I am releasing a plugin that addresses those issues in the following ways:

Per-place/game plugins for collaborators

Any developer that has this plugin installed will have scripts in TestService.LocalPlugins automatically loaded as plugins. You can sync these scripts across the entire game using the new packages feature, or even have place-specific plugins by not using packages. This means collaborators for a game only need to install this one plugin, and they will have access to all plugins in your game/place.

If you add, update, or remove a local plugin, everyone else will receive those changes without needing to install, update, or uninstall the plugin manually. You no longer need to publish private plugins to the library so collaborators can use them. And any potential issues that would arise from each collaborator managing their plugins (e.g. version 2 of a plugin fixed a bug which caused data corruption, but collaborator A is still running version 1 and corrupting data) are no longer a problem.

Per-place/game plugins for solo developers

Local plugins can be used regardless of whether you’re working on a collaborative or solo project.

Plugins editable in the explorer

As mentioned previously, plugins local to the game/place are in the explorer which gives you the ability to add, modify, or remove them without having to open the publish window or Manage Plugins. This plugin also allows you to do the same with plugins that are not place-specific by inserting scripts into TestService.GlobalPlugins. Global plugins are saved to the main plugin’s settings, and are loaded every time you open a new place.

Once you edit a plugin in the explorer, all you need to do is disable/enable the script to run the new version. Global plugins are automatically saved whenever the source changes.

Run in any DataModel

Unlike plugins like Hotswap that rely on loadstring, this plugin runs scripts as modules, so it works in client DataModels like the client view in APS or test players.

Use Cases:

  • Visual editor to place resource spawners in your game without adding models that get published with the game
  • Visual zone editor
  • DataStore editor specific to your game
  • Add utility functions to _G/shared so you can do _G.insert(assetId) or similar from the command bar in any place
  • Test plugins you are going to publish to public domain immediately after making changes, without having to

Plugin Link


  • Scripts in local/global plugins must be Scripts (or LocalScripts – there is no difference), and NOT ModuleScripts. ModuleScripts do not have a Disabled property, which is used to control whether plugins run or not
  • GlobalPlugins only have their sources serialized to plugin settings, so they cannot have any children. LocalPlugins may have children, as they are saved with the place/game.
  • Because these scripts aren’t technically plugins, they do not have the plugin variable defined. If you need this in your script (mainly if you are creating toolbars/buttons, which you may not always need), return a table containing a Start function from your script. This will be called when the plugin is loaded with the plugin variable passed as an argument. Example:
local t = {}

function t.Start(plugin)
    -- start doing stuff in my script with the 'plugin' variable

return t
  • Because of the above, all local/global plugins are part of the same actual plugin. This should mostly not be noticeable, but be careful for conflicts in plugin settings as it may conflict with others (this plugin reserves “PluginData”)
  • Similarly to Start, you can define a Stop function which is also passed a plugin argument and is run whenever the script is deleted or disabled. Note that this is not run when Studio reloads all plugins, in which the active events from both local/global and real plugins will continue to run
  • Because the plugin converts your global/local plugins to modules to run them, it needs to guarantee the script returns. It can’t unconditionally add a return nil since Roblox will throw an error if the script has already returned before then, so it needs to check if it already returns. I didn’t want to add a full Lua interpreter, so it naively matches “^return” on the last line of the script – if your script returns, make sure it is done on the last line.
  • When deleting global scripts, the plugin will prompt you with a confirmation since this will permanently delete them from saved data without any hope of recovery. Sometimes you can undo the deletion, but this is not always the case (e.g. accidentally deleted in play solo or changehistoryservice was disabled by a plugin like the animation editor)
  • Use the waitForObjectUntilGameLoad module in the plugin’s source if any of your plugins need to wait-for-object-and-create-if-not-exist. If you use FindFirstChild, the object may have not finished loading on client DataModels (play solo) or when loading TeamCreate places. If you use WaitForChild with a timeout, you’ll be waiting longer than you need to be

If an uncopylocked place is opened will the plugin be executed right away?

As long as you’ve already installed the plugin linked, yes.

1 Like

Hi. I’ve been coming across problems with the LocalPlugins functionality. When I begin a running game, a new folder with the same name appears under TestingService, and the plugin in the original folder doesn’t run. To get around this, I place the plugin directly under the TestingService. Then, upon pressing run, I execute the following code on the command line:

1 Like

Is this in a Team Create place or after reloading all plugins (happens automatically when you update a plugin/etc)?

1 Like

All I essentially did was reopen a project I was working on half a month ago. Are there checks that take place before the new folders are created?

1 Like

The plugin should search for an existing one and not create a new one if it exists. There have been race conditions in the past with Team Create and plugin reloading, which is why I was asking. I just checked and was able to reproduce it on an empty baseplate though, so doesn’t seem to require anything special.

I will fix this. Thanks for the report!

1 Like

I’m guessing it could either be a bug in that the scripts run before instances are loaded, or that there were changes to the C++ code that happened to alter the way objects are instantiated.

If you’re looking for a heads-up, try running WaitForChild and timing out after 1.27 seconds (I chose that number cos I like it).

Hi. I’m following up with my earlier bug report. When are the fixes scheduled to be published? Thank you.