Skip to content

Config Scripts

Nemu-x edited this page Aug 19, 2026 · 1 revision

Config Scripts

🌐 English · Русский · 中文

A config script is a small piece of JavaScript that rewrites your profile's configuration before the engine loads it. It lets you change anything in the config — DNS, rules, proxy groups — without editing YAML, and your changes survive every subscription update.

Open it from Profiles → ⋮ on a profile → Config script.

The contract

Your script defines one function:

function main(config) {
  // change whatever you like
  return config
}

config is the whole configuration as a plain JavaScript object, exactly as the engine would have received it. Whatever you return replaces it.

You must return config. A script that changes the object but forgets to return it saves nothing.

This is the same signature other Clash clients use, so scripts written elsewhere generally work here unchanged.

Getting started

Tap Insert template — it drops in a working no-op you can save as-is:

function main(config) {
  // config.dns.enable = true
  // config.rules.unshift("DOMAIN-SUFFIX,example.com,DIRECT")
  return config
}

Then tap Check. This actually runs your script against your config and tells you if anything is wrong — it is not a syntax highlighter, it is the real thing, so it catches mistakes that only show up on your particular subscription.

Save stores the script and rebuilds the config immediately. If the script does not run, nothing is saved — the app will not let a profile carry a broken script.

The Run this script switch turns it off without deleting the text.

Examples

Force DNS on

function main(config) {
  config.dns = config.dns || {}
  config.dns.enable = true
  return config
}

Send a domain direct, ahead of everything else

function main(config) {
  config.rules = config.rules || []
  config.rules.unshift("DOMAIN-SUFFIX,bank.example,DIRECT")
  return config
}

Drop nodes you never use

function main(config) {
  config.proxies = (config.proxies || []).filter(p => !/test|expired/i.test(p.name))
  return config
}

Raise the auto-test interval on every url-test group

function main(config) {
  for (const group of config["proxy-groups"] || []) {
    if (group.type === "url-test") group.interval = 600
  }
  return config
}

Only touch one profile — the profile name arrives as a second argument:

function main(config, profileName) {
  if (profileName === "Work") config.mode = "global"
  return config
}

What a script can and cannot do

A script sees the config and nothing else. There is no file access, no network, no require, no timers — fetch, process and friends are simply not defined. It cannot download a rule list or phone home.

It also cannot weaken the app's own protections: ClashFest applies its security pass after your script, so a script cannot reopen the local listeners the app closes or slip past URL sanitising.

A script that runs longer than 3 seconds is stopped. Config transforms are just object shuffling; anything slower is a loop that never ends.

When something breaks

If your script stops working later — say a subscription update removed a key it expected — the app does not break with it. It quietly rebuilds the config without the script and carries on, so a scheduled update at 3am never leaves you without a working tunnel. Open the editor and tap Check to see what went wrong.

Errors carry stable codes:

Code What it means
E-60 Syntax error — the script could not be parsed
E-61 No main — the script must define function main(config)
E-62 The script threw an error while running
E-63 main() returned nothing, or something that is not a config object
E-64 The script ran too long and was stopped (almost always an endless loop)
E-65 The script produced an implausibly large config

See Troubleshooting for the full code list.

Notes

Your script is stored with the profile's other edits, so it survives subscription updates, and it runs again every time the config is rebuilt — on update, on VPN start, and when you save it.

The config is re-serialised after your script runs, so config.yaml may come back with keys in a different order and comments dropped. That is expected: the engine reads the values, not the formatting. Your original subscription is kept untouched alongside it.

For operators

If you run a panel and do not want users rewriting the config you serve, send:

X-Brand-Lock-Config-Script: true

The editor becomes read-only and any script already stored on the profile stops running. Enforcement happens when the config is built, not just in the UI, so a script saved before you set the flag stops too.

Like the other policy headers this works without X-Branding-Enabled and survives the branding kill-switch. See Header Reference.

Clone this wiki locally