A subscription URL retrieves configuration data; Base64 is text encoding, share links describe individual nodes, and native JSON may define an entire core configuration. This guide is for anyone whose subscription opens but fails to import, or who needs to move nodes. Identify the response format, decode it locally, then check the protocol fields. Conversion can carry over node settings, but won’t necessarily preserve DNS, routing, or update behavior.
First, distinguish the subscription URL, response body, and node configuration
A URL such as https://sub.example.com/list?token=demo tells a client where to request content; the URL alone doesn’t reveal the response format. The domains, UUIDs, and parameters in this article are structural examples, not working connection details. Tokens in real subscription URLs often grant access, so sharing a URL can expose every node in the subscription.
Traditional V2Ray subscriptions often join multiple share links, one per line, then Base64-encode the entire text. Some services return an unencoded list of links, or vary the response based on the client making the request. As a result, what you see in a browser may differ from what the client receives when updating.
Subscription URLs and lists of links
RecommendedFor routine management of multiple nodes, keep the original subscription URL. The server can update the list, and clients can fetch it by group. The response may be Base64-encoded or contain plain-text links.
Best for: keeping nodes up to date and importing them on desktop and Android
Single-node share links
Common prefixes include vmess:// and vless://. A link carries a node’s connection parameters, not a complete runtime configuration.
Best for: sharing one node temporarily or checking the fields of a specific entry
Native core JSON
May include inbounds, outbounds, DNS, and routing. Whether a file runs depends on the target core and its configuration schema—not on whether its extension is .json.
Best for: users maintaining configurations with custom inbounds, outbounds, and routing
Where to import in v2rayN and v2rayNG
Both clients can manage common VMess and VLESS share links and traditional link subscriptions. But being able to parse text doesn’t mean the target core supports every parameter it contains. Menu names vary by version, so the instructions below refer to features rather than exact labels. Don’t paste a subscription URL into a field meant for a single node.
| Input | v2rayN for desktop | v2rayNG for Android |
|---|---|---|
| HTTPS subscription URL | Go to “Subscription Groups” → “Subscription Group Settings,” create a group, enter the URL, then update the group | Add the URL in “Subscription Group Settings,” return to the list, then select “Update Subscription” |
| Plain-text share links | Copy the full links and use an option such as “Import bulk URLs from clipboard” | Tap “+” in the upper-right corner → “Import from clipboard” |
| A full Base64 text string | Use the subscription update flow to parse it. For a manual transfer, decode it first, then import the list of links | Use a subscription group if possible. If manual import doesn’t recognize the text, decode it into share links first |
| Native core JSON | Use a custom configuration import option and select a compatible core. This isn’t a regular subscription URL | Use the custom configuration import option. Support depends on the Xray core and client version in use |
v2rayNG uses the Xray core; v2flyNG uses the V2Fly core. Valid JSON syntax doesn’t guarantee that both cores support its security features or configuration fields. For combinations involving VLESS, REALITY, and similar features, check the target core’s capabilities before converting formats. Conversion can’t add protocol support.
Decode a Base64 subscription: check the outer layer, then each line
First, make sure you have the response body—not an error page. If it starts with <!DOCTYPE html>, a login prompt, or an API error, it isn’t subscription content to decode. Changing the file extension or repeatedly trying to import it won’t fix expired authentication or an error returned by the server.
- Save the response body as a local text file and keep the original. Remove any explanatory text included when copying, and don’t mistake the URL for the Base64 response body.
- If the response already starts with
vmess://orvless://, treat it as a plain-text list of links. Don’t decode the whole response. - If the response is a long string of encoded characters, Base64-decode it once, split it by line breaks, and remove blank lines.
- Check the protocol prefix on each line. A line starting with
vmess://may contain another layer of encoded VMess share data after the prefix. That doesn’t mean the entire subscription needs to be decoded over and over.
The code below processes text you already have in the developer console of a blank local page; it doesn’t request a subscription URL. Run the function definition first, then call it with decodeSubscription(prompt("Paste the subscription response body")). Don’t process real credentials in the console of an untrusted website.
function decodeSubscription(input) {
const raw = String(input ?? "").trim();
if (!raw) throw new Error("Response body is empty");
if (/^(vmess|vless):\/\//i.test(raw)) return raw;
let encoded = raw.replace(/\s/g, "")
.replace(/-/g, "+").replace(/_/g, "/");
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encoded)) {
throw new Error("Content contains invalid Base64 characters");
}
encoded = encoded.replace(/=+$/, "");
if (encoded.length % 4 === 1) {
throw new Error("Unexpected encoded length; check that the content was copied in full");
}
encoded += "=".repeat((4 - encoded.length % 4) % 4);
const bytes = Uint8Array.from(atob(encoded), c => c.charCodeAt(0));
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
Create Base64 from share links: join lines, then encode
To turn a plain-text list into a traditional Base64 subscription body, follow this order: one link per line → UTF-8 bytes → Base64. This creates a static response body; it doesn’t set up an updateable subscription service. Saving the result to a local file also doesn’t give the client an accessible subscription URL.
const link = "vless://[email protected]:443?encryption=none&security=tls&type=tcp&sni=node.example.com#Example-TLS";
function encodeSubscription(links) {
const text = links.map(item => item.trim()).filter(Boolean).join("\n");
const bytes = new TextEncoder().encode(text);
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}
const encoded = encodeSubscription([link]);
console.log(encoded);
console.log(decodeSubscription(encoded) === link);
Chinese remarks are included in the encoded content, so don’t call btoa directly on a string containing non-ASCII characters. Convert the text to UTF-8 bytes with TextEncoder first to avoid character range errors. The round-trip comparison on the last line should return true; this confirms that encoding didn’t change the example text, not that the node can connect.
These numbers describe encoding rules and configuration examples, not speed-test results or defaults shared by every client version. Base64 isn’t compression. Ignoring trailing padding, the encoded output is about four-thirds the size of the original bytes—roughly a one-third increase.
Share links and native JSON: mapping fields isn’t a complete migration
Using the VLESS link above as an example, the host and port go into the outbound server settings, the UUID goes into the user settings, security=tls maps to TLS, and sni maps to the TLS server name. The example below is a single Xray-style outbound object, not a complete, runnable configuration file. It needs to go inside the outbounds array of a complete configuration.
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "node.example.com",
"port": 443,
"users": [
{
"id": "11111111-1111-4111-8111-111111111111",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"serverName": "node.example.com"
}
}
}
To create a working configuration, you also need to define a local inbound, select an outbound, and configure any required routing. For example, if the local SOCKS listener is 127.0.0.1:10808, the app using it must be set to that address and port. Don’t use the remote 443 port as the local proxy port; they’re at opposite ends of the connection.
| Share link fields | Corresponding native configuration fields | Commonly missed details |
|---|---|---|
| Host, port, and UUID | Server and user in settings.vnext | The port must be a number; the UUID shouldn’t have leading or trailing spaces |
type and transport parameters | streamSettings | For WebSocket, migrate both the path and Host |
security、sni | TLS or the corresponding security settings | The server address and SNI may differ; don’t assume they’re the same |
flow and REALITY parameters | User fields and corresponding security settings | Keep any server-required flow, public key, short ID, and other details |
Remark after # | Client display name or a label you maintain yourself | A remark isn’t authentication data and doesn’t automatically become a routing rule |
A common VMess share format has Base64-encoded share data after vmess://. Fields such as add, port, id, net, and path follow the share-link format; they aren’t top-level fields in the core’s native configuration. Saving the decoded object directly as a runtime config will usually fail because the structure doesn’t match.
Two migration goals: handle node settings and full configurations separately
Move nodes only
- Export a share link from the client, then import it into the target client.
- Check the UUID, transport, TLS, SNI, path, and other connection fields.
- Set up the system proxy, DNS, and routing again in the target client.
Keep the full runtime configuration
- Preserve the inbounds, outbound tags, and routing references in the JSON.
- Confirm the target core supports every field, and check for conflicts on local listening ports.
- Import through the custom configuration option; don’t force everything into a single-node link.
Converting native JSON back into a share link usually extracts only some outbound settings. There’s no universal single-link format that can losslessly represent multi-outbound chains, DNS, and routing rules.
Validate after conversion: check update and connectivity separately
A successful subscription update only means the client retrieved and parsed some or all of the content; it doesn’t mean every node works. Keep the original subscription group and import converted results into a separate test group. Test one node before processing the rest. Manually exported static links also won’t inherit the original subscription’s update behavior.
- Check the entries: Compare the number of nodes before and after conversion. Look for blank lines, duplicate entries, or unsupported protocols that were skipped.
- Check the fields: Open the node editor and verify the address, port, UUID, transport, path, SNI, and security settings. Matching remarks alone aren’t enough.
- Check that it runs: Select the test node and review the core logs. Handle configuration parsing errors, authentication failures, and connection timeouts separately.
- Check app routing: Make sure the system proxy or app proxy points to the actual local listening port, then check that routing sends the test request to the expected outbound.
Decoded text is still a long string. Should I decode it again?
First, check whether you can see vmess://. If so, process only the part after the prefix according to the VMess share format; don’t keep decoding the entire list. If there’s no protocol prefix or recognizable JSON structure, first confirm that the original response is complete and check what format the server actually returns.
The JSON opens, so why does the client say the import failed?
Check the top-level fields. An object with only fields such as add and id may be VMess share data; one containing inbounds and outbounds may be a core runtime configuration. Import the latter through the custom configuration option, then verify that the target core supports its fields.
Why does the same subscription update on desktop but import as empty on Android?
First, make sure the subscription URL is complete on both devices. Then check whether the response differs based on the client making the request. If the response is valid, compare protocol and transport support. Test a single plain-text link using “+” → “Import from clipboard” to narrow down the problem to a specific entry instead of repeatedly converting the entire subscription.
Can I convert an entire JSON config into one VLESS link?
Extracting fields into a link only makes sense when the target is a single representable VLESS outbound. Find the outbound where protocol is vless and keep its connection parameters. Save inbounds, DNS, routing, and other outbounds separately; don’t expect one conversion to preserve everything.