Output Formats
vep-rs supports five output formats, selected via --output_format or the shorthand flags --vcf, --json, --tab:
vep(default): VEP’s default tab-delimited format with anExtracolumntab: VEP’s--tabformat, every Extra key as its own named columnvcf: the input VCF with a CSQ INFO field appendedjson: line-delimited JSON, one object per input recordparquet: a Hive-partitioned Parquet directory produced via aduckdbsubprocess (see Parquet Format below)
Parity with Ensembl VEP
With the default flag set and --tab, --vcf or --json, the default, tab, vcf and json outputs match what Ensembl VEP 116.2 writes field for field: every column of every row and, for JSON, every key of every object compared as text (VEP writes JSON keys in Perl hash order, so the JSON is not byte-identical and key order is not compared). Header lines that carry a time, a cache path or the command line are compared by shape, and VEP’s Perl API component version lines have no counterpart. The contract is enforced by the golden corpora under tests/golden/<release>/<assembly>/ (crates/vep-cli/tests/golden.rs and format_parity.rs): every consequence-set combination observed on the benchmark datasets, annotated with the corpus’s own pruned cache and compared with VEP’s committed output in all four formats, plus the Parquet round trip. Consequence terms themselves are governed by the concordance measurement (scripts/README.md). The corpus manifest records the (Location, Allele, Feature) keys whose terms differ between the engines (117 of the 17,556 GRCh37 rows, 4 of the 2,818 GRCh37-hgvs rows, 323 of the 20,623 GRCh38 rows and 2 of the 977 GRCh38-release116 rows, none of them unexplained_residual) and the rows only one engine emits, each under its divergence class; for a recorded key the parity tests check only that the row’s consequence set is one of the recorded sets, compare none of its other columns, and skip the record’s most_severe_consequence in JSON. Outside the tested flag set there are known differences: --regulatory adds its (always empty) fields to the headers and columns and warns that regulatory annotation is not implemented, --overlaps does not exist, --output_format vcf switches on the same fields as --vcf (VEP ties them to the flag alone), and under --check_existing a row none of whose co-located variants is somatic or phenotype-linked prints SOMATIC=0 and PHENO=0 where VEP prints neither key.
The rules the formatters follow are VEP’s own, cited to their Perl sources in crates/vep-io/src/output/fields.rs (the single field table shared by every format), crates/vep-effects/src/display.rs (positions, codons and peptides) and the formatters beside them. The ones a reader of the output most often needs:
- Row order. Rows of one input record are transcript-major (sorted by transcript stable id) and allele-minor (ALT order); intergenic rows come last. A record’s rows are contiguous in the default and tab formats and become one line (VCF) or one object (JSON).
- Uploaded_variation. The first
;-element of a VCF record’s ID (an Ensembl-format ID column is taken whole), unless it is., empty or0(the values VEP reads as no ID) or thechr_start_allelesname VEP generates for an empty ID column, sors2;.names the recordrs2while.;rs2,;rs2,0and0;rs2do not name it. Such a record is named from its own input line, as Ensembl VEP 116 names it:CHROM_POS_REF/ALT1/ALT2with POS and every allele exactly as the VCF line spells them, case included (21_10795228_C/G/T;21_30425650_a/gfor the lowercase21 30425650 . a g;21_47612512_C/CTGfor the insertion21 47612512 . C CTG, whose variant starts at 47612513;21_33867341_C/<CN2>for the symbolic record21 33867341 . C <CN2>, whose variant starts at 33867342), andchr_start_allelesfor an Ensembl-format line, start the lower of its two coordinates. VEP names a region or HGVS input by the input string itself; vep-rs names itchr_start_allelesfrom the parsed variant. - Positions.
cDNA_position,CDS_positionandProtein_positionarestart-endranges, a single value when both ends coincide, and?for an end that falls in an intron or outside the transcript, and for the CDS and protein columns also in a UTR (?-335,493-?). An insertion prints the two flanking bases (1161-1162).cDNA_positionappears only for exon-overlapping rows; the CDS and protein columns,Amino_acidsandCodonsonly for coding rows. - Amino_acids and Codons.
ref/altpeptides, collapsed to one when unchanged (Pfor a synonymous change); a partial codon appendsX(M/X,D/DX); an allele whose codon window is empty prints-(-/Sfor an in-frame insertion,M/-for a whole-codon deletion). Codons are the reference codon window in lower case with the allele’s own bases in upper case, on both sides (gTGCTGGgc/ggcfor a deletion,-/TCTfor an insertion). Structural variants carry positions but no codons. - DISTANCE is the shortest gap between the variant and the transcript, the smallest of the four gaps between the record’s start or end and the transcript’s start or end (so the pair
POS+1..POSof a zero-length structural variant measures from whichever flank is nearer, and a<CNV:TR>contraction read under the cap measures from the run it is located on, not from the units it removes), printed on up- and downstream rows only. FLAGS lists everycds_transcript attribute (cds_start_NF,cds_end_NF). OverlapBP and OverlapPC appear on every structural variant row that overlaps its feature (not on breakends) in the default Extra column and in JSON (bp_overlap,percentage_overlap), and never intab,vcfor Parquet: VEP’s--overlapsflag, which adds them as columns there, is not implemented. - Structural routing. A record is a structural variant when REF and its ALTs together are not a run of
ACGTand either INFO/SVTYPE is set (a value Perl reads as true: not empty, not0) or an ALT is written in a structural form (an angle or bracket token without*, a dot-bounded breakend), as Ensembl VEP routes it:N GwithSVTYPE=DEL;END=...is a deletion over END,<*>withSVTYPE=DELa deletion,N Galone a sequence variant. The record’s type is read from its ALTs joined by/when the string opens with a VCF 4.4 form in upper case:<DEL,<INS,<DUP,<INV,<CNV, or<CNfollowed by an optional=and a digit (<CN0,<CN=5); a lowercase<del>is outside the forms, so SVTYPE names its type, and without SVTYPE it has no Sequence Ontology term (the table’s keys are upper case) and is an unsupported type. A gVCF variant site (A G,<NON_REF>) is its real alleles:<NON_REF>beside another allele names no variant and is dropped from the ALT list; a record whose ALTs are<NON_REF>alone, one or several, is a reference block with one intergenic row per ALT; under a true INFO/SVTYPE every ALT, the block included, is an allele of the structural record, as Ensembl VEP reads it (G,<NON_REF>withSVTYPE=DELis two deletion alleles). The type is read throughget_SO_term’s own regexes, so their edges hold: a copy number needs a digit (<CN=X>is the unsupported typeCN=X; withSVTYPE=DELit is a deletion), a list’s repeated parts collapse only when each part loses one bracket a side (<<DEL>>,<DEL>is the unsupportedDEL/DEL, while a lone<<DEL>>loses every bracket and is adeletionand<<CN0>>acopy_number_variation), a mobile element takes one colon before its name (<INS:ME::ALU>is amobile_element_insertion,<INS:ME:ALU>anAlu_insertion), and a trailing colon stays in the type (<DEL:>is unsupported,<DEL:X>adeletion). A trailing empty ALT field (G,,<DEL>,) names no allele, as Ensembl VEP’ssplitdrops it. A record whose ALT column is.is skipped at warning level whatever INFO/SVTYPE says. A record whose every ALT is*(N *,*,*) names no allele whatever INFO/SVTYPE says: no row in the default and tab outputs, the VCF line carried without CSQ, and a JSON object of the record’sid, coordinates,allele_string(N/*) and"most_severe_consequence":"?", as Ensembl VEP writes the record withoutSVTYPE. Lowercase alleles of a sequence record are upper-cased once the anchor is trimmed (a gprints AlleleG, JSONallele_stringA/G), as Ensembl VEP’svalidate_vfdoes. - Structural spans. A symbolic record starts at POS+1 and ends at POS+|SVLEN|, else at INFO/END, else at its start; a tandem repeat or tandem duplication reads the largest SVLEN. SVLEN is read as Perl numifies it (
300abcis 300,abc,.or an empty value is 0), and a repeated INFO key reads its last value.SVLEN=0, a non-numeric SVLEN or an END at POS leaves the pairPOS+1..POS, printedchr:POS-POS+1, for every class: the positional terms of a sequence insertion, withfeature_elongationfor a duplication or an insertion. An END more than one base before POS (END=0included) or not a coordinate skips the record at warning level, as Ensembl VEP 116 skips it. Coordinates print as numbers: Ensembl VEP keeps a sign or a fraction it was given (21:30425651-+30425899,21:30425651-30425950.7), where vep-rs prints 30425899 and reads a fractional SVLEN by its integer part. POS is read as the integer its text denotes, in any spelling that denotes one exactly (3e7,30425949.0,30425949., a leading or trailing blank), on both paths, as Ensembl VEP’s structural path numifies it; a POS that is not a number, or a negative one, skips the record at warning level (Invalid POS), and a structural record whose first base POS + 1 passes 2^62 - 1 fails its line. A trailing blank at the end of a data line is kept as part of the last INFO value, as Ensembl VEP keeps it (END2=30425949names the mateN[21:30425949 [, and the VCF output carriesEND2=30425949 ;CSQ=). - Multi-allelic records are one variant, as in VEP without
--minimal: no per-allele minimisation, every allele’s rows in one record. - Multi-allelic structural variants. A record with more than one ALT and a symbolic allele among them has one class for the whole record, read from its ALTs joined by
/as Ensembl VEP 116 reads them: identical symbolic ALTs collapse to their type (<INS>,<INS>is aninsertion,<DEL>,<DEL>adeletion), a DEL beside a DUP in either order,<DEL>,<DUP:TANDEM>included, is acopy_number_variation,<CN0>,<CN2>is acopy_number_variation, and a sequence ALT beside a symbolic one (G,<INV>withSVTYPE=DEL) takes the typeINFO/SVTYPEnames. Every ALT then gets its own row for each transcript, labelled with the class term as its Allele, so the rows of one transcript repeat per ALT and differ only inALLELE_NUM, which numbers the parts of Ensembl VEP’s allele string from 1: a breakpoint record’s reference base is part 1, so a bare<BND>prints 2, and a record with parsed breakends prints 0 for its own breakend and numbers its mates from 1. A list VEP has no type for (<INS>,<DEL>,<INV>,<DUP>) keeps one row per ALT under each ALT’s own class and the unsupported-type treatment below. - Records Ensembl VEP skips. Ensembl VEP 116 drops a record it marks skipped before annotation and writes nothing for it in any format (its
--dont_skiprestores it): a structural variant spanning more than--max_sv_sizebases (default 10,000,000;--max_sv_size -1lifts the limit in both engines), a symbolic ALT its type table lacks (<CPX>,<NON_REF>), a multi-allelic symbolic list it has no type for (<INS>,<DEL>,<INV>,<DUP>), and a<DEL>with neitherENDnorSVLEN. vep-rs annotates every one of them, a documented divergence (intended-divergences.md): the default, tab and Parquet outputs list their rows (an oversize or unsupported record against every transcript it overlaps; a list with no type with one row per ALT under each ALT’s own class); the VCF output carries the line of an oversize or unsupported record without CSQ; the JSON output writes an unsupported record as aninput-only object and omits an oversize one; a<DEL>with neitherENDnorSVLENis read as a deletion of the base after POS and written in every format. The golden corpora record the rows only vep-rs writes for such a record under the manifest classreference_skipped_record. A<NON_REF>gVCF reference-confidence block is not a variant allele: it is annotated against no transcript and appears in the default and tab formats as oneintergenic_variantrow spanning the block, with a warning per record. - Breakends. A breakend in bracket notation is a point record whose Location is the base after POS; a dot-notation breakend (
N.,.N) is the same point with or without INFO/SVTYPE. A record with a parsed breakend is one breakpoint whatever its other ALTs:<BND>,<DEL>withSVTYPE=BNDwrites every allele as achromosome_breakpointat the point, its END unread. A<BND>spans POS+1 to POS+|SVLEN| when INFO carries SVLEN and is otherwise the single base at POS+1 (INFO/END never ends a breakpoint’s span, as in Ensembl VEP 116; a breakpoint whose END is0or empty is skipped, as Ensembl VEP skips it); it names a mate only throughINFO/CHR2written with a value andEND2, elseEND(POS2 is never read), and its alleles are evaluated over the span. The mate allele is writtenN[CHR2:END2[with both values as written (N[chr21:00030425700[), whatever the reference base, and the record’s own breakend takes the reference base (A.); an END2 that is not a run of digits (-7,abc) names no mate, and the record’s allele string still carries the text (N/N[21:-7[); whitespace around a bracket, the chromosome or the colon is read through, as Ensembl VEP’s breakend regex reads it (N[ 21 : 30425700 [names the mate21:30425700), and an INFO value ends at a second=(END2=30425700=5is 30425700). A dot-bounded ALT beside a bracket ALT (.N,N[21:30425700[) lends the own breakend its base and writes no row of its own. A<BND>that names no mate is onechromosome_breakpointallele over its span or point, with the region terms and nofeature_truncation. A paired breakend has two alleles in the output: its local end, printed as a single breakend (.A) and annotated against the transcripts at the record’s own position, and the bracket allele (]21:27307377]A), which gets a row for a transcript only when the mate coordinate lies within the up- or downstream distance of that transcript, as VEP’s_close_to_featureadmits it. On the mate chromosome the bracket allele’s rows describe the mate breakend at the mate coordinate:feature_truncationwith the region term when the mate lies inside the transcript,upstream_gene_variantordownstream_gene_variantwith the distance when it lies outside, and theEXONandINTRONof that coordinate. A transcript the local rows already name is not repeated on the mate side. The record’s own breakend is writtenN.or.N(REF base, dot on the side of the bracket mate) whether or notINFO/MATEIDis present;chromosome_breakpointis the Allele of a record without a bracket mate, a single breakend (.N) or a bare<BND>with neitherCHR2nor a span, asclass_SO_termis Ensembl VEP’s Allele for an allele without a breakend string. A breakend record none of whose breakends lies within the up- or downstream distance of a transcript is intergenic, as Ensembl VEP 116 writes it: oneintergenic_variantrow per allele (N.and the bracket mate, or the singlechromosome_breakpoint), Feature-,IMPACT=MODIFIER, in every format; when either breakend reaches a transcript the record gets no intergenic row, and an allele of it that reached nothing gets no row.NMD_transcript_varianton a breakend row follows the record’s own position, so a mate inside an NMD transcript earnsfeature_truncationwithout it. - ENSP. Under
--proteinevery transcript row of a transcript with a translation carries its ENSP, structural-variant rows (transcript_ablation,feature_truncation, breakends) included.
VEP Default Format
One line per (allele, transcript) consequence, plus one intergenic line per allele that overlaps no feature.
Header
## ENSEMBL VARIANT EFFECT PREDICTOR v116.2
## Output produced at [TIME]
## Using cache in [PATH]/116
## Using API version 116, DB version ?
## 1000genomes version phase3
## COSMIC version 98
## ClinVar version 202306
## ...one line per source version recorded in the cache's info.json...
## Column descriptions:
## Uploaded_variation : Identifier of uploaded variant
## Location : Location of variant in standard coordinate format (chr:start or chr:start-end)
## ...one line per column...
## Extra column keys:
## IMPACT : Subjective impact classification of consequence type
## DISTANCE : Shortest distance from variant to transcript
## STRAND : Strand of the feature (1/-1)
## FLAGS : Transcript quality flags
## VEP command-line: vep --assembly GRCh37 --force_overwrite --input_file [PATH]/input.vcf --json_cache [PATH]/116 --offline --output_file [PATH]/out.txt
#Uploaded_variation Location Allele Gene Feature Feature_type Consequence cDNA_position CDS_position Protein_position Amino_acids Codons Existing_variation Extra
The source-version lines come from the cache’s info.json (source_versions, written by scripts/data/storable_to_json.pl and vep-cache-builder, or by scripts/golden/prune_json_cache.py --perl-info from a VEP cache’s info.txt). The Using cache in line prints the cache directory as given; --mask_header_cache_path prints it as [PATH]/<leaf>, its last component alone, here, in the --tab header, in the VCF cache= token and in the Parquet footer. The Extra column keys block lists every key the active flags can produce (plugin keys are not listed). --no_headers suppresses the whole block, column line included.
Columns
| # | Column | Description |
|---|---|---|
| 1 | Uploaded_variation | Input ID, else the record’s own line as CHROM_POS_REF/ALT1/ALT2 (POS and alleles as written) |
| 2 | Location | chr:pos or chr:start-end (chr:end-start for an insertion) |
| 3 | Allele | The ALT allele of this row; for a structural variant the SO term of its class (deletion, duplication, Alu_insertion), except that a breakend keeps its literal ALT (N., A[21:27348902[, N[21:00030425700[ for a mate named by CHR2; chromosome_breakpoint for a record without a parsed mate, a single breakend or a bare <BND>), every ALT of a multi-allelic structural variant prints the record’s class term, and an unsupported <CPX> prints CPX. A sequence allele whose reference base is ambiguous (N G) prints G with coding_sequence_variant and no amino acids, since no peptide is read from an ambiguous reference |
| 4 | Gene | Ensembl gene stable ID |
| 5 | Feature | Ensembl transcript stable ID |
| 6 | Feature_type | Transcript, or - on an intergenic row; VEP’s RegulatoryFeature and MotifFeature rows are not produced |
| 7 | Consequence | SO consequence term(s), comma-separated |
| 8 | cDNA_position | Position or range in the cDNA; ? for an unmappable end |
| 9 | CDS_position | Position or range in the coding sequence |
| 10 | Protein_position | Position or range in the protein |
| 11 | Amino_acids | Reference/variant peptides (G/A), one value when unchanged |
| 12 | Codons | Reference/variant codon windows, allele bases in upper case (gGa/gCa) |
| 13 | Existing_variation | Co-located known variant IDs, comma-separated |
| 14 | Extra | KEY=VALUE pairs joined by ;, in VEP’s key order (see below); - when empty |
Extra Field
Keys appear in VEP’s order: IMPACT, DISTANCE, STRAND, FLAGS (preceded by ALLELE_NUM under --allele_number and REF_ALLELE under --show_ref_allele), then the fields of the active flags in VEP’s flag order (VARIANT_CLASS, SYMBOL, SYMBOL_SOURCE, HGNC_ID, BIOTYPE, CANONICAL, MANE, TSL, APPRIS, CCDS, ENSP, SIFT, PolyPhen, EXON, INTRON, DOMAINS, HGVSc, HGVSp, HGVS_OFFSET, allele frequencies, MAX_AF, CLIN_SIG, SOMATIC, PHENO, PUBMED, …), then any other key alphabetically (the structural-variant OverlapBP/OverlapPC and plugin keys). Inside a value ; is escaped as %3B and, unless --no_escape, the = of an HGVSp value as %3D; lists join with ,. An intergenic row carries IMPACT=MODIFIER and no STRAND, DISTANCE or FLAGS; record-level keys such as VARIANT_CLASS, CLIN_SIG and variant-level plugin keys join it under their flags.
Example
Two records of the GRCh37 golden corpus as Ensembl VEP 116.2 wrote them (tests/golden/116/GRCh37/expected/default.txt.gz): 1 213061271 806345 G C, a missense change reported against four transcripts, and the ID-less multi-allelic site 21 10795228 . C G,T, which overlaps no transcript:
806345 1:213061271 C ENSG00000162769 ENST00000366971 Transcript missense_variant 1433 1235 412 G/A gGa/gCa - IMPACT=MODERATE;STRAND=1
806345 1:213061271 C ENSG00000162769 ENST00000419102 Transcript missense_variant 631 632 211 G/A gGa/gCa - IMPACT=MODERATE;STRAND=1;FLAGS=cds_start_NF
806345 1:213061271 C ENSG00000162769 ENST00000474693 Transcript downstream_gene_variant - - - - - - IMPACT=MODIFIER;DISTANCE=2364;STRAND=1
806345 1:213061271 C ENSG00000162769 ENST00000483790 Transcript intron_variant,non_coding_transcript_variant - - - - - - IMPACT=MODIFIER;STRAND=1
21_10795228_C/G/T 21:10795228 G - - - intergenic_variant - - - - - - IMPACT=MODIFIER
21_10795228_C/G/T 21:10795228 T - - - intergenic_variant - - - - - - IMPACT=MODIFIER
Tab Format
Selected with --tab or --output_format tab. The 13 default columns followed by one column per Extra key the active flags can produce, in the Extra key order above, then one per built-in plugin field; - for an absent value, lists joined by ,. The header repeats the default format’s block with a ## <column> : <description> line for every column and no Extra column keys block, and its column line names every column.
VCF Format
Selected with --vcf or --output_format vcf, either of which also switches on --symbol, --biotype and --numbers (in VEP only the --vcf flag does). One line per input record: the record’s own columns, with every allele’s consequences comma-joined into the CSQ INFO field; an earlier CSQ entry is replaced.
Header
The input’s meta lines are kept (minus any earlier CSQ definition or ##VEP line), a ##fileformat line is added when the input lacks one, then:
##VEP="v116.2" API="v116" time="[TIME]" cache="[PATH]/116" 1000genomes="phase3" COSMIC="98" ...
##INFO=<ID=CSQ,Number=.,Type=String,Description="Consequence annotations from Ensembl VEP. Format: Allele|Consequence|IMPACT|SYMBOL|Gene|Feature_type|Feature|BIOTYPE|EXON|INTRON|HGVSc|HGVSp|cDNA_position|CDS_position|Protein_position|Amino_acids|Codons|Existing_variation|DISTANCE|STRAND|FLAGS|SYMBOL_SOURCE|HGNC_ID">
##VEP-command-line='vep --assembly GRCh37 ...'
#CHROM POS ID REF ALT QUAL FILTER INFO
CSQ Field Structure
The Format list is VEP’s fixed prefix (Allele to Existing_variation) followed by the active flag fields and the built-in plugin fields. With the --vcf implied flags the default list is the one above.
The CSQ value of record 806345 above, from the corpus’s expected/vcf.vcf.gz; the file joins its four entries with , on one line:
CSQ=C|missense_variant|MODERATE|FLVCR1|ENSG00000162769|Transcript|ENST00000366971|protein_coding|6/10||||1433|1235|412|G/A|gGa/gCa|||1||HGNC|24682,
C|missense_variant|MODERATE|FLVCR1|ENSG00000162769|Transcript|ENST00000419102|protein_coding|5/9||||631|632|211|G/A|gGa/gCa|||1|cds_start_NF|HGNC|24682,
C|downstream_gene_variant|MODIFIER|FLVCR1|ENSG00000162769|Transcript|ENST00000474693|processed_transcript|||||||||||2364|1||HGNC|24682,
C|intron_variant&non_coding_transcript_variant|MODIFIER|FLVCR1|ENSG00000162769|Transcript|ENST00000483790|processed_transcript||2/5||||||||||1||HGNC|24682
EXON and INTRON are n/total or a-b/total over the exons and introns the variant strictly overlaps. An absent value is empty (not -), except Allele. An intergenic entry carries Allele, Consequence and IMPACT alone (G|intergenic_variant|MODIFIER||||...).
Encoding
Inside a CSQ value: , and | become &, ; becomes %3B, whitespace becomes _. Lists join with &.
JSON Format
Selected with --json or --output_format json. JSON Lines, one object per input record (a multi-allelic record is one object whose consequence arrays carry every allele). --json implies --no_escape.
Schema
The two corpus records above as Ensembl VEP 116.2 wrote them (expected/json.jsonl.gz), pretty-printed with the keys in the order vep-rs emits them; VEP’s key order is Perl hash order and varies from object to object. The input value is the record’s own VCF line; the first one’s INFO column is abridged here.
{
"id": "806345",
"seq_region_name": "1",
"start": 213061271,
"end": 213061271,
"strand": 1,
"allele_string": "G/C",
"assembly_name": "GRCh37",
"input": "1\t213061271\t806345\tG\tC\t.\t.\tAF_EXAC=1e-05;ALLELEID=794562;...",
"transcript_consequences": [
{
"variant_allele": "C",
"gene_id": "ENSG00000162769",
"transcript_id": "ENST00000366971",
"consequence_terms": ["missense_variant"],
"cdna_start": 1433,
"cdna_end": 1433,
"cds_start": 1235,
"cds_end": 1235,
"protein_start": 412,
"protein_end": 412,
"amino_acids": "G/A",
"codons": "gGa/gCa",
"impact": "MODERATE",
"strand": 1
},
{
"variant_allele": "C",
"gene_id": "ENSG00000162769",
"transcript_id": "ENST00000419102",
"consequence_terms": ["missense_variant"],
"cdna_start": 631,
"cdna_end": 631,
"cds_start": 632,
"cds_end": 632,
"protein_start": 211,
"protein_end": 211,
"amino_acids": "G/A",
"codons": "gGa/gCa",
"impact": "MODERATE",
"strand": 1,
"flags": ["cds_start_NF"]
},
{
"variant_allele": "C",
"gene_id": "ENSG00000162769",
"transcript_id": "ENST00000474693",
"consequence_terms": ["downstream_gene_variant"],
"impact": "MODIFIER",
"distance": 2364,
"strand": 1
},
{
"variant_allele": "C",
"gene_id": "ENSG00000162769",
"transcript_id": "ENST00000483790",
"consequence_terms": ["intron_variant", "non_coding_transcript_variant"],
"impact": "MODIFIER",
"strand": 1
}
],
"most_severe_consequence": "missense_variant"
}
{
"id": ".",
"seq_region_name": "21",
"start": 10795228,
"end": 10795228,
"strand": 1,
"allele_string": "C/G/T",
"assembly_name": "GRCh37",
"input": "21\t10795228\t.\tC\tG,T\t100\tPASS\tAC=5,8;AF=0.000998403,0.00159744;AN=5008;NS=2504;DP=64221;EAS_AF=0,0;AMR_AF=0.0029,0;AFR_AF=0.0023,0.0008;EUR_AF=0,0;SAS_AF=0,0.0072;AA=.|||;VT=SNP;MULTI_ALLELIC",
"intergenic_consequences": [
{ "variant_allele": "G", "consequence_terms": ["intergenic_variant"], "impact": "MODIFIER" },
{ "variant_allele": "T", "consequence_terms": ["intergenic_variant"], "impact": "MODIFIER" }
],
"most_severe_consequence": "intergenic_variant"
}
An allele gets an intergenic_consequences entry only when it overlaps no transcript, so a record never carries both kinds of entry for the same allele.
Field Details
idis VEP’svariation_name: the first;-joined ID of a VCF record or the whole ID column of an Ensembl-format line, the literal.when that is., and the generatedchr_start_alleleswhen it is absent, empty or0(an ID Perl reads as false). VEP names a region or HGVS input by the input string itself; vep-rs names thosechr_start_allelestoo.allele_stringis the record’s working allele string (anchor-trimmed for a multi-allelic record,REF/N[chr:pos[for a paired breakend while a single breakendN.stays bare, the symbolic ALT for other structural variants). A*keeps its written place (N/*/G,A/G/*/C/GA) and counts as an allele of one base in the trim (G/C/*forAG AC,*), a<NON_REF>beside a bracket ALT stays in a breakpoint’s string (N/N[21:30425700[/<NON_REF>), and a sequence record’s alleles are upper case (A/Gfora g), as Ensembl VEP writes them.- Column renames:
Consequencetoconsequence_terms(array),Genetogene_id,Alleletovariant_allele,Featuretotranscript_id,SYMBOLtogene_symbol,OverlapBPtobp_overlap,OverlapPCtopercentage_overlap,ENSPtoprotein_id(ENSP00000286808.3under--protein_version),RefSeqtorefseq_transcript_ids;Feature_typeis dropped;YESflags become1. - Positions split into
*_startand*_end, each written only when defined:390-?givescds_start390 alone and?-335givescdna_end335 alone, as Ensembl VEP 116 writes them. A single position fills both keys. SIFT and PolyPhen split into*_predictionand*_score. - Numeric-looking values are numbers except identifier keys; a zero fraction prints as an integer (
100, not100.0). gene_symbol,gene_symbol_source,biotype,variant_classand the other flag-gated keys appear only under their flags. Absent or-values are omitted.- Plugin fields, built-in or dylib, sit inside each consequence object; none is written at the record level.
Parquet Format
Selected with --output_format parquet. Writes a TSV intermediate (one row per allele x transcript consequence, in VEP’s row order) and post-processes it to Parquet via the duckdb CLI. Requires duckdb 1.2 or newer on PATH; the runner fails at startup otherwise.
Directory layout
A Hive-partitioned directory keyed by chrom, with the partition column also stored inside the files:
out.parquet/
├── chrom=1/data_0.parquet
├── chrom=2/data_0.parquet
└── chrom=X/data_0.parquet
Read it with read_parquet('out.parquet/**/*.parquet', hive_partitioning=false) to keep chrom as the stored VARCHAR (Hive type inference would otherwise cast chrom=21 to a number).
Schema
Five key columns, then exactly the --tab columns in --tab order:
| Column | Type | Notes |
|---|---|---|
chrom | VARCHAR | input chromosome name |
pos, end | BIGINT | the record’s VEP start and end |
ref, alt | VARCHAR | this row’s reference and alternate allele |
Uploaded_variation … Existing_variation | VARCHAR | the 13 default columns |
IMPACT, FLAGS, flag and plugin fields | VARCHAR | - is stored as NULL |
DISTANCE | INTEGER | |
STRAND | TINYINT |
Row shape (--parquet_shape)
nested(default): one row per (record, allele);Uploaded_variation,Location,AlleleandExisting_variationstay scalar and every per-consequence column is a LIST in the flat row order. Two input records at one site stay two rows when they differ in ID, END or another per-record column; identical ID-less duplicate lines merge into one row.flat: one row per (allele x transcript consequence); every column scalar.
Writer options
Chosen for the reader, not the writer: Parquet v2 pages, ZSTD level 9, row groups of 122,880 rows sorted by (chrom, pos, ref, alt) so the pos statistics are tight, a dictionary on every column (DuckDB writes a Bloom filter only for dictionary-encoded columns, so the dictionary size limit is set to the row-group size; the deprecated DICTIONARY_COMPRESSION_RATIO_THRESHOLD option is also passed and DuckDB 1.2 and later ignore it), Bloom filters at a 0.1% false-positive rate, and the run’s identity in the footer’s key-value metadata (vep_rs_version, vep_api_version, assembly, cache, command_line, every source_<key> of the cache). In the nested shape a LIST column carries a Bloom filter only when its elements are dictionary-encoded, which needs their distinct count in the row group to fit that limit (DuckDB 1.2 wrote no Bloom filter for nested types at all), so a high-cardinality column such as Feature usually has none; the scalar columns, whose distinct count cannot exceed the row count, keep theirs. parquet_round_trips_to_the_tab_output in crates/vep-cli/tests/format_parity.rs asserts, on every corpus, that both shapes round-trip to the --tab rows and, on the flat shape, that every populated column of every row group carries a dictionary, a Bloom filter and statistics and that the footer holds vep_rs_version, assembly and command_line; the page version, compression, row-group size, sort order and false-positive rate are not asserted.
Round-trip to --tab
scripts/adapters/parquet_to_vep_tab.py <dir|file> [--output tab.txt] projects the tab columns back out (NULL to -, integers to text, nested rows unnested). Rows come out in Parquet order; compare against a --tab file as a multiset of lines.
Debug flags
--keep_intermediate(hidden): preserves the TSV intermediate after DuckDB finalize for inspection.--parquet_row_group_size N(hidden): rows per row group, rounded up to a multiple of 2,048 (DuckDB writes whole vectors; the dictionary size limit follows the rounded value, otherwise a row group with a distinct value in every row loses its dictionary and its Bloom filter).parquet_row_groups_prune_on_bloom_filterswrites a corpus at 2,048-row groups and checks withparquet_bloom_probethat an absentFeatureinside the column’s value range is excluded by every row group and a single-rowFeatureby every row group but its own.
Plugin Data in Output
Built-in plugin annotations appear in all output formats:
| Format | Variant-level plugins (CADD, gnomADc) | Transcript-level plugins (REVEL, SpliceAI) |
|---|---|---|
| VEP | Extra column key (CADD_PHRED) | Extra column key (REVEL_score) |
| Tab | Named column | Named column |
| VCF | Additional fields in CSQ | Additional fields in CSQ |
| JSON | Fields repeated in every transcript_consequences object | Fields in the transcript_consequences object |
| Parquet | Additional columns | Additional columns |
Variant-level plugin data is repeated on every transcript consequence row of that variant, as in Perl VEP; no format writes it at the record level.
A C-ABI dylib plugin (--plugin <path>, or lib<Name>.so / lib<Name>.dylib under --dir_plugins) runs per transcript consequence after the built-in plugins and returns a JSON object whose entries become fields of that consequence. Those fields appear in the default format’s Extra column (alphabetically after the flag fields) and inside the JSON consequence objects only. The tab, vcf and Parquet column sets are fixed from the fields the built-in plugins declare before the first record is written, and the FFI contract has no header symbol through which a dylib could declare its own, so a dylib field has no column there and no ## header line.