VCF File Organization and Documentation: Best Practices
By Dr. Zubair Khalid, DVM, MS, PhD ·

Variant call files are the most reused artifact in a genomics pipeline. The same file gets filtered, annotated, benchmarked, subsetted, and handed to a collaborator eighteen months later. Most teams treat organization as a housekeeping task and pay for it during the first audit, the first rerun, or the first reviewer question that starts with "which version did you use?"
By the end of this guide you will have a working directory layout, a file naming convention that sorts cleanly, a header that records what produced the file, a manifest with MD5 checksums, a README and changelog pair, and a short set of commands that prove each of those is correct. You will also know the failure messages that tell you an archive has already drifted.
What you need on hand: a Linux or macOS shell, bcftools and htslib 1.18 or newer, tabix, a reference FASTA with matching .fai and .dict files, samtools, and either GATK4 or Picard for dictionary checks. md5sum ships with coreutils. Git is optional but recommended for the documentation folder only.
What a VCF Actually Contains, and Where It Sits Among Bioinformatics File Formats
The Variant Call Format is a tab-delimited text format. That is the entire trick. Every tool that reads it is parsing columns separated by tab characters, which means every deviation from the specification becomes a parsing bug somewhere downstream.
A VCF has two parts. The header is every line beginning with ##, followed by a single column line beginning with #CHROM. The body is one line per variant site, with the first eight columns mandatory and the ninth onward holding per-sample genotype data.
The eight mandatory columns are CHROM, POS, ID, REF, ALT, QUAL, FILTER, and INFO.
CHROMis the contig name exactly as it appears in the reference dictionary.chr1and1are different contigs to a computer.POSis a 1-based position on the reference allele.IDis a semicolon-free identifier field. It is often.for novel calls.REFis the reference allele as it appears in the FASTA you aligned against.ALTis a comma-separated list of alternate alleles. Multi-allelic sites put several alleles in one field, which is the single most common source of cross-tool disagreement.QUALis a Phred-scaled quality, computed as -10 log10 of the probability that the site is not variant. A QUAL of 30 corresponds to a 1-in-1000 error probability.FILTERrecords which filters the site passed or failed.PASSmeans it passed all applied filters..means no filtering was applied, which is not the same thing.INFOholds site-level annotations. Every key used here must be declared in the header with an##INFOline.
From column 10 onward, each column is one sample, and the column names must match the #CHROM line exactly. The FORMAT column (column 9) describes the per-sample fields. The first of those is almost always GT, the genotype, written as two or more allele indices separated by / for unphased or | for phased. 0/1 means heterozygous reference/first alternate. 1|1 means homozygous alternate with the two alleles known to be on the same haplotype.
Two header concepts trip people up more than any other.
The first is Number. It states how many values a field carries. Number=1 means exactly one. Number=A means one value per alternate allele. Number=R means one value per allele including the reference, so a bi-allelic site gets two. Number=G means one value per genotype. Number=. means the count varies and must be parsed carefully.
The second is the contig declaration. A VCF without ##contig=<ID=...,length=...> lines is legal and still unindexable in several workflows. Interval tools that read a BED file will refuse to intersect if the BED chromosome chr7 has no matching declared contig.
FASTQ vs FASTA, and why VCF is different from both
These three formats answer different questions. Confusing them is a foundational mistake in bioinformatics file organization.
| Format | What it stores | Structure | Typical size for a human genome study | Downstream role |
|---|---|---|---|---|
| FASTQ | Unaligned reads plus per-base quality | Four lines per read (header, sequence, plus line, quality string) | Hundreds of GB for 30x coverage | Raw input to alignment |
| FASTA | Sequence only, no per-base quality | Two lines per record (header, sequence) | About 3.1 GB for the GRCh38 primary assembly | Reference genome, protein sets, assemblies |
| BAM or CRAM | Reads aligned to a reference | Binary, indexed | Tens of GB at 30x | Alignment and variant calling |
| VCF | Variant sites relative to a reference | Tab-delimited text, header plus body | MB to a few GB depending on cohort size | Variant storage, filtering, annotation, sharing |
The practical consequence is that FASTQ quality scores let you decide whether a base is trustworthy, and VCF QUAL and FORMAT/DP, FORMAT/GQ fields carry forward a compressed version of that information. A FASTA reference has no quality at all, which is why the reference build identity in your VCF header matters so much. If you do not know which FASTA produced the REF column, you cannot left-align or normalize anything.
A gVCF is a specialized VCF that includes reference-confidence blocks, meaning it records evidence at positions where no variant was called. It is still a VCF and follows the same organizational rules, but it is much larger and is normally an intermediate that gets consolidated rather than an archive endpoint.
BCF is the binary encoding of the same data model. It is smaller and faster to parse, and it round-trips to VCF without loss.
The Organizing Principles
Ten rules cover most of the decisions. Each is cheap to follow at the start and expensive to retrofit.
- One project, one root directory. Everything for a study lives under one path that can be copied to a new machine and still work.
- Reference files are immutable and versioned by build name.
GRCh38andGRCh37never share a folder, and a reference is never edited in place. - Every file name encodes sample or cohort, assay, pipeline, pipeline version, reference build, and date. Six fields, fixed order.
- Every VCF is bgzip-compressed and indexed before it is considered finished.
- Every VCF header names its reference, its caller, and its caller version.
- Every VCF has a sibling manifest row with a checksum.
- Documentation lives in a
docs/folder next to the data, not in someone's email. - Derived files are regenerable and are marked as such. Only raw calls and the documentation packet are treated as primary data.
- Nothing is renamed after release. Corrections produce a new version and a changelog entry.
- Shared projects get explicit written rules, not implicit conventions.
That last rule is not folklore. A survey of 534 researchers found that satisfaction with shared file management was highest when the team had explicit rules for organizing and archiving, organized files regularly, and used location-based finding strategies, and that finding, re-finding, archiving, and organizing were the activities most in need of better support [1]. A follow-up interview study of 30 researchers identified three practices that distinguished successful teams: developing a set of rules for managing files, creating a guide file, and running a training session [2]. Those findings transfer directly to VCF archives, which are usually shared across more people than any single analysis.
The same logic explains why header metadata is worth the effort. A study of a standardized annotation format for de novo gene annotation argued that the field's inconsistency and difficulty in comparing results came from the absence of established standards, and that a single standardized format with integration into established formats such as FASTA or GFF restores comparability across studies [3]. VCF already has that standard. The failure mode is that people use the container without filling in the metadata.
VCF File Organization Best Practices Step by Step: A Worked Walkthrough
The example below builds an archive for a 24-sample cohort called PRJ-X on GRCh38. Adapt the names, keep the structure.
Step 1: Create the directory skeleton
mkdir -p PRJ-X/{reference/GRCh38,raw_fastq,aligned,variants/cohort,docs,scripts,logs}
mkdir -p PRJ-X/variants/per_sample
find PRJ-X -type d | sort
Expected output:
PRJ-X
PRJ-X/aligned
PRJ-X/docs
PRJ-X/logs
PRJ-X/raw_fastq
PRJ-X/reference
PRJ-X/reference/GRCh38
PRJ-X/scripts
PRJ-X/variants
PRJ-X/variants/cohort
PRJ-X/variants/per_sample
variants/cohort holds multi-sample files. variants/per_sample holds single-sample outputs from scatter-gather runs. Keeping both prevents the awkward situation where a joint call and a single-sample call with the same file name compete for the same path.
Step 2: Freeze the reference
cd PRJ-X/reference/GRCh38
samtools faidx GRCh38.fa
gatk CreateSequenceDictionary -R GRCh38.fa -O GRCh38.dict
md5sum GRCh38.fa | tee GRCh38.fa.md5
Expected output ends with a line like:
5d41402abc4b2a76b9719d911017c592 GRCh38.fa
The .fai index lets tools jump to a coordinate. The .dict file is the sequence dictionary that GATK and Picard use to check contig order and naming. If you skip the dictionary, gatk ValidateVariants and gatk SortVcf will fail later with a user error about a missing sequence dictionary. Record the checksum in the manifest. Every VCF that cites this reference inherits that checksum.
Step 3: Apply the six-field naming convention
A workable pattern is:
{cohort}_{assay}_{caller}-{version}_{build}_{YYYYMMDD}.vcf.gz
Examples:
PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.vcf.gz
PRJ-X_wgs_deepvariant-1.6.1_GRCh38_20261105.vcf.gz
PRJ-X_wes_gatk-4.5.0.0_GRCh38_20261103.vcf.gz
Two properties matter. Hyphens separate the tool from its version so a lexical sort groups versions together. The date is ISO 8601 with no separators so it sorts correctly. Avoid spaces, parentheses, and the word final, which is never true.
Step 4: Normalize before archiving
Left-align and split multi-allelic records once, early, and store the normalized file as the canonical one.
cd PRJ-X
bcftools norm -f reference/GRCh38/GRCh38.fa -m -any \
-Oz -o variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.norm.vcf.gz \
variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.vcf.gz
Expected output on stderr:
Lines total/split/realigned/skipped: 41231/1204/0/0
The four numbers are records read, records split into multiple lines by -m -any, records shifted left by realignment, and records skipped because of a REF mismatch. A nonzero skipped count is a red flag. It usually means the VCF was called against a different reference build or a different contig naming scheme than the FASTA you passed.
Key parameters:
| Option | Meaning | When to change it |
|---|---|---|
-f | Reference FASTA for left-alignment and REF checking | Always supply it |
-m -any | Split multi-allelic records into biallelic records | Use for downstream tools that assume biallelic sites |
-m +any | Merge biallelic records back into multi-allelic | Use only when reconstructing an original representation |
-c e | Error out on REF mismatch instead of warning | Use in production pipelines |
-Oz | Output compressed VCF | Always for archived files |
-T | Temporary directory | Set when the default temp filesystem is small |
If you need allele counts and frequencies written into INFO so the file documents itself, add them before indexing:
bcftools +fill-tags variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.norm.vcf.gz \
-Oz -o variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.norm.tags.vcf.gz -- -t AN,AC,AF
This writes INFO/AN, INFO/AC, and INFO/AF with headers, so a reader who never opens your README can still compute allele frequencies from the file alone.
Step 5: Compress and index properly
bcftools index -t variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.norm.tags.vcf.gz
ls -l variants/cohort/
Expected output:
-rw-r--r-- 1 analyst lab 187430221 Nov 10 09:14 PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.norm.tags.vcf.gz
-rw-r--r-- 1 analyst lab 412887 Nov 10 09:14 PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.norm.tags.vcf.gz.tbi
-t builds a tabix-style .tbi index. Use -c instead to build a CSI index, which is required for contigs longer than the tabix limit of about 512 Mb. Plant and some vertebrate assemblies hit that limit, human chromosomes do not.
Compression here is BGZF, blocked gzip. Each block is independently decompressible, which is what makes random access by coordinate possible. A plain gzip file cannot be indexed this way. If you ever see a VCF with a .gz extension that refuses to index, check whether it was compressed with gzip rather than bgzip. Re-compress with bcftools view -Oz or bgzip -c.
Step 6: Write the header you wish you had received
Header lines can be added or replaced without rewriting sample data. Put the lines in a small text file and apply them:
cat > docs/extra_header.txt <<'EOF'
##reference=file:///data/PRJ-X/reference/GRCh38/GRCh38.fa
##source=bcftools-1.21+htslib-1.21
##FILTER=<ID=LowQual,Description="QUAL below 20">
##INFO=<ID=AC,Number=A,Type=Integer,Description="Allele count in called genotypes">
##INFO=<ID=AN,Number=1,Type=Integer,Description="Total number of alleles in called genotypes">
EOF
bcftools annotate -h docs/extra_header.txt \
variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.norm.tags.vcf.gz \
-Oz -o variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.final.vcf.gz
bcftools annotate -h adds lines that are absent and replaces lines whose key already exists. It does not duplicate them. Confirm the result:
bcftools view -h variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.final.vcf.gz | head -n 8
Expected output:
##fileformat=VCFv4.3
##FILTER=<ID=PASS,Description="All filters passed">
##FILTER=<ID=LowQual,Description="QUAL below 20">
##reference=file:///data/PRJ-X/reference/GRCh38/GRCh38.fa
##source=bcftools-1.21+htslib-1.21
##contig=<ID=chr1,length=248956422>
##INFO=<ID=AC,Number=A,Type=Integer,Description="Allele count in called genotypes">
##INFO=<ID=AN,Number=1,Type=Integer,Description="Total number of alleles in called genotypes">
The ##reference line with a file:// URI is the single most valuable header line in a shared archive. It pins the exact FASTA path and, in combination with the checksum in the manifest, pins the exact bytes.
Step 7: Rename samples to stable identifiers before release
bcftools reheader -s takes a plain text file with one new sample name per line, in the same order as the existing samples. It does not take a mapping table.
bcftools query -l variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.final.vcf.gz > docs/current_samples.txt
wc -l docs/current_samples.txt
Expected output:
24 docs/current_samples.txt
Create the new list in the same order, then reheader:
bcftools reheader -s docs/new_samples.txt \
variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.final.vcf.gz \
-o variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.release.vcf.gz
bcftools index -t variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.release.vcf.gz
Reheadering invalidates the index, so it must be rebuilt. Every reheader is also a checksum change, which is why the changelog entry must record both the old and new sample list.
Step 8: Split and merge deliberately, and record which you did
Two different operations get confused constantly.
bcftools concat joins files that contain the same samples and different regions or different batches. It requires the sample sets to be identical.
bcftools concat -a -Oz -o variants/cohort/PRJ-X_joined.vcf.gz \
variants/cohort/shard.chr1.vcf.gz variants/cohort/shard.chr2.vcf.gz
bcftools merge joins files with different samples at the same sites. All inputs must be indexed.
bcftools merge -Oz -o variants/cohort/PRJ-X_merged.vcf.gz \
variants/per_sample/S001.vcf.gz variants/per_sample/S002.vcf.gz
merge produces ./. for samples with no data at a site, and it can produce records where one file has a multi-allelic site and another has it split. Normalize both inputs before merging. Write down in the README which operation produced the cohort file, because a reviewer cannot tell from the file itself.
Step 9: Generate the manifest and checksums
The manifest is one row per archived VCF with enough columns to reconstruct provenance without opening any file.
cd PRJ-X
printf 'path\tmd5\tsize_bytes\tsamples\trecords\tbuild\tcaller\tcaller_version\treference_md5\tcreated\n' > docs/vcf_manifest.tsv
for f in variants/cohort/*.release.vcf.gz; do
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$f" \
"$(md5sum "$f" | cut -d' ' -f1)" \
"$(stat -c%s "$f")" \
"$(bcftools query -l "$f" | wc -l)" \
"$(bcftools index -n "$f")" \
"GRCh38" "GATK" "4.5.0.0" \
"$(cut -d' ' -f1 reference/GRCh38/GRCh38.fa.md5)" \
"2026-11-03" >> docs/vcf_manifest.tsv
done
column -t docs/vcf_manifest.tsv
Expected output, trimmed to two columns of interest:
path md5 size_bytes samples records build caller caller_version reference_md5 created
variants/cohort/PRJ-X_..._release.vcf.gz 9f2c1e0b7a4d... 187430221 24 40027 GRCh38 GATK 4.5.0.0 5d41402abc4b2a76b9719d911017c592 2026-11-03
bcftools index -n returns the record count from the index, which is effectively instant. Piping bcftools view -H | wc -l works too but reads the entire file and takes minutes on a large cohort.
Also produce a flat checksum file for integrity checks that do not need the manifest:
find variants -type f -name '*.vcf.gz' -exec md5sum {} + | sort -k2 > docs/vcf.md5
md5sum -c docs/vcf.md5 | tail -n 3
Expected output:
variants/cohort/PRJ-X_..._release.vcf.gz: OK
variants/per_sample/S001.vcf.gz: OK
variants/per_sample/S002.vcf.gz: OK
Step 10: Write the README and changelog
The README answers what this is and how to regenerate it. The changelog answers what changed and when.
docs/README.md
docs/CHANGELOG.md
docs/samplesheet.tsv
docs/vcf_manifest.tsv
docs/vcf.md5
docs/extra_header.txt
docs/methods.txt
samplesheet.tsv is the join key across the whole project. Minimum columns: sample_id, subject_id, tissue, library, platform, read_group, fastq_r1_md5, fastq_r2_md5. Without this file, sample names in the VCF become orphan strings in six months.
docs/methods.txt holds the paragraph you will paste into the manuscript. Write it once, at the time of the run, and it will never be reconstructed from memory. It should name the reference build and checksum, the caller and version, the filtering thresholds, and the normalization step.
Step 11: Validate and record statistics
vcf-validator variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.release.vcf.gz
A clean run prints no error lines. Any error line naming a line number is a format violation that will break a downstream parser.
Run two independent checks as well:
gatk ValidateVariants -V variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.release.vcf.gz -R reference/GRCh38/GRCh38.fa
bcftools stats -F reference/GRCh38/GRCh38.fa variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.release.vcf.gz > docs/PRJ-X.stats.txt
grep '^SN' docs/PRJ-X.stats.txt | head -n 6
Expected output:
SN 0 number of samples: 24
SN 1 number of records: 40027
SN 2 number of no-ALTs: 0
SN 3 number of SNPs: 36811
SN 4 number of MNPs: 0
SN 5 number of indels: 3216
Two numbers worth eyeballing. A nonzero number of no-ALTs count means the file contains records where ALT equals . or matches REF, which is a gVCF-style reference block leaking into a variant-only archive. A sudden drop in SNPs between two runs of the same pipeline is more often a reference mismatch than a biology change.
Visualization tools are useful for the same purpose. VIVA combines several existing utility functions into one command to interactively evaluate and share genomic data from VCF files, and it produces publication-quality graphics for variant QC [4]. VCF Observer does preliminary VCF analysis and comparison through drag-and-drop uploads with Venn diagrams, clustergrams, and precision-recall plots, and it supports grouping files by metadata supplied as a data matrix [5]. That metadata grouping feature is the practical payoff of a consistent naming convention and a clean manifest, because the grouping depends entirely on the labels you can supply.
Step 12: Freeze, tag, and archive
tar -czf PRJ-X_variants_20261103.tar.gz \
variants/cohort/*release.vcf.gz* docs/ scripts/ logs/
md5sum PRJ-X_variants_20261103.tar.gz >> docs/vcf.md5
Store the manifest, the checksum, and the reference checksum in three places. The data itself can usually be regenerated from the scripts and the samplesheet. The documentation cannot.
Documenting the Header: The Metadata That Travels With the Data
A README lives next to the file. The header lives inside it. Any file that leaves your team will keep the header and lose the README, so the header carries the load.
| Header line | Required | What it records | Common mistake |
|---|---|---|---|
##fileformat | Yes, first line | Specification version, for example VCFv4.3 | Absent after a hand-edited file |
##fileDate | Recommended | Creation date in YYYYMMDD | Copied forward from the previous run |
##source | Recommended | Tool and version that wrote the file | Generic bcftools with no version |
##reference | Strongly recommended | FASTA path or URI | Points to a reference that no longer exists |
##contig | Required for interval tools | Contig names and lengths | Chromosome naming differs from the BED file |
##INFO | Required for every INFO key used | Number, Type, Description | Undeclared key that breaks strict parsers |
##FORMAT | Required for every FORMAT key used | Number, Type, Description | GT redeclared with the wrong Number |
##FILTER | Required for every FILTER value used | What the filter means | LowQual used but never declared |
##ALT | Required for symbolic alleles | Definition of <DEL>, <NON_REF> | Symbolic ALT present with no definition |
##SAMPLE | Optional | Sample-level attributes | Contains identifiers you meant to strip |
##PEDIGREE | Optional | Family structure for trio analysis | Stale after a sample swap |
The reproducibility argument for this is the same one that drives containerization. CREDO was built as a Dockerfile generator precisely because reproducibility problems in bioinformatics come from inconsistent and non-standardized analysis processes, and it stores all required package information and dependencies in a GitHub-compatible format so images can be rebuilt from scratch [6]. A VCF header is the same idea at file scale. It is the dependency list for the file's contents. If ##source says gatk-4.5.0.0, a reader can rebuild the environment. If it says gatk, the reader can only guess.
Benchmarking depends on this too. VCF Observer enables standardized benchmarking by integrating user-provided ground truth regions and variant lists [5]. That comparison is only meaningful if the two files were called against declared, matching references and normalized the same way. The header is where that is verifiable.
Verification: Proving the Archive Is Correct
Run this sequence after any hand-edited header, any reheader, any merge, and any transfer between systems. It takes under a minute for most cohorts.
F=variants/cohort/PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.release.vcf.gz
bcftools view -h "$F" | head -n 1
bcftools index -n "$F"
bcftools query -l "$F" | wc -l
bcftools view -h "$F" | grep -c '^##contig'
bcftools stats "$F" | grep '^SN' | head -n 5
md5sum -c docs/vcf.md5 | grep -v ': OK$' || echo "all checksums match"
Expected output:
##fileformat=VCFv4.3
40027
24
25
SN 0 number of samples: 24
SN 1 number of records: 40027
all checksums match
The contig count of 25 for GRCh38 reflects 24 primary chromosomes plus mitochondrial DNA, assuming the dictionary omitted alt and decoy contigs. If your dictionary includes them, the number will be much larger and that is also correct. What matters is that the count is stable and recorded.
Three cross-checks catch most real problems:
- The record count from the index matches the record count in the manifest.
- The sample count from
bcftools query -lmatches the row count of the samplesheet. - The reference checksum in the manifest matches the checksum of the FASTA currently in the reference folder.
If any of those three disagree, stop. A mismatch means the file and its documentation describe different objects.
For set comparisons across callers, use bcftools isec and record the output as documentation:
bcftools isec -n=2 -w1 \
-o docs/PRJ-X_gatk_vs_dv_shared.vcf.gz \
PRJ-X_gatk.vcf.gz PRJ-X_deepvariant.vcf.gz
Common Mistakes and Limitations
Renaming files without updating the manifest. The manifest is the identity layer. If the path column goes stale, md5sum -c fails on every row and the team stops trusting the checksum file entirely. Regenerate the manifest after any move.
Compressing with gzip instead of bgzip. The file looks fine and the index step fails. You will see [E::hts_idx_push] Unsorted positions on sequence #1 only if sorting is the problem, and a simple refusal to index if the format is wrong. Re-compress with bcftools view -Oz or bgzip -c.
Forgetting to rebuild the index after any modification. The classic symptom is [E::idx_find_and_load] Could not retrieve index file for 'cohort.vcf.gz' when the index exists but is older than the data. Every operation that writes a new VCF requires a new index.
Editing a bgzipped VCF with a text editor. It truncates the file. You then get [E::bgzf_read_block] EOF marker is absent. The input is probably truncated. There is no recovery from a truncated BGZF stream past the last complete block. Always regenerate from the upstream file.
Undeclared INFO or FORMAT keys. Strict parsers stop with Error: no such tag defined in the VCF header: INFO/ANN. Annotators such as VEP and SnpEff add their own header lines, but hand-built annotations often do not. The fix is one ##INFO line per key, with Number, Type, and Description set correctly.
Undeclared contigs. Interval intersection warns or fails with a message about contig chr1 not being defined in the header. Adding ##contig lines requires the lengths, which come from the .fai. Never invent them.
Assuming PASS and . mean the same thing. . means no filter was applied. PASS means filters were applied and the record passed. An archive full of . in the FILTER column is undocumented, not clean.
Assuming multi-allelic representation is universal. Two files can describe identical variation with different line counts. Splitting, merging, and normalization change record counts, and a reviewer comparing 40,027 to 41,231 will ask why. State the normalization choice in the README.
Trusting the checksum as a correctness proof. MD5 proves the bytes have not changed. It does not prove the call was correct, the sample was labeled correctly, or the reference was the intended build. Checksums detect corruption and silent substitution. They do not detect a pipeline bug.
Leaving identifying information in the header. ##SAMPLE and ##PEDIGREE lines can carry names, family relationships, and phenotypes. Strip or rewrite them before any uncontrolled release, and record that you did.
Depending on a reference path that only exists on one machine. Absolute paths such as /home/analyst/refs/GRCh38.fa in ##reference are honest but fragile. Record the checksum, and prefer a path that is stable within the archive layout.
Limitation: VCF does not store raw evidence. The file records calls and summary statistics, not read alignments. If a variant call is later questioned, the BAM or CRAM is the only way to re-examine it, and that means alignments must be archived alongside the VCF for any project that might be audited.
Limitation: no single tool owns the format. Different callers emit slightly different header sets, and strictness varies. A file that passes one validator can still fail another. Running two validators is cheap insurance.
Any individual genomic dataset still needs a qualified analyst and, for clinical interpretation, a licensed professional. The organizational practices here prevent bookkeeping errors. They do not validate a variant's biological meaning.
Frequently Asked Questions
Should I store VCF, VCF.gz, or BCF?
Store VCF.gz. It is bgzip-compressed text that stays readable, indexes cleanly, and converts to BCF and back without loss. Keep BCF only as a transient working format for compute-heavy steps.
Do I need a .tbi index, and should I commit it to version control?
Yes to the index, no to version control. The .tbi or .csi file is required for regional queries and is cheap to regenerate. Large binary data belongs in object storage or a data versioning tool, not in a git repository.
What belongs in the VCF header versus the README?
The header carries facts about the file itself: reference, source tool and version, contig set, and every INFO, FORMAT, and FILTER definition. The README carries facts about the project: cohort description, pipeline steps, filtering rationale, and regeneration instructions.
How do I organize VCFs from multiple calling pipelines?
Give each pipeline its own filename stem with the caller and version embedded, keep all outputs in the same dated cohort folder, and record the pipeline in a manifest column. Never overwrite one caller's output with another's.
What is the difference between a VCF and a gVCF?
A gVCF includes reference-confidence blocks that record evidence at non-variant positions. It is much larger and is normally an intermediate for joint genotyping rather than an archive endpoint.
How should I name VCF files so they sort sensibly?
Use fixed-order fields separated by underscores, with a hyphen between tool and version, and an ISO 8601 date with no separators. For example PRJ-X_wgs_gatk-4.5.0.0_GRCh38_20261103.vcf.gz.
Can I change a VCF header without rewriting the whole file?
Yes, with bcftools reheader or bcftools annotate -h, both of which rewrite the header while streaming the body. Any header change alters the checksum, so it must be followed by a new index, a new manifest row, and a changelog entry.
How do I document a filtered VCF so someone else can reproduce it?
Record the exact command, the tool version, every threshold, and the input file checksum in docs/methods.txt, and keep the unfiltered input alongside the filtered output. The filtered file's header should name the source tool and version.
Sources
- A comprehensive investigation of researchers’ shared file management practices in cloud storage
- Effective Shared File Management in Cloud Storage for Collaborative Research: Lessons from Successful Cases
- DeNoFo: a file format and toolkit for standardized, comparable de novo gene annotation.
- VIVA (VIsualization of VAriants): A VCF File Visualization Tool.
- VCF observer: a user-friendly software tool for preliminary VCF file analysis and comparison
- CREDO: a friendly Customizable, REproducible, DOcker file generator for bioinformatics applications.