Conversion · BAM → CRAM
How to Convert BAM to CRAM (and What Changes Between Builds)
CRAM cuts your disk usage roughly in half and quietly makes the file unreadable without the exact reference FASTA it was built against.
By Ming "Tommy" Tang, Director of Bioinformatics in Big Pharma · Reviewed September 2026 · 3 min read
- BAM
- .bam, .bai · coordinates: 1-based-closed
- CRAM
- .cram, .crai · coordinates: 1-based-closed
You convert BAM to CRAM when storage cost matters more than universal compatibility: archiving a cohort of aligned BAMs before submitting to SRA or EGA, or reclaiming cluster space once variant calling is done and you no longer need instant read-level access. CRAM gets you roughly 40-50% smaller files than BAM for the same alignments, because instead of storing every base call it stores only the bases that differ from a reference sequence.
Nothing about the alignment data itself changes: same reads, same positions, same FLAGs, same coordinate system. What changes is the dependency you take on. The CRAM header records the exact reference FASTA used at compression time, matched contig by contig with an MD5 checksum, and samtools needs that same FASTA to decode anything back out. A BAM is self-contained; a CRAM is not.
The failure that actually bites people isn't a coordinate shift, it's losing the reference. You convert, the CRAM opens fine, you delete the BAM to reclaim the space you converted for, and eighteen months later you need a region and the FASTA you originally aligned against has moved, been renamed, or quietly gotten swapped for a newer patch release with the same file name. samtools usually refuses to decode a mismatched reference, which is annoying but safe. The genuinely silent version is when MD5 validation gets skipped somewhere in a pipeline and you get reads back mapped against the wrong reference with no error at all. Keep the exact FASTA, forever, next to the CRAM, or embed it.
The commands
Type your file names once; every command below updates.
01samtools
bashsamtools view -C -T reference.fa -o sample.cram sample.bam-C writes CRAM output, -T points at the exact reference FASTA used when the BAM was aligned (matched by per-contig MD5), -o writes to {output}. Assumes {input} is already coordinate-sorted; unsorted input still converts but breaks region-based access afterward.
02samtools
bashsamtools sort sample.bam -o sample.cramRun this first if {input} isn't already coordinate-sorted by chromosome and position. CRAM inherits BAM's sort order, and anything that seeks by region against the CRAM assumes that order is correct.
03samtools
bashsamtools index sample.cramBuilds the .crai (or .bai) index on the file just produced by the previous step, so {output} here is that file, not the original {input}. Needed before region queries or genome browsers can seek into it.
04samtools
bashsamtools view -C --output-fmt-option store_md=1 --output-fmt-option store_nm=1 -T reference.fa -o sample.cram sample.bamSame conversion as the first command, but forces the MD (mismatch string) and NM (edit distance) tags to be written explicitly instead of recomputed on the fly. Use this if a downstream variant caller or QC script reads those tags directly rather than deriving them from CIGAR plus reference.
Coordinates, strand, names, builds
BAM and CRAM share the same coordinate handling: samtools reports 1-based, closed-interval positions for both in text output, and both track alignment start through the same underlying offset machinery, so converting from one to the other changes none of that. Strand lives in the FLAG field exactly as it did in the BAM; -C doesn't touch it. Chromosome naming is inherited verbatim from the BAM's @SQ header, so a BAM using "chr1" produces a CRAM using "chr1", and a BAM using "1" produces a CRAM using "1" - the naming risk isn't drift during conversion, it's mismatch between that header and whatever reference FASTA you hand to -T afterward. Genome build is where this pair actually breaks: CRAM stores no human-readable build label, only an MD5 checksum per contig, so "the same build" pulled from two different sources (a patched vs unpatched primary assembly, one with alt contigs and one without) can produce two different checksums and the file simply won't decode. There's no loss in the read data itself; the loss is portability, since a CRAM without its matching FASTA is closer to unreadable than a corrupt BAM.
Check the output before you trust it
01Read counts match between BAM and CRAM
bashsamtools flagstat input.bam; samtools flagstat output.cramExpected Total, mapped, and duplicate counts are identical between the two flagstat reports; any difference means the conversion silently dropped or filtered reads.
02CRAM decodes cleanly with the recorded reference
bashsamtools view output.cram | head -3Expected The first few alignment records print normally; a 'Failed to populate reference' or MD5 mismatch error means the FASTA passed to -T doesn't match what generated the CRAM.
03Header carries per-contig M5 checksums
bashsamtools view -H output.cram | grep '^@SQ'Expected Every @SQ line has an M5: field; a missing M5 means the CRAM can't be validated against any reference at all.
04File size actually shrank
bashls -lh input.bam output.cramExpected The CRAM is roughly 40-50% smaller than the BAM for typical short-read data; if it's the same size or larger, check that -C actually took effect.
05Contig names match between the BAM header and the reference FASTA
bashsamtools view -H input.bam | grep '^@SQ' | head -3; grep '>' reference.fa | head -3Expected The same naming convention on both sides (chr1 vs 1); a mismatch here is what actually causes cram_populate_ref failures, not a genuinely wrong reference build.
Errors you will see, and what they mean
- [E::cram_populate_ref] Failed to populate reference for id 0
- Cause: The FASTA passed to -T doesn't match the reference recorded in the CRAM header, usually because contig names differ (chr1 vs 1) or it's a different patch level or assembly than what was used at alignment time. Fix: Recover the exact FASTA used for alignment, checking the BAM header's @SQ SN: names and any aligner logs, and pass that exact file to -T. Don't substitute 'the same build' pulled from a different source.
- CRAM conversion fails, or produces a corrupt file, when the reference FASTA has descriptive headers
- Cause: Some samtools versions parse everything after the first whitespace on a FASTA header as part of the sequence name, so '>chr1 Homo sapiens chromosome 1' gets treated as a different contig than the BAM's 'chr1'. Fix: Strip FASTA headers down to bare contig names ('>chr1', not '>chr1 description') before using the file with -T, and regenerate the .fai index afterward.
- A CRAM that worked on one machine can't be opened on another
- Cause: The reference FASTA lives at a machine-specific path or was never copied alongside the CRAM; CRAM needs the exact bytes of that FASTA, not just 'the hg38 reference' from wherever it happens to be installed on the new machine. Fix: Archive the exact reference FASTA next to the CRAM, or convert with --output-fmt-option embed_ref=1 to store the reference inside the CRAM at the cost of some space savings.
- samtools view on the CRAM runs and returns reads, but with wrong or garbled bases and no error at all
- Cause: A reference with the same file name and roughly the same size was substituted, such as a re-downloaded 'GRCh38.fa' at a different patch level, and MD5 validation was skipped or never checked against the contig actually queried. Fix: Always run 'samtools view -H | grep M5' and compare it against a checksum of the file you're about to pass to -T before trusting output from an unfamiliar CRAM; never assume the same filename means the same content.
Questions people ask
- How much smaller is a CRAM file than the equivalent BAM?
Expect roughly 40-50% smaller for typical short-read alignments, since CRAM stores only the bases that differ from the reference instead of every base call. The exact savings depend on read length, error rate, and how much of the genome is reference-matching versus real variation.
- Do I need to keep the original BAM after converting to CRAM?
No, but you must keep the exact reference FASTA the CRAM was built against, matched by the MD5 checksums stored in its header. Losing the BAM is fine; losing or swapping the reference makes the CRAM unreadable.
- Can I convert a CRAM back to BAM?
Yes:
samtools view -b -T reference.fa -o output.bam input.cramreconstructs a full BAM, as long as you supply the same reference FASTA the CRAM was built against. Without that exact FASTA, samtools has no way to reconstruct the base calls it never stored.- Does converting BAM to CRAM change genome build or coordinates?
No. Conversion doesn't touch alignment positions, FLAGs, or the coordinate system at all; it only changes how the file is compressed. Genome build only matters because CRAM needs a specific reference FASTA to decode, and a build mismatch fails on that dependency, not on the coordinates themselves.
- What does the 'embed reference' option do and when should I use it?
--output-fmt-option embed_ref=1stores the reference sequence inside the CRAM instead of relying on an external FASTA, trading away some space savings for a self-contained file. Use it for long-term archival, or whenever you can't guarantee the exact reference FASTA will stay available at a stable path.
Related pages
Related reading on the blog
Sources
- CRAM format specification (version 3.1) — Specifies the M5 checksum and UR reference fields that make a CRAM dependent on an exact reference FASTA
- Cram-JS: reference-based decompression in node and the browser — Source for the 40-50% space savings of CRAM versus BAM