Skip to content

Structure

Overview

The basic plugin structure is as as follows:

  • main.lua - Lua script to execute when enabling the plugin.
  • plugin.json - Manifest data for the plugin
  • resources/ - Additional resources loaded for the plugin at runtime. See Plugin Resources for more information.

Info

These are the only hardcoded paths used by the plugin system, individual plugins can contain additional Lua files and folders beyond these.

Permissions

There's some functionality that although useful for plugins, can present a risk for the user. To address this, the plugin system allows for permission-restricted functionality, which plugins can declare they're using by requesting the permissions via the manifest.

Permissions can be required for plugins to function, or optional, allowing plugins to provide users additional functionality if they've accepted those permissions.

See here for a list of all currently available permissions, and this section for information on how you can check for optional permissions.

Manifest

The plugin.json manifest file defines user-friendly descriptions and names for plugins, containing also information on potential dependencies and permissions.

Field Type Description
name String The user-friendly name for this plugin.
version String The user-friendly version for this plugin.
When downloading plugins from a remote repository, this will usually be the version as reported by that repository.
contactEmailAddress String A contact email by which a user of the plugin can reach the creator. This is a legal requirement, and it is recommended to be different to the email address used to access your Mod.io account.
requiredDependencies [String] or { String -> String } Specifies the plugins this plugin depends on, with the first form resulting in dependencies being imported as their plugin name, while the later allows you to rename them, with the mapping representing Var Name -> Plugin Name.
The later form is useful in situations where a plugin starts with a leading ! which would otherwise be replaced with a _ when exposed in Lua.
optionalDependencies [String] or { String -> String } Specifies some additional plugins that this plugin can depend on, but are not required in order for this plugin to work.
The synatax is the same as for requiredDependencies.
requiredPermissions [String] Collection of permissions required for this plugin to function.
optionalPermissions [String] Collection of permissions that strictly provide added functionality for this plugin rather than being required for it to function.
urls { String -> String } Specifies the base URLs that may be used by this plugin, with the mapping representing Var Name -> URL.
These must represent HTTP or HTTPS URLs.

Example

plugin.json
{
    "name": "My HTTP Test",
    "version": "1.0",
    "contactEmailAddress": "example.email@jagex.com",
    "requiredDependencies": [],
    "optionalDependencies": [],
    "requiredPermissions": [ "httpAccess" ],
    "optionalPermissions": [],
    "urls": { "jagex" : "http://www.jagex.com", "rswiki" : "https://runescape.wiki/" },
}

Info

The manifest may contain additional fields for plugins retrieved from a remote repository, these are usually prefixed with the word remote.

Dependencies

Plugins can define other plugins as dependencies, and/or use multiple Lua files instead of containing the entire codebase in their main.lua.

To achieve the later you just need to call the require function with the path to the file you want to load, omitting its extension. This will then return the result of executing that file, or nil if execution files or the file doesn't exist.

Warning

Cyclical dependencies (eg. A depends on B depends on A) are not allowed and will trigger an error.

Info

The path passed to require needs to be relative to the plugin's root folder. For example, to load a/b/file2.lua from a/b/file1.lua you need to use local myDependency = require("a/b/file2")

Info

require only executes the specified script once, returning a cached result in subsequent calls for the same script.

This eliminates the cost of further invocations but it also means internal variables that get modified during use of a require'd script will be shared across all invocations.

In most cases this is desired behaviour, since it means you can share state between dependencies more easily. If you require scripts to behave completely independently, prefer writing library scripts such that the data obtained via require just lets users setup/create a state object, which in turn exposes all the library's functionality.

Example

myPlugin/helper.lua
local lib = {}
lib.myLocalValue = 0

function lib:ReportAndIncrementValue(value)
    self.myLocalValue = self.myLocalValue + value
    log("The local value is now", self.myLocalValue)
end

return lib
myPlugin/main.lua
local helperLibrary = require("helper")
helperLibrary:ReportAndIncrementValue(10)   -- The local value is now 10
helperLibrary:ReportAndIncrementValue(100)  -- The local value is now 100

External Plugin Dependencies

Depending on other plugins is the best way of working with library/utility plugins, allowing you to make use of them without requiring them to be embedded in your own plugin, driving down storage and download sizes, and improving reusability as long as the plugin remains compatible.

Dependencies between plugins are fairly straightforward, requiring only a change in the manifest to include the folder name of the plugin we wish to depend on. You can define both required and optional dependencies, with the latter allowing you to provide additional functionality once those dependencies are available and enabled.

MyPlugin/plugin.json
"requiredDependencies": [ "MyTestPlugin" ],
"optionalDependencies": [ "MyOptionalPlugin" ],

That dependency is then loaded before the current plugin, and its results are cloned (preventing dependents from modifying what's exposed by that dependency for other plugins) and exported to the current plugin, exposing them as a variable with the same name as the dependency:

MyTestPlugin/main.lua
local lib = {}
function lib:Test() log("Hello World") end
return lib
MyOptionalPlugin/main.lua
local lib = {}
function lib:Test() log("Hello World from Optional") end
return lib
MyPlugin/main.lua
MyTestPlugin:Test() -- Hello World

if MyOptionalPlugin then
    MyOptionalPlugin:Test() -- Hello World from Optional
end

In case the plugin's name can't be used directly as an identifier in Lua, it will be prefixed or replaced with a _ (eg. 2!GraphLib becomes _2_GraphLib). An alternative is to explicitly alias it when defining dependencies, like so:

MyPlugin/plugin.json
"requiredDependencies": { "OtherPlugin" : "MyTestPlugin" },
"optionalDependencies": { "OptPlugin" : "MyOptionalPlugin" },
MyPlugin/main.lua
OtherPlugin:Test() -- Hello World

if OptPlugin then
    OptPlugin:Test() -- Hello World from Optional
end

Warning

Since exported tables are shared across any number of imports, if you need to provide per-plugin functionality in a library plugin, consider exporting instead functionality to create a stateful object which importers can pass in calls to your library, or which wraps the calls entirely.

Similarly, importing plugins must not modify the contents of imports, as that would also affect other plugins.

Dependency Updates

Since whether or not optional dependencies are enabled can change while a plugin is running, plugins can define a PluginDependencyUpdate function in global scope which will then be called whenever the state of an optional dependency defined in plugin.json changes.

This method is automatically converted into an OptionalDependencyChanged event.

function PluginDependencyUpdate(optionalDependencyChangedEvent)

The payload values of optionalDependencyChangedEvent are listed below.

Parameter Type Description
pluginIdentifier String The identifier that would be used to access that dependency's data were it available.
enabled Bool Whether or not the dependency is enabled currently.

Example

local enableAdditionalFeatures = false

function PluginDependencyUpdate(info)
    print("Optional dependency", info.pluginIdentifier, "is now", info.enabled)

    if info.pluginIdentifier == "OptPlugin" then
        enableAdditionalFeatures = info.enabled
    end
end