Editing the subscription file directly is the classic beginner mistake: the change works — until the next subscription update erases it. Clash Verge's answer is Merge and Script: your modifications live in separate files and are re-applied automatically on top of whatever the provider sends.

Subscription config flowing through Merge and Script into the final config
Every load: subscription → Merge → Script → what the core actually runs

The pipeline

Each time the core loads a config: subscription text → Merge applied (declarative) → Script executed (JavaScript) → final config. Updates replace only the first stage; your layers persist and re-apply.

Where to edit: on the Profiles page, right-click for the global extension config (applies to every subscription), or right-click a specific profile card for its own Merge/Script (applies to that one only).

Merge: covers ninety percent of needs

A Merge file is YAML with prefixed keys describing how to combine: prepend- (insert before), append- (add after), or a bare field name (replace outright).

# my rules first — highest priority
prepend-rules:
  - DOMAIN-SUFFIX,company-vpn.example,DIRECT
  - PROCESS-NAME,Telegram.exe,PROXY

# replace the DNS section entirely
dns:
  enable: true
  enhanced-mode: fake-ip
  nameserver:
    - https://1.1.1.1/dns-query

# add a group of my own
append-proxy-groups:
  - name: "Streaming"
    type: select
    proxies: [HK-01, SG-02]

Prepend vs append matters: rules that must win over the subscription's (say, forcing a domain direct) go in prepend; fallback additions go in append. Rule syntax itself: custom rules.

Script: for the dynamic cases

Anything Merge cannot express — conditional edits, bulk filtering, generated groups — goes in Script: a JavaScript function that receives the merged config and returns the final one.

function main(config) {
  // drop "informational" nodes (expiry notices, traffic counters)
  config.proxies = config.proxies.filter(
    p => !/expire|traffic|remaining/i.test(p.name)
  );
  // force UDP on for all vmess nodes
  config.proxies.forEach(p => {
    if (p.type === "vmess") p.udp = true;
  });
  return config;
}

An exception thrown in Script aborts the config load — the core keeps running the previous config. After editing, check the Logs page for errors before assuming your change took effect.

Changes not applying?

  1. Confirm you edited the profile that is currently selected (or the global extension config);
  2. Check the Logs page for YAML errors — one wrong indent invalidates the block;
  3. Right-click the profile → "Reactivate" to force a reload;
  4. Use the Connections page to see which rule traffic actually hits, verifying your prepend order.

If the three config sections are still fuzzy, read the config structure explained first — Merge makes much more sense afterwards.