Hello everyone.
If the only thing standing between you and trying Chute is the configuration you have spent two years tuning, that excuse expired. Anyone who has run a proxy client for a while has a config they are not going to rewrite: a hundred and twenty rules, a dozen groups, a few scripts, DNS tuned by hand, and everything working. For years, “try another app” was not a question about features — it was a question about whether a weekend disappears.
That is the problem the import path exists to solve, and this month we finished making it trustworthy rather than merely possible. Chute reads Surge, Shadowrocket, Clash/mihomo and sing-box configurations; the parts it cannot carry are named instead of dropped; and the parts it does carry now land where the author of the config intended. Here is what a move looks like in practice, and what does not come across.
What Chute Reads
- Surge and Shadowrocket profiles — read directly, with no conversion step.
- Clash / mihomo YAML and sing-box JSON — converted into native configuration.
- Share links —
ss://,vmess://,vless://,trojan://,hysteria2://,tuic://,anytls://,socks5://,ssh://: one link, or a whole payload of them, base64-wrapped or not. - Subscription URLs — an
http://orhttps://address is fetched first, then imported according to what the body turns out to be.
The format is decided by the content, not the filename: something starting with { is sing-box JSON whatever it is called, a [Section] header or a #!MANAGED-CONFIG directive means a native profile, and proxies: or rules: means Clash. A file that parses but contains none of the sections that define its dialect is rejected, never reported as a successful import of nothing.
Whatever the source, the result is an ordinary native profile: [Proxy], [Proxy Group], [Rule], saved and edited like any other. Everything Chute does works on it immediately, because nothing interprets a foreign file at runtime.
Surge and Shadowrocket Profiles Are Read, Not Converted
No translation step means no translation loss — you paste your existing profile and keep using it. Chute accepts each dialect’s own spellings, including the ones that look nothing like its native keys:
- Groups. Surge’s
policy-path=<url>withupdate-interval=andpolicy-regex-filter=,include-all-proxies=true,include-other-group=<name>; Shadowrocket’suse=truemember lists,select=andpolicy-select-name=. A group type Chute does not implement — Shadowrocket’srandom, Surge’sexternal— keeps its members and runs as a select group, with a notice. It used to become an empty group whose rules rejected. - Rules.
DST-PORT, port ranges,HOSTNAME-TYPEwithIPv4/IPv6/DOMAIN/SIMPLE,IP-ASN,AS13335,FINAL,<policy>,dns-failed,notification-text=, and inline[Ruleset <name>]sections. Module rules run ahead of the profile’s own rules, as they do in Surge. - DNS.
direct-dns-server,proxy-dns-server,fallback-dns-server,dns-direct-system,dns-fallback-system,always-ip-address,[Host]entries of the formssid:<name> = server:…, Surge’sencrypted-dns-serverand[Host] <domain> = script:<name>. An encrypted resolver written indns-server—https://,h3://,tls://,quic://, with or without a#proxysuffix — moves to the encrypted pool instead of being rejected, and one bad entry no longer takes the rest of the line with it. - General.
proxy-test-urlandtest-timeoutas the defaults for the testing groups,udp-policy-not-supported-behaviour,encrypted-dns-skip-cert-verification,block-quicwith the values each app actually writes, andhttp-api/http-api-web-dashboardmapped onto the control plane. - HTTP tooling.
header-replace-regex, thehttp-request/http-responsebody-rewrite form,[Map Local]withdata-type=andstatus-code=, URL Rewrite’sreject-arrayandreject-tinygif, and[MITM]‘sh2,hostname-disabledandauto-quic-block. - Scripts and modules.
cronexp=,type=generic,event-name=; module headers such as#!IOS-ONLY/#!MACOS-ONLY/#!TVOS-ONLY;%APPEND%and%INSERT%.
Two behaviours matter more than the list. #!include works: those lines used to vanish without a word, leaving the included section non-existent and every rule referencing it failed closed. Includes now expand for real, *.list globs included, with a depth limit and cycle detection — and the platform directives in a main profile are honoured, so #!MACOS-ONLY Proxy = … no longer makes a group disappear everywhere.
Unknown sections are preserved. [Keystore], [Port Forwarding], [Testing], [DHCP], [Snell Server], [Panel], [Ponte], [MTProto] load with one notice each and are written back unchanged. And a proxy line naming a protocol Chute does not implement is not a configuration error: the line survives as written, and anything referencing it fails closed to REJECT rather than leaking to DIRECT. An unsupported node should never mean your traffic quietly goes out unprotected.
Clash and sing-box Are Converted, Entry by Entry
These two need a real conversion, so the interesting question is what happens to the parts with no Chute equivalent. The answer: nothing takes the whole file down, and nothing leaves without telling you.
Every proxy, group and rule converts on its own. The ones with no equivalent are skipped and reported — in a warning list, and as comments written into the converted text next to the section they came from:
1 | [Proxy] |
Then the converted text is re-parsed by Chute’s own parser. Anything it rejects is commented out as # KL-CONVERT-DROPPED and reported, so a profile that is 95% convertible still imports and runs instead of failing as a whole.
Names get the same care. A node name containing , or = would break the configuration format, so it is rewritten — and every group member and rule destination is rewritten to match, so no reference silently points at a name that no longer exists. direct becomes the built-in DIRECT; reject / block / dns become REJECT.
Why You Can Trust the Import: 57 Real Configs
An import is only worth using if the result behaves like the config you wrote. So we took 57 in-use community profiles — 16 Surge profiles, 11 Surge modules, 8 mihomo, 18 sing-box, plus Loon and Quantumult X versions written by the same authors — and graded the 53 comparable ones by consequences rather than by error count.
44 imported cleanly, and two of the nine failures turned out to be the source files’ own syntax errors. But “clean” only meant “no hard error”, and several profiles that read as clean were dropping things silently. Three classes, in increasing order of nastiness.
Hard failures — you see them immediately. dns-server carrying an encrypted URL (https://doh.pub/dns-query, 223.5.5.5, …) was rejected, and because that key was all-or-nothing the plain resolvers on the same line died with it. A single malformed #!MANAGED-CONFIG line at the top discarded everything after it — one profile arrived with 1 of its 29 rules. [Host] mapping one domain to several addresses, the documented way to answer a name with both an A and an AAAA record, was an error; the IPv4-only variant was worse, silently becoming an alias whose target was the literal string 1.1.1.1,2.2.2.2.
Silent loss — the config loads, and part of it is not there. mihomo’s .mrs and sing-box’s .srs are compiled payloads: the rule lines survived, the sets did not, so a profile whose rules are almost entirely RULE-SET,<provider>,<policy> looked complete while every flow fell through to its final rule. Measured on six in-use mihomo configs, 73–90% of rule lines were dead; the sing-box side had the same shape, with every remote set in the corpus — 85 of 85 — compiled. Surge’s and Shadowrocket’s #!include vanished with no warning at all, and a group type we did not implement became an empty group whose rules then rejected.
Silent inversions — the config loads, nothing looks wrong, and the traffic does the opposite of what you wrote. These are the ones that keep us up at night.
- Surge and Shadowrocket let a policy name contain spaces if you quote it:
Proxy = select, "Hong Kong", …. Chute stripped the quotes on one side of the reference and not the other, so the group was defined asHong Kongand referenced as"Hong Kong"— a dangling reference, which fails closed to REJECT. On one real profile, nearly every important group (Proxy, Automatic, Netflix, YouTube, AI, Telegram) pointed at exactly that name. - mihomo’s
PASSgroup member means “skip this group and keep matching”. Chute turned it into a dangling reference — a REJECT slot, the precise opposite. - sing-box’s
clash_mode: rulemeans “this is rule mode”, which is always true. Chute discarded the entire rule, which is how several published templates lost their ad-blocking rules.
Where that left us:
| Measured | Before | After |
|---|---|---|
| Scenarios importing cleanly | 44 / 53 | 51 / 53 |
| Dead rule lines, six in-use mihomo configs | 73–90% | 0–4% |
| Rules surviving in one Surge profile | 1 / 29 | 29 / 29 |
| False “undefined policy” warnings, rule-only profiles | 450 | 0 |
| Warnings across the whole corpus | 1157 | 698 |
The last reduction is accounted for line by line: the three groups that disappeared — 450, 5 and 4 — sum exactly to the total. Nothing got quieter. The warnings that remain are real, and the two failures that remain are the source files’ own errors, not ours.
What Does Not Cross
A migration post that only lists successes is not worth reading. These are the boundaries, so nothing surprises you after the move.
- Proxy chaining is not implemented. mihomo’s
dialer-proxy, sing-box’sdetour, Clashrelaygroups and Surge’sunderlying-proxyall describe a chain of proxies. The option is parsed, preserved and reported — and the policy connects directly to its own server. - Loon and Quantumult X profiles are not supported. Their section systems are different enough that this is a project of its own, not a mapping table.
- Some protocols are not implemented: Snell, Hysteria v1, Juicity, Mieru, Sudoku, OpenConnect and others. Their lines are preserved and their references fail closed, but they will not connect.
- Binary rule sets on iPhone and Apple TV are skipped, with a message naming the set. On macOS the importer points the entry at the text form of the same set, published alongside it — which is how the numbers above were reached.
- GeoIP and GeoSite categories get the same treatment. mihomo’s
GEOSITEand non-countryGEOIPbecome real rule sets on the desktop; a phone keeps a payload ceiling per set and says so rather than trying. - sing-box’s
dns.rulesonly partly lands. Plain domain rules convert; rule-set conditions,query_typeand per-rule outbounds have no equivalent here. - Clash’s
mode: globalandmode: directconvert to rule mode with a warning. Chute has one routing model. - Process-name rules are macOS-only, because iOS and tvOS do not tell a sandboxed process which app owns a socket. They are gated rather than left silently never matching.
- A converted profile is a static snapshot. It is saved without an auto-update URL, so it will not refresh; import it again when the source changes. A native subscription keeps updating normally.
- The fake-IP range is fixed and not configurable.
What You Gain After Moving
Once the configuration is native, it runs on the same engine as everything else in Chute — and some of that is not on offer in the client you are coming from.
- Rule sets are indexed. A 50,000-line remote list is no longer scanned per connection: p50 lookup went from ~0.60 ms to ~0.06 ms, and each indexed set costs about 105 KB resident instead of ~316 KB. We wrote that one up here.
- You can ask the engine why. The web console, the Dashboard and the
chutecommand line run the same match.chute rule match example.comreports both passes — before and after resolution — because the engine matches a domain twice and the two passes can land on different policies. Reporting only the first would describe a decision that never happens. - DNS gained per-server options:
#h3=true,#skip-cert-verify=true,#disable-ipv4,#disable-ipv6,#disable-qtype=, and#proxy=<policy>to resolve through a specific policy. - Small things that used to be mysteries.
pingis answered inside the tunnel. An HTTP/3 connection to a bare IP can still match domain rules, because the server name in the QUIC handshake is read from the first packets. A Wi-Fi ↔ cellular switch no longer tears down every flow whose address lives inside a tunnel. HAR capture, one-click and offline diagnostic bundles, and a web console that follows the current run are all part of the same profile’s toolkit. - Protocol breadth. The protocols Shadowrocket added this year — MASQUE, Tailscale, AnyTLS, SSH, WireGuard with AmneziaWG — were already in the engine. One profile runs on iPhone, iPad, Mac and Apple TV, and on the desktop half a Mac can serve as a Tailscale exit node and subnet router.
- Scripts. Surge’s script surface is implemented, including the
$surgetoggles for capture and rewriting and$httpClientwith a policy, plus Chute’s own$klnecontrol API: dial through a policy, switch a rewrite family, close a connection by id.
Making the Move
Nothing about your existing file is modified — an import writes a new profile — and your old client can stay installed, so the move is reversible until you decide it isn’t.
On iPhone and iPad the entry points are Import from Local File, a subscription URL, a QR code, the clipboard, iCloud, or a file uploaded over Wi-Fi; on the Mac they are Import Configuration… from the menu bar, the Profiles tab, or the Dashboard’s import page; on Apple TV the configuration is delivered from an iPhone or iPad, which is also where you will see the import’s notices.
Afterwards, two things are worth a look. The warning list names every skipped entry with its reason, and the same comments sit in the converted file if you want to edit it by hand. The rule-set list shows the engine’s own message for a set that failed to load, in the app’s provider list and in the web console, instead of leaving it looking healthy.
If your config is one of the 51, the move is a paste and a couple of minutes. If it is one of the two, or if it uses something on the “does not cross” list, the import will tell you which part and why — before you commit to anything.
Thanks.
Chute Devs