See here for server-specific best practices for plugins. Webapp-specific best practices are incoming.
Once a plugin is installed, Administrators have access to the plugin’s configuration page in the System Console > Plugins section. The configurable settings must first be defined in the plugin’s manifest setting schema. The web app supports several basic pre-defined settings type, e.g. bool
and dropdown
, for which the corresponding UI components are provided in order to complete configuration in the System Console.
These settings are stored within the server configuration under [Plugins
] indexed by plugin ids. The plugin’s server code can access their current configuration calling the getConfig
API call and can also make changes as needed with saveConfig
.
A plugin could define its own type of setting with a corresponding custom user interface that will be displayed in the System Console following the process below.
Define a type: custom
setting in the plugins manifest settings_schema
"settings_schema": {
"settings": [{
"key": "NormalSetting",
"type": "text",
+ }, {
+ "key": "CustomSetting",
+ "type": "custom"
}]
}
In the plugin’s web app code, define a custom component to manage the plugin’s custom setting and register it in the web app with registerAdminConsoleCustomSetting
. This component will be instantiated in the System Console with the following props
passed in:
id
: The setting key
as defined in the plugin manifest within settings_schema.settings
.label
: The text for the component label based on the setting’s displayName
defined in the manifest.helpText
: The help text based on the setting’s helpText
defined in the manifest.value
: The setting’s current json value in the config at the time the component is loaded.disabled
: Boolean indicating if the setting is disabled by a parent component within the System Console.config
: The server configuration loaded by the web app.license
: The license information for the related Mattermost server.setByEnv
: Boolean that indicates if the setting is based on a server environment variable.onChange
: Function that receives the setting id and current json value of the setting when it has been changed within the custom component.setSaveNeeded
: Function that will prompt the System Console to enable the Save button in the plugin settings screen.registerSaveAction
: Registers the given function to be executed when the setting is saved. This is registered when the custom component is mounted.unRegisterSaveAction
: On unmount of the custom component, unRegisterSaveAction will remove the registered function executed on save of the custom component.On initialization of the custom component, the current value of the custom setting is passed in the props.value
in a json format as read from the config. This value can be processed as necessary to display in your custom UI and ready to be modified by the end user. In the example below, it processes the initial props.value
and sets it in a local state for the component to use as needed:
constructor(props) {
super(props);
this.state = {
attributes: this.initAttributes(props.value),
}
}
When a user makes a change in the UI, the OnChange
handler sends back the current value of the setting as a json. Additionally, setSaveNeeded
should be called to enable the Save
button in order for the changes to be saved.
handleChange = () => {
// ...
this.props.onChange(this.props.id, Array.from(this.state.attributes.values()));
this.props.setSaveNeeded()
};
Once the user saves the changes, any handler that was registered with registerSaveAction
will be executed to perform any additional custom actions the plugin may require, such as calling an additional endpoint within the plugin.
For examples of custom settings see: Demo Plugin `CustomSetting` and Custom Attributes Plugin implementation.
Sometimes, you have been working on a personal repository for a new plugin, most probably based on the mattermost-plugin-starter-template repo. As it was a personal project, you may have pushed all of your commits directly to master
. And now that it’s functional, you need a reviewer to take a look at the whole thing.
For this, it is useful to create a PR with only the commits you added. Follow these steps to do so:
First of all, you need to obtain the identifier of the oldest commit that should be reviewed. You can review your history with git log --oneline
, where you need to look for the very first commit that you added. Imagine that the output is something like the following:
f7d89b8 (HEAD -> master, origin/master) Lint code
fa99500 Fix bug
0b3b5bd Add feature
8f6aef3 My first commit to the plugin
...
... rest of commits from mattermost-plugin-starter-template
...
In this case, the identifier that we need to copy is 8f6aef3
.
Create a new branch without the commits that you added. Using the SHA that you copied, create the branch base
and push it:
git branch base 8f6aef3~1
git push origin base
Note that 8f6aef3~1
means the parent commit of 8f6aef3
, effectively selecting all the commits in the branch except the ones that you added.
Create a branch with all the commits, included the ones that you added, and push it. This branch, compare
, will be an exact copy of master
:
git branch compare master
git push origin compare
Now you have two new branches in the repository: base
and compare
. In Github, create a new PR in your repository, setting the base branch to base
and the compare branch to compare
.
Request a code review on the resulting PR.
For future changes, you can always repeat this process, making sure to identify the first commit you want to be reviewed. You can also consider the more common scenario of creating a feature branch (using something like git checkout -b my.feature.branch
) and opening a PR whenever you want to merge the changes into master
. It’s up to you!
Don’t be afraid to extend the API or hooks to support brand new functionality. Consider accepting an options struct instead of a list of parameters to simplify extending the API in the future:
// GetUsers a list of users based on search options.
//
// Minimum server version: 5.10
GetUsers(options *model.UserGetOptions) ([]*model.User, *model.AppError)
Old servers won’t do anything with new, unrecognized fields, but also won’t break if they are present.
From Mattermost v9.4, a ServeMetrics
hook can be used to expose performance metrics in the open metrics format under the common HTTP listener controlled by the MetricsSettings.ListenAddress
config setting.
Data returned by the hook’s implementation through the given http.ResponseWriter
object will be served through the http://SITE_URL:8067/plugins/PLUGIN_ID/metrics
URL.
Here’s a sample implementation using the Prometheus HTTP client library:
import (
"net/http"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func (p *Plugin) initMetrics() {
p.registry = prometheus.NewRegistry()
// ... Registrations
}
func (p *Plugin) ServeMetrics(_ *plugin.Context, w http.ResponseWriter, r *http.Request) {
promhttp.HandlerFor(p.registry, promhttp.HandlerOpts{}).ServeHTTP(w, r)
}