xlsxcfg is a Go CLI that converts Excel (.xlsx) sheets into Protocol Buffer-defined config data (JSON, msgpack, protobuf binary). It parses .proto files at runtime — no protoc-gen step needed.
Protocol Buffer is optional. Without proto, xlsxcfg outputs raw JSON and msgpack from the parsed xlsx data.
-
Download from releases.
Recommended to add it to your
PATHenv. -
Build from source (requires Go 1.26+):
go install github.com/das6ng/xlsxcfg/bin/xlsxcfg@latest
Usage:
xlsxcfg [flags] [xlsx files...]
Flags:
-c, --config string config file (default "xlsxcfg.yaml")
--example-config print example config to stdout
-h, --help help for xlsxcfg
Any unrecognized --key=value flags are deep-merged into the config (see Dynamic flags).
Dir structure:
./
├── proto/
│ └── example.proto
├── example.xlsx
└── xlsxcfg.yaml
-
With explicit config:
xlsxcfg -c xlsxcfg.yaml example.xlsx -
Config file defaults to
xlsxcfg.yamlin the current directory:xlsxcfg example.xlsx -
Multiple xlsx files at once:
xlsxcfg -c xlsxcfg.yaml example.xlsx example1.xlsx example2.xlsx -
Without proto — raw JSON output only:
xlsxcfg --proto.enabled=false example.xlsx
Print an example config with all options and comments:
xlsxcfg --example-config
Full config reference:
# Protocol Buffer source config
proto:
# Set true to enable proto-based output formats.
enabled: true
# Proto files to parse at runtime.
files: ["example.proto"]
# Proto import paths.
import_path: ["."]
# xlsx sheet config
sheet:
# Row indices (1-based) to skip as comments.
comment_rows: [1]
# Row index (1-based) containing column metadata.
meta_row: 2
# First row index (1-based) of config data.
data_row_start: 3
# Suffix appended to sheet name to find the
# wrapper proto message.
type_suffix: "Sheet"
# Field name for the row list inside the
# wrapper message.
list_field_name: "List"
# Suffix appended to sheet name to find the
# per-row proto message.
row_type_suffix: "SheetRow"
# Optional prefix or suffix that marks a sheet for
# transposed parsing (column-per-record).
# e.g. "~" means sheet "Hero~" or "~Hero" is parsed
# column-wise. The mark is stripped before
# proto type lookup and output file naming.
transpose_mark: ""
# Optional constant value replacement.
# Cell values matching ref_quote delimiters (e.g. [Key])
# are replaced with the corresponding constant value.
constant:
enabled: false
# Number of leading rows to skip in constant sheets.
skip_rows: 1
# Prefix that marks a key as a comment line.
comment: "#"
# Delimiters wrapping reference keys in cell values.
ref_quote:
l: "["
r: "]"
# xlsx files containing key-value constant sheets.
files: []
# Output file config
output:
dir: "."
# Field order for proto-validated output:
# "schema" (default) — order by proto field number
# "source" — order by xlsx source column order
field_order: "schema"
# Raw JSON (no proto required)
raw_json:
enabled: true
dir: ""
extension: ".json"
indent: " "
# Raw msgpack (no proto required)
raw_msgpack:
enabled: false
dir: ""
extension: ".msgpack"
# Proto JSON (requires proto)
json:
enabled: false
dir: ""
extension: ".json"
indent: " "
# Proto msgpack (requires proto)
msgpack:
enabled: false
dir: ""
extension: ".msgpack"
# Protobuf binary (requires proto)
pb_bytes:
enabled: false
dir: ""
extension: ".bytes"| Format | Key | Proto required | Extension | Description |
|---|---|---|---|---|
| Raw JSON | raw_json |
No | .json |
OrderedMap → JSON (source column order) |
| Raw Msgpack | raw_msgpack |
No | .msgpack |
OrderedMap → msgpack (source column order) |
| Proto JSON | json |
Yes | .json |
dynamicpb → protojson (field order per field_order) |
| Proto Msgpack | msgpack |
Yes | .msgpack |
proto → JSON → msgpack (field order per field_order) |
| Protobuf Binary | pb_bytes |
Yes | .bytes |
proto.Marshal binary (always field number order) |
All output is deterministic — running the same input twice produces byte-identical output.
| Mode | Field order |
|---|---|
| Schema-less (raw JSON, raw msgpack) | xlsx source column order |
Proto JSON/msgpack with field_order: "schema" (default) |
proto field number order |
Proto JSON/msgpack with field_order: "source" |
xlsx source column order |
| Protobuf binary | always proto field number order (wire format) |
Any --key=value flag not recognized by the CLI is deep-merged into the config via YAML round-trip:
# Override nested fields with dot-separated paths
xlsxcfg example.xlsx --output.raw_json.enabled=false
xlsxcfg example.xlsx --output.pb_bytes.enabled=true
# Override arrays
xlsxcfg example.xlsx --sheet.comment_rows=[1,2]
# Enable value replacement via flags
xlsxcfg example.xlsx --constant.enabled=true --constant.files=[constants.xlsx]Proto definition:
message PhoneNumber {
int64 Region = 1;
int64 No = 2;
string Ext = 3;
}
message MemberSheetRow {
int32 ID = 1;
string Name = 2;
string Address = 3;
PhoneNumber Phone = 4;
repeated string Cities = 5;
repeated PhoneNumber PP = 6;
}
message MemberSheet {
repeated MemberSheetRow List = 1;
}Excel sheet:
Enum fields support both integer values and name resolution in cells:
enum Status {
UNKNOWN = 0;
ACTIVE = 1;
INACTIVE = 2;
}
message HeroSheetRow {
int32 ID = 1;
string Name = 2;
Status Status = 3;
}Cells can contain 1 or ACTIVE — both resolve to the same enum value.
Set transpose_mark (e.g. "~") to parse sheets column-wise — each column becomes one record instead of each row. The mark can be a prefix or suffix of the sheet name. Useful for data laid out horizontally in xlsx.
Sheets Hero~ or ~Hero with transpose_mark: "~":
- Column indices follow the same
comment_rows/meta_row/data_row_startrules as row indices in normal mode. - The mark is stripped for proto type lookup (
HeroSheetRow) and output file naming (Hero.json).
Enable the constant section to substitute cell values at parse time. Constant xlsx files contain key-value pairs (two columns: key, value). Cell values wrapped in ref_quote delimiters (e.g. [HeroHP]) are replaced with the corresponding constant value before type conversion.
constant:
enabled: true
skip_rows: 1
comment: "#"
ref_quote:
l: "["
r: "]"
files: ["constants.xlsx"]In this example, a cell containing [HeroHP] is looked up in the constant data and replaced with the stored value (e.g. 1000). Keys not found in the constant data are left unchanged. Later files overwrite earlier keys.
