Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ Detailed context for AI agents working on this codebase.

## What This Project Is

A **Mattermost plugin starter template** that synchronizes user profile attributes from external systems into Mattermost's Custom Profile Attributes (CPA). It's a reference implementation and educational resource — designed to be read, understood, and adapted. This is not a plugin that can be used as-is as a plug-and-play solution. It is expected that a developer takes this and uses it as the foundation of their own custom plugin.
A **Mattermost plugin starter template** that synchronizes user attributes from an external system into Mattermost. The synced attributes appear on user profiles in the UI and are also addressable as `user.attributes.<field_name>` from attribute-based access control (ABAC) policy rules — which is why the plugin writes into the `access_control` property group. It's a reference implementation and educational resource — designed to be read, understood, and adapted. This is not a plugin that can be used as-is as a plug-and-play solution. It is expected that a developer takes this and uses it as the foundation of their own custom plugin.

**Plugin ID:** `com.mattermost.user-attribute-sync-starter-template`
**Min Mattermost version:** 11.5.0
**Languages:** Go 1.24+ (server), TypeScript/React (webapp)
**Min Mattermost version:** 11.8.0
**Languages:** Go 1.26.3+ (server), TypeScript/React (webapp)

## Architecture

```
Plugin Activation (Once)
├─> Create/Update CPA Fields (schema)
├─> Create/Update User Attribute Fields (schema)
└─> Start Background Job (cluster-aware)

Background Job (Configurable interval, default 60min)
Expand All @@ -24,7 +24,9 @@ Background Job (Configurable interval, default 60min)

The plugin has two phases:
1. **Field sync** — Creates/updates field definitions (schema) in Mattermost on activation
2. **Value sync** — Periodically fetches user data from a provider and writes values to CPA
2. **Value sync** — Periodically fetches user data from a provider and writes per-user values

All fields and values are stored in the `access_control` property group (`model.AccessControlPropertyGroupName`), with `ObjectType=user` and `TargetType=system`. Living in that group is what makes the fields addressable from ABAC policy expressions.

## Key Files and Their Roles

Expand All @@ -36,7 +38,7 @@ The plugin has two phases:
| `server/job.go` | Cluster-aware job scheduling via `cluster.Schedule()`. Contains `nextWaitInterval()` (calculates delay) and `runSync()` (executes sync). |
| `server/configuration.go` | Thread-safe config management with RWMutex. Settings: `SyncIntervalMinutes` (default 60). |
| `server/sync/provider.go` | `AttributeProvider` interface: `GetUserAttributes() ([]map[string]interface{}, error)` and `Close() error`. |
| `server/sync/field_sync.go` | Field definitions array and schema management. Creates/updates CPA fields. Maintains `FieldIDCache` mapping external names to Mattermost-generated IDs. |
| `server/sync/field_sync.go` | Field definitions array and schema management. Creates/updates user attribute fields. Maintains `FieldIDCache` mapping external names to Mattermost-generated IDs. |
| `server/sync/value_sync.go` | `SyncUsers()` — matches users by email, builds PropertyValue objects, bulk upserts. Handles text, date, and multiselect value types. |
| `server/sync/file_provider.go` | Example `AttributeProvider` implementation. Reads JSON from Mattermost data directory. Tracks file modification time for incremental sync. |
| `server/main.go` | Plugin entry point (minimal). |
Expand All @@ -61,7 +63,7 @@ The plugin has two phases:

## Field Definitions

Defined in `server/sync/field_sync.go` we have a few example field definitions in the `fieldDefinitions` array:
Defined in `server/sync/field_sync.go` we have a few example user attribute field definitions in the `fieldDefinitions` array:

1. **Job Title** — `job_title`, Text type, Public access
2. **Programs** — `programs`, Multiselect type, SharedOnly access, options: Apples/Oranges/Lemons/Grapes
Expand Down Expand Up @@ -142,7 +144,7 @@ make logs-watch # Tail plugin logs on running server

Used via `pluginapi.Client`:

- `Property.GetPropertyGroup(name)` — Fetch CPA group
- `Property.GetPropertyGroup(name)` — Fetch the `access_control` property group
- `Property.GetPropertyFieldByName(groupID, objectID, fieldName)` — Lookup field by name
- `Property.CreatePropertyField(field)` — Create field (returns generated ID)
- `Property.UpdatePropertyField(groupID, field)` — Modify existing field
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

**This is a starter template, not a production-ready plugin. It is meant to be forked and adapted to your own external data source and field definitions. Do not install it as-is expecting a working integration.**

A Mattermost plugin starter template that demonstrates how to synchronize user profile attributes from external systems into Mattermost's Custom Profile Attributes (CPA). This template serves as both a working reference implementation and an educational resource for plugin developers.
A Mattermost plugin starter template that demonstrates how to synchronize user attributes from an external system into Mattermost. Synced attributes appear on user profiles in the UI and can also be referenced from attribute-based access control (ABAC) policy rules — this is why the plugin writes into the `access_control` property group rather than a plugin-private group. The template serves as both a working reference implementation and an educational resource for plugin developers.

## What This Template Demonstrates

Mattermost's Custom Profile Attributes system (also called Properties) allows you to store structured metadata about users. A **field** defines the schema (name, type, options), while a **value** stores the actual data for a specific user. For multiselect fields, **options** define the allowed choices that users can select from.
Mattermost's property system lets you store structured per-user metadata. A **field** defines the schema (name, type, options), while a **value** stores the actual data for a specific user. For multiselect fields, **options** define the allowed choices. Fields written into the `access_control` group also become available as `user.attributes.<field_name>` inside ABAC policy expressions.

This plugin demonstrates how to create fields with hardcoded definitions and synchronize values from external data sources. Fields are defined explicitly in code with their types (text, date, multiselect), and the plugin uses Mattermost's cluster job system to run periodic synchronization tasks. The implementation includes incremental synchronization that processes only changed data after the initial sync.
This plugin shows how to create fields with hardcoded definitions and synchronize values from external data sources. Fields are defined explicitly in code with their types (text, date, multiselect), and the plugin uses Mattermost's cluster job system to run periodic synchronization tasks. The implementation includes incremental synchronization that processes only changed data after the initial sync.

The template creates three example fields that demonstrate different access control modes: Job Title (text, public access), Programs (multiselect with options, shared-only access), and Start Date (date, source-only access). All fields are marked as visible in the UI and protected (only this plugin can modify structure and write values).
The template creates three example user attribute fields that demonstrate different access control modes: Job Title (text, public access), Programs (multiselect with options, shared-only access), and Start Date (date, source-only access). All fields are marked as visible in the UI and protected (only this plugin can modify structure and write values).

## Architecture Overview

```
Plugin Activation (Once)
├─> Create/Update CPA Fields
├─> Create/Update User Attribute Fields
└─> Start Background Job

