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.

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

Retest in Order

  1. Back up the original config and note the original proxy count, group names, and routing mode. Don’t overwrite your only working config file.
  2. Check that the converted config includes authentication and transport parameters. Compare sensitive fields locally; don’t post full screenshots in public forums.
  3. If you have the mihomo command-line tool, run mihomo -t -f ./config.yaml to check the config. The test may still read external files or fetch remote resources, so review the config first.
  4. After loading the config, select a proxy manually, confirm rule mode is active, and check the connection log to see which policies actually match.
  5. If you’re using the sample port 7890, run curl --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.
  6. 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.