snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
161
docs/develop/integrate/plugins/best-practices.md
Normal file
@@ -0,0 +1,161 @@
|
||||
---
|
||||
title: "Best practices"
|
||||
sidebar_position: 100
|
||||
---
|
||||
|
||||
See here for [server-specific best practices for plugins](/developers/integrate/plugins/components/server/best-practices). Webapp-specific best practices are incoming.
|
||||
|
||||
## How can a plugin enable its configuration through the System Console?
|
||||
|
||||
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](/developers/integrate/plugins/manifest-reference#settings_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`](/developers/integrate/reference/server#API.GetConfig) API call and can also make changes as needed with [`saveConfig`](/developers/integrate/reference/server#API.SaveConfig).
|
||||
|
||||
## How can a plugin define its own setting type?
|
||||
|
||||
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.
|
||||
|
||||
1. Define a `type: custom` setting in the plugins manifest `settings_schema`
|
||||
|
||||
```diff
|
||||
"settings_schema": {
|
||||
"settings": [{
|
||||
"key": "NormalSetting",
|
||||
"type": "text",
|
||||
+ }, {
|
||||
+ "key": "CustomSetting",
|
||||
+ "type": "custom"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
2. 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`](/developers/integrate/reference/webapp#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.
|
||||
|
||||
3. 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:
|
||||
|
||||
```js
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
attributes: this.initAttributes(props.value),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. 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.
|
||||
|
||||
```js
|
||||
handleChange = () => {
|
||||
// ...
|
||||
this.props.onChange(this.props.id, Array.from(this.state.attributes.values()));
|
||||
this.props.setSaveNeeded()
|
||||
};
|
||||
```
|
||||
|
||||
5. 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`](https://github.com/mattermost/mattermost-plugin-demo/blob/master/webapp/src/components/admin_settings/custom_setting.jsx) and Custom Attributes Plugin [implementation](https://github.com/mattermost/mattermost-plugin-custom-attributes/pull/18).
|
||||
|
||||
## How can I review the entire code base of a plugin?
|
||||
|
||||
Sometimes, you have been working on a personal repository for a new plugin, most probably based on the [mattermost-plugin-starter-template](https://github.com/mattermost/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:
|
||||
|
||||
1. 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
|
||||
...
|
||||
```
|
||||
|
||||
```text
|
||||
In this case, the identifier that we need to copy is `8f6aef3`.
|
||||
```
|
||||
|
||||
2. Create a new branch without the commits that you added. Using the SHA that you copied, create the branch `base` and push it:
|
||||
|
||||
```shell
|
||||
git branch base 8f6aef3~1
|
||||
git push origin base
|
||||
```
|
||||
|
||||
```text
|
||||
Note that `8f6aef3~1` means _the parent commit of `8f6aef3`_, effectively selecting all the commits in the branch except the ones that you added.
|
||||
```
|
||||
|
||||
3. 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`:
|
||||
|
||||
```sh
|
||||
git branch compare master
|
||||
git push origin compare
|
||||
```
|
||||
|
||||
4. 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`.
|
||||
|
||||
5. 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!
|
||||
|
||||
## When to write a new API method or hook?
|
||||
|
||||
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:
|
||||
|
||||
```go
|
||||
// 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.
|
||||
|
||||
|
||||
## How to expose performance metrics for a plugin?
|
||||
|
||||
From Mattermost v9.4, a [`ServeMetrics`](/developers/integrate/reference/server#API.ServeMetrics) hook can be used to expose performance metrics in the [open metrics format](https://openmetrics.io/) under the common HTTP listener controlled by the [`MetricsSettings.ListenAddress`](https://docs.mattermost.com/configure/environment-configuration-settings.html#listen-address-for-performance) 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](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus):
|
||||
|
||||
```go
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "Community plugins in the Marketplace"
|
||||
sidebar_position: 110
|
||||
---
|
||||
|
||||
Once your plugin has reached a certain level of quality, you might consider submitting it to the web Marketplace. The [Mattermost web Marketplace](https://mattermost.com/marketplace/) supports discovery, installation, and updates of extensions of the Mattermost platform.
|
||||
|
||||
If you have built a plugin, app, playbook, integration, or tool that helps other users get more from Mattermost, the web Marketplace is a great way to get feedback on your contribution and help make it more popular.
|
||||
|
||||
Read more about the process on the [Marketplace submissions](/developers/integrate/marketplace-submissions) page.
|
||||
81
docs/develop/integrate/plugins/community_process.md
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: "Process to include plugin on community"
|
||||
sidebar_position: 120
|
||||
---
|
||||
|
||||
Getting your plugin onto our Community server https://community.mattermost.com is a valuable source of feedback. Whether you're a [core committer](/developers/contribute/more-info/getting-started/core-committers) or anyone from the community, we want you to get feedback to improve your plugin.
|
||||
|
||||
However we must ensure that our Community server remains stable for everyone. This document outlines the process of getting your plugin onto the Community server and some of these steps are required to get your plugin into the [Marketplace](/developers/integrate/plugins/community-plugin-marketplace).
|
||||
|
||||
When you're ready to begin this process for your plugin, ask in the [Toolkit channel](https://community.mattermost.com/core/channels/developer-toolkit) on the Community server. The PM, or someone else from the Integrations team, will help you start the process.
|
||||
|
||||
## Checklist
|
||||
|
||||
### Deploy to ci-extensions
|
||||
|
||||
- [Basic Code Review](#basic-code-review) passed
|
||||
- [CI system setup](#ci-system-setup) to build master
|
||||
- Has a [compatible licence](#compatible-licence).
|
||||
|
||||
### Deploy to ci-extensions-release
|
||||
|
||||
- Complete code review by two core committers. One focused on security.
|
||||
- [QA pass](#qa-pass)
|
||||
- [PM/UX review](#pmux-review)
|
||||
- Release created and [CI system setup](#ci-system-setup) to build releases
|
||||
|
||||
### Deploy to community.mattermost.com
|
||||
|
||||
- QA pass on ci-extensions-release of the release to deploy.
|
||||
|
||||
## Step definitions
|
||||
|
||||
### Basic code review
|
||||
|
||||
Basic code review of an experimental plugin involves a quick review by a [core committer](/developers/contribute/more-info/getting-started/core-committers) to verify that the plugin does what it says it does and to provide any guidance and feedback. To make it easier to provide feedback, a PR can be made that contains all the code of the plugin that isn't the boilerplate from mattermost-plugin-starter-template.
|
||||
|
||||
- When you are ready for your plugin to start this process, post an introduction in the [Integrations and Apps channel](https://community.mattermost.com/core/channels/integrations) on the Community server. Here is [an example](https://community.mattermost.com/core/pl/6dci1ssexjrh9rmdzt4pdpb9zy).
|
||||
|
||||
### CI system setup
|
||||
|
||||
Setting up the CI system for your plugin will allow continuous testing of your master branch and releases on our testing servers. Master branch testing is done on https://ci-extensions.azure.k8s.mattermost.com/ and release testing is done on https://ci-extensions-release.azure.k8s.mattermost.com/.
|
||||
|
||||
In order to set this up, the plugin creator needs to provide a URL that hosts latest master build, which we can pull on a nightly basis. Once that exists you can make a request in the [Integrations and Apps](https://community.mattermost.com/core/channels/integrations) channel.
|
||||
|
||||
### Compatible licence
|
||||
|
||||
Recommended licences:
|
||||
|
||||
- Apache Licence 2.0
|
||||
- MIT
|
||||
- BSD 2-clause
|
||||
- BSD 3-clause
|
||||
|
||||
[More info](https://www.apache.org/legal/resolved.html#category-a)
|
||||
|
||||
### Complete code review
|
||||
|
||||
A more thorough code review is performed before allowing a plugin on `ci-extensions-release`. This review works the same as the basic code review, but the developers performing the review will be more thorough. If the developer that performed the first review is available, they should be one of the reviewers. Another of the reviewers should focus their review on any security implications of the plugin.
|
||||
|
||||
### QA pass
|
||||
|
||||
QA pass involves getting a member of our QA team to take a look and verify the functionality advertised by your plugin.
|
||||
|
||||
Steps involved:
|
||||
|
||||
- Ensure all setup documentation needed is clear and can be successfully followed.
|
||||
- Dedicated instance or test account to access and test the third-party service, if applicable.
|
||||
- Functional testing has been done to ensure the integration works as expected.
|
||||
- For plugins owned by Mattermost, release testing is added to cover the main functionality of the plugin.
|
||||
|
||||
### PM/UX review
|
||||
|
||||
A PM/UX pass involves getting PM support in ironing out any user experience or UI issues with the plugin.
|
||||
|
||||
Steps involved:
|
||||
|
||||
- Create a one paragraph summary of the integration.
|
||||
- Document the main use cases in bullet form.
|
||||
- Review the primary use cases and run through them to ensure they are complete, clear, and functional.
|
||||
- Ensure there is documentation to support the plugin. This may include having sufficient helper text in the plugin.
|
||||
- Consider if communication to other teams or users is required as part of the rollout to our Community server.
|
||||
10
docs/develop/integrate/plugins/components/index.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "Components"
|
||||
sidebar_position: 40
|
||||
---
|
||||
|
||||
A Mattermost plugin consists of several components:
|
||||
|
||||
- [Server](/server): subprocesses invoked by the Mattermost Server that communicate using RPC
|
||||
- [Webapp](/webapp): extensions/modifications to the Mattermost web and desktop apps
|
||||
- [Mobile](/mobile): mobile device support
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Mobile plugins"
|
||||
sidebar_position: 30
|
||||
---
|
||||
|
||||
[Server plugins](/developers/integrate/plugins/components/server) may use [interactive dialogs](/developers/integrate/plugins/interactive-dialogs) and [interactive messages](/developers/integrate/plugins/interactive-messages) to build cross platform interactions that are compatible with mobile.
|
||||
|
||||
It isn't possible to customize the mobile user experience in the same way the [webapp](/developers/integrate/plugins/components/webapp) can be extended.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "Best practices"
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
## How should plugins serve publicly available static files?
|
||||
|
||||
Add all static files under a file directory named `public` within the plugin directory, and include the files in the plugin bundle using the Makefile.
|
||||
|
||||
## How do plugins make sure http requests are authentic?
|
||||
|
||||
Plugins can implement the [`ServeHTTP`](/developers/integrate/reference/server#Hooks.ServeHTTP) to listen to http requests. This can be used to receive post action requests when [Interactive Messages Buttons and Menus](https://docs.mattermost.com/developer/interactive-messages.html) are triggered by users.
|
||||
|
||||
When plugins act as an HTTP server, they serve requests from Mattermost clients (which are authenticated in a Mattermost sense), but may also serve HTTP requests from external services like webhooks. These requests from external services might use the Authorization header to authorize themselves against the plugin.
|
||||
|
||||
Since these requests are just HTTP requests, anyone can send them to the plugin. Hence, the plugin must make sure the requests are authentic. The Mattermost Server sets the HTTP header ``Mattermost-User-Id`` when the request is made by an authenticated client. If the plugin is expecting the request to come from an authenticated Mattermost user, and the ``Mattermost-User-Id`` is blank, it's the plugin's responsibility to reject the request.
|
||||
|
||||
From Mattermost v9.4, external systems can use the ``Authorization`` header to authenticate with the plugin. HTTP requests to server-side plugins can use an ``Authorization`` header in the request for the plugin to use, and the header value will be present in the request received by the plugin, as long as the token provided in the header is not a user token issued by the Mattermost server. This is useful for connecting external systems that require authentication through an Authorization header with their own token.
|
||||
|
||||
## How should plugins handle request cancellation?
|
||||
|
||||
Long-running `ServeHTTP` handlers should pass `r.Context()` to downstream operations and stop work when it is canceled. Mattermost v12.0 and later propagates deadlines and explicit cancellation for server-to-plugin and inter-plugin HTTP requests when the destination plugin is built with a context-aware plugin runtime.
|
||||
|
||||
Inter-plugin callers can opt in with `http.NewRequestWithContext` or `req.WithContext`. If cancellation happens before response headers arrive, `PluginHTTP` returns `nil` and the caller can inspect `request.Context().Err()`. If it happens after headers arrive, reading the response body returns `context.Canceled` or `context.DeadlineExceeded`. Callers must close response bodies as usual.
|
||||
|
||||
Context values are not propagated. Use headers or `plugin.Context` for supported cross-process metadata. Plugins that require end-to-end cancellation should declare Mattermost v12.0 as their minimum server version. Older servers retain legacy behavior: they continue to process the request without context propagation, and the call may not return promptly after cancellation.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "Debug Server plugins"
|
||||
---
|
||||
|
||||
Plugins communicate with the main Mattermost Server by [RPC](https://github.com/hashicorp/go-plugin). In order to debug them with Delve, a few steps are necessary.
|
||||
### macOS
|
||||
1. After starting the main Mattermost application, run `ps aux | grep name.of.your.plugin`. This will print a list of running processes that match that name, as such: `username 78836 0.0 0.1 4397696 12492 s006 S 7:07AM 0:00.03 plugins/name.of.your.plugin/server/dist/plugin-darwin-amd64`.
|
||||
2. Grab the `pid`, which is the second number after your username in the output above. Run `dlv attach pid`, where `pid` is that number. let the plugin continue executing soon after connecting; Otherwise, the server will detect it as stopped and attempt to restart it.
|
||||
3. You're done. You should have access to your plugin's code through Delve and be able to set breakpoints, etc.
|
||||
42
docs/develop/integrate/plugins/components/server/ha.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "High availability"
|
||||
sidebar_position: 60
|
||||
---
|
||||
|
||||
Mattermost Enterprise Edition servers with an E20 license have the ability to run in [High Availability (HA)](https://docs.mattermost.com/deployment/cluster.html) mode, meaning a cluster of Mattermost app servers running together as a single Mattermost deployment.
|
||||
|
||||
It is important that all plugins consider HA environments when being built.
|
||||
|
||||
Plugins are started as subprocesses of the main Mattermost process on each app server. This means a Mattermost deployment that has three app servers will have three separate copies of the same plugin running. Each running copy of the plugin will be isolated from one another on different servers. Therefore, to run properly in HA the plugin's server-side code must be stateless.
|
||||
|
||||
To be stateless, the plugin must not retain any information or status in memory that may be needed across multiple events (e.g. HTTP requests or in other hooks). This data should instead be stored in a place that all running copies of the plugin have access to. For example, the [key-value store](/developers/integrate/reference/server#API.KVSet) the plugin API provides.
|
||||
|
||||
To better explain the problem with having a plugin store data in-memory, consider this case:
|
||||
|
||||
Let's say we have two Mattermost app servers running together in HA and an installed plugin with a feature that alerts a user any time a trigger word is posted in a channel.
|
||||
|
||||
1. The user uses the web app side of the plugin to set the word `hello` as the trigger word
|
||||
2. An HTTP request is made to the server-side of the plugin to set `hello`
|
||||
3. The plugin process running on app server 1 handles the request and stores `hello` in a variable
|
||||
|
||||
In this scenario, the trigger word is now only set for the plugin process running on app server 1. The plugin process running on app server 2 is unaware that the trigger word is `hello`. This means when someone posts a message containing `hello` and the request is load balanced to app server 2, the plugin is not going to alert our user when it should.
|
||||
|
||||
The proper way to deal with this case would be for the plugin to store the trigger word in a global store, such as the KV store. Then any time a user posts the plugin can pull the trigger word from the store and properly alert the user, regardless of which app server handles the request.
|
||||
|
||||
## Run a scheduled job in high availability mode
|
||||
|
||||
Using the [mattermost/public/pluginapi/cluster](https://github.com/mattermost/mattermost/blob/d2c3710265c293281c2b445d4f72f27871c2e127/server/public/pluginapi/cluster/job.go#L115-L116) package, we can schedule jobs to perform background activity at regular intervals, without having to explicitly coordinate with other instances of the same plugin. Here's an example from the [Demo Plugin](https://github.com/mattermost/mattermost-plugin-demo/blob/d647f1ed7fdc384f5bc163a6bba689ab4293704e/server/activate_hooks.go#L72):
|
||||
|
||||
```go
|
||||
job, cronErr := cluster.Schedule(
|
||||
p.API,
|
||||
"BackgroundJob",
|
||||
cluster.MakeWaitForRoundedInterval(15*time.Minute),
|
||||
p.BackgroundJobFunc,
|
||||
)
|
||||
if cronErr != nil {
|
||||
return errors.Wrap(cronErr, "failed to schedule background job")
|
||||
}
|
||||
|
||||
p.backgroundJob = job
|
||||
```
|
||||
113
docs/develop/integrate/plugins/components/server/hello-world.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: "Plugin quick start"
|
||||
sidebar_position: -10
|
||||
---
|
||||
|
||||
This tutorial will walk you through the basics of writing a Mattermost plugin with a server component.
|
||||
|
||||
Note that the steps below are intentionally very manual to explain all of the pieces fitting together. In practice, we recommend referencing [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template) for helpful build scripts. Also, the plugin API changed in Mattermost 5.2. Consult the [migration](/developers/integrate/plugins/migration) document to upgrade older plugins.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Mattermost plugins extend the server using a Go API. In the future, gRPC may be supported, allowing you to write plugins in any language. For now, you'll need a functioning Go environment, so follow [Go's Getting Started](https://golang.org/doc/install) guide if needed.
|
||||
|
||||
You'll also need a Mattermost server to install and test the plugin. This server must have [Enable](https://docs.mattermost.com/administration/config-settings.html#enable-plugins) set to true in the [PluginSettings](https://docs.mattermost.com/administration/config-settings.html#plugins-beta) section of its config file. If you want to upload plugins via the System Console or API, you'll also need to set [EnableUploads](https://docs.mattermost.com/administration/config-settings.html#enable-plugin-uploads) to true in the same section.
|
||||
|
||||
## Build the plugin
|
||||
|
||||
The process that will communicate with the Mattermost server is built using a set of APIs provided by the source code for the Mattermost server.
|
||||
|
||||
Download the source code for the Mattermost server:
|
||||
|
||||
```bash
|
||||
go get -u github.com/mattermost/mattermost/server/v8
|
||||
```
|
||||
|
||||
Define `$GOPATH`. By default, this is already `$HOME/go`, but it's helpful to make this explicit:
|
||||
```shell
|
||||
export GOPATH=$HOME/go
|
||||
```
|
||||
|
||||
Now, create a directory to act as your workspace:
|
||||
|
||||
```bash
|
||||
mkdir -p $GOPATH/src/my-plugin
|
||||
cd $GOPATH/src/my-plugin
|
||||
```
|
||||
|
||||
Create a file named `plugin.go` with the following contents:
|
||||
|
||||
<PluginGoExample name="_helloWorld" />
|
||||
|
||||
This plugin will register an HTTP handler that will respond with "Hello, world!" when requested.
|
||||
|
||||
Build the executable that will be distributed with your plugin:
|
||||
|
||||
```bash
|
||||
go build -o plugin.exe plugin.go
|
||||
```
|
||||
|
||||
|
||||
<Note title="Note">
|
||||
Your executable is platform specific! If you're building the plugin for a server running on a different operating system, you'll need to use a slightly different command. For example, if you're developing the plugin from MacOS and deploying to a Linux server, you'll need to use this command:
|
||||
</Note>
|
||||
|
||||
|
||||
```bash
|
||||
GOOS=linux GOARCH=amd64 go build -o plugin.exe plugin.go
|
||||
```
|
||||
|
||||
Also note that the ".exe" extension is required if you'd like your plugin to run on Windows, but is otherwise optional. Consider referencing [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template) for helpful build scripts.
|
||||
|
||||
Now, we'll need to define the required manifest describing your plugin's entry point. Create a file named `plugin.json` with the following contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "com.mattermost.server-hello-world",
|
||||
"name": "Hello World",
|
||||
"server": {
|
||||
"executable": "plugin.exe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This manifest gives the server the location of your executable within your plugin bundle. Consult the [manifest reference](/developers/integrate/plugins/manifest-reference) for more details, including how to define a cross-platform bundle by defining multiple executables, and how to define a minimum required server version for your plugin.
|
||||
|
||||
Note that you may also use `plugin.yaml` to define the manifest.
|
||||
|
||||
Bundle the manifest and executable into a tar file:
|
||||
|
||||
```bash
|
||||
tar -czvf plugin.tar.gz plugin.exe plugin.json
|
||||
```
|
||||
|
||||
You should now have a file named `plugin.tar.gz` in your workspace. Congratulations! This is your first server plugin!
|
||||
|
||||
## Install the plugin
|
||||
|
||||
Install the plugin in one of the following ways:
|
||||
|
||||
1) Through System Console UI:
|
||||
|
||||
```text
|
||||
- Log in to Mattermost as a System Admin.
|
||||
- Open the System Console at `/admin_console`
|
||||
- Navigate to *Plugins > Plugin Management** and upload the `plugin.tar.gz` you generated above.
|
||||
- Click **Enable** under the plugin after it has uploaded.
|
||||
```
|
||||
|
||||
2) Or, manually:
|
||||
|
||||
- Extract `plugin.tar.gz` to a folder with the same name as the plugin id you specified in ``plugin.json``, in this case `com.mattermost.server-hello-world/`.
|
||||
- Add the plugin to the directory set by **PluginSettings > Directory** in your ``config.json`` file. If none is set, defaults to `./plugins` relative to your Mattermost installation directory. The resulting directory structure should look something like:
|
||||
|
||||
```
|
||||
mattermost/
|
||||
plugins/
|
||||
com.mattermost.server-hello-world/
|
||||
plugin.json
|
||||
plugin.exe
|
||||
```
|
||||
- Restart the Mattermost server.
|
||||
|
||||
Once you've installed the plugin in one of the ways above, browse to `https://<your-mattermost-server>/plugins/com.mattermost.server-hello-world`, and you'll be greeted by your plugin.
|
||||
48
docs/develop/integrate/plugins/components/server/index.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: "Server plugins"
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
Server plugins are subprocesses invoked by the server that communicate with Mattermost using remote procedure calls (RPC).
|
||||
|
||||
Looking for a quick start? [See our "Hello, world!" tutorial](/developers/integrate/plugins/components/server/hello-world).
|
||||
|
||||
Want the Server SDK reference doc? [Find it here](/developers/integrate/reference/server).
|
||||
|
||||
## Features
|
||||
|
||||
#### RPC API
|
||||
|
||||
Use the [RPC API](/developers/integrate/reference/server#API) to execute create, read, update and delete (CRUD) operations on server data models.
|
||||
|
||||
For example, your plugin can consume events from a third-party webhook and create corresponding posts in Mattermost, without having to host your code outside Mattermost.
|
||||
|
||||
#### Hooks
|
||||
|
||||
Register for [hooks](/developers/integrate/reference/server#Hooks) and get alerted when certain events occur.
|
||||
|
||||
For example, consume the [OnConfigurationChange](/developers/integrate/reference/server#Hooks.OnConfigurationChange) hook to respond to server configuration changes, or the [MessageHasBeenPosted](/developers/integrate/reference/server#Hooks.MessageHasBeenPosted) hook to respond to posts.
|
||||
|
||||
#### REST API
|
||||
|
||||
Implement the [ServeHTTP](/developers/integrate/) hook to extend the existing Mattermost REST API.
|
||||
|
||||
Plugins with both a web app and server component can leverage this REST API to exchange data. Alternatively, expose your REST API to services and developers connecting from outside Mattermost.
|
||||
|
||||
## How it works
|
||||
|
||||
When starting a plugin, the server consults the [plugin's manifest](/developers/integrate/plugins/manifest-reference) to determine if a server component was included. If found, the server launches a new process using the executable included with the plugin.
|
||||
|
||||
The server will trigger the [OnActivate](/developers/integrate/reference/server#Hooks.OnActivate) hook if the plugin is successfully started, allowing you to perform startup events. If the plugin is disabled, the server will trigger the [OnDeactivate](/developers/integrate/reference/server#Hooks.OnDeactivate) hook. While running, the server plugin can consume hook events, make API calls, launch threads or subprocesses of its own, interact with third-party services or do anything else a regular program can do.
|
||||
|
||||
## High availability
|
||||
|
||||
Considerations for plugins in a high availability configuration are documented here: [High Availability](/developers/integrate/plugins/components/server/ha)
|
||||
|
||||
## Best practices
|
||||
|
||||
Some best practices for working with the server component of a plugin are documented here: [Best Practices](/developers/integrate/plugins/components/server/best-practices)
|
||||
|
||||
## Debug Server plugins
|
||||
|
||||
Guidelines for debugging the server-side of plugins are documented here: [Debug Server plugins](/developers/integrate/plugins/components/server/debugging)
|
||||
282
docs/develop/integrate/plugins/components/webapp/actions.md
Normal file
@@ -0,0 +1,282 @@
|
||||
---
|
||||
title: "Redux actions"
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
When building web app plugins, it's common to perform actions or access the state that web and mobile apps already support. The majority of these actions exist in [mattermost-redux](https://github.com/mattermost/mattermost-redux), which is our library of shared code between Mattermost JavaScript clients. The `mattermost-redux` library exports types and functions that are imported by the web application. These functions can be imported by plugins and used the same way. There are a few different kinds of functions exported by the library:
|
||||
|
||||
* [actions](https://github.com/mattermost/mattermost-redux/tree/master/src/actions): Actions perform API requests and can change the state of Mattermost.
|
||||
* [client](https://github.com/mattermost/mattermost-redux/tree/master/src/client): The client package can be used to instantiate a Client4 object to interact with the Mattermost API directly. This is useful in plugins as well as JavaScript server applications communicating with Mattermost.
|
||||
* [constants](https://github.com/mattermost/mattermost-redux/tree/master/src/constants): An assortment of constants within Mattermost's data model.
|
||||
* [selectors](https://github.com/mattermost/mattermost-redux/tree/master/src/selectors): Selectors return certain data from the Redux store, such as getPost which allows you get a post by id.
|
||||
* [store](https://github.com/mattermost/mattermost-redux/tree/master/src/store): Functions related to the Redux store itself.
|
||||
* [types](https://github.com/mattermost/mattermost-redux/tree/master/src/types): Various types of objects in Mattermost's data model. These are useful for plugins written in Typescript.
|
||||
* [utils](https://github.com/mattermost/mattermost-redux/tree/master/src/utils): Various utility functions shared across the web application.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
It's assumed you have already set up your plugin development environment for web app plugins to match [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template). If not, follow the README instructions of that repository first, or [see the Hello, World! guide](/developers/integrate/plugins/components/webapp/hello-world).
|
||||
|
||||
## Basic example
|
||||
|
||||
Here's an example of a web app plugin making use of [mattermost-redux's](https://github.com/mattermost/mattermost-redux) selectors:
|
||||
|
||||
```ts
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {getPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
const MyComponent = ({postId}) => {
|
||||
const post = useSelector((state) => getPost(state, postId));
|
||||
const currentUser = useSelector(getCurrentUser);
|
||||
const currentChannel = useSelector(getCurrentChannel);
|
||||
const currentTeam = useSelector(getCurrentTeam);
|
||||
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## Some common actions
|
||||
|
||||
We've listed out some of the commonly-used actions that you can use in your web app plugin. You can find all the actions are available for your plugin to import [in the source code for mattermost-redux](https://github.com/mattermost/mattermost-redux/tree/master/src/actions).
|
||||
|
||||
* ### [createChannel(channel: Channel, userId: string)](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/channels.ts#L47)
|
||||
|
||||
Dispatch this action to create a new channel.
|
||||
|
||||
|
||||
* ### [getCustomEmoji(emojiId: string)](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/emojis.ts#L32)
|
||||
|
||||
Dispatch this action to fetch a specific emoji associated with the `emojiId` provided.
|
||||
|
||||
|
||||
* ### [createPost(post: Post, files: any\[\] = \[\])](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/posts.ts#L162)
|
||||
|
||||
Dispatch this action to create a new post.
|
||||
|
||||
|
||||
* ### [getMyTeams()](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/teams.ts#L67)
|
||||
|
||||
Dispatch this action to fetch all the team types associated with the current user.
|
||||
|
||||
|
||||
* ### [createUser(user: UserProfile, token: string, inviteId: string, redirect: string)](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/users.ts#L53)
|
||||
|
||||
Dispatch this action to create a new user profile.
|
||||
|
||||
|
||||
## Some common selectors
|
||||
|
||||
Here are some examples of commonly-used selectors you can use in your web app plugin. You can find all the selectors that are available for your plugin to import [in the source code for mattermost-redux](https://github.com/mattermost/mattermost-redux/tree/master/src/selectors).
|
||||
|
||||
* ### [getCurrentUserId(state)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/common.ts#L46)
|
||||
|
||||
Retrieves the `userId` of the current user from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCurrentUser(state: GlobalState): UserProfile](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/common.ts#L42)
|
||||
|
||||
Retrieves the user profile of the current user from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getUsers(state: GlobalState): IDMappedObjects<UserProfile>](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/common.ts#L50)
|
||||
|
||||
Retrieves all user profiles from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getChannel(state: GlobalState, id: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/channels.ts#L218)
|
||||
|
||||
Retrieves a channel as it exists in the store without filling in any additional details such as the `display_name` for Direct Messages/Group Messages.
|
||||
|
||||
|
||||
* ### [getCurrentChannelId(state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/common.ts#L14)
|
||||
|
||||
Retrieves the channel ID of the current channel from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCurrentChannel: (state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/channels.ts#L235)
|
||||
|
||||
Retrieves the complete channel info of the current channel from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getPost(state: GlobalState, postId: $ID<Post>)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/posts.ts#L46)
|
||||
|
||||
Retrieves the specific post associated with the supplied `postID` from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCurrentTeamId(state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/teams.ts#L20)
|
||||
|
||||
Retrieves the `teamId` of the current team from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCurrentTeam: (state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/teams.ts#L57)
|
||||
|
||||
Retrieves the team info of the current team from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCustomEmojisByName: (state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/emojis.ts#L37)
|
||||
|
||||
Retrieves the the specific emoji associated with the supplied `customEmojiName` from the `Redux store`.
|
||||
|
||||
|
||||
## Some common client functions
|
||||
|
||||
We've listed out some of the commonly-used client functions you can use in your web app plugin. You can find all the client functions that are available for your plugin to import [in the source code for mattermost-redux](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts).
|
||||
|
||||
* ### [getUser = (userId: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L846)
|
||||
|
||||
Routes to the user profile of the specified `userId` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getUserByUsername = (username: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L853)
|
||||
|
||||
Routes to the user profile of the specified `username` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getChannel = (channelId: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L1600)
|
||||
|
||||
Routes to the channel of the specified `channelId` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getChannelByName = (teamId: string, channelName: string, includeDeleted = false)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L1609)
|
||||
|
||||
Routes to the channel of the specified `channelName` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getTeam = (teamId: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L1192)
|
||||
|
||||
Routes to the team of the specified `teamId` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getTeamByName = (teamName: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L1199)
|
||||
|
||||
Routes to the team of the specified `teamName` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [executeCommand = (command: string, commandArgs: CommandArgs)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L2463)
|
||||
|
||||
Executes the specified command with the arguments provided and fetches the response.
|
||||
|
||||
|
||||
* ### [getOptions(options: Options) \{const newOptions: Options = \{...options\}](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L440)
|
||||
|
||||
Get the client options to make requests to the server. Use this to create your own custom requests.
|
||||
|
||||
## Custom reducers and actions
|
||||
|
||||
Reducers in Redux are pure functions that describe how the data in the store changes after any given action. Reducers will always produce the same resulting state for a given state and action. You can register a custom reducer for your plugin against the Redux store with the `registerReducer` function.
|
||||
|
||||
### [registerReducer(reducer)](/developers/integrate/reference/webapp#registerReducer)
|
||||
|
||||
Registers a reducer against the Redux store. It will be accessible in Redux state under `state['plugins-<yourpluginid>']`. It generally accepts a reducer and returns undefined.
|
||||
|
||||
When building web app plugins, it is common to perform actions that web and mobile apps already support. The majority of these actions exist in [mattermost-redux](https://github.com/mattermost/mattermost-redux), our library of shared code between Mattermost JavaScript clients.
|
||||
|
||||
Here we'll show how to use Redux actions with a plugin. To learn more about these actions, see the [contributor documentation](/developers/contribute/more-info/webapp/redux/actions).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This guide assumes you have already set up your plugin development environment for web app plugins to match [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template). If not, follow the README instructions of that repository first, or [see the Hello, World! guide](/developers/integrate/plugins/components/webapp/hello-world).
|
||||
|
||||
## Import mattermost-redux
|
||||
|
||||
First, you'll need to add `mattermost-redux` as a dependency of your web app plugin.
|
||||
|
||||
```bash
|
||||
cd /path/to/plugin/webapp
|
||||
npm install mattermost-redux
|
||||
```
|
||||
|
||||
That will add `mattermost-redux` as a dependency in your `package.json` file, allowing it to be imported into any of your plugin's JavaScript files.
|
||||
|
||||
## Use an action
|
||||
|
||||
Actions are used as part of components. To give components access to these actions, we pass them in as React props from the component's container `index.js` file. To demonstrate this, we'll create a new component.
|
||||
|
||||
In the `webapp` directory, let's create a component folder called `action_example` and switch into it.
|
||||
|
||||
```bash
|
||||
mkdir -p src/components/action_example
|
||||
cd src/components/action_example
|
||||
```
|
||||
|
||||
In there, create two files: `index.js` and `action_example.jsx`. If you're not familiar with why we're creating these directories and files, [read the contributor documentation on using React with Redux](/developers/contribute/more-info/webapp/redux/react-redux).
|
||||
|
||||
Open up `action_example.jsx` and add the following:
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class ActionExample extends React.PureComponent {
|
||||
static propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
patchUser: PropTypes.func.isRequired, // here we define the action as a prop
|
||||
}
|
||||
|
||||
updateFirstName = () => {
|
||||
const patchedUser = {
|
||||
id: this.props.user.id,
|
||||
first_name: 'Jim',
|
||||
};
|
||||
|
||||
this.props.patchUser(patchedUser); // here we use the action
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{'First name: ' + this.props.user.first_name}
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.updateFirstName}
|
||||
>
|
||||
Click me to update the first name!
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This component will display a user's first name and then, when the link is clicked, use an action to update that user's first name to "Jim".
|
||||
|
||||
The action `patchUser` is from mattermost-redux. It takes in a subset of a user object and updates the user on the server, [using the `PUT /users/\{user_id\}/patch` endpoint](https://api.mattermost.com/#tag/users%2Fpaths%2F~1users~1%7Buser_id%7D~1patch%2Fput).
|
||||
|
||||
We must now use our container to import this action and pass it our component. Open up the `index.js` file and add:
|
||||
|
||||
```javascript
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {patchUser} from 'mattermost-redux/actions'; // importing the action
|
||||
|
||||
import ActionExample from './action_example.jsx';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const currentUserId = state.entities.users.currentUserId;
|
||||
|
||||
return {
|
||||
user: state.entities.users.profiles[currentUserId],
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => bindActionCreators({
|
||||
patchUser, // passing the action as a prop
|
||||
}, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ActionExample);
|
||||
```
|
||||
|
||||
The container is doing two things. First, it's grabbing the current (logged in) user from the Redux state and passing it in as a prop. Anytime the Redux state updates, for example when we use the `patchUser` action, our component will get the updated copy of the current user. Second, the container is importing the `patchUser` action from mattermost-redux and passing it in as an action prop to our component.
|
||||
|
||||
Now we can use `this.props.patchUser()` to update a user. The example component we made uses it to patch the current user's first name.
|
||||
|
||||
To use our component in our plugin we would then use the registry in the initialization function of the plugin to register the component somewhere in the Mattermost UI. That is beyond the scope of this guide, but you can [read more about that here](/developers/integrate/reference/webapp).
|
||||
|
||||
## Available actions
|
||||
|
||||
The actions that are available for your plugin to import can be [found in the source code for mattermost-redux](https://github.com/mattermost/mattermost-redux/tree/master/src/actions).
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: "Best practices"
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
## Design best practices
|
||||
|
||||
### Actions that apply to specific Channels
|
||||
- Recommendation: Have your plugin register the actions to [the channel header](/developers/integrate/reference/webapp#registerChannelHeaderButtonAction). This makes it quickly accessible for users and the actions apply on the channel they're viewing.
|
||||
- Example: Zoom meeting posts to a channel
|
||||
|
||||

|
||||
|
||||
You can additionally [register a slash command](/developers/integrate/reference/server#API.RegisterCommand) on the server-side to take channel-specific actions.
|
||||
- Example: Jira project actions
|
||||
|
||||

|
||||
|
||||
### Actions that apply to specific messages
|
||||
- Recommendation: Have your plugin register a [post dropdown menu component](/developers/integrate/reference/webapp#registerPostDropdownMenuComponent) with some text, icon and an action function. This adds your action to the "More Actions" post menu dropdown for easy discovery.
|
||||
- Examples: Create or attach to Jira issue from a message; copy a message to another channel; report an inappropriate message
|
||||
- Sample code: [mattermost/mattermost-plugin-todo/webapp/src/index.js](https://github.com/mattermost/mattermost-plugin-todo/blob/0c4dbfb58a72f8392ea66e101996afd06fdb2913/webapp/src/index.js#L30-L37)
|
||||
|
||||

|
||||
|
||||
### Actions related to files or images
|
||||
- Recommendation: Have your plugin register a [file upload method](/developers/integrate/reference/webapp#registerFileUploadMethod) with some text, icon and an action function. This adds your new action to the file upload menu.
|
||||
- Examples: File sharing from OneDrive or GDrive; Draw plugin for sketches
|
||||
|
||||

|
||||
|
||||
### Actions that apply to specific teams
|
||||
- Recommendation: Have your plugin register [left sidebar header component](/developers/integrate/reference/webapp#registerLeftSidebarHeaderComponent) with some text, icon and an action function. This adds your action above your team's channels in the sidebar.
|
||||
- Examples: Trello kanban board plugin, GitHub Plugin
|
||||
|
||||

|
||||
|
||||
### Quick links or status summaries of workflows
|
||||
- Recommendation: Have your plugin register a [bottom team sidebar component](/developers/integrate/reference/webapp#registerBottomTeamSidebarComponent). This adds icons to the lower left corner of the UI.
|
||||
- Examples: GitHub sidebar links with summary of outstanding reviews or unread messages; ServiceNow incident status summary
|
||||
|
||||

|
||||
|
||||
### Global actions that can be taken anywhere in the server and not directly related to teams, channels or users
|
||||
- Recommendation: Have your plugin register [main menu action](/developers/integrate/reference/webapp#registerMainMenuAction) with some text, icon for mobile, and an action function. This adds your action to the Main Menu. You can additionally [register a slash command](/developers/integrate/reference/server#API.RegisterCommand) on the server-side.
|
||||
- Examples: Share feedback plugin in Main Menu; /jira slash commands for quick actions
|
||||
|
||||

|
||||
|
||||
### Actions that apply to specific users
|
||||
- Recommendation: Have your plugin register a [popover user actions component](/developers/integrate/reference/webapp#registerPopoverUserActionsComponent). This adds your action button to the user profile popover.
|
||||
- Examples: Report User plugin; Display extra information about the user from an LDAP server
|
||||
|
||||

|
||||
|
||||
### Extra information on a user profile
|
||||
- Recommendation: Have your plugin register a [popover user attribute component](/developers/integrate/reference/webapp#registerPopoverUserAttributesComponent). This adds your custom attributes to the user profile popover.
|
||||
- Examples: Custom User Attributes plugin
|
||||
|
||||

|
||||
|
||||
### Actions related to emoji and GIFs
|
||||
- Recommendation: Have your plugin add a component to the emoji picker. This is not yet supported, but some [work had previously started](https://github.com/mattermost/mattermost/issues/10412#issuecomment-481776595) with the issue currently opened as Help Wanted.
|
||||
- Examples: Bitmoji plugin; GIFs via Giphy or Gfycat
|
||||
|
||||
## Set up the plugin to properly communicate with the Mattermost Server
|
||||
|
||||
### Use the Mattermost Server's `SiteURL` in your web app plugin
|
||||
|
||||
In order to make sure your plugin has full compatibility with your Mattermost server, you should use the server's configured SiteURL in each API call you send to the server from the webapp. Here's an [example](https://github.com/mattermost/mattermost-plugin-jira/blob/19a9c2442817132b4eee5c77e259b80a40188a6a/webapp/src/selectors/index.js#L13-L26) of how to compute the SiteURL:
|
||||
|
||||
```js
|
||||
export const getPluginServerRoute = (state) => {
|
||||
const config = getConfig(state);
|
||||
|
||||
let basePath = '';
|
||||
if (config && config.SiteURL) {
|
||||
basePath = new URL(config.SiteURL).pathname;
|
||||
|
||||
if (basePath && basePath[basePath.length - 1] === '/') {
|
||||
basePath = basePath.substr(0, basePath.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return basePath + '/plugins/' + PluginId;
|
||||
};
|
||||
```
|
||||
|
||||
### Include the server's CSRF token in your web app plugin's requests
|
||||
|
||||
The Mattermost server can be configured to require a CSRF token to be present in HTTP requests sent from the webapp. In order to include the token in each request, you can use the `mattermost-redux` library's `Client4.getOptions` function to add the token to your `fetch` request. Here's an [example](https://github.com/mattermost/mattermost-plugin-jira/blob/19a9c2442817132b4eee5c77e259b80a40188a6a/webapp/src/client/index.js#L14) of how to include the CSRF token.
|
||||
|
||||
```js
|
||||
const response = await fetch(url, Client4.getOptions(options));
|
||||
```
|
||||
178
docs/develop/integrate/plugins/components/webapp/hello-world.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: "Web app quick start"
|
||||
sidebar_position: -10
|
||||
---
|
||||
|
||||
This tutorial will walk you through the basics of extending the Mattermost web app.
|
||||
|
||||
Note that the steps below are intentionally very manual to explain all of the pieces fitting together. In practice, we recommend referencing [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template) for helpful build scripts. Also, the plugin API changed in Mattermost 5.2. Consult the [migration](/developers/integrate/plugins/migration) document to upgrade older plugins.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Plugins, just like the Mattermost web app itself, are built using [ReactJS](https://react.dev/) with [Redux](https://redux.js.org/). Make sure to install [npm](https://www.npmjs.com/get-npm) to manage your JavaScript dependencies.
|
||||
|
||||
You'll also need a Mattermost server to install and test the plugin. This server must have [Enable](https://docs.mattermost.com/administration/config-settings.html#enable-plugins) set to true in the [PluginSettings](https://docs.mattermost.com/administration/config-settings.html#plugins-beta) section of its config file. If you want to upload plugins via the System Console or API, you'll also need to set [EnableUploads](https://docs.mattermost.com/administration/config-settings.html#enable-plugin-uploads) to true in the same section.
|
||||
|
||||
## Set up the workspace
|
||||
|
||||
Create a directory to act as your plugin workspace. With that directory, create and switch to a `webapp` directory:
|
||||
|
||||
```bash
|
||||
mkdir webapp
|
||||
cd webapp
|
||||
```
|
||||
|
||||
Install the necessary NPM dependencies:
|
||||
|
||||
```bash
|
||||
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli
|
||||
npm install --save react
|
||||
```
|
||||
|
||||
Configure Webpack by creating a `webpack.config.js` file:
|
||||
|
||||
```js
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/index.jsx',
|
||||
],
|
||||
resolve: {
|
||||
modules: [
|
||||
'src',
|
||||
'node_modules',
|
||||
],
|
||||
extensions: ['*', '.js', '.jsx'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-react',
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": "commonjs",
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
react: 'React',
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, '/dist'),
|
||||
publicPath: '/',
|
||||
filename: 'main.js',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Observe that `react` is specified as an external library. This allows you to test your code locally (e.g. with [jest](https://jestjs.io/) and snapshots) but leverage the version of React shipped with Mattermost to avoid bloating your plugin.
|
||||
|
||||
Now create the entry point file and output directory:
|
||||
```bash
|
||||
mkdir src dist
|
||||
touch src/index.jsx
|
||||
```
|
||||
|
||||
Then populate `src/index.jsx` with the following:
|
||||
```jsx
|
||||
import React from 'react';
|
||||
|
||||
// Courtesy of https://feathericons.com/
|
||||
const Icon = () => <i className='icon fa fa-plug'/>;
|
||||
|
||||
class HelloWorldPlugin {
|
||||
initialize(registry, store) {
|
||||
registry.registerChannelHeaderButtonAction(
|
||||
// icon - JSX element to use as the button's icon
|
||||
<Icon />,
|
||||
// action - a function called when the button is clicked, passed the channel and channel member as arguments
|
||||
// null,
|
||||
() => {
|
||||
alert("Hello World!");
|
||||
},
|
||||
// dropdown_text - string or JSX element shown for the dropdown button description
|
||||
"Hello World",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.registerPlugin('com.mattermost.webapp-hello-world', new HelloWorldPlugin());
|
||||
```
|
||||
|
||||
Generate a minified bundle ready to install as a web app plugin:
|
||||
|
||||
```bash
|
||||
./node_modules/.bin/webpack --mode=production
|
||||
```
|
||||
|
||||
Now, we'll need to define the required manifest describing your plugin's entry point. Create a file named `plugin.json` with the following contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "com.mattermost.webapp-hello-world",
|
||||
"name": "Hello World",
|
||||
"webapp": {
|
||||
"bundle_path": "main.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This manifest gives the server the location of your components within your plugin bundle. Consult the [manifest reference](/developers/integrate/plugins/manifest-reference) for more details, including how to define a minimum required server version for your plugin.
|
||||
|
||||
Note that you may also use `plugin.yaml` to define the manifest.
|
||||
|
||||
Bundle the manifest and entry point into a tar file:
|
||||
|
||||
```bash
|
||||
mkdir -p com.mattermost.webapp-hello-world
|
||||
cp -r dist/main.js com.mattermost.webapp-hello-world/
|
||||
cp plugin.json com.mattermost.webapp-hello-world/
|
||||
tar -czvf plugin.tar.gz com.mattermost.webapp-hello-world
|
||||
```
|
||||
|
||||
You should now have a file named `plugin.tar.gz` in your workspace. Congratulations! This is your first web app plugin!
|
||||
|
||||
## Install the plugin
|
||||
|
||||
Install the plugin in one of the following ways:
|
||||
|
||||
1) Through System Console UI:
|
||||
|
||||
```text
|
||||
- Log in to Mattermost as a System Admin.
|
||||
- Open the System Console at `/admin_console`
|
||||
- Navigate to **Plugins > Plugin Management** and upload the `plugin.tar.gz` you generated above.
|
||||
- Click **Enable** under the plugin after it has uploaded.
|
||||
```
|
||||
|
||||
2) Or, manually:
|
||||
|
||||
- Extract `plugin.tar.gz` to a folder with the same name as the plugin id you specified in ``plugin.json``, in this case `com.mattermost.server-hello-world/`.
|
||||
- Add the plugin to the directory set by **PluginSettings > Directory** in your ``config.json`` file. If none is set, defaults to `./plugins` relative to your Mattermost installation directory. The resulting directory structure should look something like:
|
||||
|
||||
```
|
||||
mattermost/
|
||||
plugins/
|
||||
com.mattermost.webapp-hello-world/
|
||||
plugin.json
|
||||
main.js
|
||||
```
|
||||
- Restart the Mattermost server.
|
||||
- Enable the plugin in **System Console > Plugins > Plugin Management**.
|
||||
|
||||
Navigate to a regular Mattermost page and observe the new icon in the channel header. Click the icon and observe the alert dialog.
|
||||
54
docs/develop/integrate/plugins/components/webapp/index.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: "Web app plugins"
|
||||
sidebar_position: 20
|
||||
---
|
||||
|
||||
Web app plugins extend and modify the Mattermost web and desktop apps, without having to fork and rebase on every Mattermost release.
|
||||
|
||||
Looking for a quick start? [See our "Hello, world!" tutorial](/developers/integrate/plugins/components/webapp/hello-world).
|
||||
|
||||
Want the web app SDK reference doc? [Find it here](/developers/integrate/reference/webapp).
|
||||
|
||||
## Features
|
||||
|
||||
#### Extend existing components
|
||||
|
||||
Register your own React components to be added to the channel header, sidebars, user details popover, main menu and other supported components. Multiple plugins can add to the same component simultaneously. This API focuses on ease of use and maintaining compatibility with future Mattermost releases.
|
||||
|
||||
For example, the [Mattermost Zoom Plugin](https://github.com/mattermost/mattermost-plugin-zoom) registers a button in the channel header to trigger a Zoom call and post a link to the call in the current channel.
|
||||
|
||||
#### Add new root components
|
||||
|
||||
Register your own React components alongside other root components like the sidebars and modals. This enables whole new interactions that aren't constrained by the set of existing components. This API is geared towards power and flexibility, but may require fine tuning with future Mattermost releases.
|
||||
|
||||
#### Custom post type components
|
||||
|
||||
Web app plugins can also render different post components based on the post's type. Any time the web app encounters a post with this post type, it replaces the default rendering of the post component with your own custom implementation. Only one plugin can own the rendering for a given custom post type at a time: the last plugin to register will own the rendering for that custom post type.
|
||||
|
||||
For example, you can register a custom post type `custom_poll` using [registerPostTypeComponent](/developers/integrate/reference/webapp#registerPostTypeComponent). Then, any time the web app sees that post type, it replaces the regular rendering of the post component with your own custom implementation.
|
||||
|
||||
Use this in conjunction with setting the post type in webhooks or slash commands, through the REST API or with a server plugin, and you can deeply integrate or extend Mattermost posts to fit your needs.
|
||||
|
||||
## How it works
|
||||
|
||||
When a plugin is uploaded to a Mattermost server and activated, the server checks to see if there is a webapp portion included as part of the plugin by looking at the [plugin's manifest](/developers/integrate/plugins/manifest-reference). If one is found, the server copies the bundled JavaScript included with the plugin into the static directory for serving. A WebSocket event is then fired off to the connected clients signalling the activation of a new plugin.
|
||||
|
||||
On web app launch, a request is made to the server to get a list of plugins that contain web app components. The web app then proceeds to download and execute the JavaScript bundles for each plugin. A similar process happens if an already launched web app receives a WebSocket event for a newly activated plugin.
|
||||
|
||||
Once downloaded and executed, each plugin should have registered itself via the global [registerPlugin](/developers/integrate/reference/webapp#registerPlugin). The web app then invokes the `initialize` function defined on the plugin class, passing a registry and store. The registry passed allows the plugin to register (and unregister) components, event callbacks and Redux reducers to track plugin state. The store passed is the same Redux store used by the web app, giving the plugin access to the full state of the web app.
|
||||
|
||||
Components registered by the plugin via the registry are tracked in the Redux store and used by `Pluggable` components throughout the web app. `Pluggable` components with a `pluggableName` attribute can render multiple such components registered by plugins.
|
||||
|
||||
Custom post types work similarly, but are registered slightly differently, use a separate reducer and have more of a custom implementation.
|
||||
|
||||
## Redux actions
|
||||
|
||||
Further information on the available Redux Actions is documented here: [Redux Actions](/developers/integrate/plugins/components/webapp/actions)
|
||||
|
||||
## Best practices
|
||||
|
||||
Some best practices for working with the webapp component of a plugin are documented here: [Best Practices](/developers/integrate/plugins/components/webapp/best-practices)
|
||||
|
||||
## React and JSX runtime compatibility
|
||||
|
||||
Plugins that render inside Mattermost must use the host's React, ReactDOM, and JSX runtime. See [React JSX runtime compatibility](/developers/integrate/plugins/components/webapp/react-jsx-runtime) to configure the host exports and require Mattermost 12.0 or later.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: "React JSX runtime compatibility"
|
||||
sidebar_position: 30
|
||||
---
|
||||
|
||||
Mattermost v12 uses React 19. Web app plugins must use the host's React, ReactDOM, and JSX runtime. Externalizing only `react` can leave an older JSX runtime bundled in the plugin or a dependency, causing `ReactCurrentOwner` or incompatible-element errors.
|
||||
|
||||
## 1. Require Mattermost 12.0
|
||||
|
||||
Set `"min_server_version": "12.0.0"` in the plugin manifest, or keep a higher minimum if your plugin already requires one.
|
||||
|
||||
## 2. Use the host's JSX runtime
|
||||
|
||||
Add all five module paths below to your plugin's Webpack `externals` configuration:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ...
|
||||
externals: {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
'react-dom/client': 'ReactDOM',
|
||||
'react/jsx-runtime': 'ReactJSXRuntime',
|
||||
'react/jsx-dev-runtime': 'ReactJSXDevRuntime',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Continue writing JSX normally. With the automatic JSX transform, your compiler imports helpers from `react/jsx-runtime` or `react/jsx-dev-runtime`; this configuration resolves those imports to Mattermost's runtime. Include `react-dom/client` because dependencies may import it directly.
|
||||
|
||||
Mattermost provides these exports before plugin code runs. Development plugin builds also work on production hosts, but development-only JSX diagnostics are unavailable there.
|
||||
|
||||
## 3. Rebuild and test
|
||||
|
||||
Rebuild and release the plugin after updating its configuration. Existing published bundles are not changed by the host exports.
|
||||
|
||||
Check dependencies for embedded JSX runtimes. Webpack externals handle module imports, but cannot replace a runtime already embedded in a dependency's code. Update affected dependencies to versions that import the runtime separately.
|
||||
|
||||
Test the rebuilt plugin on Mattermost 12.0, including modals, menus, settings, and other UI that appears after startup. Exercise production and development plugin builds, including a development plugin on a production host. If your plugin requires a higher minimum server version, test on that version.
|
||||
|
||||
The shared JSX runtime does not restore APIs removed by React 19. Replace `findDOMNode` with DOM refs and legacy ReactDOM rendering calls with root APIs. See the [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for the remaining migration steps.
|
||||
79
docs/develop/integrate/plugins/developer-setup.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: "Developer setup"
|
||||
sidebar_position: 20
|
||||
---
|
||||
|
||||
Once you have your [server](/developers/contribute/developer-setup) and [webapp](/developers/contribute/developer-setup) set up, you can start developing on plugins.
|
||||
|
||||
|
||||
<Note title="Note">
|
||||
Plugin development doesn't require a development build of Mattermost. Development builds of Mattermost are only required if you want to develop for Mattermost internally.
|
||||
</Note>
|
||||
|
||||
|
||||
For developing on Mattermost-managed plugins, each plugin's setup instructions can be found in the plugin repository's README. Some plugins do not have external dependencies and require little to no setup, like the [Todo Plugin](https://github.com/mattermost/mattermost-plugin-todo) while others require an external service to be set up, like the [Jira Plugin](https://github.com/mattermost/mattermost-plugin-jira) and [GitHub Plugin](https://github.com/mattermost/mattermost-plugin-github).
|
||||
|
||||
## Set up your environment to deploy plugins
|
||||
|
||||
### Deploy with local mode
|
||||
|
||||
|
||||
<Note title="Note">
|
||||
Deploying with local mode will only work for plugins that have been updated with the functionality from the [Plugin Starter Template](https://github.com/mattermost/mattermost-plugin-starter-template).
|
||||
</Note>
|
||||
|
||||
|
||||
If your Mattermost server is running locally, you can enable [local mode](https://docs.mattermost.com/manage/mmctl-command-line-tool.html#local-mode) and [plugin uploads](https://docs.mattermost.com/configure/configuration-settings.html#enable-plugin-uploads) to streamline deploying your plugin. Edit your server configuration as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"ServiceSettings": {
|
||||
// ...
|
||||
"EnableLocalMode": true,
|
||||
"LocalModeSocketLocation": "/var/tmp/mattermost_local.socket"
|
||||
},
|
||||
"PluginSettings": {
|
||||
// ...
|
||||
"Enable": true,
|
||||
"EnableUploads": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and then deploy your plugin:
|
||||
|
||||
```shell
|
||||
make deploy
|
||||
```
|
||||
|
||||
You may also customize the Unix socket path:
|
||||
|
||||
```shell
|
||||
export MM_LOCALSOCKETPATH=/var/tmp/alternate_local.socket
|
||||
make deploy
|
||||
```
|
||||
|
||||
### Deploy with authentication credentials
|
||||
|
||||
Alternatively, you can authenticate with the server's API with credentials:
|
||||
|
||||
```shell
|
||||
export MM_SERVICESETTINGS_SITEURL=http://localhost:8065
|
||||
export MM_ADMIN_USERNAME=admin
|
||||
export MM_ADMIN_PASSWORD=password
|
||||
make deploy
|
||||
```
|
||||
|
||||
or with a [personal access token](https://developers.mattermost.com/integrate/reference/personal-access-token/):
|
||||
|
||||
```shell
|
||||
export MM_SERVICESETTINGS_SITEURL=http://localhost:8065
|
||||
export MM_ADMIN_TOKEN=j44acwd8obn78cdcx7koid4jkr
|
||||
make deploy
|
||||
```
|
||||
|
||||
|
||||
<Note title="Tip">
|
||||
Different plugin projects may require specific versions of Node.js. The recommended version for the given project is always defined in a file in the root of the repository called `.nvmrc`. You can use the tool [Node Version Manager](https://nvm.sh) to install and switch between node versions in your terminal. If you have `nvm` installed, you can run `nvm install` anywhere in the plugin repository, and it will automatically find the `.nvmrc` file in the root, and install and use that version. If you already have that version installed, you can run `nvm use`, though `nvm install` can be easier since you don't need to check if you already have the specific Node version installed.
|
||||
</Note>
|
||||
|
||||
163
docs/develop/integrate/plugins/developer-workflow.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
title: "Developer workflow"
|
||||
sidebar_position: 30
|
||||
---
|
||||
|
||||
### Common `make` commands for working with plugins
|
||||
|
||||
- `make test` - Runs the plugin's server tests and webapp tests
|
||||
- `make check-style` - Runs linting checks on the plugin's server and webapp folders
|
||||
- `make deploy` - Compiles the plugin using the `make dist` command, then automatically deploys the plugin to the Mattermost server. Enabling [Local Mode](/developers/integrate/plugins/developer-setup#deploying-with-local-mode) on your server is the easiest way to use this command.
|
||||
- `make watch` - Uses webpack's watch feature to re-compile and deploy the webapp portion of your plugin on any change to the `webapp/src` folder.
|
||||
- `make dist` - Compile the plugin into a g-zipped file, ready to upload to a Mattermost server. The file is saved in the plugin repo's `dist` folder.
|
||||
- `make enable` - Enables the plugin on the Mattermost server
|
||||
- `make disable` - Disables the plugin on the Mattermost server.
|
||||
- `make reset` - Disables and re-enables the plugin on the Mattermost server.
|
||||
- `make attach-headless` - Starts a `delve` process and attaches it to your running plugin.
|
||||
- `make clean` - Force deletes the content of build-related files. Use when running into build issues.
|
||||
|
||||
You can run the development build of the plugin by setting the environment variable `MM_DEBUG=1`, or prefixing the variable at the beginning of the `make` command. For example, `MM_DEBUG=1 make deploy` will deploy the development build of the plugin to your server, allowing you to have a more fluid debugging experience. To use the production build of the plugin instead, unset the `MM_DEBUG` environment variable before running the `make` commands.
|
||||
|
||||
### Develop in the plugin's webapp folder
|
||||
|
||||
In order for your IDE to know the root directory of the plugin's webapp code, it is advantageous to open the IDE in the webapp folder itself when working on the webapp portion of the plugin. This way, the IDE is aware of files such as `webpack.config.js` and `tsconfig.json`.
|
||||
|
||||
### Expose the Mattermost server using `ngrok`
|
||||
|
||||
When a plugin integrates with an external service, webhooks and/or authentication redirects are necessary, which requires your local server to be available on the web. In order for your Mattermost server to be available to process webhook requests, it needs to expose its port to an external address. A common way to do this is to use the command line tool [ngrok](https://ngrok.com). Follow these steps to set up `ngrok` with your server:
|
||||
|
||||
- Download the ngrok tool from [here](https://ngrok.com/download).
|
||||
- Put the executable somewhere within your shell's `PATH`.
|
||||
- With your Mattermost server already running, use the command `ngrok http 8065` to make your Mattermost server available for webhook requests.
|
||||
- Visit the `https` URL from the `ngrok` command's output, and log into Mattermost.
|
||||
- Set your Mattermost server's [Site URL](http://localhost:8065/admin_console/environment/web_server) to the `https` address given from the `ngrok` command output.
|
||||
- Monitor incoming webhook requests with ngrok's request inspector. Visit [http://localhost:4040](http://localhost:4040) once you have your tunnel open. You can analyze the contents of the HTTP request from the external service, and the response from your plugin.
|
||||
|
||||
If you're using a free ngrok account, the URL given by the output of the `ngrok http` command will be different each time you run the command. As a result, you'll need to adjust the webhook URL on Mattermost's side and the external service's side (e.g. GitHub) each time you run the command.
|
||||
|
||||
With this setup, many integrations require you to be logged into Mattermost using your ngrok URL. After logging into your ngrok URL pointed to your Mattermost server, in most cases you can continue using your `localhost` address in your browser for quicker network requests to your server. If you receive an error like `unauthorized` or `enable third-party cookies` when connecting to an external service, make sure you're logged into your ngrok URL in the same browser.
|
||||
|
||||
##### Use `localhost.run` instead of `ngrok`
|
||||
|
||||
If you would like to avoid using ngrok, there is another free option that you can run from your terminal, called [`localhost.run`](https://localhost.run). Use this command to expose your server:
|
||||
|
||||
```sh
|
||||
ssh -R 80:localhost:8065 ssh.localhost.run
|
||||
```
|
||||
|
||||
An `http` URL pointing to your server should show in the terminal. The `https` version of this same URL should also work, which is what you will want to use for your webhook URLs. One disadvantage of using `localhost.run` is there is no request/response logging dashboard that is available with ngrok.
|
||||
|
||||
### Debug server-side plugins using `delve`
|
||||
|
||||
Using the `delve` debugger, we can step through code for a running plugin on our local Mattermost server. There are a few steps for setup to make this work properly.
|
||||
|
||||
#### Configure Mattermost server for debugging plugins
|
||||
|
||||
In order to allow the debugger to pause code execution, we need to disable Mattermost's "health check" for plugins and the Hashicorp `go-plugin` package's "keep alive" feature for its RPC connection. We'll configure the server with the following steps:
|
||||
|
||||
- In the server's `config.json`, set `PluginSettings.EnableHealthCheck` to `false`
|
||||
- Run the script below with `./patch_go_plugin.sh $GO_PLUGIN_PACKAGE_VERSION` where `GO_PLUGIN_PACKAGE_VERSION` is the version of `go-plugin` that your Mattermost server is using. This can be found in the monorepo at [server/go.mod](https://github.com/mattermost/mattermost/blob/4bdd8bb18e47d16f9680905972516526b6fd61d8/server/go.mod#L141) on your local server.
|
||||
- Restart the server
|
||||
|
||||
More details on this are explained below:
|
||||
|
||||
##### Disable Mattermost plugin health check job
|
||||
|
||||
To disable the Mattermost plugin health check job, go into your `config.json` and set `PluginSettings.EnableHealthCheck` to `false`. Note that this will make it so if your plugin panics/crashes for any reason during your development, the Mattermost server will not restart the plugin or notice that it crashed. It will remain with the status of "running" in the plugin management page, even though it has crashed. Because of this, you'll need to watch server logs for any information related to plugin panics during your debugging.
|
||||
|
||||
##### Disable `go-plugin` RPC client "keep alive"
|
||||
|
||||
We'll be editing external library source code directly, so we only want to do this in a development environment.
|
||||
|
||||
By default, the `go-plugin` package runs plugins with a "keep alive" feature enabled, which essentially pings the plugin RPC connection every 30 seconds, and if the plugin doesn't respond, the RPC connection will be terminated. There is a way to disable this, though the `go-plugin` currently doesn't expose a way to configure this setting, so we need to edit the `go-plugin` package's source code to have the right configuration for our debugging use case.
|
||||
|
||||
In the script below, we automatically modify the file located at `${GOPATH}/pkg/mod/github.com/hashicorp/go-plugin@${GO_PLUGIN_PACKAGE_VERSION}/rpc_client.go`, where `GO_PLUGIN_PACKAGE_VERSION` is the version of `go-plugin` that your Mattermost server is using. This can be found in your local copy of the monorepo at [server/go.mod](https://github.com/mattermost/mattermost/blob/4bdd8bb18e47d16f9680905972516526b6fd61d8/server/go.mod#L141). This script essentially replaces the line in [go-plugin/rpc_client.go](https://github.com/hashicorp/go-plugin/blob/586d14f3dcef1eb42bfb7da4c7af102ec6638668/rpc_client.go#L66) to have a custom configuration for the RPC client connection, that disables the "keep alive" feature. This makes it so the debugger can be paused for long amounts of time, and the Mattermost server will keep the connection with the plugin open.
|
||||
|
||||
```sh
|
||||
# patch_go_plugin.sh
|
||||
|
||||
GO_PLUGIN_PACKAGE_VERSION=$1
|
||||
|
||||
GO_PLUGIN_RPC_CLIENT_PATH=${GOPATH}/pkg/mod/github.com/hashicorp/go-plugin@${GO_PLUGIN_PACKAGE_VERSION}/rpc_client.go
|
||||
|
||||
echo "Patching $GO_PLUGIN_RPC_CLIENT_PATH for debugging Mattermost plugins"
|
||||
|
||||
if ! grep -q 'mux, err := yamux.Client(conn, nil)' "$GO_PLUGIN_RPC_CLIENT_PATH"; then
|
||||
echo "The file has already been patched or the target line was not found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sudo sudo sed -i '' '/import (/a\
|
||||
"time"
|
||||
' $GO_PLUGIN_RPC_CLIENT_PATH
|
||||
|
||||
sudo sed -i '' '/mux, err := yamux.Client(conn, nil)/c\
|
||||
sessionConfig := yamux.DefaultConfig()\
|
||||
sessionConfig.EnableKeepAlive = false\
|
||||
sessionConfig.ConnectionWriteTimeout = time.Minute * 5\
|
||||
mux, err := yamux.Client(conn, sessionConfig)
|
||||
' $GO_PLUGIN_RPC_CLIENT_PATH
|
||||
|
||||
echo "Patched go-plugin's rpc_client.go for debugging Mattermost plugins"
|
||||
```
|
||||
|
||||
Then run the script like so:
|
||||
|
||||
```sh
|
||||
chmod +x patch_go_plugin.sh
|
||||
./patch_go_plugin.sh v1.6.0 # as of writing, this is the version of `go-plugin` being used by the server
|
||||
```
|
||||
|
||||
#### Configure VSCode for debugging
|
||||
|
||||
This section assumes you are using VSCode to debug your plugin. If you want to use a different IDE, the process will be mostly the same. If you want to debug in your terminal directly with `delve` instead of using an IDE, you can run `make attach` instead of `make attach-headless` below, which will launch a `delve` process as an interactive terminal.
|
||||
|
||||
Include this configuration in your VSCode instance's [launch.json](https://learn.microsoft.com/en-us/microsoft-edge/visual-studio-code/microsoft-edge-devtools-extension/launch-json):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Attach to Mattermost plugin",
|
||||
"type": "go",
|
||||
"request": "attach",
|
||||
"mode": "remote",
|
||||
"port": 2346,
|
||||
"host": "127.0.0.1",
|
||||
"apiVersion": 2
|
||||
}
|
||||
```
|
||||
|
||||
#### Attach headless `delve` process to the running plugin
|
||||
|
||||
Build the plugin and deploy to your local Mattermost server:
|
||||
|
||||
```sh
|
||||
make deploy
|
||||
```
|
||||
|
||||
In a separate terminal, open a `delve` process for VSCode to connect to:
|
||||
|
||||
```sh
|
||||
make attach-headless
|
||||
```
|
||||
|
||||
This starts a headless `delve` process for your IDE to connect to. The process listens on port `2346`, which is the port defined in our `launch.json` configuration. Somewhat related, the Mattermost server's `Makefile` has a command `debug-server-headless`, which starts a headless `delve` process for the Mattermost server, listening on port `2345`. So you can create a similar `launch.json` configuration in the server directory of the monorepo to connect to your server by using that port.
|
||||
|
||||
#### Attach the debugger to the `delve` process
|
||||
|
||||
Run the debugger in VSCode by navigating to the "Run and Debug" tab on the left side of the IDE, selecting your launch configuration, and clicking the green play button. This should bring up a debugging widget that looks like this:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Your IDE's debugger is now running and ready to pause your plugin's execution at any breakpoints you set in the IDE.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If you run into issues with debugging, first make sure you've stopped any active debugging sessions by clicking the red disconnect button in the VSCode debugging widget.
|
||||
|
||||
You can then use the `make reset` command in the plugin repository to do the following:
|
||||
- Disable and re-enable the plugin
|
||||
- Terminate any running `delve` processes running for this plugin
|
||||
|
||||
For more discussion on this, please join the Toolkit channel on our community server: https://community.mattermost.com/core/channels/developer-toolkit
|
||||
68
docs/develop/integrate/plugins/example-plugins.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: "Example plugins"
|
||||
sidebar_position: 90
|
||||
---
|
||||
|
||||
## Server "Hello, world!"
|
||||
|
||||
To get started extending server-side functionality with plugins, take a look at our [server "Hello, world!" tutorial](/developers/integrate/plugins/components/server/hello-world).
|
||||
|
||||
## Web app "Hello, world!"
|
||||
|
||||
To get started extending browser-side functionality with plugins, take a look at our [web app "Hello, world!" tutorial](/developers/integrate/plugins/components/webapp/hello-world).
|
||||
|
||||
## Demo plugin
|
||||
|
||||
To see a demonstration of all server-side hooks and webapp components, take a look at our [demo plugin](https://github.com/mattermost/mattermost-plugin-demo).
|
||||
|
||||
## Sample plugin
|
||||
|
||||
To see a stripped down version of the demo plugin with just the build scripts and templates to get started, take a look at our [plugin starter template](https://github.com/mattermost/mattermost-plugin-starter-template).
|
||||
|
||||
## Zoom
|
||||
|
||||
The [Zoom plugin for Mattermost](https://github.com/mattermost/mattermost-plugin-zoom) adds UI elements that allow users to easily create and join Zoom meetings:
|
||||
|
||||
<img src="/img/extend/zoom-plugin-screenshot.png" width="445" height="295" />
|
||||
|
||||
Topics demonstrated:
|
||||
|
||||
* Uses a custom HTTP handler to integrate with external systems.
|
||||
* Defines a settings schema, allowing system administrators to configure the plugin via system console UI.
|
||||
* Implements tests using the [plugin/plugintest](https://godoc.org/github.com/mattermost/mattermost/server/public/plugin/plugintest) package.
|
||||
* Creates rich posts using custom post types.
|
||||
* Extends existing webapp components to add elements to the UI.
|
||||
|
||||
## JIRA
|
||||
|
||||
The [JIRA plugin for Mattermost](https://github.com/mattermost/mattermost-plugin-jira) creates a webhook that your JIRA server can use to post messages to Mattermost when issues are created:
|
||||
|
||||
<img src="/img/extend/jira-plugin-screenshot.png" width="445" height="263" />
|
||||
|
||||
Topics demonstrated:
|
||||
|
||||
* Uses a custom HTTP handler to integrate with external systems.
|
||||
* Defines a settings schema, allowing system administrators to configure the plugin via system console UI.
|
||||
* Implements tests using the [plugin/plugintest](https://godoc.org/github.com/mattermost/mattermost/server/public/plugin/plugintest) package.
|
||||
* Compiles and publishes releases for multiple platforms using Travis-CI.
|
||||
|
||||
## Profanity filter
|
||||
|
||||
The [profanity filter plugin for Mattermost](https://github.com/mattermost/mattermost-plugin-profanity-filter) automatically detects restricted words in posts and censors them prior to writing to the database. For more use cases, [see this forum post](https://forum.mattermost.com/t/coming-soon-apiv4-mattermost-post-intercept/4982).
|
||||
|
||||
Topics demonstrated:
|
||||
|
||||
* Interception and modification of posts prior to writing them into the database.
|
||||
* Rejection of posts prior to writing them into the database.
|
||||
|
||||
## Memes
|
||||
|
||||
The [Memes plugin for Mattermost](https://github.com/mattermost/mattermost-plugin-memes) creates a slash command that can be used to create dank memes:
|
||||
|
||||
<img src="/img/extend/memes-plugin-screenshot.png" width="445" height="325" />
|
||||
|
||||
Topics demonstrated:
|
||||
|
||||
* Registers a custom slash command.
|
||||
* Uses a custom HTTP handler to generate and serve content.
|
||||
* Compiles and publishes releases for multiple platforms using Travis-CI.
|
||||
6
docs/develop/integrate/plugins/helpers.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: "Plugin helpers"
|
||||
sidebar_position: 105
|
||||
---
|
||||
|
||||
In Mattermost v6.0, Plugin Helpers have been removed in favor of the [Mattermost plugin API](https://github.com/mattermost/mattermost-plugin-api).
|
||||
34
docs/develop/integrate/plugins/index.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "Plugins"
|
||||
sidebar_position: 40
|
||||
---
|
||||
|
||||
Mattermost supports plugins that offer powerful features for extending and deeply integrating with both the Server and Web/Desktop Apps.
|
||||
|
||||
Share constructive feedback [on our forum post](https://forum.mattermost.com/t/plugin-system-upgrade-in-mattermost-5-2/5498) or join the [Toolkit channel](https://community.mattermost.com/core/channels/developer-toolkit) on our Mattermost community server.
|
||||
|
||||
## Features
|
||||
|
||||
### Customize user interfaces
|
||||
|
||||
Write a Web App plugin to add to the channel header, sidebars, main menu, and more. Register your plugin against a post type to render custom posts or wire up a root component to build an entirely new experience. All this is possible without having to fork the source code and rebase on every Mattermost release.
|
||||
|
||||
### Launch tightly-integrated services
|
||||
|
||||
Launch and manage Server plugins as services from your Mattermost server over RPC. Handle events via real-time hooks and invoke Mattermost server methods directly using a dedicated plugin API.
|
||||
|
||||
### Extend the Mattermost REST API
|
||||
|
||||
Extend the Mattermost REST API with custom endpoints for use by Web App plugins or third-party services. Custom endpoints have access to all the features of the standard Mattermost REST API, including personal access tokens and OAuth 2.0.
|
||||
|
||||
|
||||
<Note title="Tip">
|
||||
See the [Mattermost Server SDK Reference](/developers/integrate/reference/server) and [Mattermost Client UI SDK Reference](/developers/integrate/reference/webapp) documentation for details on available server API endpoints and client methods.
|
||||
</Note>
|
||||
|
||||
|
||||
### Simple development and installation
|
||||
|
||||
It's simple to set up a plugin development environment with the [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template). Just select "Use this template" when cloning the repository. Please see the [developer setup](https://developers.mattermost.com/integrate/plugins/developer-setup) and [developer workflow](https://developers.mattermost.com/integrate/plugins/developer-workflow) pages for more information.
|
||||
|
||||
Read the plugins [overview](/developers/integrate/plugins/overview) to learn more.
|
||||
1212
docs/develop/integrate/plugins/interactive-dialogs/index.md
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 58 KiB |
492
docs/develop/integrate/plugins/interactive-messages/index.md
Normal file
@@ -0,0 +1,492 @@
|
||||
---
|
||||
title: "Interactive messages"
|
||||
description: "Mattermost supports interactive message buttons and menus through Mattermost Blocks, markdown action buttons, and legacy message attachments so integrations can complete common tasks inside conversations."
|
||||
sidebar_position: 80
|
||||
---
|
||||
|
||||

|
||||
|
||||
For information on interactive dialogs, [see here](/developers/integrate/plugins/interactive-dialogs).
|
||||
|
||||
Use interactive messages to simplify complex workflows by allowing users to take quick actions directly through your integration post. For example, they enable your integration to:
|
||||
|
||||
- Mark a task complete in your project management tracker.
|
||||
- Conduct a customer survey or a poll.
|
||||
- Initiate a command to merge a branch into a release.
|
||||
|
||||
To try it out, you can use this [Matterpoll plugin](https://github.com/matterpoll/matterpoll) to add polling to Mattermost channels via a `/poll` slash command.
|
||||
|
||||

|
||||
|
||||
## Mattermost Blocks (recommended)
|
||||
|
||||
[Mattermost Blocks](/developers/integrate/reference/mm-blocks) are the recommended way to build interactive integration posts. Send a block tree in `props.mm_blocks` for layout, text, images, buttons, and menus, and register action handlers in `props.mm_blocks_actions`.
|
||||
|
||||
See [Mattermost Blocks](/developers/integrate/reference/mm-blocks) for the full block schema, action types, validation limits, and examples.
|
||||
|
||||
## Markdown action buttons
|
||||
|
||||
Embed interactive affordances directly in a post's markdown body using `mmaction://` links backed by the same `mm_blocks_actions` registry. This is useful when a short message reads naturally with an inline "Approve" or "Reject" link.
|
||||
|
||||
See [markdown action buttons](/developers/integrate/reference/markdown-actions) for the full schema, limits, and end-to-end flow.
|
||||
|
||||
## Legacy message attachment buttons and menus
|
||||
|
||||
:::note[Legacy format]
|
||||
The sections below describe interactive buttons and menus placed inside legacy [message attachments](/developers/integrate/reference/message-attachments). Existing integrations continue to work — Mattermost translates attachment actions into Mattermost Blocks at render time — but new integrations should use [Mattermost Blocks](/developers/integrate/reference/mm-blocks) directly.
|
||||
:::
|
||||
|
||||
## Message buttons
|
||||
|
||||
Add message buttons as `actions` in your integration [message attachments](/developers/integrate/reference/message-attachments).
|
||||
|
||||
The following payload gives an example that uses message buttons.
|
||||
|
||||
```json
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"pretext": "This is the attachment pretext.",
|
||||
"text": "This is the attachment text.",
|
||||
"actions": [
|
||||
{
|
||||
"id": "message",
|
||||
"name": "Ephemeral Message",
|
||||
"tooltip": "Send a message only visible to you",
|
||||
"integration": {
|
||||
"url": "http://127.0.0.1:7357",
|
||||
"context": {
|
||||
"action": "do_something_ephemeral"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "update",
|
||||
"name": "Update",
|
||||
"tooltip": "Update this message",
|
||||
"integration": {
|
||||
"url": "http://127.0.0.1:7357",
|
||||
"context": {
|
||||
"action": "do_something_update"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In the HTTP response for this request, the integration can choose to update the original post, and/or respond with an ephemeral message:
|
||||
|
||||
```json
|
||||
{
|
||||
"update": {
|
||||
"message": "Updated!",
|
||||
"props": {}
|
||||
},
|
||||
"ephemeral_text": "You updated the post!"
|
||||
}
|
||||
```
|
||||
|
||||
To return a custom error message to the user, your integration can respond with an error object:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"message": "Unable to complete action. Please check your permissions."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The error message will be displayed to the user below the interactive content. If no custom error message is provided, a default "Action failed to execute" message is shown. This feature is available in Mattermost v10.5 and later.
|
||||
|
||||

|
||||
|
||||
Button actions support a style parameter to change the color of the button. The possible values for style are: `good`, `warning`, `danger`, `default`, `primary`, and `success`. It's also possible to pass a theme variable or a hex color, but we discourage this approach because it won't be resilient against theme changes.
|
||||
|
||||

|
||||
|
||||
The actions used in the previous example include the following:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "vote0",
|
||||
"type": "button",
|
||||
"name": "Yes",
|
||||
"style": "default"
|
||||
},
|
||||
{
|
||||
"id": "vote1",
|
||||
"type": "button",
|
||||
"name": "No",
|
||||
"style": "primary"
|
||||
},
|
||||
{
|
||||
"id": "addOption",
|
||||
"type": "button",
|
||||
"name": "Add Option",
|
||||
"style": "warning"
|
||||
},
|
||||
{
|
||||
"id": "deletePoll",
|
||||
"type": "button",
|
||||
"name": "Delete Poll",
|
||||
"style": "success"
|
||||
},
|
||||
{
|
||||
"id": "endPoll",
|
||||
"type": "button",
|
||||
"name": "End Poll",
|
||||
"style": "danger"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Tooltips on buttons
|
||||
|
||||
You can add tooltips to provide helpful information when users hover over action buttons:
|
||||
|
||||
```json
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"pretext": "Review this pull request",
|
||||
"text": "Pull request #1234: Add new feature",
|
||||
"actions": [
|
||||
{
|
||||
"id": "approve",
|
||||
"type": "button",
|
||||
"name": "Approve",
|
||||
"tooltip": "Click to approve this pull request",
|
||||
"style": "primary",
|
||||
"integration": {
|
||||
"url": "http://127.0.0.1:7357",
|
||||
"context": {
|
||||
"action": "approve",
|
||||
"pr_id": 1234
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "reject",
|
||||
"type": "button",
|
||||
"name": "Reject",
|
||||
"tooltip": "Click to reject this pull request",
|
||||
"style": "danger",
|
||||
"integration": {
|
||||
"url": "http://127.0.0.1:7357",
|
||||
"context": {
|
||||
"action": "reject",
|
||||
"pr_id": 1234
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Message menus
|
||||
|
||||
Similar to buttons, add message menus as `actions` in your integration [message attachments](/developers/integrate/reference/message-attachments).
|
||||
|
||||

|
||||
|
||||
The following payload gives an example that uses message menus (where id in the actions array may only consist of letters and numbers, no other characters are allowed):
|
||||
|
||||
```json
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"pretext": "This is the attachment pretext.",
|
||||
"text": "This is the attachment text.",
|
||||
"actions": [
|
||||
{
|
||||
"id": "actionoptions",
|
||||
"name": "Select an option...",
|
||||
"integration": {
|
||||
"url": "http://127.0.0.1:7357/actionoptions",
|
||||
"context": {
|
||||
"action": "do_something"
|
||||
}
|
||||
},
|
||||
"type": "select",
|
||||
"options": [
|
||||
{
|
||||
"text": "Option1",
|
||||
"value": "opt1"
|
||||
},
|
||||
{
|
||||
"text": "Option2",
|
||||
"value": "opt2"
|
||||
},
|
||||
{
|
||||
"text": "Option3",
|
||||
"value": "opt3"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The integration can respond with an update to the original post, or with an ephemeral message:
|
||||
|
||||
```json
|
||||
{
|
||||
"update": {
|
||||
"message": "Updated!",
|
||||
"props": {}
|
||||
},
|
||||
"ephemeral_text": "You updated the post!"
|
||||
}
|
||||
```
|
||||
|
||||
### Message menus for channels
|
||||
|
||||
You can provide a list of channels for message menus for users to select from. Users can only select from public channels in their teams.
|
||||
|
||||
Specify `channels` as your action's `data_source` as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"pretext": "This is the attachment pretext.",
|
||||
"text": "This is the attachment text.",
|
||||
"actions": [
|
||||
{
|
||||
"id": "actionoptions",
|
||||
"name": "Select an option...",
|
||||
"integration": {
|
||||
"url": "http://127.0.0.1:7357/actionoptions",
|
||||
"context": {
|
||||
"action": "do_something"
|
||||
}
|
||||
},
|
||||
"type": "select",
|
||||
"data_source": "channels"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Message menus for users
|
||||
|
||||
Similar to channels, you can also provide a list of users for message menus. The user can choose the user who is part of the Mattermost system.
|
||||
|
||||
Specify `users` as your action's `data_source` as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"id": "actionoptions",
|
||||
"pretext": "This is the attachment pretext.",
|
||||
"text": "This is the attachment text.",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Select an option...",
|
||||
"integration": {
|
||||
"url": "http://127.0.0.1:7357/actionoptions",
|
||||
"context": {
|
||||
"action": "do_something"
|
||||
}
|
||||
},
|
||||
"type": "select",
|
||||
"data_source": "users"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Below is a brief description of each parameter to help you customize legacy attachment interactive buttons and menus in Mattermost. For new integrations, see [Mattermost Blocks](/developers/integrate/reference/mm-blocks). For more information on message attachments, see [message attachments](/developers/integrate/reference/message-attachments).
|
||||
|
||||
**ID**<br/>
|
||||
A per post unique identifier.
|
||||
|
||||
**Name**<br/>
|
||||
Give your action a descriptive name.
|
||||
|
||||
**Tooltip** (optional)<br/>
|
||||
Display helpful text when users hover over the button. Available in Mattermost v10.5 and later.
|
||||
|
||||
**URL**<br/>
|
||||
The actions are backed by an integration that handles HTTP POST requests when users select the message button. The URL parameter determines where this action is sent. The request contains an `application/json` JSON string. As of 5.14, relative URLs are accepted, simplifying the workflow when a plugin handles the action.
|
||||
|
||||
**Context**<br/>
|
||||
The requests sent to the specified URL contain the user ID, post ID, channel ID, team ID, and any context that was provided in the action definition. If the post was of type `Message Menus`, then context also contains the `selected_option` field with the user-selected option value. The post ID can be used to, for example, delete or edit the post after selecting a message button.
|
||||
|
||||
A simple example of a request is given below:
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "rd49ehbqyjytddasoownkuqrxe",
|
||||
"post_id": "gqrnh3675jfxzftnjyjfe4udeh",
|
||||
"channel_id": "j6j53p28k6urx15fpcgsr20psq",
|
||||
"team_id": "5xxzt146eax4tul69409opqjlf",
|
||||
"context": {
|
||||
"action": "do_something"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In most cases, your integration will do one or both of these things:
|
||||
|
||||
1. **Identifying which action was triggered**. For example, a GitHub integration might store something like this in the context:
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "rd49ehbqyjytddasoownkuqrxe",
|
||||
"post_id": "gqrnh3675jfxzftnjyjfe4udeh",
|
||||
"channel_id": "j6j53p28k6urx15fpcgsr20psq",
|
||||
"team_id": "5xxzt146eax4tul69409opqjlf",
|
||||
"context": {
|
||||
"repo": "mattermost/mattermost",
|
||||
"pr": 1234,
|
||||
"action": "merge"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, when the message button is selected, your integration sends a request to the specified URL with the intention to merge the pull request identified by the context.
|
||||
|
||||
2. **Authenticating the server**. An important property of the context parameter is that it's kept confidential. If your integration is not behind a firewall, you could add a token to your context without users ever being able to see it:
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "rd49ehbqyjytddasoownkuqrxe",
|
||||
"post_id": "gqrnh3675jfxzftnjyjfe4udeh",
|
||||
"channel_id": "j6j53p28k6urx15fpcgsr20psq",
|
||||
"team_id": "5xxzt146eax4tul69409opqjlf",
|
||||
"context": {
|
||||
"repo": "mattermost/mattermost",
|
||||
"pr": 1234,
|
||||
"action": "merge",
|
||||
"token": "somerandomlygeneratedsecret"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, when your integration receives the request, it can verify that the token matches one that you previously generated and know that the request is legitimately coming from the Mattermost server and is not forged.
|
||||
|
||||
Depending on the application, integrations can also perform authentication statelessly with cryptographic signatures such as:
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "rd49ehbqyjytddasoownkuqrxe",
|
||||
"post_id": "gqrnh3675jfxzftnjyjfe4udeh",
|
||||
"channel_id": "j6j53p28k6urx15fpcgsr20psq",
|
||||
"team_id": "5xxzt146eax4tul69409opqjlf",
|
||||
"context": {
|
||||
"repo": "mattermost/mattermost",
|
||||
"pr": 1234,
|
||||
"action": "merge",
|
||||
"signature": "mycryptographicsignature"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It's also possible for integrations to do both of these things with a single token and use something like this as context:
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "rd49ehbqyjytddasoownkuqrxe",
|
||||
"post_id": "gqrnh3675jfxzftnjyjfe4udeh",
|
||||
"channel_id": "j6j53p28k6urx15fpcgsr20psq",
|
||||
"team_id": "5xxzt146eax4tul69409opqjlf",
|
||||
"context": {
|
||||
"action_id": "someunguessableactionid"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, when the integration receives the request, it can act based on the action ID.
|
||||
|
||||
## Error handling
|
||||
|
||||
When an action button integration fails, Mattermost automatically displays an error message to the user below the interactive content. This provides immediate feedback when button actions don't work as expected.
|
||||
|
||||

|
||||
|
||||
**Key behaviors:**
|
||||
- Integrations can return custom error messages using the error response format (see above)
|
||||
- If no custom message is provided, a default "Action failed to execute" message is shown
|
||||
- Previous errors are automatically cleared when a new action is triggered
|
||||
- Error display works for both button and menu actions
|
||||
|
||||
This feature is available in Mattermost v10.5 and later.
|
||||
|
||||
**Automatic error display scenarios:**
|
||||
|
||||
When your integration returns an error response with a custom message, that message is displayed to the user. For system-level errors where no custom message can be returned, a default error message is shown:
|
||||
|
||||
- Network connection fails when contacting the integration URL
|
||||
- Integration URL is invalid or unreachable
|
||||
- Integration returns a non-200 HTTP status code
|
||||
- Integration returns invalid JSON
|
||||
|
||||
For troubleshooting integration errors from the server side, see the FAQ section "Why does an interactive button or menu return a 400 error?" below.
|
||||
|
||||
## Tips and best practices
|
||||
|
||||
1. The external application may be written in any programming language. It needs to provide a URL which receives the request sent by your Mattermost server and responds within the required JSON format.
|
||||
2. To get started, you can use this [sample plugin](https://github.com/matterpoll/matterpoll) to add polling to Mattermost channels via a `/poll` slash command.
|
||||
|
||||
## Share your integration
|
||||
|
||||
If you've built an integration for Mattermost, please consider sharing your work in our [app directory](https://integrations.mattermost.com).
|
||||
|
||||
The [app directory](https://integrations.mattermost.com) lists open source integrations developed by the Mattermost community and are available for download, customization, and deployment to your private cloud or self-hosted infrastructure.
|
||||
|
||||
## Slack compatibility
|
||||
|
||||
Like Slack, actions are specified in an **Actions** list within the message attachment. Moreover, your integrations can react with ephemeral messages or message updates similar to Slack.
|
||||
|
||||
However, the schema for these objects is slightly different given Slack requires a Slack App and action URL to be pre-configured beforehand. Mattermost instead allows an integration to create an interactive message without pre-configuration.
|
||||
|
||||
If your `ephemeral_text` gets incorrectly handled by the Slack-compatibility logic, send `"skip_slack_parsing":true` along your `ephemeral_text` to bypass it.
|
||||
|
||||
```json
|
||||
{
|
||||
"update": {
|
||||
"message": "Updated!"
|
||||
},
|
||||
"ephemeral_text": "You updated the post!",
|
||||
"skip_slack_parsing": true
|
||||
}
|
||||
```
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
### Are message buttons and menus supported in ephemeral messages?
|
||||
|
||||
Yes, message buttons and menus are supported in ephemeral messages in Mattermost 5.10 and later. This applies to integrations using plugins, the RESTful API and webhooks, across the browser and Desktop App.
|
||||
|
||||
As an advanced feature, you can also use plugins to update the contents of an ephemeral message with message buttons or menus with the [UpdateEphemeralMessage plugin API](/developers/integrate/reference/server#API.UpdateEphemeralPost).
|
||||
|
||||
### Why does an interactive button or menu return a 400 error?
|
||||
|
||||
It is likely for one of three reasons:
|
||||
|
||||
1. Mattermost wasn't able to connect to the integration. If the integration is on your internal infrastructure, it'll need to be whitelisted (see [AllowedUntrustedInternalConnections config.json setting](https://docs.mattermost.com/configure/configuration-settings.html#allow-untrusted-internal-connections-to)). The log will include the text `err=address forbidden` in the error message.
|
||||
2. The integration didn't return HTTP status 200. The log will include the text `status=XXX` in the error message.
|
||||
3. The integration didn't return a valid JSON response. The log will include the text `err=some json error message` in the error message.
|
||||
|
||||
### How do I manage properties of an interactive message?
|
||||
|
||||
Use `update.Props` in the following ways to manage properties (`Props`) of an interactive message after a user performs an action via an interactive button or menu. When using Mattermost Blocks, include updated `mm_blocks` and `mm_blocks_actions` in `update.props` as needed:
|
||||
|
||||
- `update.Props == nil` - Do not update `Props` field.
|
||||
- `update.Props == {}` - Clear all properties, except the username and icon of the original message, as well as whether the message was pinned to channel or contained emoji reactions.
|
||||
- `update.Props == some_props` - Post will be updated to `some_props`. Username and icon of the original message, and whether the message was pinned to channel or contained emoji reactions will not be updated.
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/develop/integrate/plugins/interactive-messages/poll.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
6
docs/develop/integrate/plugins/manifest-reference.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: "Manifest reference"
|
||||
sidebar_position: 130
|
||||
---
|
||||
|
||||
<PluginManifestDocs />
|
||||
262
docs/develop/integrate/plugins/migration.md
Normal file
@@ -0,0 +1,262 @@
|
||||
---
|
||||
title: "Migrate plugins"
|
||||
sidebar_position: 60
|
||||
---
|
||||
|
||||
The plugin package exposed by Mattermost 5.6 and later drops support for automatically unmarshalling a plugin's configuration onto the struct embedding `MattermostPlugin`. As server plugins are inherently concurrent (hooks being called asynchronously) and the plugin configuration can change at any time, access to the configuration must be synchronized.
|
||||
|
||||
Plugins compiled against 5.5 and earlier will continue to work without modification, automatically unmarshalling a plugin's configuration but with the existing risk of a corrupted read or write. Once the plugin is recompiled against Mattermost 5.6, it will be necessary to manually unmarshal your plugin's configuration. Client-only plugins and server plugins without public fields require no modifications.
|
||||
|
||||
Note that you do not need to wait until Mattermost 5.6 to make these changes, as the hardened approach explained below will work with Mattermost 5.5 and earlier. Any implementation of `OnConfigurationChange` you define overrides the one automatically unmarshalling.
|
||||
|
||||
### Server changes
|
||||
|
||||
#### Unmarshalling configuration
|
||||
|
||||
Previously, any public fields defined on the struct embedding `MattermostPlugin` would be automatically unmarshalled from the plugin's configuration:
|
||||
|
||||
```go
|
||||
type Plugin struct {
|
||||
plugin.MattermostPlugin
|
||||
|
||||
Greeting string
|
||||
}
|
||||
|
||||
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello %s!", p.Greeting)
|
||||
}
|
||||
```
|
||||
|
||||
Writing to `Greeting` while the plugin may be concurrently reading from same could result in a corrupted read or write. The [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template) and [mattermost-plugin-demo](https://github.com/mattermost/mattermost-plugin-demo) have both been updated with more complete examples, but the general idea is to manually handle the `OnConfigurationChange` hook and synchronize access to these variables. One such way is with a [sync.RWMutex](https://golang.org/pkg/sync/#RWMutex):
|
||||
|
||||
```go
|
||||
type Plugin struct {
|
||||
plugin.MattermostPlugin
|
||||
|
||||
greetingLock sync.Mutex
|
||||
greeting string
|
||||
}
|
||||
|
||||
func (p *Plugin) OnConfigurationChange() error {
|
||||
type configuration struct {
|
||||
Greeting string
|
||||
}
|
||||
|
||||
// Load the public configuration fields from the Mattermost server configuration.
|
||||
if err := p.API.LoadPluginConfiguration(configuration); err != nil {
|
||||
return errors.Wrap(err, "failed to load plugin configuration")
|
||||
}
|
||||
|
||||
p.configurationLock.Lock()
|
||||
defer p.configurationLock.Unlock()
|
||||
p.greeting = configuration.Greeting
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||
p.configurationLock.RLock()
|
||||
defer p.configurationLock.RUnlock()
|
||||
|
||||
fmt.Fprintf(w, "Hello %s!", p.greeting)
|
||||
}
|
||||
```
|
||||
|
||||
Unfortunately, this adds a fair bit of extra complexity. You may wish to base your updated implementation off of [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template) or [mattermost-plugin-demo](https://github.com/mattermost/mattermost-plugin-demo) to simplify your code.
|
||||
|
||||
## Migrate plugins from Mattermost 5.1 and earlier
|
||||
|
||||
Mattermost 5.2 introduces breaking changes to the plugins beta. This page documents the changes necessary to migrate your existing plugins to be compatible with Mattermost 5.2 and later.
|
||||
|
||||
See [mattermost-plugin-zoom](https://github.com/mattermost/mattermost-plugin-zoom/compare/98eca6653e1a62c6b758e39b24d6ea075905c210...master) for an example migration involving both a server and web app component.
|
||||
|
||||
### Server changes
|
||||
|
||||
Although the underlying changes are significant, the required migration for server plugins is minimal.
|
||||
|
||||
#### Entry point
|
||||
|
||||
The plugin entry point was previously:
|
||||
|
||||
```go
|
||||
import "github.com/mattermost/server/public/plugin/rpcplugin"
|
||||
|
||||
func main() {
|
||||
rpcplugin.Main(&HelloWorldPlugin{})
|
||||
}
|
||||
```
|
||||
|
||||
Change the imported package and invoke `ClientMain` instead:
|
||||
|
||||
```go
|
||||
import "github.com/mattermost/mattermost/server/public/plugin"
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&HelloWorldPlugin{})
|
||||
}
|
||||
```
|
||||
|
||||
#### Hook parameters
|
||||
|
||||
Most hook callbacks now contain a leading `plugin.Context` parameter. Consult the [Hooks](/developers/integrate/reference/server#Hooks) documentation for more details, but for example, the `ServeHTTP` hook was previously:
|
||||
|
||||
```go
|
||||
func (p *MyPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Change it to:
|
||||
|
||||
```go
|
||||
func (p *MyPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### API changes
|
||||
|
||||
Most of the previous API calls remain available and unchanged, with the notable exception of removing the `KeyValueStore()`. Use [KVSet](/developers/integrate/reference/server#API.KVSet), [KVGet](/developers/integrate/reference/server#API.KVGet) and [KVDelete](/developers/integrate/reference/server#API.KVDelete) instead test:
|
||||
|
||||
```go
|
||||
func (p *MyPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||
key := r.URL.Query().Get("key")
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
value, _ := p.API.KVGet(key)
|
||||
fmt.Fprintf(w, string(value))
|
||||
case http.MethodPut:
|
||||
value := r.URL.Query().Get("value")
|
||||
p.API.KVSet(key, []byte(value))
|
||||
case http.MethodDelete:
|
||||
p.API.KVDelete(key)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Any standard error from your plugin will now be captured in the server logs, including output from the standard [log](https://golang.org/pkg/log/) package, but there are also explicit API methods for emitting structured logs:
|
||||
|
||||
```go
|
||||
func (p *MyPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||
p.API.LogDebug("received http request", "user_agent", r.UserAgent())
|
||||
if r.Referer() == "" {
|
||||
p.API.LogError("missing referer")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would generate something like the following in your server logs:
|
||||
```json
|
||||
{"level":"debug","ts":1531494669.83655,"caller":"app/plugin_api.go:254","msg":"received http request","plugin_id":"my-plugin","user_agent":"HTTPie/0.9.9"}
|
||||
{"level":"error","ts":1531494669.8368616,"caller":"app/plugin_api.go:260","msg":"missing referer","plugin_id":"my-plugin"}
|
||||
```
|
||||
|
||||
### Web app changes
|
||||
|
||||
The changes to web app plugins are more significant than server plugins.
|
||||
|
||||
#### Entry point
|
||||
|
||||
The plugin entry point was previously registered by directly manipulating a global variable:
|
||||
|
||||
```js
|
||||
window.plugins['my-plugin'] = new MyPlugin();
|
||||
```
|
||||
|
||||
Instead, use the globally exported `registerPlugin` method:
|
||||
|
||||
```js
|
||||
window.registerPlugin('my-plugin', new MyPlugin());
|
||||
```
|
||||
|
||||
#### Externalize dependencies
|
||||
|
||||
The plugins beta suggested relying on the global export of common libraries from the web app:
|
||||
|
||||
```js
|
||||
const React = window.react;
|
||||
```
|
||||
|
||||
While this remains supported, it is more natural to leverage Webpack [Externals](https://webpack.js.org/configuration/externals/). Configure this in your `.webpack.config.js`:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ...
|
||||
externals: {
|
||||
react: 'react',
|
||||
},
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
and then import your modules naturally:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
```
|
||||
|
||||
Note however that the exported variables have changed to the following:
|
||||
|
||||
| Prior to Mattermost 5.2 | Mattermost 5.2 |
|
||||
|---------------------------|-----------------------|
|
||||
| window.react | window.React |
|
||||
| window['react-dom'] | window.ReactDom |
|
||||
| window.redux | window.Redux |
|
||||
| window['react-redux'] | window.ReactRedux |
|
||||
| window['react-bootstrap'] | window.ReactBootstrap |
|
||||
| window['post-utils'] | window.PostUtils |
|
||||
| _N/A_ | window.PropTypes |
|
||||
|
||||
#### Initialization
|
||||
|
||||
The `initialize` callback used to receive a `registerComponents` callback to configure components, post types and main menu overrides:
|
||||
|
||||
```js
|
||||
import ChannelHeaderButton from './components/channel_header_button';
|
||||
import MobileChannelHeaderButton from './components/mobile_channel_header_button';
|
||||
import PostTypeZoom from './components/post_type_zoom';
|
||||
import {configureZoom} from './actions/zoom';
|
||||
|
||||
class MyPlugin {
|
||||
initialize(registerComponents) {
|
||||
registerComponents(
|
||||
{ChannelHeaderButton, MobileChannelHeaderButton},
|
||||
{custom_zoom: PostTypeZoom},
|
||||
{
|
||||
id: 'zoom-configuration',
|
||||
text: 'Zoom Configuration',
|
||||
action: configureZoom,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `initialize` callback now receives an instance of the plugin [registry](/developers/integrate/reference/webapp#registry). In some cases, the registry's API now requires a more discrete breakdown of the registered component to allow the web app to handle various rendering scenarios:
|
||||
|
||||
```js
|
||||
import ChannelHeaderButtonIcon from './components/channel_header_button/icon';
|
||||
import MobileChannelHeaderButton from './components/mobile_channel_header_button';
|
||||
import PostTypeZoom from './components/post_type_zoom';
|
||||
import {startZoomMeeting, configureZoom} from './actions/zoom';
|
||||
|
||||
class MyPlugin {
|
||||
initialize(registry) {
|
||||
registry.registerChannelHeaderButtonAction(
|
||||
ChannelHeaderButtonIcon,
|
||||
startZoomMeeting,
|
||||
'Start Zoom Meeting',
|
||||
);
|
||||
|
||||
registry.registerPostTypeComponent('custom_zoom', PostTypeZoom);
|
||||
|
||||
registry.registerMainMenuAction(
|
||||
'Zoom Configuration',
|
||||
configureZoom,
|
||||
MobileChannelHeaderButton,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Restructuring your plugin to use the new registry API will likely prove to be the hardest part of migrating.
|
||||
36
docs/develop/integrate/plugins/overview.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "Overview"
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
Plugins are defined by a manifest file and contain at least a server or web app component, or both.
|
||||
|
||||
The [Plugin Starter Template](https://github.com/mattermost/mattermost-plugin-starter-template) is a starting point and illustrates the different components of a Mattermost plugin.
|
||||
|
||||
A more detailed example is the [Demo Plugin](https://github.com/mattermost/mattermost-plugin-demo), which showcases many of the features of plugins.
|
||||
|
||||
If you'd like to better understand how plugins work, [see the contributor documentation on plugins](/developers/contribute/more-info/server/plugins).
|
||||
|
||||
### Manifest
|
||||
The plugin manifest provides required metadata about the plugin, such as name and ID. It is defined in JSON or YAML. This is `plugin.json` in both the [sample](https://github.com/mattermost/mattermost-plugin-starter-template/blob/master/plugin.json) and [demo](https://github.com/mattermost/mattermost-plugin-demo/blob/master/plugin.json) plugins.
|
||||
|
||||
See the [manifest reference](/developers/integrate/plugins/manifest-reference) for more information.
|
||||
|
||||
### Server
|
||||
The server component of a plugin is written in Go and runs as a subprocess of the Mattermost server process. The Go code extends the [MattermostPlugin](https://godoc.org/github.com/mattermost/mattermost/server/public/plugin#MattermostPlugin) struct that contains an [API](/developers/integrate/reference/server#API) and allows for the implementation of [Hook](/developers/integrate/reference/server#Hooks) methods that enable the plugin to interact with the Mattermost server.
|
||||
|
||||
The sample plugin implements this simply in [plugin.go](https://github.com/mattermost/mattermost-plugin-starter-template/blob/master/server/plugin.go) and the demo plugin splits the API and hook usage throughout [multiple files](https://github.com/mattermost/mattermost-plugin-demo/tree/master/server).
|
||||
|
||||
Read more about the server-side of plugins [here](/developers/integrate/plugins/components/server).
|
||||
|
||||
### Web/desktop app
|
||||
The web app component of a plugin is written in JavaScript with [React](https://react.dev/) and [Redux](https://redux.js.org/). The plugin's bundled JavaScript is included on the page and runs alongside the web app code as a [PluginClass](/developers/integrate/reference/webapp#pluginclass) that has initialize and uninitialize methods available for implementation. The initialize function is passed through the [registry](/developers/integrate/reference/webapp#registry) which allows the plugin to register React components, actions and hooks to modify and interact with the Mattermost web app.
|
||||
|
||||
The sample plugin has a [shell of an implemented PluginClass](https://github.com/mattermost/mattermost-plugin-starter-template/blob/master/webapp/src/index.tsx), while the demo plugin [contains a more complete example](https://github.com/mattermost/mattermost-plugin-demo/blob/master/webapp/src/plugin.jsx).
|
||||
|
||||
The desktop app is a shim of the web app, meaning any plugin that works in the web app will also work in the desktop app.
|
||||
|
||||
Read more about the web app component of plugins [here](/developers/integrate/plugins/components/webapp).
|
||||
|
||||
### Mobile app
|
||||
Currently there is no mobile app component of plugins but it is planned for the near term.
|
||||
34
docs/develop/integrate/plugins/source-available-license.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "Source available license"
|
||||
sidebar_position: 140
|
||||
---
|
||||
|
||||
Some plugins authored by Mattermost are licensed under the [Mattermost Source Available License](https://docs.mattermost.com/overview/faq.html#mattermost-source-available-license). This document outlines how to apply the license in various situations.
|
||||
|
||||
## How do I apply the license to an enterprise-only plugin?
|
||||
|
||||
An Enterprise-only plugin is a plugin that requires a valid Mattermost Enterprise E20 license. It is not designed to be used with Team Edition or any other Enterprise license.
|
||||
|
||||
1. Add the [LICENSE](LICENSE) file to the root of your plugin repository.
|
||||
2. Add the following section to your README.md directly below the opening paragraph:
|
||||
|
||||
```md
|
||||
## License
|
||||
|
||||
This repository is licensed under the [Mattermost Source Available License](LICENSE) and requires a valid Enterprise E20 license. See [Mattermost Source Available License](https://docs.mattermost.com/overview/faq.html#mattermost-source-available-license) to learn more.
|
||||
```
|
||||
|
||||
## How do I apply the license to a mixed-license plugin?
|
||||
|
||||
A mixed-license plugin includes components that require a valid Mattermost Enterprise E20 license. Not all features are available when used with Team Edition or any other enterprise license.
|
||||
|
||||
1. Organize the Enterprise-only, server-side parts of your plugin in a dedicated folder, typically `server/enterprise`.
|
||||
2. Add the [LICENSE](LICENSE) file to the `server/enterprise` folder.
|
||||
3. Symlink that license in the root of your repository as `LICENSE.enterprise`.
|
||||
4. Add the following section to your README.md directly below the opening paragraph:
|
||||
|
||||
```md
|
||||
## License
|
||||
|
||||
This repository is licensed under the Apache 2.0 License, except for the [server/enterprise](server/enterprise) directory which is licensed under the [Mattermost Source Available License](LICENSE.enterprise). See [Mattermost Source Available License](https://docs.mattermost.com/overview/faq.html#mattermost-source-available-license) to learn more.
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
The Mattermost Source Available License license (the “Source Available License”)
|
||||
Copyright (c) 2015-present Mattermost
|
||||
|
||||
With regard to the Mattermost Software:
|
||||
|
||||
This software and associated documentation files (the "Software") may only be
|
||||
used in production, if you (and any entity that you represent) have agreed to,
|
||||
and are in compliance with, the Mattermost Terms of Service, available at
|
||||
https://mattermost.com/enterprise-edition-terms/ (the “EE Terms”), or other
|
||||
agreement governing the use of the Software, as agreed by you and Mattermost,
|
||||
and otherwise have a valid Mattermost Enterprise E20 subscription for the
|
||||
correct number of user seats. Subject to the foregoing sentence, you are free
|
||||
to modify this Software and publish patches to the Software. You agree that
|
||||
Mattermost and/or its licensors (as applicable) retain all right, title and
|
||||
interest in and to all such modifications and/or patches, and all such
|
||||
modifications and/or patches may only be used, copied, modified, displayed,
|
||||
distributed, or otherwise exploited with a valid Mattermost Enterprise E20
|
||||
Edition subscription for the correct number of user seats. Notwithstanding
|
||||
the foregoing, you may copy and modify the Software for development and testing
|
||||
purposes, without requiring a subscription. You agree that Mattermost and/or
|
||||
its licensors (as applicable) retain all right, title and interest in and to
|
||||
all such modifications. You are not granted any other rights beyond what is
|
||||
expressly stated herein. Subject to the foregoing, it is forbidden to copy,
|
||||
merge, publish, distribute, sublicense, and/or sell the Software.
|
||||
|
||||
The full text of this EE License shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
For all third party components incorporated into the Mattermost Software, those
|
||||
components are licensed under the original license provided by the owner of the
|
||||
applicable component.
|
||||
288
docs/develop/integrate/plugins/using-and-managing-plugins.md
Normal file
@@ -0,0 +1,288 @@
|
||||
---
|
||||
title: "Use plugins with Mattermost"
|
||||
description: "Mattermost supports plugins to customize and extend the platform."
|
||||
sidebar_label: "Use and manage plugins"
|
||||
sidebar_position: 50
|
||||
---
|
||||
|
||||
See our [demo plugin](https://github.com/mattermost/mattermost-plugin-demo) that illustrates the potential for a Mattermost plugin. To start writing your own plugin, consult our [starter template](https://github.com/mattermost/mattermost-plugin-starter-template).
|
||||
|
||||
Consider using a plugin in the following scenarios:
|
||||
|
||||
- When you want to customize the Mattermost user interface.
|
||||
- When you want to extend Mattermost functionality to meet a specific, complex requirement.
|
||||
- When you want to build integrations that are managed by your Mattermost server.
|
||||
|
||||
Plugins are fully supported in both Team Edition and Enterprise Edition.
|
||||
|
||||
## Marketplace
|
||||
|
||||
The Marketplace is a collection of plugins that can greatly increase the value and capabilities of your Mattermost deployment. End users can see increased productivity through quick access to systems such as Jira, Zoom, Webex, and GitHub. Mattermost System Admins can discover new plugins and quickly deploy them to their servers, including High Availability clusters.
|
||||
|
||||
The Marketplace is available from v5.16 and is accessed via **Product menu > Marketplace**. More listings will be added as we add new features and plugins that our customers request.
|
||||
|
||||
### Plugin labels
|
||||
|
||||
Plugins in the Marketplace are labeled to make it easier for administrators to choose plugins that fit their company's security and risk policies if they do not allow for community plugins to be used.
|
||||
|
||||
**Community plugins**
|
||||
|
||||
Plugins identified as "Community" are produced by the open-source community or partners and the features/roadmap are not controlled directly by Mattermost. Prior to being listed on the Marketplace, they are reviewed by the Mattermost development team and code-signed to ensure the code Mattermost reviewed, is delivered. Mattermost does not directly support these plugins in production environments.
|
||||
|
||||
**Beta plugins**
|
||||
|
||||
Plugins may be labeled as "Beta" if they're released to the Marketplace early for customer previews. We do not recommend running beta plugins on production servers.
|
||||
|
||||
**Experimental**
|
||||
|
||||
Plugins labeled as "Experimental" are still being tested. These should not be run on production servers.
|
||||
|
||||
**Partner**
|
||||
|
||||
Plugins identified as "Partner" are created and maintained by a Mattermost partner.
|
||||
|
||||
### Install a plugin
|
||||
|
||||
When a new plugin becomes available on the Marketplace, it's listed with an option to **Install**. Select **Install** to download and install the latest plugin binary from its respective GitHub repository. If there's a cluster present, the plugin will be distributed to each server automatically.
|
||||
|
||||
### Configure and enable a plugin
|
||||
|
||||
Once a plugin is installed (or pre-installed if shipped with Mattermost binary release):
|
||||
|
||||
1. Select **Configure > Settings**.
|
||||
2. Enter the plugin settings as required.
|
||||
3. Set **Enable Plugin** setting to **True**. If this flag is not enabled, the plugin will not become active.
|
||||
4. Test out the plugin as needed.
|
||||
|
||||
### Upgrade plugins
|
||||
|
||||
Upgrade a plugin on demand when a new version becomes available. New versions of plugins that you have already installed will display a link to easily install the upgraded plugins. Some plugin versions may have breaking changes; please check the release notes if you're performing a major version change.
|
||||
|
||||
### Upgrade plugins (prior to v5.18)
|
||||
|
||||
In v5.16 and v5.17 the Marketplace only supports the installation of new plugins. To upgrade a plugin, you need to manually update it by downloading the binary file from the GitHub repository and then upload it in **System Console > Plugin Management**.
|
||||
|
||||
## Marketplace server
|
||||
|
||||
There are two settings in **System Console > Plugin Management**:
|
||||
|
||||

|
||||
|
||||
- **Enable Marketplace:** Turns the Marketplace user interface on or off for System Admins (end users cannot see the Marketplace).
|
||||
- **Marketplace URL:** The location of the Marketplace server to query for new plugins. Mattermost hosts a Marketplace for the community and this is the default value for this field. You can also set up your own Marketplace server.
|
||||
|
||||
When you first access the Marketplace, your Mattermost server will attempt to contact the Mattermost Marketplace server and return a list of available plugins that are appropriate based on the server version that is currently running. Only your server version and search query is passed over to the Mattermost Marketplace; we retain an anonymized record for product analytics whenever a new plugin is installed, unless you have opted out of [Telemetry](https://docs.mattermost.com/manage/telemetry.html).
|
||||
|
||||
The [Marketplace server code](https://github.com/mattermost/mattermost-marketplace) is available as an open source project and can be used to set up your own private Marketplace if desired.
|
||||
|
||||
### Mattermost Marketplace
|
||||
|
||||
The [Mattermost Marketplace](https://github.com/mattermost/mattermost-marketplace) is a service run by Mattermost that contains listings of plugins that we have reviewed and, in many cases, built. In the future, we plan to include community-developed plugins that will be labeled separately to Mattermost-developed plugins. as well as settings that would restrict which types of plugins you can install. Comments in our forum are welcome as we develop this feature further.
|
||||
|
||||
## Mattermost integration directory
|
||||
|
||||
There are many ways to integrate Mattermost aside from plugins, and we have created a directory of integration "recipes", some of which are scripts, plugins, or instructions on how to connect Mattermost with your Enterprise systems. Many are sourced from our community of customers. You can browse the directory at [https://integrations.mattermost.com](https://integrations.mattermost.com).
|
||||
|
||||
## About plugins
|
||||
|
||||
Plugins may have one or both of the following parts:
|
||||
|
||||
- **Web App plugins:** Customize the Mattermost user interface by adding buttons to the channel header, overriding the `RHS`, or even rendering a custom post type within the center channel. All this is possible without having to fork the source code and rebase on every Mattermost release. For a sample plugin, see [our Zoom plugin](https://github.com/mattermost/mattermost-plugin-zoom).
|
||||
- **Server plugins:** Run a Go process alongside the server, filtering messages, or integrating with third-party systems such as Jira, GitLab, or Jenkins. For a sample plugin, see [our Jira plugin](https://github.com/mattermost/mattermost-plugin-jira).
|
||||
|
||||
## Security
|
||||
|
||||
Plugins are intentionally powerful and not artificially sandboxed in any way and effectively become part of the Mattermost server. Server plugins can execute arbitrary code alongside your server and webapp plugins can deploy arbitrary code in client browsers.
|
||||
|
||||
While this power enables deep customization and integration, it can be abused in the wrong hands. Plugins have full access to your server configuration and thus also to your Mattermost database. Plugins can read any message in any channel, or perform any action on behalf of any user in the Web App.
|
||||
|
||||
You should only install custom plugins from sources you trust to avoid compromising the security of your installation.
|
||||
|
||||
## Plugin signing
|
||||
|
||||
The Marketplace allows System Admins to download and install plugins from a central repository. Plugins installed via the Marketplace must be signed by a public key trusted by the local Mattermost server.
|
||||
|
||||
While the server ships with a default public key used to verify plugins from the default Mattermost Marketplace, the server can be configured to trust different public keys and point at a different plugin marketplace. This document outlines the steps for generating a public key pair and signing plugins for use with a custom plugin marketplace. It assumes access to the [GNU Privacy Guard (GPG)](https://gnupg.org) tool.
|
||||
|
||||
### Configuration
|
||||
|
||||
Configuring plugin signatures allows finer control over the verification process:
|
||||
|
||||
`PluginSettings.RequirePluginSignature = true`
|
||||
|
||||
This is used to enforce plugin signature verification. With flag on, only Marketplace plugins will be installed and verified. With flag off, customers will be able to install plugins manually without signature verification.
|
||||
|
||||
Note that the Marketplace plugins will still be verified even if the flag is off.
|
||||
|
||||
### Key generation
|
||||
|
||||
Public and private key pairs are needed to sign and verify plugins. The private key is used for signing and should be kept in a secure location. The public key is used for verification and can be distributed freely. To generate a key pair, run the following command:
|
||||
|
||||
```
|
||||
gpg --full-generate-key
|
||||
|
||||
Please select what kind of key you want:
|
||||
(1) RSA and RSA (default)
|
||||
(2) DSA and Elgamal
|
||||
(3) DSA (sign only)
|
||||
(4) RSA (sign only)
|
||||
Your selection? 1
|
||||
|
||||
RSA keys may be between 1024 and 4096 bits long.
|
||||
What keysize do you want? (2048) 3072
|
||||
|
||||
Requested keysize is 3072 bits
|
||||
|
||||
Please specify how long the key should be valid.
|
||||
0 = key does not expire
|
||||
<n> = key expires in n days
|
||||
<n>w = key expires in n weeks
|
||||
<n>m = key expires in n months
|
||||
<n>y = key expires in n years
|
||||
Key is valid for? (0) 0
|
||||
|
||||
Key expires at ...
|
||||
|
||||
Is this correct? (y/N) y
|
||||
|
||||
GnuPG needs to construct a user ID to identify your key.
|
||||
Real name: Mattermost Inc
|
||||
|
||||
Email address: info@mattermost.com
|
||||
Comment:
|
||||
|
||||
You selected this USER-ID:
|
||||
"Mattermost Inc <info@mattermost.com>"
|
||||
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
|
||||
```
|
||||
|
||||
Key size should be at least 3072 bits.
|
||||
|
||||
### Export the private key
|
||||
|
||||
Find the ID of your private key first. The ID is a hexadecimal number.
|
||||
|
||||
`gpg --list-secret-keys`
|
||||
|
||||
This is your private key and should be kept secret. Your hexadecimal key ID will, of course, be different.
|
||||
|
||||
`gpg --export-secret-keys F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F > ./my-priv-key`
|
||||
|
||||
### Export the public key
|
||||
|
||||
Find the ID of your public key first. The ID is a hexadecimal number.
|
||||
|
||||
`gpg --list-keys`
|
||||
|
||||
`gpg --export F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F > ./my-pub-key`
|
||||
|
||||
### Importing the key
|
||||
|
||||
If you already have a public and private key pair, you can import them to the GPG.
|
||||
|
||||
`gpg --import ./my-priv-gpg-key`
|
||||
`gpg --import ./my-pub-gpg-key`
|
||||
|
||||
## Sign the plugin
|
||||
|
||||
For plugin signing, you have to know the hexadecimal ID of the private key. Let's assume you want to sign `com.mattermost.demo-plugin-0.1.0.tar.gz` file, run:
|
||||
|
||||
`gpg -u F3FACE45E0DE642C8BD6A8E64C7C6562C192CC1F --verbose --personal-digest-preferences SHA256 --detach-sign com.mattermost.demo-plugin-0.1.0.tar.gz`
|
||||
|
||||
This command will generate `com.mattermost.demo-plugin-0.1.0.tar.gz.sig`, which is the signature of your plugin.
|
||||
|
||||
## Plugin verification
|
||||
|
||||
Mattermost server will verify plugin signatures downloaded from the Marketplace. Plugins are verified against a hard-coded Mattermost public key, as well as any additional public keys configured on the server.
|
||||
|
||||
To add custom public keys for plugin signature verification, add the key file names to the `PluginSettings.SignaturePublicKeyFiles` setting in your `config.json`:
|
||||
|
||||
```json
|
||||
"PluginSettings": {
|
||||
"SignaturePublicKeyFiles": ["my-pub-key-1", "my-pub-key-2"]
|
||||
}
|
||||
```
|
||||
|
||||
The key files themselves must be stored in the Mattermost server's config directory.
|
||||
|
||||
### Implementation
|
||||
|
||||
See the [implementation document](https://docs.google.com/document/d/1qABE7VEx4k_ZAeh6Ydn4pGbu6BQfZt65x68i2s65MOQ) for more information.
|
||||
|
||||
## Set up guide
|
||||
|
||||
To manage plugins, go to **System Console > Plugins > Plugin Management**. From here, you can:
|
||||
|
||||
- Enable or disable pre-packaged plugins.
|
||||
- Install and manage custom plugins.
|
||||
|
||||
### Pre-packaged plugins
|
||||
|
||||
Mattermost ships with a number of pre-packaged plugins written and maintained by Mattermost. Instead of building these features directly into the product, you can selectively enable the functionality your installation requires. Install pre-packaged plugins from the Marketplace, even if your system cannot directly connect to the internet.
|
||||
|
||||
Prior to v5.20, pre-packaged plugins were installed by default and could not be uninstalled without manually modifying the `prepackaged_plugins` directory. Any pre-packaged plugins installed prior to v5.20 and left enabled on upgrade will remain installed, but can now be uninstalled.
|
||||
|
||||
### Custom plugins
|
||||
|
||||
Installing a custom plugin introduces some risks. As a result, plugin uploads are disabled by default and cannot be enabled via the System Console or REST API.
|
||||
|
||||
To enable plugin uploads, manually set `PluginSettings > EnableUploads` to `true` in your `config.json` file and restart your server.
|
||||
|
||||
If you have installed Mattermost Omnibus, to enable plugin uploads, manually set `enable_plugin_uploads:` true in the `mmomni.yml` file and restart your server.
|
||||
|
||||
You can disable plugin uploads at any time without affecting previously uploaded plugins.
|
||||
|
||||
With plugin uploads enabled, navigate to **System Console > Plugins > Management** and upload a plugin bundle. Plugin bundles are `*.tar.gz` files containing the server executables and web app resources for the plugin. You can also specify a URL to install a plugin bundle from a remote source.
|
||||
|
||||
|
||||
<Note title="Note">
|
||||
1. When `RequirePluginSignature` is `true`, plugin uploads cannot be enabled, and may only be installed via the Marketplace (which verifies Plugin Code Signatures).
|
||||
2. `EnableRemoteMarketplaceURL` also remains disabled as long as `EnableUploads` is disabled.
|
||||
</Note>
|
||||
|
||||
|
||||
Custom plugins may also be installed via the [command line interface](https://docs.mattermost.com/administration/command-line-tools.html#mattermost-plugin).
|
||||
|
||||
While no longer recommended, plugins may also be installed manually by unpacking the plugin bundle inside the `plugins` directory of a Mattermost installation.
|
||||
|
||||
### Plugin uploads in high availability mode
|
||||
|
||||
Prior to Mattermost 5.14, Mattermost servers configured for [High Availability mode](https://docs.mattermost.com/deployment/cluster.html) required plugins to be installed manually. As of Mattermost 5.14, plugins uploaded via the System Console or the CLI are persisted to the configured file store and automatically installed on all servers that join the cluster.
|
||||
|
||||
Manually installed plugins remain supported, but must be individually installed on each server in the cluster.
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
### Where can I share feedback on plugins?
|
||||
|
||||
Join our community server discussion in the [Toolkit channel](https://community.mattermost.com/core/channels/developer-toolkit).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Please see common questions below. For further assistance, review the [Troubleshooting forum](https://forum.mattermost.com/c/trouble-shoot) for previously reported errors, or [join the Mattermost user community for troubleshooting help](https://mattermost.com/pl/default-ask-mattermost-community).
|
||||
|
||||
### Plugin uploads fail even though uploads are enabled
|
||||
|
||||
If plugin uploads fail and you see `permission denied` errors in **System Console > Logs** such as:
|
||||
|
||||
`[2017/11/13 20:42:18 UTC] [EROR] failed to start up plugins: mkdir /home/ubuntu/mattermost/client/plugins: permission denied`
|
||||
|
||||
It's likely that the Mattermost server doesn't have the necessary permissions for uploading plugins. Ensure the Mattermost server has write access to the `mattermost/client` directory.
|
||||
|
||||
It may also be that the working directory for the service running Mattermost is not correct. On Ubuntu you might see:
|
||||
|
||||
`[2018/01/03 08:34:47 EST] [EROR] failed to start up plugins: mkdir ./client/plugins: no such file or directory`
|
||||
|
||||
This can be fixed on Ubuntu 16.04 and RHEL by opening the service configuration file and setting `WorkingDirectory` to the path to Mattermost (generally it's `/opt/mattermost`).
|
||||
|
||||
A similar problem can occur on Windows:
|
||||
|
||||
`[EROR] failed to start up plugins: mkdir ./client/plugins: The system cannot find the path specified.`
|
||||
|
||||
To fix this, set the `AppDirectory` of your service using `nssm set mattermost AppDirectory c:\mattermost`.
|
||||
|
||||
### `x509: certificate signed by unknown authority`
|
||||
|
||||
If you're seeing `x509: certificate signed by unknown authority` in your server logs, it usually means that the CA for a self-signed certificate for a server your plugin is trying to access has not been added to your local trust store of the machine the Mattermost server is running on.
|
||||
|
||||
You can add one in Linux [following instructions in this StackExchange article](https://unix.stackexchange.com/questions/90450/adding-a-self-signed-certificate-to-the-trusted-list), or set up a load balancer like NGINX per [production install guide](https://docs.mattermost.com/config-ssl-http2-nginx.html) to resolve the issue.
|
||||