It breaks base64 subscriptions, native JSON configs and vmess/vless share links down side by side, lays out the full conversion path from subscription to link list to single-node fields to native JSON, and lists the fields most often lost along the way. Written for readers who can already connect to a node and now need to edit configs by hand or move between clients.
What each of the three formats is
The same node details look completely different depending on the container they travel in. A base64 subscription is a bulk list, native JSON is a full config, and a share link is a single record.
The three do not carry equal amounts of information: one link inside a subscription usually describes a single outbound, while native JSON also has to cover client-side settings such as inbounds, routing, DNS and logging. Mixing this up is where every conversion problem starts.
| Aspect | base64 subscription | Native JSON | Share link |
|---|---|---|---|
| What it carries | Multiple share links, one per line | Full config with outbounds, inbounds and routing | Connection parameters for one node |
| Auto update | Client fetches on a schedule | Replace the file by hand | Not supported |
| Typical source | Bulk export from a panel | Server config file or written by hand | Copied one at a time from a client or panel |
| Best for | Sharing one node list across devices | Fine-grained routing and fixed local ports | Quick imports and moving between clients |
Encoding rules for base64 subscriptions
A base64 subscription adds exactly one layer of encoding: the server joins multiple share links into plain text, base64-encodes the whole block once, and returns it as an HTTP response. The client fetches it, decodes it first, and gets back a list with one link per line.
When decoding fails, clients fall back to treating the response as plain text, so the same parsing logic handles both base64 and plain-text subscriptions. If you want to decode it yourself, one command is all you need.
# save the raw subscription response as sub.txt, then decode the whole file once
base64 -d sub.txt > nodes.txt # GNU coreutils
base64 -D sub.txt > nodes.txt # macOS / BSD
wc -l nodes.txt # the line count usually equals the number of nodes
head -n 2 nodes.txt
In the decoded output, each line starts with a protocol prefix, most often vmess:// or vless://. It is normal for one subscription to mix several prefixes; clients parse it line by line.
- URL-safe variants: some panels swap
+and/for-and_. Convert them back before decoding, or use a decoder that understands URL-safe base64. - Padding: the trailing
=characters are often stripped. If the length is not a multiple of 4, add them back before decoding. - Per line vs. whole block: a few subscriptions encode each link separately. Those must be decoded line by line — decoding the whole block at once only produces gibberish.
If the first decoded line is neither vmess:// nor vless://, the source was already a plain-text subscription and needs no further decoding.
Field structure of a share link
The difference between vmess and vless comes down to encoding: a vmess link is base64 wrapping a JSON object, while a vless link is a URI with query parameters. The first decodes into abbreviated field names; the second can be read straight from the address bar.
Decode the entire base64 block after vmess:// once and you get this object:
{
"v": "2", // link format version, always 2
"ps": "Hong Kong-01", // remark, used as the node name after import
"add": "example.com", // server address
"port": "443", // port, a string here
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"aid": "0", // alterId, VMess only
"scy": "auto", // encryption method
"net": "ws", // transport
"type": "none", // header type
"host": "example.com", // Host header for WS
"path": "/ws", // WS path
"tls": "tls" // whether TLS is enabled
}
A vless link has no inner base64 layer: every parameter lives in the query string after ?, and the remark sits after #. The parameter names are more readable than the vmess abbreviations, but they still have to be URL-encoded per URI rules.
vless://[email protected]:443?encryption=none&security=reality&sni=www.example.com&fp=chrome&pbk=UuMBgl8KtNqHqY7p&sid=0123abcd&flow=xtls-rprx-vision&type=tcp#Hong Kong-01
Both link types describe the same thing; only the field names and their placement differ. The four cards below line up the common fields, the top-level structure of native JSON, and the local port conventions.
vmess link fields
- add / port
- Address and port; port is a string
- id / aid
- UUID and alterId
- scy
- Encryption method, usually auto or none
- net / type
- Transport and header type
- path / host
- WS path and Host header
The whole string is base64 (JSON); one decode makes it readable.
vless link parameters
- encryption
- Always none; cannot be omitted
- security
- none / tls / reality
- sni
- TLS certificate domain name
- flow
- Use xtls-rprx-vision for Reality nodes
- pbk / sid
- Reality public key and short ID
Parameters live in the query string; watch the URL encoding.
Native JSON top level
- inbounds
- Local listeners, e.g. SOCKS 10808
- outbounds
- Outbound nodes, with tag and streamSettings
- routing
- Routing rules that point at outbounds via outboundTag
- dns
- Resolution mode and upstream servers
- log
- Log level; use debug when troubleshooting
Field names are case-sensitive, and port must be a number.
Local port conventions
- SOCKS
- 10808
- HTTP
- 10809
- Log level
- warning / debug
- Outbound tag
- The three common names: proxy, direct, block
Ports and tags are decided by the local config, not by the subscription.
How to convert between the three formats
The conversion path always has four steps: decode the subscription into a link list, restore each link into fields, then assemble those fields into native JSON. Walk it backwards and you have an export.
Fetch the raw subscription
Open the subscription URL in a browser and save the whole response as sub.txt. What comes back may be one base64 block or a plain-text list of links.
Decode the link list
Run
base64 -d sub.txt > nodes.txtto get one share link per line; the protocol prefix sits at the start of the line and the remark after the trailing#.Restore a single node
For vmess, base64-decode the content after
vmess://once more to get JSON; for vless, read the query parameters after?directly — no further decoding needed.Assemble native JSON
Fill the fields into
vnextandstreamSettingsunderoutbounds: turnportinto a number, pick your owntag, and keepaddressfree of any protocol prefix.
The outbound you assemble in step four looks roughly like this, with fields matching the vless link above one for one:
{
"outbounds": [{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "example.com",
"port": 443,
"users": [{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"encryption": "none",
"flow": "xtls-rprx-vision"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"serverName": "www.example.com",
"publicKey": "UuMBgl8KtNqHqY7p",
"shortId": "0123abcd",
"fingerprint": "chrome"
}
}
}] // outbound array
}
The reverse works the same way: settings.vnext[0] and streamSettings in native JSON are just the link parameters spelled out. Pull out address, port and id, then encode them back into a URI following the protocol rules. For VMess, write alterId back into aid as well.
Clients offer two shortcuts. v2rayN supports a "Custom configuration" server type: paste the full JSON into the config box and the core reads it directly instead of the client assembling an outbound. In the other direction, copy a single node's share link from the subscription list and paste it into v2rayNG on another device via "⋮" → "Import config from clipboard".
Fields most often lost in conversion
Dropped fields almost always happen during manual copying: a paste that gets truncated, a string where a number belongs, an abbreviation missing one letter. Check the spots below one by one and you will cover most cases of "import succeeded but nothing connects".
flow=xtls-rprx-vision: required for Reality nodes. Leave it out and the client treats the node as plain VLESS, so the handshake fails on the server side.pbkandsid: the Reality public key and short ID come as a pair; copying only one is as good as copying neither.encryption=none: a fixed value for VLESS. Omit it and some clients refuse to import the link.aid: the VMess alterId. It is a string in the link and a number in native JSON; older servers often use 2 or 64, and leaving it out makes the client assume 0, which fails the handshake.porttype: it is"443"in the link JSON but must be443in native JSON — quotes make the core reject the config as invalid.serviceName: used instead ofpathwhentype=grpc; swapping the two leaves the request path mismatched.hostandsni: the first is the WS Host header, the second is the TLS SNI. Behind a CDN it is normal for the two to differ; swap them and you get a 403.outboundTag: the value inrouting.rulesmust match thetaginoutboundscharacter for character, case included.
Note
When editing native JSON by hand, run the editor's JSON syntax check before saving; trailing commas and missing quotes are the two most common errors. While troubleshooting, set log.loglevel to debug so the core writes the offending field to the log, then switch it back to warning once you have found it.
Which format fits which scenario
None of the three formats is better than the others; they simply divide the work. Two questions decide it: will the node list change, and do you need routing rules on this machine?
How to choose: whether nodes change, and whether local routing is needed
base64 subscription
- One URL manages every node
- Enter it once per device and the list stays in sync
- Nodes are added or removed server-side, with no local edits
- Best when you use several devices and nodes change
Native JSON
- Lets you write routing rules and DNS policies
- Fixed local ports: SOCKS 10808, HTTP 10809
- Node changes mean replacing the file by hand
- Best on a single machine that needs fine-grained control
The two can coexist: the subscription handles the node list, while routing in native JSON decides which traffic goes through the proxy.
Share links sit in the middle: better than a subscription for one-off imports, better than native JSON for copying between clients. Paste the same link into v2rayN and v2rayNG and you get identical outbound parameters; the only difference is that each client keeps local ports and routing rules in its own settings.
In practice the more common setup is a subscription URL maintained on the server plus a local routing ruleset in the client. Nodes follow the subscription updates, routing logic stays local, and neither side interferes with the other.
FAQ
The subscription URL opens as gibberish in a browser?
That is the raw base64, not an error. Copy the whole block and decode it once, or just paste the URL into the client's subscription settings and update.
Node name turns into question marks after importing a vmess link?
The ps field is UTF-8 Chinese text, so a decoder that treats it as GBK produces mojibake. Decode it again as UTF-8 before importing.
Importing a vless link reports a missing flow?
Reality nodes need xtls-rprx-vision in flow, along with all three of pbk, sid and fp.
A hand-written JSON config is rejected as invalid?
Check for trailing commas and stray quotes first, then change port from a string to a number, and finally verify that routing.rules[].outboundTag matches the outbound tag.
The relationship between the three formats is not complicated: decode a subscription once to get links, decode a vmess link once more to get fields, and those fields spelled out become native JSON. The slow part is never the decoding — it is moving every field to the right place without losing one.
Download v2rayN / v2rayNG
Download links for the Windows, macOS and Linux desktop builds and the Android build are on the download page; subscription import and routing setup are covered in the tutorials.