Xray-core Configuration Format: How to Convert Between JSON, Protobuf, and TOML

Xray-core supports JSON, YAML, TOML, and protobuf configuration formats, with built-in convert commands to transform between them losslessly via the internal protobuf representation.

The Xray-core proxy framework accepts multiple human-readable configuration formats, all of which resolve to the same internal protobuf structures. This guide explains the configuration architecture, demonstrates format conversion using the official CLI tools, and provides working examples based on the XTLS/Xray-core source code.

How Xray-core Handles Configuration Formats

Xray-core defines a ConfigFormat struct in core/config.go that pairs file extensions with loader functions. The registration system allows each format to implement its own parsing logic while converging on identical protobuf output.

The ConfigFormat Architecture

// core/config.go – ConfigFormat definition
type ConfigFormat struct {
    Name      string
    Extension []string
    Loader    ConfigLoader
}

The RegisterConfigLoader function in the same file populates a global map (configLoaderByExt) that GetFormatByExtension queries at runtime:

// core/config.go – extension to format resolution
func GetFormatByExtension(ext string) string {
    switch strings.ToLower(ext) {
    case "pb", "protobuf": return "protobuf"
    case "yaml", "yml":    return "yaml"
    case "toml":           return "toml"
    case "json", "jsonc":  return "json"
    default:               return ""
    }
}

Format-Specific Loader Implementations

Format Extensions Source File Implementation
JSON / JSONC json, jsonc main/main/json/json.go Uses jsonpb for protobuf conversion
YAML yaml, yml main/main/yaml/yaml.go Parses to map, then to protobuf
TOML toml main/main/toml/toml.go Uses github.com/pelletier/go-toml
Protobuf pb, protobuf Internal Binary serialized format

The TOML loader in main/main/toml/toml.go specifically uses the third-party go-toml library to transform TOML structures into intermediate maps before protobuf conversion, as seen in infra/conf/serial/loader.go.

Converting Between Xray-core Configuration Formats

Xray-core includes a convert command suite in main/main/commands/all/convert/ for bidirectional transformation. All conversions pass through the internal protobuf representation, ensuring no data loss.

JSON to Protobuf Conversion

The convert pb command merges multiple JSON files and outputs a binary protobuf file:


# Merge two JSON fragments into a single protobuf file

xray convert pb -outpbfile config.pb config1.json config2.json

This command delegates to ConfigMergedFormFiles in core/config.go, which loads each JSON file through its registered loader, merges the resulting protobuf structures, and serializes the final config to binary format.

Example input files:

// config1.json
{
  "log": { "level": "info" }
}
// config2.json
{
  "policy": { "system": "auto" }
}

Protobuf to JSON Conversion

The convert json command reverses the process, reading a binary protobuf file and producing human-readable JSON:


# Convert protobuf back to JSON

xray convert json -type Config config.pb

The -type flag specifies the root protobuf message type (typically Config). The source in main/main/commands/all/convert/json.go uses jsonpb marshaling to produce pretty-printed output suitable for editing or version control.

TOML Configuration Direct Usage

Xray-core can load TOML directly without explicit conversion:


# myconfig.toml

[log]
  level = "warning"

[policy]
  system = "auto"

# Run Xray with TOML config

xray run -c myconfig.toml

The run command calls GetFormat("myconfig.toml"), resolves the toml format, and invokes the loader from main/main/toml/toml.go to transform the TOML into the internal protobuf Config object.

Why Protobuf Is the Canonical Format

All Xray configuration objects originate from protobuf definitions in *.proto files distributed throughout the repository. For example, VMess outbound configuration uses proxy/vmess/outbound/config.proto.

This design makes the conversion pipeline inherently consistent:


JSON/YAML/TOML → loader → internal protobuf Config → (optional) serialize → .pb file

The convert commands simply expose this internal pipeline as CLI operations. Because all formats resolve to the same protobuf structs, converting between them is lossless and idempotent.

Summary

  • Xray-core configuration supports JSON, JSONC, YAML, TOML, and binary protobuf formats through a unified ConfigFormat registration system in core/config.go
  • Format loaders in main/main/json/json.go, main/main/yaml/yaml.go, and main/main/toml/toml.go parse human-readable formats into internal protobuf structures
  • Conversion commands xray convert pb and xray convert json in main/main/commands/all/convert/ provide bidirectional transformation with no data loss
  • Protobuf is the canonical representation — all formats derive from .proto schema definitions, making conversions reliable and consistent

Frequently Asked Questions

Does Xray-core support TOML configuration natively?

Yes. Xray-core registers a TOML loader in main/main/toml/toml.go that uses the github.com/pelletier/go-toml library. You can start Xray with xray run -c config.toml without any manual conversion. The loader transforms TOML into the same internal protobuf representation used by JSON and YAML.

How do I convert multiple JSON configuration files into a single binary file?

Use the convert pb command with multiple input files: xray convert pb -outpbfile merged.pb config1.json config2.json. The command loads each file through its registered format loader, merges the resulting protobuf structures, and outputs a single serialized binary file. This is useful for distributing compact, tamper-evident configurations.

Why would I use protobuf format instead of JSON?

The protobuf format (*.pb) provides smaller file sizes, faster parsing, and deterministic serialization. Since all Xray configurations ultimately resolve to protobuf according to the schema definitions in proxy/*/config.proto and related files, the binary format eliminates any ambiguity from textual parsing. Use xray convert json -type Config file.pb to recover human-readable JSON when editing is needed.

Where are the configuration format loaders registered at runtime?

Format loaders register during program initialization through calls to core.RegisterConfigLoader in each format's package. The JSON loader registers in main/main/json/json.go, YAML in main/main/yaml/yaml.go, and TOML in main/main/toml/toml.go. The registration system lives in core/config.go, which maintains the configLoaderByExt map used by GetFormatByExtension to resolve file paths to appropriate parsers.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →