Written and maintained by CASRAI Editorial Board
Last updated
A BED file (Browser Extensible Data) is a plain-text, tab-delimited format for describing genomic intervals — a chromosome, a start coordinate, an end coordinate, and up to nine optional fields for name, score, strand, and exon structure. It is the working format for genomic regions across bioinformatics tooling: peak calls from ChIP-seq or ATAC-seq, exon and gene models, capture-panel targets, and the region files fed to bedtools, UCSC and Ensembl browsers, and variant annotators. The format itself is simple. The part that causes real bugs is its coordinate system: BED start coordinates are 0-based and its intervals are half-open, which is not how VCF, GFF3, GTF, or SAM/BAM express position. Converting between them without accounting for that difference is one of the most common silent errors in a genomics pipeline — silent because the script runs, produces a file, and is off by exactly one base at every boundary.
What a BED file contains
Each line of a BED file is one interval, with fields separated by tabs, not spaces. The specification (maintained as UCSC’s BED format documentation, part of the same family as the genome browser ecosystem) defines three required fields and nine optional ones, and a file can stop at any point after the third column — BED3, BED4, BED6, and BED12 are all valid, named for how many columns they carry.
The three required columns
| Column | Field | Definition |
|---|---|---|
| 1 | chrom |
Chromosome or scaffold name (e.g. chr1, chrX, scaffold_12). |
| 2 | chromStart |
The starting position of the feature. The first base of a chromosome is numbered 0. |
| 3 | chromEnd |
The ending position of the feature. The base at this position is not part of the feature — the interval is half-open. |
The nine optional columns (BED4–BED12)
| Column | Field | Definition |
|---|---|---|
| 4 | name |
A label for the BED line, shown on browser tracks. |
| 5 | score |
An integer from 0–1000, often used to shade the feature on a browser display. |
| 6 | strand |
+, -, or . for unstranded/unknown. |
| 7 | thickStart |
Where the “thick” (typically coding) part of the feature begins, for display. |
| 8 | thickEnd |
Where the thick part ends. |
| 9 | itemRgb |
An R,G,B triplet controlling display colour, e.g. 255,0,0. |
| 10 | blockCount |
Number of sub-features (typically exons) within the line. |
| 11 | blockSizes |
Comma-separated list of block (exon) sizes. |
| 12 | blockStarts |
Comma-separated list of block start positions, relative to chromStart. |
Columns 7–12 exist specifically to describe a multi-exon transcript as a single BED line: chromStart/chromEnd span the whole gene model, while blockCount/blockSizes/blockStarts carve out the individual exons within it. A tool reading a BED12 file has to combine all twelve fields to reconstruct the exon structure — there is no separate “exon” line the way GFF3 uses parent/child feature rows.
0-based, half-open: what the coordinate system actually means
Two independent design choices combine to define BED coordinates, and it is worth separating them:
- 0-based: the first base of a chromosome is position
0, not position1. This applies tochromStart. - Half-open: the interval
[chromStart, chromEnd)includeschromStartbut excludeschromEnd— the base atchromEndis the first base not in the feature.
Put together, the number of bases in a BED feature is simply chromEnd − chromStart, with no off-by-one correction needed — a property that makes half-open coordinates convenient for programmatic interval arithmetic (concatenating two adjacent intervals, computing overlaps, or measuring length all reduce to plain subtraction). The trade-off is that chromStart and chromEnd are not symmetric: chromStart is 0-based, but because chromEnd points one base past the end of the feature, it has the same numeric value as the 1-based, inclusive coordinate a biologist would read off a browser or a paper. That asymmetry is exactly what trips people up when they convert by hand.
Why VCF, GFF3, GTF, and SAM disagree with BED
BED is the exception, not the rule, among genomics formats. VCF’s POS field, GFF3’s and GTF’s start/end columns, and SAM’s POS field are all 1-based and fully closed: the first base of a chromosome is position 1, and an interval’s start and end coordinates are both included in the feature. A 10-base feature beginning at the first base of a chromosome is start=1, end=10 in a VCF or GFF3 row, but chromStart=0, chromEnd=10 in a BED row — the end coordinate is numerically identical across the two systems; only the start shifts by one.
Worked example: the same interval, two conventions
Take a feature spanning eleven bases, positions 1000 through 1010 inclusive, as it would appear read off a genome browser or written in a GFF3/VCF-style 1-based file:
| Representation | Start | End | Bases covered | Feature length |
|---|---|---|---|---|
| 1-based, fully-closed (GFF3, GTF, VCF, SAM) | 1000 | 1010 | 1000, 1001, … 1010 | end − start + 1 = 11 |
| 0-based, half-open (BED) | 999 | 1010 | 999, 1000, … 1009 | chromEnd − chromStart = 11 |
Both rows describe the identical eleven bases of sequence. The conversion rule that falls out of the example generalizes cleanly: to go from a 1-based closed interval to BED, subtract 1 from the start and leave the end unchanged; to go the other direction, add 1 to chromStart and leave chromEnd unchanged. Get the direction backwards, or apply the subtraction to both ends instead of just the start, and every feature in the output file is shifted by one base — a bug that produces a file that loads, runs through bedtools without error, and returns results that are subtly wrong at every boundary.
Where the off-by-one bug actually bites
This is not a theoretical concern. The conversion between coordinate systems happens constantly in a real analysis, because BED-format tools like bedtools are the standard way to intersect, merge, and annotate intervals, while the files coming out of variant callers, annotation databases, and alignment steps are 1-based:
- VCF → BED, to intersect variants against a target region. Naively copying
POSinto bothchromStartandchromEnd(instead ofPOS−1andPOS) produces a zero-length interval that silently fails to match anything, or a single-base shift depending on which side is miscoded. - GFF3/GTF → BED, to extract exon or gene regions. The same subtract-one-from-start rule applies to the
startcolumn; forgetting it shifts every feature’s 5′ boundary by one base, which matters most at the transcription start site and splice junctions. - BED → VCF-style reporting, when publishing or comparing coordinates. Reporting a BED
chromStartdirectly as a “1-based position” without adding 1 understates every start coordinate by one base — a common source of coordinates that are off by exactly one when a result set is checked against a paper or a browser. - Liftover between genome builds. Coordinate-liftover tools generally expect BED-style 0-based half-open input; feeding them 1-based coordinates unmodified introduces the same one-base error into every lifted feature.
The reason this class of bug is so persistent is that it does not throw an error. A shifted BED file has the right number of lines, the right chromosomes, and intervals that are almost always the right length — it is wrong by exactly one base at each boundary, which is easy to miss in a spot check and can matter enormously at a splice site, a transcription start site, or a small variant call.
Converting between coordinate systems correctly
The general-purpose fix is to be explicit about the subtraction rather than relying on a tool’s default. Converting single-base VCF positions to BED with awk, skipping header lines:
awk 'BEGIN{OFS="\t"} !/^#/ {print $1, $2-1, $2, $3}' variants.vcf > variants.bed
Here $2 is the VCF POS column; $2-1 becomes chromStart and the unmodified $2 becomes chromEnd — a single-base variant becomes a one-base-wide BED interval. For multi-base indels, check how your variant caller reports POS and the reference/alt allele lengths before assuming this simple form is sufficient; a deletion spanning several bases needs chromEnd extended by the deleted length, not left at POS.
Converting a GFF3 or GTF feature (columns 4 and 5 hold start/end in both formats) to BED follows the same pattern:
awk 'BEGIN{OFS="\t"} !/^#/ {print $1, $4-1, $5, $3}' features.gff3 > features.bed
Where a full-featured converter already exists — UCSC’s gtfToGenePred/genePredToBed chain, or a library function in a package like pybedtools or Bioconductor’s rtracklayer — prefer it over a hand-rolled awk line for anything beyond a quick check; those tools already handle the exon-block and strand edge cases that a one-line script does not. Whichever route you take, spot-check the result against a known feature in a genome browser (which displays 1-based coordinates) before trusting the converted file downstream, and treat any pipeline step that touches genomic coordinates as needing an explicit, commented note on which convention is in play at that point — especially in a Snakemake or Nextflow pipeline where a conversion step can be buried several rules deep and easy to lose track of.
Related interval-based formats
Several other formats extend or piggyback on BED’s conventions, and are worth distinguishing:
- bedGraph — BED4 restricted so the fourth column is a numeric signal value rather than free text, used for coverage tracks and continuous scores. Same 0-based, half-open coordinates as BED.
- BEDPE — pairs two BED intervals on one line (
chrom1, start1, end1, chrom2, start2, end2, ...), used bybedtoolsfor paired-end and structural-variant data where a “feature” connects two separate genomic locations. - narrowPeak / broadPeak (ENCODE formats) — BED6+3/BED6+4 extensions used for ChIP-seq and ATAC-seq peak calls, adding signal, p-value, and q-value columns on top of the standard BED6 fields.
All three inherit BED’s coordinate system, so the same conversion caution applies to each of them.
Frequently asked questions
Is a BED file 0-based or 1-based?
chromStart is 0-based. chromEnd is numerically what a 1-based, inclusive end coordinate would be, because the interval is half-open — so the honest answer is “both,” depending on which column you’re looking at, which is exactly why hand-converting a single coordinate without checking whether it is a start or an end is a common source of error.
Why do half-open intervals make length calculation easier?
Because chromEnd − chromStart gives the feature length directly, with no +1 or -1 correction. In a fully-closed system like VCF or GFF3, length is end − start + 1; forgetting the +1 there is its own, mirror-image off-by-one bug.
Does GTF use the same coordinates as GFF3?
Yes. GTF (Gene Transfer Format) is a restricted dialect of GFF2 with a fixed attribute structure, but it shares GFF’s 1-based, fully-closed coordinate convention for the start/end columns — the same conversion rule to and from BED applies to both.
Is a SAM/BAM alignment position 0-based or 1-based?
SAM’s POS field is 1-based, matching VCF and GFF3, not BED — a common assumption to double-check when a pipeline moves data between alignment output and interval files. This is a separate coordinate question from the per-base Phred quality scores carried in the same alignment record, which describe base-call confidence rather than position.
Where does BED coordinate confusion typically surface in a real pipeline?
Most often at the seam between two tools written by different groups: a variant caller or aligner producing 1-based output feeding into a bedtools-based filtering or annotation step, or a custom region file built by hand for a targeted panel or a differential expression analysis and then intersected against BED-format peak calls. Any pipeline step, whether run interactively or scheduled as part of a batch job on shared compute, is worth an explicit unit test on a small, known interval before it runs on a full dataset.
Does the coordinate system depend on which tool or environment I’m using?
No — 0-based/half-open is a property of the BED file format itself, not of any particular tool, and it holds regardless of whether you built your environment with conda, pip, or mamba. A tool can choose to display 1-based coordinates to a user while storing 0-based coordinates internally (most genome browsers do exactly this), but the underlying BED file on disk is always 0-based, half-open by specification.








