Understanding Profile Merge and Script Enhancement in Clash Verge Rev

Profile merge and script enhancement in Clash Verge Rev allow users to inject global proxy configurations and runtime JavaScript helpers into every profile without manually editing individual YAML files.

These features provide a powerful abstraction layer in the clash-verge-rev/clash-verge-rev repository, enabling centralized management of rules and dynamic configuration through code. The implementation spans both the React frontend and Rust backend, with clear separation between the UI components that capture user input and the Tauri commands that perform deep YAML merging.

What Are Profile Merge and Script Enhancement?

Profile merge is a YAML overlay system that combines a global Merge file (and optional per-profile merge files) with each active profile. This allows you to define shared proxy groups, rule-providers, or sub-rules once and have them automatically injected into every configuration.

Script enhancement treats a JavaScript file named Script as a runtime helper that can be edited through the UI, opened in your OS default editor, and validated before execution. It is primarily used by the Script tab of the profile page to perform dynamic configuration adjustments that YAML alone cannot achieve.

Architecture and Data Flow

Type Definitions and Data Models

The type system in src/types/global.d.ts defines the structure for these features. The IProfileItem interface includes optional merge and script string fields that store file paths, while the profile type itself can be "local" | "remote" | "merge" | "script".

// src/types/global.d.ts (lines 262-294)
interface IProfileItem {
  uid: string;
  type?: "local" | "remote" | "merge" | "script";
  // ... other fields
  merge?: string;   // Path to per-profile merge file
  script?: string; // Path to script file
}

Frontend Components

The ProfileMore component (src/components/profile/profile-more.tsx) renders the UI chips for Merge and Script functionality. When a user clicks Edit, it opens an EditorViewer that reads and writes the corresponding file using the readProfileFile and saveProfileFile commands.

The RulesEditorViewer (src/components/profile/rules-editor-viewer.tsx) handles the actual merge logic in the UI layer. It receives three UIDs—profileUid, groupsUid, and mergeUid—then reads the profile, its per-profile groups file, and the global merge file to combine proxy-groups, rule-providers, and sub-rules on the fly.

Backend Merge Implementation

The Rust backend in src-tauri/src/feat/profile.rs contains the enhance_profiles function (lines 225-260) that performs the deep merge operation. This command:

  1. Loads the global Merge file from the profile directory
  2. Iterates through every profile and loads its base YAML
  3. Applies optional per-profile merge files if option?.merge is defined
  4. Performs a deep merge of proxy-groups, rules, rule-providers, and sub-rules
  5. Writes the merged result to the runtime configuration directory
// src-tauri/src/feat/profile.rs (simplified)
pub async fn enhance_profiles() -> Result<(bool, String)> {
    let global_merge = read_merge_file("Merge")?;
    
    for profile in get_all_profiles()? {
        let mut yaml = read_profile_yaml(&profile)?;
        
        // Apply per-profile merge
        if let Some(merge_path) = profile.option.as_ref().and_then(|o| o.merge.clone()) {
            let per_merge = read_merge_file(&merge_path)?;
            yaml = deep_merge(yaml, per_merge);
        }
        
        // Apply global merge
        yaml = deep_merge(yaml, global_merge.clone());
        write_runtime_yaml(&profile.uid, yaml)?;
    }
    Ok((true, "merged".into()))
}

Notification and Error Handling

Validation errors from merge operations—such as YAML syntax errors, mapping conflicts, or missing keys—are converted to internationalized messages and displayed via the notice service. The handler in src/pages/_layout/utils/notification-handlers.ts (lines 70-78) specifically catches merge validation errors and surfaces them as user-friendly notifications.

How to Use Profile Merge

Creating a Global Merge File

Create a file named Merge (no extension) in your Clash Verge Rev configuration directory (typically ~/.config/clash-verge-rev/ on Linux/macOS or %APPDATA%\clash-verge-rev\ on Windows). This YAML file supports standard Clash configuration keys that will be deep-merged into every profile.


# Merge – global additions applied to all profiles

proxy-groups:
  - name: "ADBLOCK"
    type: "select"
    proxies:
      - "PROXY"
      - "DIRECT"

rule-providers:
  adguard:
    type: "http"
    behavior: "domain"
    url: "https://filters.adtidy.org/extension/ublock/filters/2.txt"
    path: "./adguard.yaml"
    interval: 86400

Per-Profile Merge Configuration

Individual profiles can specify their own merge file through the option.merge field. When both global and per-profile merges exist, the per-profile merge is applied first, followed by the global merge, ensuring global settings take precedence for conflicting keys.

To set this via the UI:

  1. Open the Profiles page
  2. Click the More button on your target profile
  3. Select the Merge chip and choose your custom merge file

