Choosing Between Server-Side and Webapp Plugins in Mattermost
Deciding between Mattermost server-side (Go) and webapp (JS/TS) plugins is critical for stability and performance. Learn when to use each and how to combine them for complex integrations.
16 Feb 2026, 01:34 UTC

The Integration Dilemma: Logic vs. Interface
When extending Mattermost, the primary technical hurdle isn't writing the code, but choosing where that code lives. Developers often mistake "plugins" for a single category, but Mattermost splits functionality between Server-Side Plugins (Go) and Webapp Plugins (JavaScript/TypeScript). Choosing the wrong one can lead to inefficient API calls, a sluggish user interface, or an unstable server.
The core takeaway: Use server plugins for data processing, automation, and system-level integrations; use webapp plugins for custom UI components, interactive dashboards, and client-side UX enhancements.
Server-Side Plugins: The Engine Room
Server plugins are written in Go and run directly within the Mattermost server process. Because they share the same memory space as the server, they have high-performance access to the internal Go API, the Key-Value (KV) store, and the HTTP router.
- Best for: Webhooks, scheduled tasks, complex data transformations, and integrations with external databases.
- Capabilities: They can intercept messages via hooks, manage their own state in the KV store, and create new REST endpoints that other services can call.
- Risk Factor: Since they run in-process, a memory leak or a
panicin a Go plugin can crash the entire Mattermost server. Rigorous error handling is mandatory.
Webapp Plugins: The User Experience
Webapp plugins are bundled as JavaScript/TypeScript and delivered to the user's browser. They leverage React and Redux to modify the Mattermost interface without requiring a full rebuild of the client application.
- Best for: Adding new tabs to the channel header, creating custom modals, or adding buttons to the message action menu.
- Capabilities: They interact with the server via the standard REST and WebSocket APIs, meaning they are subject to the same permissions and rate limits as a standard user.
- Risk Factor: Buggy JS can freeze the browser tab or cause layout shifts, impacting the end-user experience across the organization.
Decision Matrix: Which one to build?
| Requirement | Server Plugin (Go) | Webapp Plugin (JS/TS) |
|---|---|---|
| Access to internal server state | Direct/Fast | Via REST API |
| Custom UI Components | No (only via API responses) | Yes (React) |
| Background Processing | Yes | No (Client must be open) |
| Security Context | System-level permissions | User-level permissions |
Worked Example: Building a "Ticket Status" Indicator
Imagine you want to show a "Ticket Open" badge next to a user's name when they have an active support ticket in an external system like Jira.
The Hybrid Approach:
- The Server Plugin: Create a Go plugin that polls the Jira API every 5 minutes. It stores the ticket status for each user in the Mattermost Plugin KV store. It exposes a custom REST endpoint:
/plugins/ticket-status/get?userId=xyz. - The Webapp Plugin: Create a React component that triggers on the user profile view. It calls the server plugin's endpoint and renders a green or red badge based on the response.
Deployment Command:
To install the resulting plugin bundle via the CLI (assuming you have mmctl configured), run this from your terminal with administrative permissions:
mmctl plugin install /path/to/plugin-bundle.tar.gz
Verification: Run mmctl plugin list to ensure the plugin is listed as "enabled". If the badge does not appear in the UI, check the server logs for 404 errors on the custom REST endpoint.
Limitations and Trade-offs
One critical limitation is state management. Server plugins use a KV store for persistence, but Mattermost does not provide automatic schema migration tools for this data. If you update your plugin and change the data format in the KV store, you must write your own migration logic to prevent the plugin from crashing on startup.
Additionally, in High Availability (HA) environments, plugins must be installed on every single node. If you use local disk storage for plugin assets, you will face inconsistency; a shared file store (like S3) is required to ensure all nodes serve the same webapp assets.
Actionable Closing
Before writing a single line of code, map your feature's data flow. If the feature is triggered by a system event (like a message being posted), start with a server plugin. If the feature is triggered by a user click, start with a webapp plugin. For complex features, use a hybrid model: Go for the heavy lifting and React for the presentation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.