First distinguish a subscription URL, a server share link, and a full config before deciding whether conversion is possible. A Base64 subscription is usually just a list of share links wrapped in an encoding. Native JSON also includes local listeners, DNS, and routing, so changing the file extension won’t turn it into a subscription. This guide is for anyone troubleshooting an unrecognized subscription, a failed JSON import, or a migration between clients. It covers local encoding, field mappings, and checks to run after importing.
Identify the content first: a subscription URL isn’t a subscription format
What people commonly call a “V2Ray subscription” isn’t a single file standard supported by every client. A subscription URL beginning with https:// only tells you that the client needs to access an endpoint; the response body determines whether it contains a list of links, Base64 text, or a config file. The same endpoint may return different content based on request parameters or client identifiers, so the URL suffix alone can’t tell you whether it’s compatible.
| Content type | What it looks like | What it actually contains | Best used for |
|---|---|---|---|
| Link-list subscription | Multiple protocol links on separate lines, or one Base64-encoded block | Connection details for multiple servers | Subscription groups and updates |
| Single share link | vmess:// or vless:// | Connection details and a label for one server | Importing a server from the clipboard |
| Full native JSON | Fields such as inbounds and outbounds | A complete runtime config for the core | A custom config option compatible with the core |
When retrieving a subscription response, check that it isn’t actually an error page. HTML at the start, a sign-in prompt, or a message saying access has expired means the request didn’t return the expected node data. Base64-encoding the error page again only gives you an encoded error page; it won’t fix the subscription.
Choose a format for the task: manage routine updates separately from full configs
In v2rayN, v2rayNG, and v2flyNG, server lists and runtime configs are separate layers. A client can generate a core config from server details, but local ports and routing rules in a full config may not fit into a share link. Before choosing a format, decide whether you’re migrating “nodes” or “nodes plus local policies.”
Client-compatible link-list subscription
RecommendedKeep the remote subscription URL so the client can fetch the server list on demand or at regular intervals. When the provider changes its nodes, update the subscription to get the latest list.
Best for: managing multiple servers day to day and keeping the node list up to date
Single VMess / VLESS share link
Useful for importing one server and checking its connection details field by field. Once imported, it’s usually a local server entry and won’t automatically stay linked to the original subscription.
Best for: testing one node temporarily or manually moving a few servers
Full JSON compatible with the core
Can express settings such as inbounds, outbounds, DNS, and routing. Make sure the target core supports the relevant fields; the file extension alone isn’t enough.
Best for: custom routing, fixed local listeners, and multiple outbounds
v2rayNG uses the Xray core, while v2flyNG uses the V2Fly core. For v2rayN, compatibility depends on the selected core. JSON configs aren’t automatically interchangeable, and support for VLESS doesn’t mean every transport and security setting works in every target version. Check the target client and core’s actual capabilities before converting.
How to decode a Base64 subscription: unwrap it, then check each line
A common way to create a Base64 subscription is to join multiple share links with newlines, then encode the whole string. After decoding the outer layer, you may see a mix of vmess:// and vless:// links. Note that traditional VMess share links contain another Base64 layer, so decoding the subscription doesn’t also decode each VMess node.
Base64 is an encoding, not encryption. Standard Base64 commonly uses +, /, and trailing = padding. The URL-safe variant uses - and _, and some outputs omit padding. Handle Chinese labels as UTF-8; converting character encodings directly can produce garbled text.
The function below can be run locally in a blank browser tab’s developer console. It only processes the text you pass in and makes no network requests. The example domain, UUID, and port illustrate the structure; they aren’t usable connection details.
function decodeBase64Utf8(input) {
let text = input.trim().replace(/\s+/g, "")
.replace(/-/g, "+").replace(/_/g, "/");
if (text.length % 4 === 1) {
throw new Error("Invalid Base64 length");
}
text = text.padEnd(Math.ceil(text.length / 4) * 4, "=");
const bytes = Uint8Array.from(atob(text), ch => ch.charCodeAt(0));
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
function encodeBase64Utf8(input) {
const bytes = new TextEncoder().encode(input);
const binary = Array.from(bytes, byte => String.fromCharCode(byte)).join("");
return btoa(binary);
}
const exampleLink = "vless://[email protected]:443?encryption=none&security=tls&type=tcp&sni=example.com#TLS-demo";
const encodedList = encodeBase64Utf8(exampleLink + "\n");
console.log(decodeBase64Utf8(encodedList));
- First check whether the response already lists protocol links in plain text. If it does, inspect the list directly; there’s no need to decode it.
- If the response is one encoded block, use a decoder to remove the outer wrapper, split the result by line breaks, and skip blank lines.
- Check the protocol prefix on each line. Only decode the content after the prefix when you’ve identified the traditional VMess format.
- If decoding fails, keep the original text and check for truncation, webpage content, or character substitutions. Repeated blind decoding won’t fix the problem.
VMess and VLESS share links: different structures, not interchangeable prefixes
Traditional VMess links: JSON encoded as Base64
A common traditional VMess share link consists of vmess:// followed by Base64-encoded node JSON. This node JSON isn’t a full core config; it doesn’t describe all local inbounds and routing rules. The example below shows a typical field structure before encoding. Here, v identifies the share format version, not the client or core version.
{
"v": "2",
"ps": "VMess TLS example",
"add": "example.com",
"port": "443",
"id": "11111111-1111-4111-8111-111111111111",
"aid": "0",
"scy": "auto",
"net": "tcp",
"type": "none",
"host": "",
"path": "",
"tls": "tls",
"sni": "example.com"
}
Serialize this object, then use the encoding function above to build a traditional link: "vmess://" + encodeBase64Utf8(JSON.stringify(node)), where node is the object shown above. To parse one, remove the prefix, decode the rest, and read it with JSON.parse. Here, aid: "0" corresponds to a common VMess AEAD config; don’t change it to another value based on outdated guides.
VLESS links: URI fields and query parameters
VLESS usually uses a URI structure: the user identifier comes before @, the server address and port follow it, transport and security settings go in the query string, and the label follows #. Encode Chinese labels as percent-encoded text. Special characters in other fields should also be encoded with a URI utility; don’t concatenate raw text without handling it.
vless://[email protected]:443?encryption=none&security=tls&type=tcp&sni=example.com#TLS-demo
encryption=noneis a VLESS protocol setting; it doesn’t disable TLS. Use settings such assecurity=tlsto indicate whether TLS is enabled.sniis the server name used in the TLS handshake. Don’t confuse it with the WebSocket HTTP Host or connection address.type=tcpspecifies the transport type in this example. Switching to another transport also requires its corresponding parameters and server-side support.- VMess and VLESS use different authentication and config structures. Changing
vmess://tovless://won’t convert the protocol or change the server.
Converting native JSON: extract a node, don’t expect to preserve the entire setup
A full config covers both how traffic enters the local device and how it’s sent to remote servers. The compact Xray-style example below listens for SOCKS traffic on a loopback address and routes it through a VLESS TLS outbound to an example domain. It illustrates the field hierarchy; it contains no usable credentials and isn’t a universal config for every core.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "local-socks",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "example.com",
"port": 443,
"users": [
{
"id": "11111111-1111-4111-8111-111111111111",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "example.com"
}
}
}
]
}
| JSON field | Share link location | Example value |
|---|---|---|
settings.vnext[0].address | Hostname | example.com |
settings.vnext[0].port | Remote port | 443 |
settings.vnext[0].users[0].id | URI user info | Example UUID |
streamSettings.security | security query parameter | tls |
tlsSettings.serverName | sni query parameter | example.com |
inbounds[0].port | Not included in a server share link | 10808 is a local port |
The outbound subfields in the table above refer to outbounds[0]. When extracting fields manually, first select the actual proxy outbound, then read its protocol and transport settings. Don’t always take the first array item: in a real config, the first may be a direct outbound, and there may be multiple proxy outbounds, chained connections, or load balancing.
- JSON to share link: Extract the fields that can be represented by the target outbound, then generate a link using the relevant protocol’s share format. Save DNS, routing, local listeners, and other outbounds separately.
- Share link to JSON: Import the node into a client first so it can generate a runtime config using its current settings. If writing the config by hand, define the local inbound and default outbound behavior as well as the remote fields.
- Link list to a Base64 subscription body: Join valid share links on separate lines, then Base64-encode the text as UTF-8. The result is a subscription body, not a remotely updateable subscription URL.
- Base64 to full JSON: Decode the text and identify its contents, then convert each node individually. Putting the encoded text directly in
outboundsdoesn’t produce a valid core config.
Importing and migrating between clients: back up first, then test one node
Before converting anything manually, export or back up your current settings and keep the original subscription URL. Don’t overwrite a subscription group you’re actively using. Create a test group and import a few nodes to distinguish a format parsing error from a server connection failure. The menu names below describe common Chinese-language interfaces and may vary by client version.
Migration plan: handle server details and local config separately
Desktop: v2rayN
- Add the remote URL under “Subscription Group” → “Subscription Group Settings,” then update the target group.
- For a single link, use “Server” → “Import bulk URL from clipboard,” then check the import results.
- Check the local listener port, selected core, and routing settings. Don’t use the remote port 443 as your local proxy port.
Android: v2rayNG / v2flyNG
- Add remote subscriptions in the subscription group settings. For a single link, use the clipboard import option on the main screen.
- Check parameters against Xray capabilities in v2rayNG and V2Fly capabilities in v2flyNG.
- Recheck VPN permission and per-app settings. These local options don’t transfer with a regular node link.
The same subscription can serve as a shared source on both platforms, but check update timing, protocol support, and local policies separately.
After importing, open the node editor and compare the address, port, user identifier, transport, and TLS settings with the original. Then start the connection and check the logs. If the list looks fine but the handshake fails, check the connection details and server status. If there are no nodes, investigate the response body and import method before changing routing rules.
- Local connection: Apps using the system proxy or a manually configured proxy should connect to the address and port the client is actually listening on. The example
127.0.0.1:10808isn’t a default for every installation. - Remote connection: Connection timeouts, authentication failures, and TLS name mismatches are different problems. Use the logs to investigate each one separately; don’t change every field at once.
- Update behavior: A static node imported from the clipboard won’t necessarily update when the original subscription URL changes. To get automatic updates, save the subscription URL and its group.
Subscription conversion FAQs: decoding doesn’t guarantee a connection
Why does the decoded Base64 still start with vmess? Is it only partially decoded?
That’s usually a normal outer subscription list. Split it into lines first. For a traditional VMess link, decode the text after vmess:// to get the node JSON. Don’t repeatedly decode a mixed list of links as a whole; URI punctuation will be treated as encoded data.
Why does “Import from clipboard” say there are no servers when I have JSON?
Check the top-level fields first. If it contains inbounds and outbounds, it may be a full core config. Use a custom config option supported by the target version, or extract a single proxy outbound as described in this guide. The clipboard share-link importer isn’t a general-purpose JSON importer.
Why won’t the client import it after I convert it to Base64?
Decode it back to the original text to make sure nothing was corrupted, then check that every line is a protocol link supported by the target client. Base64 only changes how text is represented. It won’t fill in a missing user identifier, turn a webpage into a node, or add protocol support the core doesn’t have.
Why didn’t my routing rules transfer when the converted node works?
Regular share links mainly describe a single remote node; they don’t migrate DNS, routing, or per-app settings as a complete setup. Check local policies in the target client. To reproduce the full routing setup, keep the native config and confirm its fields are compatible with the core. A successful node connection doesn’t mean the config migration is complete.
Follow this order: verify that you received the right response body, identify the encoding layers, check the node fields, then verify the target core and local traffic entry point. Subscriptions, share links, and native JSON each serve a different purpose. Manual conversion is safer when you know what each step preserves and discards, rather than mixing up connection issues with config issues. For related concepts, see the Glossary. For routing and core settings, see Advanced configuration.