How to Use Script Enhancement

Editing the Script File

The script enhancement feature allows you to write JavaScript that manipulates the configuration at runtime. Access this through the Script chip in the More panel of any profile.

// Example: Script file content (JavaScript)
function main(config) {
  // Add a custom DNS configuration
  config.dns = {
    enable: true,
    listen: "0.0.0.0:53",
    nameserver: ["8.8.8.8", "1.1.1.1"]
  };
  
  // Filter proxy groups
  config["proxy-groups"] = config["proxy-groups"].filter(g => g.name !== "Unused-Group");
  
  return config;
}

When you save the script via the EditorViewer component, the frontend calls saveProfileFile('Script', content) followed by enhanceProfiles() to re-apply the configuration. The backend validates the JavaScript syntax and execution safety before allowing it to affect the runtime config.

Programmatic Integration

Triggering Manual Merge Refresh

You can programmatically trigger the merge process from your own components using the enhanceProfiles command from src/services/cmds.ts:

import { enhanceProfiles } from '@/services/cmds';
import { showNotice } from '@/services/notice-service';
import { useLockFn } from 'ahooks';

// Inside a React component
const onEmergencyRefresh = useLockFn(async () => {
  try {
    await enhanceProfiles();  // Calls the Tauri command
    showNotice.success('profiles.page.feedback.notices.forceRefreshCompleted');
  } catch (e) {
    showNotice.error('profiles.page.feedback.notices.emergencyRefreshFailed', { 
      message: String(e) 
    });
  }
});

This pattern is used by the Emergency Refresh button on the main profiles page (src/pages/profiles.tsx) to force a complete re-merge of all configurations without restarting the application.

Handling Merge Validation Errors

When the backend detects invalid YAML during the merge process, it emits specific error types that the frontend handles in src/pages/_layout/utils/notification-handlers.ts:

// Error handling for merge operations
case 'merge::syntax':
  showNotice.error('notifications.mergeSyntaxError', { message: error.details });
  break;
case 'merge::mapping':
  showNotice.error('notifications.mergeMappingError', { key: error.conflictKey });
  break;

These handlers ensure users receive immediate feedback when their global or per-profile merge files contain syntax errors or conflicting key mappings.

Summary

  • Profile merge enables centralized configuration management by deep-merging a global Merge YAML file (and optional per-profile merges) into every active profile, automatically injecting shared proxy groups, rules, and rule-providers.
  • Script enhancement provides a JavaScript runtime hook that allows dynamic configuration manipulation through a dedicated Script file, editable via the UI and validated before execution.
  • The architecture cleanly separates concerns: React components (ProfileMore, RulesEditorViewer) handle the UI, TypeScript commands (enhanceProfiles) bridge to the backend, and Rust logic (src-tauri/src/feat/profile.rs) performs the safe deep-merge operation with comprehensive error handling.
  • Both features persist files in the configuration directory and react to changes through the useProfiles hook, ensuring the runtime configuration stays synchronized with user edits.

Frequently Asked Questions

What is the difference between global merge and per-profile merge?

Global merge applies to every profile in your configuration through the single Merge file in the config directory, while per-profile merge allows individual profiles to specify their own merge file via the option.merge field. When both exist, the per-profile merge is applied first, followed by the global merge, meaning global settings will override conflicting keys from the per-profile merge.

How does the script enhancement feature validate JavaScript code?

The script enhancement feature validates JavaScript through the backend's script validation logic before allowing it to affect the runtime configuration. When you save the script via the EditorViewer component, the frontend calls saveProfileFile('Script', content) followed by enhanceProfiles(), which triggers the backend to check syntax and execution safety. Any validation errors are surfaced through the notification system defined in src/pages/_layout/utils/notification-handlers.ts.

Where are the merge and script files stored?

Both the merge and script files are stored in the Clash Verge Rev configuration directory, which is typically located at ~/.config/clash-verge-rev/ on Linux and macOS, or %APPDATA%\clash-verge-rev\ on Windows. The global merge file is named simply Merge (without extension), while the script file is named Script. Per-profile merge files can reside anywhere accessible to the application and are referenced by path in the profile's option.merge field.

Can I use profile merge to override existing proxy groups?

Yes, the deep merge algorithm implemented in src-tauri/src/feat/profile.rs allows you to override existing proxy groups, rules, and rule-providers defined in the base profile. When the backend performs the merge operation, it combines the YAML structures such that values from the merge files (per-profile first, then global) take precedence over the original profile configuration. This enables you to centrally modify proxy group behaviors or inject new rules without editing each subscription file individually.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →