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
{
"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
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.
"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:
local lib = {}
function lib:Test() log("Hello World from Optional") end
return lib
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:
"requiredDependencies": { "OtherPlugin" : "MyTestPlugin" },
"optionalDependencies": { "OptPlugin" : "MyOptionalPlugin" },
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.
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. |