V2Ray Subscription Formats: Base64 Lists, Native JSON, and vmess:// Links

Learn how Base64 subscription lists, share links, and native JSON differ, what v2rayN and v2rayNG support, and which fields to check when decoding or converting formats.

At a glance

A subscription URL is an entry point for getting configuration; Base64 is text encoding, vmess:// is a node-sharing format, and native JSON describes how the core runs. This guide is for users importing nodes across devices, inspecting subscription contents, or migrating custom configurations. Identify the response format, decode it locally, then check the protocol, transport, and TLS fields—successful parsing does not guarantee a complete configuration.

First, distinguish subscription URLs, node lists, and native JSON

“V2Ray subscription” is not a single file format. An HTTPS URL might return a Base64-encoded list of share links, a plain-text list, full JSON, or even a login page. Neither the file extension nor the presence of “subscribe” in the URL tells you what it contains; the client processes the response body returned by the server.

A typical Base64 subscription has two layers: the entire list is encoded once, and each line in the decoded text is a share link. If a line is a traditional JSON-based vmess:// link, its payload after the protocol prefix is Base64-encoded again. These layers encode different data, so you can’t parse the entire subscription as a single VMess node.

Subscription with a list of share links

Recommended

Provides multiple share links for managing regular nodes. When the provider updates an address or port, the client can fetch the latest list by refreshing the subscription. Whether the outer layer uses Base64 depends on the provider’s output.

Best for: managing nodes from the same source in v2rayN and v2rayNG

Single vmess:// share link

Contains the connection parameters for one VMess node; it is not a subscription URL that stays up to date. Usually imported from the clipboard or a QR code.

Best for: moving one node temporarily or troubleshooting import issues field by field

Native core JSON

Can include inbounds, multiple outbounds, DNS, routing, and more. It is more than a node list and must be compatible with the configuration format supported by the core in use.

Best for: custom configurations that require explicit control over inbounds and routing

Where to import in v2rayN and v2rayNG

Choose the import method based on the content before considering conversion. Importing an HTTPS subscription URL from the clipboard as a node, or pasting full JSON into a subscription URL field, usually won’t work as expected. The steps below use common Chinese UI labels; wording may vary slightly by release, so choose the menu item by function.

Use the same share-link subscription on both clients

Desktop: v2rayN
  • Open “Subscription Group” → “Subscription Group Settings,” add a group, and enter the subscription URL.
  • After saving, open the “Subscription Group” menu and select “Update All Subscriptions (without proxy).” If the source requires a proxy, choose the option to update through a proxy instead.
  • For a single share link, use “Servers” → “Import share links from clipboard.” Don’t create a subscription group with a vmess:// URL.
Android: v2rayNG
  • Open “Subscription Group Settings” from the sidebar, tap the add button, enter a remark and subscription URL, then save.
  • Return to the main screen and select “Update subscription” from the top-right menu. Check that nodes from the group appear in the list.
  • For a single share link, use “+” → “Import from clipboard” on the main screen. For a full configuration, use the custom configuration import option for a file or clipboard.

The two clients may not support exactly the same parameters from a given source. After updating, check the node count, transport type, and TLS settings.

Both clients can handle common traditional JSON-based VMess share links and typical subscriptions containing lists of share links. Import full native JSON through the custom configuration option; support for VMess does not mean support for every JSON subscription structure. Some configurations contain core-specific fields, so check both the client version and the selected core.

v2rayNG uses the Xray core; v2flyNG uses the v2fly core. They are not the same configuration interpreter with different interfaces. Before loading an Xray-specific configuration in v2flyNG, check protocol and field support instead of simply renaming the file. In v2rayN, confirm which core will run the configuration.

Decode locally: inspect the list, then the vmess:// payload

The JavaScript below is for inspecting text in your local browser’s developer tools; it makes no network requests. Read the code first, then run it in the console of a blank offline page. The functions take text you’ve already copied locally and do not download subscriptions. Don’t handle credentials in the console of an unfamiliar website.

