-
Notifications
You must be signed in to change notification settings - Fork 4
Config Scripts
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.
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.
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.
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
}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.
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.
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.
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.
📱 User Guide
- Getting Started
- Profiles & Nodes
- Routing & Rules
- Settings
- Deep Links
- Encrypted Subscriptions
- Config Scripts
- Troubleshooting
🏢 Operator API
📺 Companion