The VCF File Format Explained: From CHROM/POS to GT, DP, and GQ
A field-by-field guide to the VCF format: meta-information lines, the 8 fixed columns, and how FORMAT encodes genotype calls with GT, DP, GQ, and AD.
Every variant caller, whether it is GATK HaplotypeCaller, DeepVariant, or bcftools call, outputs the same kind of file: a VCF. At first glance it looks like a plain tab-delimited table, but tucked inside a handful of columns is a compact encoding scheme that describes every SNP, insertion, and deletion in a sample, along with how confident the caller is in each one. This guide walks through the VCF specification section by section, so a genotype string like 0/1:45:99:20,25 stops looking like noise and starts reading like a sentence.
What Is a VCF File?
VCF stands for Variant Call Format. It was developed during the 1000 Genomes Project and is now maintained as an open specification (hts-specs, currently version 4.2/4.3) alongside the SAM/BAM specs. A VCF file is plain text, tab-delimited, with one row per variant site rather than one row per read. In practice it is almost always compressed with bgzip (.vcf.gz) and indexed with tabix or bcftools index (.tbi/.csi), so tools can jump straight to a region instead of scanning the whole file.
The Three Sections of a VCF
Every valid VCF file is built from three distinct blocks, always in this order.
1. Meta-information lines
Lines beginning with a double ## carry file metadata. The first line declares the spec version, and later lines describe every abbreviation that will show up in the INFO and FORMAT columns — its ID, expected Number of values, Type, and a Description. A parser reads these first so it knows how to interpret a tag like DP before it appears in a data row.
##fileformat=VCFv4.2
##source=GATK-4.5.0
##reference=GRCh38.fasta
##contig=<ID=chr7,length=159345973>
##INFO=<ID=DP,Number=1,Type=Integer,Description="Total Depth">
##INFO=<ID=AF,Number=A,Type=Float,Description="Allele Frequency">
##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Read Depth">
##FORMAT=<ID=GQ,Number=1,Type=Integer,Description="Genotype Quality">
##FORMAT=<ID=AD,Number=R,Type=Integer,Description="Allelic depths">2. The header line
A single line starting with one # names every column, tab-separated. It always contains the 8 fixed columns described below, and if the file carries per-sample genotypes it adds a FORMAT column followed by one column per sample.
#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT Sample1 Sample23. Data lines
Everything after the header is a data line — one row per variant, with values in the same order as the header. This is where the actual calls live, and it is where the rest of this guide is focused.
The 8 Fixed Columns
Every data line starts with exactly these eight columns, always in this order, regardless of whether genotype data follows.
| Column | Meaning |
|---|---|
CHROM | Chromosome or contig name the variant sits on, e.g. chr7 |
POS | 1-based position of the first base of REF on that chromosome |
ID | Variant identifier such as a dbSNP rsID, or . if none is known |
REF | The reference allele exactly as it appears in the reference genome |
ALT | One or more alternate alleles, comma-separated for multi-allelic sites |
QUAL | Phred-scaled quality score for the assertion that a variant exists at this site |
FILTER | PASS if the site passed all filters, . if not evaluated, or a semicolon-separated list of failed filter names |
INFO | Semicolon-separated key=value (or bare flag) annotations describing the site as a whole |
A couple of details worth internalizing:
POSis 1-based. This trips people up constantly, since several neighboring formats (like BED) are 0-based instead.QUALis Phred-scaled, the same-10 * log10(P)math as base quality scores. AQUALof 30 means roughly a 1-in-1000 chance the call is wrong;QUALof 10 means a 1-in-10 chance. Values are uncapped, from single digits up into the thousands on strong calls.INFOdescribes the site, not one sample.DP=56here is depth summed across every sample,AF=0.5is population allele frequency,ANis total alleles called — easy to confuse with the per-sampleFORMATfields covered next.
FORMAT and Sample Columns: Where Genotypes Live
If a VCF includes actual genotype calls, the fixed 8 columns are followed by a FORMAT column and then one column per sample. FORMAT is a colon-separated list of field IDs, and each sample column that follows holds colon-separated values in that exact same order.
FORMAT Sample1
GT:AD:DP:GQ:PL 0/1:20,25:45:99:850,0,700Read that pairing positionally: the first field in FORMAT (GT) matches the first value in the sample column (0/1), the second (AD) matches 20,25, and so on.
GT — the genotype call
GT encodes which alleles the sample carries, as integers indexing into REF and ALT: 0 is the reference allele, 1 the first ALT allele, 2 the second, and so on for multi-allelic sites. For a diploid organism you get two integers separated by / (unphased — order carries no information) or | (phased — order is known, usually from a trio or long-read phasing).
| GT value | Meaning |
|---|---|
0/0 | Homozygous reference — matches the reference genome on both copies |
0/1 | Heterozygous — one reference copy, one alternate copy |
1/1 | Homozygous alternate — both copies carry the alternate allele |
1/2 | Compound heterozygous — two different alternate alleles, no reference copy |
./. | Missing genotype — no confident call could be made at this site |
DP, GQ, and AD — how much to trust the call
Three FORMAT fields do most of the work of telling you whether a genotype call is solid:
DPhere is the per-sample read depth at this position. Do not confuse it with the site-levelDPinINFO, which sums across all samples.GQis the Phred-scaled confidence in the genotype call itself, capped at 99 by most callers.GQof 99 is about as confident as it gets; below 20 is a shaky call worth flagging.AD(allele depth) splits that read depth by which allele each read supported, inREF,ALTorder.ADof20,25on a0/1call means 20 reads backed the reference and 25 backed the alternate — close to the 1:1 ratio expected for a true heterozygote. A skewed ratio like40,2next to a0/1call is a red flag.
You will also see PL, the normalized Phred-scaled likelihood for each possible genotype (0/0, 0/1, 1/1 for a biallelic site). The lowest value is the called genotype, and 0 always sits there since likelihoods are normalized relative to the best call.
A Worked Example
Put it all together with a single, realistic data line:
#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT Sample1
chr7 140453136 rs121913343 A T 87.30 PASS DP=45;AF=0.500;AN=2 GT:AD:DP:GQ:PL 0/1:20,25:45:99:850,0,700Reading left to right: a SNP on chr7 at position 140453136, matching a known dbSNP entry, changing A to T, with site quality 87.30 and a PASS. Across all samples, 45 reads covered this position and the alternate allele frequency is 0.5. For Sample1, GT of 0/1 calls a heterozygote, backed by 20 reads for A and 25 for T, GQ of 99 — about as confident as it gets.
Reading and Filtering VCFs with bcftools
bcftools is the standard command-line tool for working with VCF and its binary sibling BCF. If you have not installed it yet, our samtools & bcftools install guide walks through setting it up in an isolated conda environment. A few commands cover most day-to-day needs:
# print just the meta-information and header lines
bcftools view -h sample.vcf.gz
# pull CHROM, POS, and per-sample GT/DP/GQ into a flat table
bcftools query -f '%CHROM\t%POS\t[%GT\t%DP\t%GQ\t]\n' sample.vcf.gz
# keep only PASS-filtered variants with QUAL of at least 30
bcftools view -f PASS -e 'QUAL<30' sample.vcf.gz -Oz -o filtered.vcf.gz
bcftools index -t filtered.vcf.gzThe query -f format string is the workhorse: %CHROM and %POS pull fixed columns, while anything in square brackets — [%GT\t%DP\t%GQ\t] — repeats once per sample, exactly what you need when a file carries multiple samples.
Common Pitfalls
- Multi-allelic sites break naive parsing. When
ALTlists more than one allele (T,TA),GTvalues can be2,3, and beyond, not just0or1. Many tools expect biallelic sites, sobcftools norm -m -anyis commonly used to split multi-allelic records apart first. - Structural variants use symbolic alleles. Large deletions or insertions may appear as a symbolic tag like
`<DEL>`rather than real sequence, with coordinates described byINFOfields such asENDandSVTYPE. - Plain gzip is not bgzip. Tools expecting
.vcf.gzneed the BGZF block-compressed formatbgzipproduces, so a tabix index can seek into it. A plaingzipfile will fail to index despite the identical extension. - A missing
FILTERvalue is not the same as passing. A.means the site was never evaluated;PASSmeans it was evaluated and cleared. Do not treat the two as equivalent when filtering a callset.
Wrap-up
The VCF format looks intimidating mostly because of density, not complexity: three sections, eight fixed columns, and a colon-separated FORMAT/sample pairing repeated for every genotype. Once you can read GT for the call, DP and AD for the depth behind it, and GQ for how much to trust it, most of what you need from a VCF is legible without opening a manual. Tools like GATK4 (see our install guide) produce these files as their primary output, and bcftools remains the fastest way to filter and query them from the command line.