# How bat Detects File Language for Syntax Highlighting: The 5-Stage Algorithm Explained

> Discover how bat detects file language for syntax highlighting with its unique 5-stage algorithm. Learn about explicit mappings, file names, extensions, shebangs, and fallback to plain text.

- Repository: [David Peter/bat](https://github.com/sharkdp/bat)
- Tags: internals
- Published: 2026-03-06

---

**bat determines file language through a deterministic five-stage pipeline that prioritizes explicit user mappings, file names, extensions, and first-line shebangs before falling back to plain text.**

The `bat` command-line tool by sharkdp/bat delivers syntax highlighting for hundreds of programming languages through an intelligent, multi-layered detection system. Understanding how bat detects the language of a file for syntax highlighting requires examining the Rust source code in [`src/assets.rs`](https://github.com/sharkdp/bat/blob/main/src/assets.rs) and [`src/syntax_mapping.rs`](https://github.com/sharkdp/bat/blob/main/src/syntax_mapping.rs), which implement a cascading priority algorithm designed for both speed and accuracy.

## The Five-Stage Language Detection Pipeline

The detection process is orchestrated by `HighlightingAssets::get_syntax` in [`src/assets.rs`](https://github.com/sharkdp/bat/blob/main/src/assets.rs) (lines 210-242). When processing input, bat evaluates these stages in strict sequential order until a match is found.

### Stage 1: Explicit CLI Language Override

If the user specifies `--language <name>` or `-l <name>`, bat immediately uses the corresponding syntax definition without executing any file analysis. This bypasses all automatic detection heuristics.

### Stage 2: Built-in and Runtime Syntax Mappings

Bat first consults `SyntaxMapping::get_syntax_for` in [`src/syntax_mapping.rs`](https://github.com/sharkdp/bat/blob/main/src/syntax_mapping.rs) (lines 148-168). This function matches the full file path against glob patterns from two sources:

- **Built-in mappings**: Compiled into `BUILTIN_MAPPINGS` in [`src/syntax_mapping/builtin.rs`](https://github.com/sharkdp/bat/blob/main/src/syntax_mapping/builtin.rs) (lines 51-84), generated at build time via [`build/syntax_mapping.rs`](https://github.com/sharkdp/bat/blob/main/build/syntax_mapping.rs)
- **Runtime mappings**: Added via `--map-syntax` or `--map-extension` CLI arguments

Mappings can explicitly target a syntax (`MapTo("<syntax>")`) or defer to later stages (`MapToUnknown`).

### Stage 3: File Name and Extension Analysis

When no mapping matches, bat examines the path components via `HighlightingAssets::get_syntax_for_path`. This stage executes two specific checks in [`src/assets.rs`](https://github.com/sharkdp/bat/blob/main/src/assets.rs):

- **File name detection**: `get_syntax_for_file_name` (lines 172-176) performs exact lookups for names like `Dockerfile` or `Makefile` using the syntect syntax set
- **Extension detection**: `get_syntax_for_file_extension` (lines 188-194) extracts the substring after the final `.` and queries `syntax_set.find_syntax_by_extension`

Both functions utilize `IgnoredSuffixes` to strip backup suffixes like `~` or `.bak` before matching.

### Stage 4: First-Line Heuristic Detection

If path-based detection returns `Error::UndetectedSyntax`, bat invokes `HighlightingAssets::get_first_line_syntax` in [`src/assets.rs`](https://github.com/sharkdp/bat/blob/main/src/assets.rs) (lines 304-316). This reads the file's first line—stripping any UTF-8 BOM—and passes it to `syntax_set.find_syntax_by_first_line` to detect shebangs (`#!/usr/bin/env python`), XML declarations (`<?xml`), or PHP tags (`<?php`).

### Stage 5: Plain Text Fallback

When all detection attempts fail, the `UndetectedSyntax` error is caught within `HighlightingAssets::get_syntax` (lines 231-242), triggering a fallback to the plain-text syntax. This guarantees bat always produces readable output even for unknown file types.

## Handling Backup Files and Ignored Suffixes

Before executing name or extension comparisons, bat processes filenames through `IgnoredSuffixes` in [`src/syntax_mapping/ignored_suffixes.rs`](https://github.com/sharkdp/bat/blob/main/src/syntax_mapping/ignored_suffixes.rs). This module strips common backup suffixes (e.g., `file.rs~`, `config.bak`) ensuring that temporary and backup files receive the same syntax highlighting as their originals.

## Language Detection Code Examples

The detection algorithm exposes a programmatic API that CLI options invoke internally:

```rust
// Explicit language via CLI equivalent (e.g., `bat --language Rust file.txt`)
let syntax = assets.get_syntax(Some("Rust"), &mut opened, &mapping)?;

```

```rust
// Standard path-based detection flow
let path = Path::new("src/main.rs");
let syntax_ref = assets.get_syntax_for_path(path, &mapping)?;   // Returns "Rust"

```

```rust
// First-line detection for scripts without extensions
let mut input = Input::stdin().with_name(Some("my_script"));
let mut opened = input.open(b"#!/usr/bin/env python\nprint('hi')", None)?;
let syntax = assets.get_syntax(None, &mut opened, &mapping)?; // Returns "Python"

```

```rust
// Adding custom runtime mappings
mapping.insert("*.myext", MappingTarget::MapTo("C")).ok(); // Maps *.myext to C syntax

```

## Summary

- bat implements a **deterministic five-stage pipeline** for language detection, defined in [`src/assets.rs`](https://github.com/sharkdp/bat/blob/main/src/assets.rs) and [`src/syntax_mapping.rs`](https://github.com/sharkdp/bat/blob/main/src/syntax_mapping.rs)
- **Syntax mappings** (built-in and user-defined) take highest priority after explicit CLI flags, evaluated via `SyntaxMapping::get_syntax_for`
- **File name and extension** detection uses exact matching and syntect integration, with `IgnoredSuffixes` handling backup files
- **First-line heuristics** analyze shebangs and markers when path-based methods return `UndetectedSyntax`
- A **plain-text fallback** ensures bat never fails to display file contents, even for unrecognized formats

## Frequently Asked Questions

### How does bat handle files with multiple extensions like `.tar.gz`?

bat examines the final extension after the last dot via `get_syntax_for_file_extension` in [`src/assets.rs`](https://github.com/sharkdp/bat/blob/main/src/assets.rs) (lines 188-194). For `.tar.gz`, it detects `gz`. Complex multi-extension logic is typically handled by built-in glob mappings in `BUILTIN_MAPPINGS` rather than the extension detector itself.

### Can I force bat to treat a file as a specific language without changing the file name?

Yes. Use the `--language` or `-l` flag to bypass all automatic detection. Alternatively, add a runtime mapping with `--map-syntax 'pattern:syntax'` which inserts into the `SyntaxMapping` structure evaluated by `get_syntax_for` in [`src/syntax_mapping.rs`](https://github.com/sharkdp/bat/blob/main/src/syntax_mapping.rs).

### Why does bat correctly highlight my `Makefile~` backup but other tools fail?

bat strips ignored suffixes like `~` and `.bak` via the `IgnoredSuffixes` processor in [`src/syntax_mapping/ignored_suffixes.rs`](https://github.com/sharkdp/bat/blob/main/src/syntax_mapping/ignored_suffixes.rs) before checking the file name against syntax definitions. This ensures backup files inherit the original file's language detection.

### What happens if bat cannot detect any language from the file extension or content?

If `HighlightingAssets::get_syntax` encounters `Error::UndetectedSyntax` after all detection stages (lines 231-242 in [`src/assets.rs`](https://github.com/sharkdp/bat/blob/main/src/assets.rs)), it automatically falls back to the plain-text syntax definition. This guarantees output is always readable, even for binary or unrecognized text formats.