Background Job (On timed interval)
Expand All @@ -35,8 +35,8 @@ Background Job (On timed interval)

### Prerequisites

- Mattermost server 11.5.0 or later
- Go 1.24 or later
- Mattermost server 11.8.0 or later
- Go 1.26.3 or later (matches the version Mattermost server pins)
- Node v16 and npm v8 (if modifying webapp)

### Installation
Expand Down Expand Up @@ -66,13 +66,13 @@ Background Job (On timed interval)

## What to Expect

When the plugin activates, it creates the three Custom Profile Attribute fields (Job Title, Programs, and Start Date) in Mattermost. These fields appear in System Console → User Attributes. If the fields already exist from a previous activation, the plugin updates them to match the hardcoded definitions.
When the plugin activates, it creates the three user attribute fields (Job Title, Programs, and Start Date) in Mattermost. These fields appear in System Console → User Attributes. If the fields already exist from a previous activation, the plugin updates them to match the hardcoded definitions.

Immediately after activation, the plugin runs its first synchronization. It reads the `user_attributes.json` file from the Mattermost data directory, matches users by email address, and populates the Custom Profile Attribute values for each user found in the data file. The plugin logs its progress and any errors (such as users not found in Mattermost) during this process.
Immediately after activation, the plugin runs its first synchronization. It reads the `user_attributes.json` file from the Mattermost data directory, matches users by email address, and populates the user attribute values for each user found in the data file. The plugin logs its progress and any errors (such as users not found in Mattermost) during this process.

After the initial sync, the plugin checks for changes every 60 minutes by default. The file provider tracks the modification time of `user_attributes.json` and only processes the file if it has been modified since the last sync. When changes are detected, the plugin syncs all users in the file again. You can adjust the sync interval in the plugin configuration settings.

The synced attribute values are stored as Custom Profile Attributes and can be viewed in System Console → User Attributes or through the Mattermost API.
The synced user attribute values can be viewed in System Console → User Attributes or through the Mattermost API, and they are also available to ABAC policy rules as `user.attributes.<field_name>`.

## Access Control

Expand Down
39 changes: 19 additions & 20 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,61 +1,60 @@
module github.com/mattermost/user-attribute-sync-starter-template

go 1.24.13
go 1.26.3

require (
github.com/mattermost/mattermost/server/public v0.1.23-0.20260213163939-568ab01e75ee
github.com/mattermost/mattermost/server/public v0.4.1
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.11.1
)

require (
github.com/Masterminds/semver/v3 v3.5.0 // indirect
github.com/beevik/etree v1.6.0 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.7.0 // indirect
github.com/hashicorp/go-plugin v1.8.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/lib/pq v1.12.3 // indirect
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
github.com/mattermost/gosaml2 v0.10.0 // indirect
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect
github.com/mattermost/logr/v2 v2.0.22 // indirect
github.com/mattermost/mattermost/server/v8 v8.0.0-20251014075701-833e0125320d // indirect
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/oklog/run v1.2.0 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/russellhaering/goxmldsig v1.5.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/russellhaering/goxmldsig v1.6.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/tinylib/msgp v1.4.0 // indirect
github.com/tinylib/msgp v1.6.4 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wiggin77/merror v1.0.5 // indirect
github.com/wiggin77/srslog v1.0.1 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/mod v0.36.0 // indirect
golang.org/x/net v0.54.0 // indirect
golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.37.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect
google.golang.org/grpc v1.81.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading