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[];
}

There are three levels of settings:

LevelScopeWhere Rendered
credentialsGlobal (all projects)Settings > Plugins
configGlobal (all projects)Settings > Plugins
projectConfigPer-projectProject Settings > Plugins

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",
},
],
},

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

  • On macOS, credentials are stored in the system Keychain via Tauri’s secure storage API.
  • Credential values are never written to disk in plain text.
  • The MCP server accesses credentials through environment variables or the Tauri command bridge.

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");
},
};