This guide explains how IDEs can integrate with the Snyk Language Server's configuration dialog to provide a user-friendly interface for managing Snyk settings.
The configuration dialog is an HTML-based interface that allows users to view and modify all Snyk Language Server settings. The dialog is triggered via an LSP command and displayed in the IDE's webview or browser component.
The configuration dialog follows a client-server pattern:
- Language Server: Generates HTML content with current settings, handles configuration updates
- IDE Client: Displays the HTML, injects JavaScript functions for user interactions, applies configuration changes
The dialog uses a modular JavaScript architecture with all modules organized under infrastructure/configuration/template/js/:
- utils.js: Core utilities (deep cloning, debouncing, normalization, IE7 polyfills)
- dirty-tracker.js: DirtyTracker class for tracking form state changes
- helpers.js: DOM manipulation helpers (get, addEvent, addClass, removeClass)
- validation.js: Form validation logic (endpoint, risk score, additional env)
- form-data.js: Form data collection and serialization
- auto-save.js: Auto-save functionality with debouncing
- authentication.js: Login and logout operations
- folder-management.js: Folder-specific configuration management
- trusted-folders.js: Trusted folder list management
- form-state-tracking.js: Consolidated form state tracking that integrates DirtyTracker with the form and triggers both dirty tracking and auto-save on blur/change events
- init.js: Main initialization that wires up all event handlers
All modules are namespaced under window.ConfigApp to minimize global namespace pollution. The DirtyTracker class and FormUtils are exposed globally as they are referenced by multiple modules and the IDE integration layer.
The IDE triggers the configuration dialog by executing the LSP command snyk.workspace.configuration.
Command Details:
- Command:
workspace/executeCommand - Command ID:
snyk.workspace.configuration - Arguments:
[](no arguments required)
Response:
"<html>... full HTML content ...</html>"
The command returns the complete HTML content as a string. The IDE can directly display this in a webview without any additional processing.
See Opening Configuration Dialog Sequence for the detailed flow.
The IDE should:
- Execute the command and receive the response
- Extract the
contentfield from the command response - Create a webview or browser component
- Inject IDE-specific JavaScript functions (see Function Injection)
- Load the HTML content and display it
Example (conceptual):
// Execute command and receive HTML
const html = await client.sendRequest('workspace/executeCommand', {
command: 'snyk.workspace.configuration',
arguments: []
});
// Create webview and display
const webview = createWebview();
webview.html = injectFunctions(html);
webview.show();The IDE must expose the following functions on the window object for the dialog to function:
| Function | Purpose | Required | Parameters |
|---|---|---|---|
window.__ideLogin__() |
Handle authentication | Yes | None |
window.__saveIdeConfig__(jsonString) |
Save configuration | Yes | JSON string of config data |
window.__ideLogout__() |
Handle logout | Yes | None |
window.__IS_IDE_AUTOSAVE_ENABLED__ |
Enable auto-save mode | No | Boolean (default: false) |
window.__onFormDirtyChange__(isDirty) |
Dirty state notifications | No | Boolean indicating dirty state |
Additional IDE-Callable Functions:
The dialog also exposes these functions that the IDE can call:
| Function | Purpose | Returns |
|---|---|---|
window.getAndSaveIdeConfig() |
Collect and save current form data | void |
window.__isFormDirty__() |
Check if form has unsaved changes | Boolean |
window.__resetDirtyState__() |
Reset dirty tracker after save | void |
Injection Example:
// Expose functions to webview before loading HTML
webview.window.__ideLogin__ = async () => {
const token = await handleLogin();
// Optionally refresh dialog or update token field
};
webview.window.__saveIdeConfig__ = async (jsonString: string) => {
const data = JSON.parse(jsonString);
await handleSaveConfig(data);
};
webview.window.__ideLogout__ = async () => {
await handleLogout();
};
// Optional: Enable auto-save
webview.window.__IS_IDE_AUTOSAVE_ENABLED__ = true;
// Optional: Listen for dirty state changes
webview.window.__onFormDirtyChange__ = (isDirty: boolean) => {
// Update IDE UI to show unsaved changes indicator
updateTabTitle(isDirty ? "* Settings" : "Settings");
};See Function Injection Flow for the detailed sequence.
When the user clicks "Save Configuration", the dialog collects all form data and calls ideSaveConfig(data).
Configuration Data Format:
interface ConfigurationData {
// Core Authentication
token?: string;
endpoint?: string;
organization?: string;
automaticAuthentication?: string; // "true" | "false"
// Product Activation
activateSnykOpenSource?: string;
activateSnykCode?: string;
activateSnykIac?: string;
activateSnykCodeSecurity?: string;
activateSnykCodeQuality?: string;
// CLI Settings
cliPath?: string;
path?: string;
insecure?: string;
manageBinariesAutomatically?: string;
// Operational Settings
sendErrorReports?: string;
scanningMode?: string; // "auto" | "manual"
// Feature Toggles
enableSnykLearnCodeActions?: string;
enableSnykOSSQuickFixCodeActions?: string;
enableSnykOpenBrowserActions?: string;
enableDeltaFindings?: string;
enableTrustedFoldersFeature?: string;
// Advanced Settings
filterSeverity?: {
critical?: boolean;
high?: boolean;
medium?: boolean;
low?: boolean;
};
issueViewOptions?: {
openIssues?: boolean;
ignoredIssues?: boolean;
};
// Folder-specific settings (dynamic)
folder_0_folderPath?: string;
folder_0_baseBranch?: string;
folder_0_localBranches?: string;
folder_0_additionalParameters?: string;
folder_0_referenceFolderPath?: string;
folder_0_preferredOrg?: string;
folder_0_riskScoreThreshold?: number;
// Scan command configuration per product per folder
folder_0_scanConfig_Snyk_Open_Source_preScanCommand?: string;
folder_0_scanConfig_Snyk_Open_Source_preScanOnlyReferenceFolder?: boolean;
folder_0_scanConfig_Snyk_Open_Source_postScanCommand?: string;
folder_0_scanConfig_Snyk_Open_Source_postScanOnlyReferenceFolder?: boolean;
folder_0_scanConfig_Snyk_Code_preScanCommand?: string;
folder_0_scanConfig_Snyk_Code_preScanOnlyReferenceFolder?: boolean;
folder_0_scanConfig_Snyk_Code_postScanCommand?: string;
folder_0_scanConfig_Snyk_Code_postScanOnlyReferenceFolder?: boolean;
folder_0_scanConfig_Snyk_IaC_preScanCommand?: string;
folder_0_scanConfig_Snyk_IaC_preScanOnlyReferenceFolder?: boolean;
folder_0_scanConfig_Snyk_IaC_postScanCommand?: string;
folder_0_scanConfig_Snyk_IaC_postScanOnlyReferenceFolder?: boolean;
// ... additional folders follow the same pattern with folder_1_, folder_2_, etc.
}Sending Configuration to Language Server:
The IDE should send the configuration using the workspace/didChangeConfiguration notification:
client.sendNotification('workspace/didChangeConfiguration', {
settings: configData
});How the Language Server Processes Configuration:
- Receives Notification: The language server receives the
workspace/didChangeConfigurationnotification - Validates Settings: Validates all configuration values (e.g., endpoint URLs, numeric ranges)
- Applies Configuration: Updates the active configuration in memory
- Persists Settings: Saves configuration to persistent storage (typically in the user's config directory)
- Applies Changes: Immediately applies changes that affect behavior (e.g., enables/disables products, updates tokens)
- Acknowledgment: The notification is fire-and-forget (no response), but the IDE can verify success by:
- Monitoring for configuration-related errors via
window/showMessage - Re-executing
snyk.workspace.configurationto see if changes were applied
- Monitoring for configuration-related errors via
Complete Implementation Example:
async function handleSaveConfig(configData: ConfigurationData) {
try {
// 1. Optional: Validate configuration data on IDE side
if (configData.endpoint && !isValidEndpoint(configData.endpoint)) {
showError('Invalid endpoint URL');
return;
}
// 2. Send configuration to language server
await client.sendNotification('workspace/didChangeConfiguration', {
settings: configData
});
// 3. Provide user feedback
showMessage('Configuration saved successfully');
// 4. Optional: Refresh the dialog to show updated values
// await refreshConfigurationDialog();
} catch (error) {
showError('Failed to save configuration: ' + error.message);
}
}Important Notes:
- The
workspace/didChangeConfigurationnotification is one-way (no response expected) - All settings are optional - only include fields you want to change
- The language server merges provided settings with existing configuration
- Invalid settings are logged but don't fail the entire configuration update
- Sensitive data (tokens) is encrypted when persisted to disk
- Changes take effect immediately after the language server processes them
Error Handling:
- Monitor
window/showMessagenotifications for configuration-related errors from the language server - Validate critical fields (endpoint, paths) on the IDE side before sending
- Provide immediate user feedback for both success and failure cases
See Saving Configuration Flow for the detailed sequence diagram.
When the user clicks "Authenticate", the dialog calls ideLogin().
IDE Responsibilities:
- Initiate OAuth or token-based authentication
- On success, update the token in the webview (if needed)
- Notify the language server of the new authentication state
Example:
async function handleLogin() {
try {
const token = await authenticateWithSnyk();
// Update language server
await client.sendNotification('workspace/didChangeConfiguration', {
settings: { token }
});
// Optionally, refresh the dialog to show authenticated state
await refreshConfigurationDialog();
} catch (error) {
showError('Authentication failed: ' + error.message);
}
}See Authentication Flow for the detailed sequence.
When the user clicks "Logout", the dialog calls ideLogout().
IDE Responsibilities:
- Clear stored authentication credentials
- Execute the logout command on the language server
- Update UI to reflect logged-out state
Example:
async function handleLogout() {
try {
// Execute logout command
await client.sendRequest('workspace/executeCommand', {
command: 'snyk.logout',
arguments: []
});
// Clear local credentials
await clearStoredCredentials();
// Optionally, refresh the dialog
await refreshConfigurationDialog();
} catch (error) {
showError('Logout failed: ' + error.message);
}
}See Logout Flow for the detailed sequence.
The dialog includes a dirty tracking system that monitors form changes and notifies the IDE when there are unsaved changes.
How it Works:
- Initial State Capture: When the dialog loads, the
DirtyTrackercaptures a deep clone of the initial form state - Change Detection: Form inputs are monitored with event listeners - text inputs and textareas use
inputandchangeevents, while select dropdowns and checkboxes usechangeevents - Deep Comparison: Before comparison, values are normalized (empty strings → null, "true"/"false" → booleans, numeric strings → numbers). The tracker then performs deep equality checks between normalized current and original state
- State Transition Events: When dirty state transitions (clean→dirty or dirty→clean),
window.__onFormDirtyChange__(isDirty)is called - Reset After Save: After successful save, the tracker resets with the new saved state as the baseline
IDE Integration:
// Listen for dirty state changes
webview.window.__onFormDirtyChange__ = (isDirty: boolean) => {
if (isDirty) {
// Show unsaved changes indicator
setDocumentIcon("*");
enableSaveButton();
} else {
// Clear indicator
setDocumentIcon("");
disableSaveButton();
}
};
// Check dirty state before closing dialog
function beforeClose() {
if (webview.window.__isFormDirty__()) {
const shouldClose = confirm("You have unsaved changes. Close anyway?");
if (!shouldClose) return false;
}
return true;
}
// Reset dirty state after successful save
async function handleSave(jsonString: string) {
try {
await saveConfiguration(jsonString);
webview.window.__resetDirtyState__(); // Only reset on success
} catch (error) {
// Keep dirty state on save failure
showError('Failed to save: ' + error.message);
}
}Features:
- Deep equality comparison handles nested objects and arrays
- Value normalization (empty strings = null, "true"/"false" to booleans)
- Debounced change detection for performance
- Automatic reset after successful save
sequenceDiagram
participant IDE as IDE Client
participant LSP as Language Server
participant Config as Config System
participant HTML as HTML Renderer
IDE->>LSP: workspace/executeCommand<br/>{command: "snyk.workspace.configuration"}
LSP->>Config: Get current configuration
Config-->>LSP: Configuration data
LSP->>Config: Construct Settings from Config
Config-->>LSP: Settings object
LSP->>HTML: Generate HTML with settings
HTML-->>LSP: HTML content
LSP-->>IDE: Command response<br/>"<html>..." (HTML string)
IDE->>IDE: Create webview
IDE->>IDE: Inject IDE functions into HTML
IDE->>IDE: Display HTML in webview
sequenceDiagram
participant IDE as IDE Client
participant Webview as Webview Component
participant HTML as HTML Content
IDE->>IDE: Receive HTML content
IDE->>Webview: Create webview instance
IDE->>Webview: Expose functions on window object:<br/>- window.__ideLogin__()<br/>- window.__saveIdeConfig__(jsonString)<br/>- window.__ideLogout__()
IDE->>Webview: Load HTML content
Webview-->>IDE: Webview ready
Note over HTML,Webview: User interacts with dialog
HTML->>Webview: User clicks "Authenticate"
Webview->>IDE: Call window.__ideLogin__()
IDE->>IDE: Handle authentication
HTML->>Webview: User clicks "Save"
Webview->>Webview: collectData()
Webview->>IDE: Call window.__saveIdeConfig__(jsonString)
IDE->>IDE: Handle save
HTML->>Webview: User clicks "Logout"
Webview->>IDE: Call window.__ideLogout__()
IDE->>IDE: Handle logout
sequenceDiagram
participant User as User
participant Dialog as Configuration Dialog
participant Webview as Webview
participant IDE as IDE Client
participant LSP as Language Server
participant Config as Config System
User->>Dialog: Modify settings
User->>Dialog: Click "Save Configuration"
Dialog->>Dialog: collectData()<br/>Gather all form values
Dialog->>Webview: Call window.__saveIdeConfig__(jsonString)
Webview->>IDE: Post message with config data
IDE->>IDE: Validate configuration data
IDE->>LSP: workspace/didChangeConfiguration<br/>{settings: data}
LSP->>Config: Update configuration
Config->>Config: Validate settings
Config->>Config: Apply settings
Config->>Config: Persist to storage
Config-->>LSP: Configuration updated
LSP-->>IDE: Acknowledgment
IDE->>Webview: Show success message
Webview->>Dialog: Display "Configuration saved"
Dialog->>User: Visual feedback
sequenceDiagram
participant User as User
participant Dialog as Configuration Dialog
participant IDE as IDE Client
participant Auth as Auth Service
participant LSP as Language Server
participant Snyk as Snyk API
User->>Dialog: Click "Authenticate"
Dialog->>IDE: Call window.ideLogin()
IDE->>Auth: Initiate authentication
alt OAuth Flow
Auth->>Snyk: Request OAuth authorization
Snyk-->>User: Open browser with auth page
User->>Snyk: Approve authorization
Snyk->>Auth: OAuth callback with code
Auth->>Snyk: Exchange code for token
Snyk-->>Auth: Access token
else Token Authentication
Auth->>User: Prompt for API token
User->>Auth: Provide token
end
Auth-->>IDE: Authentication successful<br/>{token: "..."}
IDE->>LSP: workspace/didChangeConfiguration<br/>{settings: {token: "..."}}
LSP->>Snyk: Verify token
Snyk-->>LSP: Token valid
LSP-->>IDE: Configuration updated
IDE->>Dialog: Refresh with authenticated state
Dialog->>User: Show "Authenticated" status
sequenceDiagram
participant User as User
participant Dialog as Configuration Dialog
participant IDE as IDE Client
participant LSP as Language Server
participant Auth as Auth Service
participant Storage as Credential Storage
User->>Dialog: Click "Logout"
Dialog->>IDE: Call window.ideLogout()
IDE->>LSP: workspace/executeCommand<br/>{command: "snyk.logout"}
LSP->>Auth: Clear authentication state
Auth->>Storage: Remove credentials
Storage-->>Auth: Credentials cleared
Auth-->>LSP: Logout complete
LSP-->>IDE: Command completed
IDE->>IDE: Clear local token storage
IDE->>Dialog: Refresh with logged-out state
Dialog->>User: Show "Not authenticated" status
- Execute
snyk.workspace.configurationcommand - Extract HTML content from command response
- Create webview/browser component for display
- Expose required window functions (
__ideLogin__,__saveIdeConfig__,__ideLogout__) - Display HTML content in webview
- Implement
window.__saveIdeConfig__(jsonString)to receive config data - Parse JSON configuration data
- Send
workspace/didChangeConfigurationnotification - Validate configuration data before sending
- Handle configuration errors gracefully
- Provide user feedback on save success/failure
- Call
window.__resetDirtyState__()after successful save
- Implement
window.__ideLogin__()function - Support OAuth flow (recommended)
- Support PAT (Personal Access Token) authentication
- Support API token authentication (legacy)
- Update language server on successful authentication
- Handle authentication errors
- Optionally refresh dialog after authentication
- Implement
window.__ideLogout__()function - Execute
snyk.logoutcommand - Clear stored credentials
- Update UI to reflect logged-out state
- Optionally refresh dialog after logout
- Implement
window.__onFormDirtyChange__(isDirty)callback - Show unsaved changes indicator in IDE UI (e.g., "*" in tab title)
- Warn user before closing dialog with unsaved changes using
window.__isFormDirty__() - Disable save button when form is clean
- Enable save button when form is dirty
- Set
window.__IS_IDE_AUTOSAVE_ENABLED__ = truebefore loading HTML - Handle automatic saves triggered by form changes
- Provide feedback for auto-save operations
- Display loading indicators during operations
- Show success/error messages
- Provide validation feedback for form fields
- Support dialog refresh after configuration changes
- Handle dialog close/cancel actions
- Implement beforeunload confirmation for unsaved changes
-
Error Handling: Always wrap LSP calls in try-catch blocks and provide meaningful error messages to users.
-
Validation: Validate configuration data on the IDE side before sending to the language server.
-
Security:
- Never log or expose authentication tokens
- Use secure credential storage
- Clear sensitive data on logout
-
User Feedback: Provide immediate visual feedback for all user actions (save, authenticate, logout).
-
Refresh Strategy: After authentication or logout, consider refreshing the dialog to show the updated state.
-
Webview Isolation: Use proper webview security settings to isolate the dialog from other IDE components.
- Verify the command ID is correct:
snyk.workspace.configuration - Check that the language server is initialized
- Ensure
workspace/executeCommandcapability is supported
- Verify function injection is replacing all placeholders
- Check that webview message passing is configured correctly
- Ensure functions are exposed to the webview's global scope
- Verify the
workspace/didChangeConfigurationnotification format - Check that the language server has
workspace.configurationcapability - Validate configuration data structure
- Ensure network connectivity to Snyk API
- Verify OAuth redirect URLs are configured correctly
- Check that the token is valid and not expired
For issues or questions about the configuration dialog integration:
- Open an issue on GitHub
- Join the discussion in Snyk Community




