Subscription URLs and Formats
A subscription URL is the entry point for retrieving a config; the format is the structure of the response. An HTTPS URL might return Clash YAML, a Base64-encoded proxy list, or even a login page. The URL suffix alone doesn’t identify the format: a URL without a .yaml suffix can still provide a complete config.
When troubleshooting, check three layers separately: whether the HTTP request succeeded, what format the response uses, and whether the current core supports its protocols. Converting a subscription to YAML only adapts the config structure. It won’t add support for newer protocols to an older core or restore expired proxy credentials.
How Three Common Formats Differ
| Format | How to identify it | Typically contains | What you can’t assume |
|---|---|---|---|
| Proxy URI | ss://、trojan://、vmess:// |
A single proxy’s address, authentication, and some transport settings | It doesn’t necessarily include routing rules, DNS settings, or proxy groups |
| Base64 proxy list | Decodes to one URI per line | Encoded text containing multiple proxy links | It doesn’t necessarily decode to Clash YAML |
| Clash YAML | Keys such as proxies:, proxy-groups:, and rules: |
Proxies, proxy groups, rules, and optional DNS or listener settings | It doesn’t mean every Clash-based core supports the same fields |
URI: A Shareable Format for a Single Proxy
A typical URI contains a protocol, authentication details, server, port, query parameters, and a name. Protocols don’t all follow the same field conventions. For example, a common vmess:// link wraps JSON in Base64, while a Shadowsocks link may encode only the authentication details. Don’t parse every protocol with the same string-splitting function.
The fictional Trojan link below illustrates the fields. example.com is a reserved example domain, and the password is sample text; neither provides a working proxy. The sni query parameter is the TLS server name, and the text after the hash is the proxy name.
trojan://[email protected]:443?security=tls&type=tcp&sni=example.com#Lab-Trojan
Base64: An Encoding Layer for Lists
Many subscriptions separate multiple URIs with newlines, then Base64-encode the entire text. The correct process is to decode the outer layer first, then pass each line to the parser for its protocol. If a decoded line still contains Base64, that may be part of the protocol’s own sharing format; don’t blindly decode the entire text repeatedly.
Some providers use the URL-safe variant, replacing + and / with - and _, respectively, and may omit the trailing =. These are encoding differences, not a different proxy protocol.
YAML: Keep Full Configs and Proxy Providers Separate
A full config can contain sections such as proxies, proxy-groups, rules, and dns. A file containing only proxies: is often used as a proxy-provider file; it isn’t a complete client config with proxy groups, rules, and listener settings. Importing one as a full config may leave you with proxies but no routing rules, so traffic goes direct.
Can Your Client Import It Directly?
Clash-based clients generally use YAML configs, but import options depend on the client. The import features in clients such as Clash Verge Rev and FlClash are separate from the mihomo core’s ability to read a config file. An interface that can scan URIs, paste proxies, or convert subscriptions doesn’t mean the core can load any Base64 text as a config.
- Provider offers Clash or Mihomo formats: Choose the target format first, then import the subscription URL. If you need VLESS, Reality, or other extended fields, check that the target core supports them.
- Only a single URI: Check whether the client supports importing a single proxy. If not, convert it to a
proxiesentry and add proxy groups and rules. - Only a Base64 list: Identify the protocols in the decoded content before choosing the client’s built-in converter or a local conversion tool. Changing the file extension won’t convert the content.
- Already have a full YAML config: Import it directly if possible. Avoid exporting it to URIs and converting it back; the intermediate format usually can’t carry over the original rules and DNS settings.
Don’t treat “Clash format” as a permanent guarantee of compatibility. Traditional Clash, Clash Premium, and Clash Meta (now commonly called mihomo) support different feature sets. If you see an unsupported proxy type or unknown-field error, note the client version, actual core version, and protocol type, then check compatibility. Updating the UI alone may not update the core it uses.
Local Conversion: Decode First, Then Map Proxy Fields
Step 1: Confirm You Have the Subscription Content
Save the response to a local file and inspect the beginning in a text editor. If it starts with <!DOCTYPE html>, a login message, or a JSON error object, resolve the request failure first. HTTP 200 only means the server returned a response; it doesn’t guarantee a valid subscription. 401 and 403 often indicate authentication or access restrictions.
Some services return different formats based on request parameters or User-Agent. If a browser downloads a proxy list while a client receives YAML, the server may be negotiating the format. Follow the provider’s documentation instead of blindly adding conversion parameters found online.
Step 2: Decode the List Locally
The Node.js script below uses only built-in modules and performs Base64 decoding—not protocol conversion. Save the encoded response as subscription.txt and save the script as decode-subscription.cjs. The input and output stay on your machine, but the output file still contains sensitive credentials.
const fs = require('node:fs');
const { TextDecoder } = require('node:util');
const input = fs.readFileSync('subscription.txt', 'utf8').trim();
const compact = input.replace(/\s+/g, '');
if (!compact || !/^[A-Za-z0-9+/_-]+={0,2}$/.test(compact)) {
throw new Error('Input is not the expected Base64 list. Check the response body first.');
}
const normalized = compact.replace(/-/g, '+').replace(/_/g, '/');
const bare = normalized.replace(/=+$/, '');
if (bare.length % 4 === 1) {
throw new Error('Invalid Base64 length; the input may be incomplete.');
}
const bytes = Buffer.from(bare, 'base64');
if (bytes.toString('base64').replace(/=+$/, '') !== bare) {
throw new Error('Invalid Base64 content or malformed trailing bits.');
}
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
fs.writeFileSync('nodes.txt', text, { mode: 0o600 });
console.log('Wrote nodes.txt; check the protocols and line count locally.');
node decode-subscription.cjs
Don’t run this step if the original content is already a URI list or YAML. File permission settings behave differently across operating systems. Keep the output in a personal directory, not in a folder that syncs automatically or is shared publicly. After decoding, check that each proxy occupies one line and that no traffic notes or other non-proxy text were included.
Step 3: Map URI Fields to YAML
The Trojan example above can be turned into this minimal sample config. It sets the local mixed-proxy port to 7890, uses rule mode, and sends connections that don’t match earlier rules to Proxy Select. This config illustrates the structure; the sample server can’t be used to test connectivity.
mixed-port: 7890
allow-lan: false
mode: rule
log-level: info
proxies:
- name: "Lab-Trojan"
type: trojan
server: example.com
port: 443
password: "training-only"
sni: example.com
skip-cert-verify: false
proxy-groups:
- name: "Proxy Select"
type: select
proxies:
- "Lab-Trojan"
- DIRECT
rules:
- MATCH,Proxy Select
server is the server address, port is an integer, password holds the URI’s authentication details, and sni is the TLS name. The example’s type=tcp isn’t mechanically copied as YAML type: tcp, because here type refers to the proxy protocol and should be trojan. That’s why conversion can’t simply replace fields with matching names.
If the actual link uses WebSocket, gRPC, or Reality, you’ll also need to map parameters such as the path, Host, service name, public key, and short ID to the target core’s syntax. Keeping only the server and password may produce a syntactically valid proxy that can’t connect. If you’re unsure what a field does, use a local converter that explicitly supports the protocol and transport instead of deleting fields that trigger errors.
Preserving Rules, Proxy Groups, and Proxy Fields
| Source field or config section | What to check during conversion | Typical impact if omitted |
|---|---|---|
| Proxy name | Keep names unique and references in groups identical | Proxy-group references fail or become ambiguous |
| TLS and transport parameters | SNI, certificate verification, paths, and protocol extensions | Handshake failure or connection closed |
proxy-groups |
Group name, type, members, and health-check settings | Can’t choose an exit; automatic selection behavior changes |
rules |
Original order, target names, and final fallback rule | Traffic that should use a proxy goes direct |
dns、tun |
Continued management by local config or client overrides | Changes to DNS resolution or traffic interception |
When YAML is generated from a URI list, routing rules and proxy groups are usually created by a conversion template; they aren’t recovered from the proxies. If the template adds DOMAIN-SUFFIX,example.org,DIRECT, that’s a template rule—the original proxy links didn’t include it. Compare proxy parameters and routing behavior separately before and after migration; don’t just compare proxy counts.
Exporting a full YAML config to URIs loses a lot of context. Nested proxy groups, rule-providers, DNS policies, and TUN settings have no universal single-proxy URI representation. Converting back can only regenerate settings from a template; it can’t restore the original comments, ordering, or every feature.
Reduce Repeat Conversions with Proxy Providers
If your provider offers node-based YAML, you can manage proxy updates separately from local rules. Here’s how to use a local proxy-providers entry in mihomo: update ./providers/lab.yaml through an external process, then reference it from a proxy group with use. The file must use a supported provider format; it can’t be a Base64 list that’s still encoded.
proxy-providers:
lab:
type: file
path: ./providers/lab.yaml
proxy-groups:
- name: "Proxy Select"
type: select
use:
- lab
rules:
- MATCH,Proxy Select
Use the proxy wrapper structure below for provider files; the fields inside each entry match those used in proxies. Relative paths are resolved based on the core’s working directory or how the client manages files—not necessarily the directory containing the YAML you opened manually. If the file can’t be found, check the actual path in the core logs.
proxies:
- name: "Lab-Trojan"
type: trojan
server: example.com
port: 443
password: "training-only"
sni: example.com
skip-cert-verify: false
If you use an HTTP provider, its update URL must still return proxy content accepted by the target core. An update interval doesn’t convert formats. Review remote rules and auto-update URLs in the full config as well. To keep local settings from being overwritten by a subscription, use the client’s override or merge feature. The steps vary by client.
Import Errors and Post-Conversion Checks
Identify Which Layer Is Failing
- YAML parse error: Check that indentation uses spaces, that there’s a space after each colon, and that passwords and names are quoted correctly. Unquoted text containing
#may be treated as a comment. - Policy not found: Check the rule target against the proxy-group name.
MATCH,Proxy Selectmust reference a group with the same name. Update references whenever you rename a proxy. - Protocol not supported: Check which core is actually running. Changing
typeto another protocol name won’t convert the protocol. - Config loads but connections time out: Check proxy authentication, TLS, and transport settings, then check the network and server status. Valid syntax doesn’t mean the proxy can connect.
- Changes disappear after restart: A subscription update may be regenerating the file. Put persistent changes in the client’s override config or maintain a separate local config.
Retest in Order
- Back up the original config and note the original proxy count, group names, and routing mode. Don’t overwrite your only working config file.
- Check that the converted config includes authentication and transport parameters. Compare sensitive fields locally; don’t post full screenshots in public forums.
- If you have the mihomo command-line tool, run
mihomo -t -f ./config.yamlto check the config. The test may still read external files or fetch remote resources, so review the config first. - After loading the config, select a proxy manually, confirm rule mode is active, and check the connection log to see which policies actually match.
- If you’re using the sample port
7890, runcurl --proxy http://127.0.0.1:7890 https://example.com/to check the local proxy listener. Replace the sample with a working proxy config. This doesn’t verify TUN, UDP, or every routing rule. - Finally, update the subscription and restart the client. Confirm that the proxies are still there, overrides remain active, and rules don’t point to missing names.
When choosing a conversion method, use this order of preference: get the target YAML directly from the provider, use an import method the client explicitly supports, then use a reviewable local converter. For ongoing updates, managing proxy sources separately from local rules makes troubleshooting easier than repeatedly converting the entire config to URIs and back. See the configuration reference for field syntax, and the client comparison for help choosing a client and core.