Skip to content
K

Settings & Credentials

Plugins can declare a settings schema that the desktop app uses to auto-render a configuration UI. This eliminates the need for custom Svelte components — you describe what settings you need, and the app builds the form.

The settings property on MondayMorningPlugin accepts a PluginSettingsSchema:

interface PluginSettingsSchema {
/** Credential fields (API keys, tokens, passwords) */
credentials?: CredentialField[];
/** Global configuration fields (apply to all projects) */
config?: ConfigField[];
/** Per-project configuration fields */
projectConfig?: ConfigField[];
/** OAuth configuration for browser-based authorization flow */
oauth?: OAuthConfig;
/** Optional connection health check ("Test" button) */
healthCheck?: PluginHealthCheck;
}

There are three levels of settings:

LevelScope
credentialsGlobal (all projects)
configGlobal (all projects)
projectConfigPer-project (scoped to the active project)

In the desktop app, this schema is rendered in two settings sections. The Plugins section gives each installed plugin an inline Configure control that renders the full form — credentials, global config, and per-project config for the active project. The Integrations section shows per-project connection state: GitHub, Trello, and Harvest get dedicated inline connect rows, and every other enabled plugin with a settings schema appears with a Connected / Not connected status derived from whether its declared credential keys are present.

Use CredentialField for API tokens, passwords, and secrets. The desktop app renders these with masked input fields and optional help links.

interface CredentialField {
/** Storage key, e.g., "github_token" */
key: string;
/** Display label */
label: string;
/** Input type: "text" for visible, "password" for masked */
type: "text" | "password";
/** Whether the credential is required for the plugin to function */
required: boolean;
/** URL to help the user generate/find this credential */
helpUrl?: string;
/** Explanatory text shown below the input */
helpText?: string;
/** OAuth scopes or permissions needed */
scopes?: string[];
/** Endpoint to test the credential against */
testEndpoint?: string;
}
settings: {
credentials: [
{
key: "github_token",
label: "Personal Access Token",
type: "password",
required: true,
helpUrl: "https://github.com/settings/tokens/new",
helpText: "Generate a PAT with 'repo' and 'read:org' scopes",
scopes: ["repo", "read:org"],
},
],
},

The desktop app renders this as:

  • A masked password input for the token value
  • A clickable help link that opens the GitHub token creation page
  • A description showing the required scopes
  • A “Test Connection” button if testEndpoint is provided

Some integrations need more than one credential:

settings: {
credentials: [
{
key: "api_key",
label: "API Key",
type: "password",
required: true,
helpUrl: "https://example.com/api-keys",
helpText: "Find your API key in Account Settings",
},
{
key: "workspace_id",
label: "Workspace ID",
type: "text",
required: true,
helpText: "The numeric workspace ID from your dashboard URL",
},
],
},

Use ConfigField for non-secret settings like feature toggles, default values, and picker fields.

interface ConfigField {
/** Storage key */
key: string;
/** Display label */
label: string;
/** Input type */
type: "text" | "number" | "boolean" | "select" | "channel" | "repo-picker" | "board-picker";
/** Default value */
default?: string | number | boolean;
/** Options for "select" type */
options?: { label: string; value: string }[];
/** Description shown below the input */
description?: string;
/** Only show this field when another field has a truthy value */
dependsOn?: string;
/** Placeholder text for text/number inputs */
placeholder?: string;
/** Tauri command to fetch options for picker types */
fetchCommand?: string;
}
{
key: "default_label",
label: "Default Label",
type: "text",
default: "bug",
placeholder: "Enter a label name",
description: "Applied to new issues by default",
}
{
key: "sync_interval",
label: "Sync Interval (minutes)",
type: "number",
default: 30,
description: "How often to check for changes",
}
{
key: "auto_sync",
label: "Auto-sync on project open",
type: "boolean",
default: true,
description: "Automatically sync when switching to this project",
}
{
key: "sync_direction",
label: "Sync Direction",
type: "select",
default: "both",
options: [
{ label: "Pull only", value: "pull" },
{ label: "Push only", value: "push" },
{ label: "Bidirectional", value: "both" },
],
}

Picker types (channel, repo-picker, board-picker) fetch their options dynamically from a Tauri command:

{
key: "github_repo",
label: "Repository",
type: "repo-picker",
description: "Select the GitHub repository for this project",
fetchCommand: "list_github_repos",
}

Use dependsOn to show a field only when another setting is enabled:

config: [
{
key: "notifications_enabled",
label: "Enable Notifications",
type: "boolean",
default: false,
},
{
key: "notification_channel",
label: "Notification Channel",
type: "channel",
dependsOn: "notifications_enabled",
description: "Only shown when notifications are enabled",
},
],

Use projectConfig for settings that vary between projects. These appear in the project settings panel rather than global plugin settings:

settings: {
credentials: [
{
key: "api_token",
label: "API Token",
type: "password",
required: true,
},
],
projectConfig: [
{
key: "board_id",
label: "Board",
type: "board-picker",
description: "Select the board to sync with this project",
fetchCommand: "list_boards",
},
{
key: "sync_labels",
label: "Sync Labels",
type: "boolean",
default: true,
description: "Include labels when syncing issues",
},
],
},

Two optional schema fields upgrade the rendered form:

  • oauth (OAuthConfig) — declares a browser-based authorization flow. The app renders a “Connect with {provider}” button (e.g. connectLabel: "Connect with Slack"), optionally preceded by a one-time OAuth-app setup form for setupCredentials like client_id / client_secret. Set hideManualEntry: true to suppress the paste-a-token fallback when the credential cannot meaningfully be entered by hand.
  • healthCheck (PluginHealthCheck) — names a Tauri command (e.g. test_trello_connection) the app invokes to verify that saved credentials actually work against the upstream API. When present, the form shows a Test control next to the Connected badge; a thrown error’s message is surfaced in the UI.

Credentials entered through the settings UI are stored by the desktop app:

  • Credential values are saved to the system credential store (the Keychain on macOS) rather than in app config files.
  • For plugins whose MCP tools need the credential at call time, the app also writes a per-project bridge file at .mm/integrations/{plugin-id}.json (e.g. Slack’s bot token, GitHub’s PAT). .mm/ is gitignored by default, so bridge files stay local — but they are plain JSON on disk, which is worth knowing when you decide what to store there.
  • Some plugins also support environment-variable fallbacks (e.g. GITHUB_TOKEN), resolved after the bridge file.

Tool handlers can read credential values from the integration config files that the desktop app writes:

import * as fs from "fs/promises";
import * as path from "path";
export async function myToolHandler(input: MyToolInput): Promise<MyToolOutput> {
// Read integration config written by the desktop app
const configPath = path.join(input.project_path, ".mm", "integrations", "my-plugin.json");
try {
const raw = await fs.readFile(configPath, "utf-8");
const config = JSON.parse(raw);
const token = config.api_token;
if (!token) {
return {
success: false,
error: "API token not configured. Set it in Settings > Plugins.",
};
}
// Use the token...
} catch {
return {
success: false,
error: "Plugin not configured. Set credentials in Settings > Plugins.",
};
}
}

Here is a full plugin with credentials, global config, and per-project config:

const slackPlugin: MondayMorningPlugin = {
id: "slack",
name: "Slack Integration",
description: "Post updates and digests to Slack channels",
version: "1.0.0",
category: "integration",
uiCategory: "communications",
settings: {
credentials: [
{
key: "slack_bot_token",
label: "Bot Token",
type: "password",
required: true,
helpUrl: "https://api.slack.com/apps",
helpText: "Create a Slack app and install it to your workspace",
scopes: ["chat:write", "channels:read"],
},
],
config: [
{
key: "digest_enabled",
label: "Daily Digest",
type: "boolean",
default: false,
description: "Post a daily summary to the configured channel",
},
{
key: "digest_time",
label: "Digest Time",
type: "select",
default: "09:00",
dependsOn: "digest_enabled",
options: [
{ label: "8:00 AM", value: "08:00" },
{ label: "9:00 AM", value: "09:00" },
{ label: "10:00 AM", value: "10:00" },
],
},
],
projectConfig: [
{
key: "slack_channel",
label: "Channel",
type: "channel",
description: "Slack channel for this project's updates",
fetchCommand: "list_slack_channels",
},
],
},
tools: [
// ... tool definitions
],
register: async (context) => {
context.logger.info("Slack plugin registered");
},
};