function decodeBase64Utf8(input) {
  let text = input.trim().replace(/\s+/g, "");
  text = text.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(text) {
  const bytes = new TextEncoder().encode(text);
  let binary = "";
  for (const byte of bytes) binary += String.fromCharCode(byte);
  return btoa(binary);
}

function decodeVmessJson(uri) {
  const text = uri.trim();
  if (!text.startsWith("vmess://")) {
    throw new Error("Not a VMess share link");
  }
  return JSON.parse(decodeBase64Utf8(text.slice(8)));
}

// Self-test uses local examples only; no subscription credentials included.
console.log(decodeBase64Utf8(encodeBase64Utf8("Sample node")));
  1. Identify the response: If the body already contains lines beginning with protocol prefixes such as vmess://, process them as-is; don’t decode the whole response again. If it starts with HTML or a login prompt, resolve the subscription request issue first.
  2. Decode the outer layer: For a confirmed Base64 list, call decodeBase64Utf8, then split it into lines with split(/\r?\n/) and filter out empty lines.
  3. Decode an individual link: Pass a traditional JSON-based VMess link to decodeVmessJson. The expected result is an object with fields such as add, port, and id.
  4. Don’t keep retrying blindly: If decoding doesn’t produce JSON, check for a different share-link format. Not every legacy implementation using the same protocol prefix follows the JSON payload convention described here.

atob returns a string representing bytes; it does not decode UTF-8 for you. Without TextDecoder, Chinese node names may appear garbled. Calling btoa directly on Chinese text may throw an error. Also distinguish Base64URL’s - and _ from standard Base64’s + and /; don’t apply these substitutions to the subscription URL itself.

4 characters
Each Base64 group represents up to 3 bytes
443
Example server-side TLS port used below
10808
Example local SOCKS inbound port used below

How fields map between vmess:// and native JSON

The example below is a traditional VMess share payload for demonstration only. The domain and UUID illustrate the structure and do not represent a connectable service. v is the share-format version, not the core version. aid is alterId; modern VMess AEAD configurations typically use 0, but check the server’s requirements when migrating.

{
  "v": "2",
  "ps": "Sample VMess WS TLS",
  "add": "node.example.com",
  "port": "443",
  "id": "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
  "aid": "0",
  "scy": "auto",
  "net": "ws",
  "type": "none",
  "host": "node.example.com",
  "path": "/gateway",
  "tls": "tls",
  "sni": "node.example.com"
}
Share-link fieldCommon location in core configurationConversion notes
add / portsettings.vnext[].address / portThe port in a share link is often a string; core configuration uses a number.
id / aid / scyusers[].id / alterId / securityThese represent the UUID, alterId, and VMess encryption method, respectively.
netstreamSettings.networkThis example uses ws; changing the label alone won’t convert it to another transport.
host / pathwsSettings.headers.Host / pathThese are WebSocket request parameters; they are not necessarily the server address.
tls / snistreamSettings.security / tlsSettings.serverNameTLS and VMess encryption are separate layers of configuration.
psClient remark or custom outbound tagA remark is not a connection parameter and does not automatically create a routing rule.

The full example below uses the common vnext structure and includes one local SOCKS inbound and one VMess outbound. Whether the client retains inbound settings after import depends on how it runs custom configurations. Before use, check that the current core supports this field structure.

{
  "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": "vmess",
      "settings": {
        "vnext": [
          {
            "address": "node.example.com",
            "port": 443,
            "users": [
              {
                "id": "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
                "alterId": 0,
                "security": "auto"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "ws",
        "security": "tls",
        "tlsSettings": {
          "serverName": "node.example.com",
          "allowInsecure": false
        },
        "wsSettings": {
          "path": "/gateway",
          "headers": {
            "Host": "node.example.com"
          }
        }
      }
    }
  ]
}

Re-encoding and converting back: what can be lost

To create a share link from a traditional JSON-based VMess payload, encode the JSON as UTF-8 and prepend the protocol prefix. Join multiple links with newlines and Base64-encode the result to create a common Base64 list body. The code below continues from the encoding functions above and still uses demonstration data.

const sample = {
  v: "2",
  ps: "Sample VMess WS TLS",
  add: "node.example.com",
  port: "443",
  id: "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
  aid: "0",
  scy: "auto",
  net: "ws",
  type: "none",
  host: "node.example.com",
  path: "/gateway",
  tls: "tls",
  sni: "node.example.com"
};

const share = "vmess://" + encodeBase64Utf8(JSON.stringify(sample));
const links = [share];
const subscriptionBody = encodeBase64Utf8(links.join("\n"));

console.log(share);
console.log(subscriptionBody);

Generating a list body does not create a subscription service. It becomes a refreshable remote entry point only when hosted at an address the client can access, with appropriate access controls. If you’re only moving nodes between devices, use the share links exported by the client; there’s no need to host a file.

Troubleshooting failed imports, empty node lists, and garbled text

The subscription updated, so why is the node count still zero?

First, make sure you’re viewing the correct group, then check the update log for parsing messages. Inspect the response body locally: if it’s a login page, an expired-token notice, or an unsupported configuration structure, decoding it as Base64 again won’t help. Ask the provider for a share-link list compatible with v2rayN and v2rayNG.

Decoding failed. Could part of the text be missing?

Check whether a chat app inserted an ellipsis, whether the text includes stray quotes, and whether its length leaves a remainder of 1 when divided by 4. Missing trailing padding can be normalized, but padding can’t restore a truncated body. Don’t pass the full HTTPS subscription URL to a decoding function.

The node name is garbled. Can the connection settings still work?

First, decode the text as UTF-8 again and check ps in the payload. If only the remark is garbled, the connection usually isn’t affected. If the path or other fields also contain non-ASCII characters, verify each one. Keep the original text; don’t repeatedly re-encode garbled output.

The import worked, but the connection times out. Should I convert the format again?

First, verify the server address and confirm that port 443 is the value provided by the service. Then check the UUID, WebSocket path, Host, and SNI. If the log reports that port 10808 is in use, resolve the local inbound conflict. If the TLS name is wrong, correct the server name instead of repeatedly encoding the same content.

Why did my manual node edits revert after updating the subscription?

Subscription nodes are usually regenerated from remote content. To keep local changes, first export or copy the node into a separate configuration, then edit its fields. Exact behavior depends on the client version. Updating a subscription refreshes its source data; it does not sync local edits back to the provider.

For a reliable migration, keep three things to compare: the original subscription body, the decoded node fields, and the imported client settings. Test one node first, then move on to the full list. For DNS and routing in a full JSON configuration, check each field alongside Advanced Setup. If you’re unsure what a field means, consult the glossary before trying multiple converters—it’s easier to pinpoint differences that way.

Download clientsv2rayN / v2rayNG