A subscription URL is an entry point; base64 is an encoding; a share link describes one server; and native JSON may contain an entire runtime configuration. This guide is for anyone who sees “update succeeded, but no servers appeared,” can’t import JSON, or needs to switch clients. First identify the response, then choose the right import method, and finally determine which fields can be converted and which settings must be preserved separately.
1. Distinguish the subscription URL, its response, and the node protocol
A subscription link is typically a URL that retrieves content over HTTPS. The client requests it, receives a server list or configuration text, and attempts to parse the response. The URL may contain an access token; it is not itself a VMess or VLESS protocol, and its suffix alone won’t tell you the response format. The same endpoint may also return different content depending on request parameters or the client identifier.
A typical server list consists of share links, such as lines beginning with vmess:// or vless://. Multiple links can be returned as plain text, one per line, or encoded together as base64. Another possible response is JSON—but being valid JSON only means a JSON parser can read it. It doesn’t tell the client whether to treat it as a server list or a complete configuration.
| Format | How to recognize it | What it usually contains | Use this import method |
|---|---|---|---|
| Plain-text share links | Each line starts with a protocol prefix | Connection details for multiple servers | Share-link or clipboard import |
| Base64 list | Decodes to multiple lines of share links | The same content as a plain-text list | Subscription update that supports this format |
| Native config JSON | Common top-level keys include inbounds and outbounds | Settings for inbounds, outbounds, DNS, routing, and more | Custom-config or config-file import |
2. Base64 changes the representation, not the protocol capabilities
Base64 represents bytes as text that’s easier to transfer; it does not provide confidentiality. A note containing Chinese characters must first be converted to UTF-8 bytes before base64 encoding. To reverse the process, decode the bytes and then interpret them as UTF-8. Calling the browser’s btoa() directly on a string containing Chinese characters may throw an error or produce incorrect results.
Standard base64 commonly uses + and /, and may end with =. The URL-safe variant replaces the first two characters with - and _, and may omit padding. Some clients support these variations, but don’t assume every importer will fix spaces, truncation, or extra quotation marks automatically.
- Decode the outer layer: Decode the entire subscription response. The result is usually a list of share links, one per line.
- Decode the inner layer: Classic VMess share links contain another base64 string after
vmess://. Decode it to get the JSON for a single node. - Know when to stop: Once you see a complete URI list or clearly structured JSON, identify the content before trying to decode it again.
- Signs of a failed request: If the response starts with an HTML login page, an error message, or a CAPTCHA, troubleshoot subscription access before attempting conversion.
Bottom line: decode once, then choose how to import
If decoding produces multiple share links, use the link-list import option. If it produces an object with inbounds, outbounds, and routing, check for a custom-config import option. The number of encoding layers says nothing about protocol compatibility.
3. VMess share JSON and native configs use different structures
A classic VMess share link typically uses the format “vmess:// followed by base64-encoded JSON.” The example below illustrates a share-link object; it is not a working server and cannot be used directly as a core runtime configuration. The domain is reserved for examples, and port 443 is shown only to illustrate a TLS setup.
{
"v": "2",
"ps": "Format example",
"add": "example.invalid",
"port": "443",
"id": "123e4567-e89b-42d3-a456-426614174000",
"aid": "0",
"scy": "auto",
"net": "ws",
"type": "none",
"host": "example.invalid",
"path": "/edge",
"tls": "tls",
"sni": "example.invalid"
}
Here, v is a share-format marker, not a client version; ps is a remark; and add, port, and id refer to the address, port, and user identifier. net describes the transport. In this example, host and path are used for WebSocket, while sni specifies the TLS server name. These fields serve different purposes; matching values don’t make them interchangeable.
| Share-link field | Typical location in a native config | Conversion notes |
|---|---|---|
add、port | Address and port in outbounds[].settings.vnext[] | Native configs usually expect the port to be a number, not a copied string |
id、scy | id and security in the VMess outbound user entry | Field names don’t match exactly; map them according to the core’s config schema |
net、path | streamSettings and the corresponding transport settings | A field only applies when the transport type matches |
tls、sni | streamSettings.security and TLS settings | Protocol encryption parameters and TLS settings belong to different layers |
A native JSON config may define multiple outbounds and use routing rules to select one. Exporting a single node usually won’t include DNS, routing rules, inbound listen addresses, or outbound chains. So converting native JSON to a share link usually means extracting connection details—not reversibly compressing the entire config.
4. Convert locally: handle the text first, then the config semantics
Converting a share-link list to base64, or decoding base64 back into a list, is a text-encoding operation you can do locally. The JavaScript below only defines text-processing functions; it makes no network requests. Run it in the developer tools on a blank page you created yourself. Don’t paste real subscription content into an unfamiliar website’s console or run conversion scripts from unknown sources.
function encodeUtf8Base64(text) {
const bytes = new TextEncoder().encode(text);
const binary = Array.from(bytes, byte =>
String.fromCharCode(byte)
).join("");
return btoa(binary);
}
function decodeUtf8Base64(input) {
let text = input.trim()
.replace(/\s+/g, "")
.replace(/-/g, "+")
.replace(/_/g, "/");
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(text)) {
throw new Error("Content is not valid base64 text");
}
if (text.length % 4 === 1) {
throw new Error("Unexpected length; check that the content was copied in full");
}
text += "=".repeat((4 - text.length % 4) % 4);
const binary = atob(text);
const bytes = Uint8Array.from(binary, char =>
char.charCodeAt(0)
);
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
function packShareList(links) {
return encodeUtf8Base64(links.join("\n"));
}
function makeClassicVmessLink(shareObject) {
return "vmess://" + encodeUtf8Base64(JSON.stringify(shareObject));
}
// Test only a UTF-8 round trip; do not use real subscription credentials.
const sample = "Sample note\nSecond line";
console.assert(
decodeUtf8Base64(encodeUtf8Base64(sample)) === sample
);
- Decode the list: Pass the response text to
decodeUtf8Base64(), then check that the output is actually a list of share links, one per line. This function won’t identify protocols or repair damaged content. - Encode the list: Put each complete URI in an array and pass it to
packShareList(). Newlines separate entries; don’t concatenate multiple URIs without a separator. - Build a VMess link: Use
makeClassicVmessLink()only when the input object already matches the classic VMess share-link fields. It can’t automatically turn an arbitrary native config into a valid node. - Verify the result: Import it into the client as a test entry. Check the address, port, UUID, transport, TLS, and server name one by one before testing the connection.
Converting a native config to a share link requires a semantic converter: choose which outbound to export, identify the protocol and transport, map the fields, and check whether the target share format can represent every parameter. If a field can’t be represented, stop or document what will be omitted. Don’t silently drop settings and claim the conversion succeeded.
5. Choose the right import method in three clients
Start with what you need: use a subscription for a server list that needs ongoing updates, a share link to move one node temporarily, or native JSON when you need full control over DNS, routing, and outbounds. The client interface handles importing and management; the core runs the protocols. Check compatibility for each separately.
Subscription import
RecommendedSave the subscription URL and let the client fetch the server list manually or on a schedule. Updates are convenient, but the server’s response must be a format the current importer can recognize.
Best for: Server lists that change regularly and need ongoing management.
Share-link import
Import one or more URIs from the clipboard to inspect their parameters. This usually won’t preserve the link to the original subscription for future updates.
Best for: Temporary testing, moving a single node, and troubleshooting format issues.
Custom-config import
Load native JSON using the client’s custom-config feature. The config must match the core in use; check separately how routing and DNS are handled.
Best for: Users with a complete config who need to keep advanced outbounds and routing rules.
v2rayN is for desktop; v2rayNG is an Android client that uses the Xray core; and v2flyNG is an Android client that uses the v2fly core. VMess share links are a common way to exchange nodes, but support for specific transports and extended parameters depends on the version. In particular, don’t assume v2fly can run extended configs designed for Xray.
-
Keep the original config
Keep your working entries and the original response before making changes. Troubleshoot in a separate subscription group; don’t clear all your servers first.
-
Add a subscription on desktop
In v2rayN, the usual path is “Subscription Group” → “Subscription Group Settings” → “Add.” Enter a remark and URL, save, then update from the “Subscription Group” menu. Labels may vary slightly between platforms and versions.
-
Add a subscription on Android
In v2rayNG, the usual path is the top-right menu “⋮” → “Subscription Group Settings” → “+”. After saving, return to the main screen and select “⋮” → “Update Subscription” to fetch the entries. Don’t enter the subscription URL as a server address.
-
Test a single link
In v2rayN, select clipboard import from the “Servers” menu. In v2rayNG, the usual path is “+” on the main screen → “Import from clipboard.” Test the same share link in both to distinguish a subscription-fetch problem from a node-parsing problem.
-
Check the core
In v2flyNG, use its subscription settings or clipboard-import option, and check whether v2fly supports the parameters in question. Import native JSON through the custom-config option provided by your version; don’t paste it into the subscription URL field.
6. Troubleshoot by stage: fetch, parse, then connect
An “update complete” message only means the update process reached its end; it doesn’t prove that every entry was parsed successfully. Likewise, seeing an entry in the list doesn’t mean its transport and TLS parameters are correct. Pinpointing the stage where the error occurred is more useful than repeatedly changing the encoding.
| What you see | Check first | What to do next |
|---|---|---|
| Subscription request fails | Is the URL complete? Is the token still valid? Did the server return 401 or 403? | Check your authorization with the service provider; don’t try to bypass access controls by re-encoding the content |
| HTTP 200, but no entries | Is the response a login page, an error JSON object, or an unsupported format? | Identify the response body, then use the appropriate import method |
| Base64 decoding fails | Was the text truncated or wrapped in quotes? Does the client support this base64 variant? | Copy the original text again and check its length and the actual response content |
| JSON parses but won’t import | Are you confusing a share object, a config file, and an API error object? | Check the top-level keys and the structure expected by the client |
| Entry appears, but the connection fails | Port, UUID, transport, TLS, SNI, and path | Compare each field with the original config, then check network reachability |
Can I base64-encode a whole native JSON config to make a universal subscription?
No. Encoding doesn’t change the data structure. After decoding, it’s still a native config. If a client only accepts a list of share links, it still won’t be able to parse that config as a list. Use an import method that supports the config, or selectively extract node parameters that the target format can represent.
Why did my routing rules disappear after importing a share link?
A share link usually describes one node and doesn’t include a complete routing setup. The client’s own routing settings may also be managed separately from rules in a native config. Before migrating, save DNS, routing, inbounds, and any other outbounds separately.
Why were my manual changes reverted after updating the subscription?
Subscription entries usually follow the remote version, so an update may recreate or overwrite local entries. Save changes you need to keep in a separate config, and remember they won’t automatically track changes to the original subscription.
Bottom line: preserve every field before worrying about conversion
Use a subscription format explicitly supported by the service provider whenever possible. If you need to migrate, test the conversion with one node first and save routing and DNS separately. Process the rest only after confirming the test works.