Skip to content
Back to skills

Fastp Fastq Preprocessing

ASecurity

All-in-one FASTQ QC and adapter trimming. Auto-detects Illumina adapters, filters low-quality reads, corrects paired-end overlaps, emits HTML+JSON QC in one pass. 3-10x faster than Trim Galore/Trimmomatic. First step before STAR, BWA-MEM2, or Salmon.

  • 368 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 5, 2026
datapythongoshellbashgitperformancedocumentation

Security analysis

A96/100
  • mediumUses curl or wget to download content

Pro shows the line behind each finding and how to fix it

Scanned September 29, 2026

npx -y skills add jaechang-hits/SciAgent-Skills --skill fastp-fastq-preprocessing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fastp Fastq Preprocessing?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fastp Fastq Preprocessing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jaechang-hits-fastp-fastq-preprocessing/badge)](https://www.skillsdirectory.com/skills/jaechang-hits-fastp-fastq-preprocessing)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: "fastp-fastq-preprocessing"
description: "All-in-one FASTQ QC and adapter trimming. Auto-detects Illumina adapters, filters low-quality reads, corrects paired-end overlaps, emits HTML+JSON QC in one pass. 3-10x faster than Trim Galore/Trimmomatic. First step before STAR, BWA-MEM2, or Salmon."
license: "MIT"
---

# fastp — Fast FASTQ Quality Control and Adapter Trimming

## Overview

fastp performs adapter trimming, quality filtering, and QC reporting for Illumina FASTQ files in a single multi-threaded pass. It automatically detects adapter sequences from paired-end read overlaps — eliminating the need to specify adapters manually. fastp corrects mismatches in paired-end overlap regions, filters reads by quality score and length, removes polyX tails (polyA for RNA-seq), and generates interactive HTML and machine-readable JSON QC reports. Being 3–10× faster than Trim Galore and Trimmomatic while providing comparable or better results, fastp has become the standard preprocessing step before alignment in WGS, RNA-seq, and ChIP-seq pipelines.

## When to Use

- Trimming Illumina adapters and low-quality bases before alignment in any NGS pipeline (RNA-seq, WGS, WES, ChIP-seq, ATAC-seq)
- Generating per-sample QC reports (HTML + JSON) as the first step of a pipeline, before MultiQC aggregation
- Processing paired-end reads where adapter auto-detection from overlap is preferred over manual adapter specification
- Removing polyA tails from RNA-seq reads from 3′ end-enriched protocols (Smart-seq, QuantSeq)
- Splitting a FASTQ file by UMI or by index for demultiplexing workflows
- Use **Trim Galore** as an alternative when TrimGalore's detailed per-base quality report from FastQC is required alongside trimming
- Use **Trimmomatic** as an alternative for fine-grained control of sliding-window trimming steps

## Prerequisites

- **Software**: fastp (conda or pre-compiled binary)
- **Input**: raw Illumina FASTQ files (single-end or paired-end, .fastq or .fastq.gz)

> **Check before installing**: The tool may already be available in the current environment (e.g., inside a `pixi` / `conda` env). Run `command -v fastp` first and skip the install commands below if it returns a path. When running inside a pixi project, invoke the tool via `pixi run fastp` rather than bare `fastp`.

```bash
# Install with conda
conda install -c bioconda fastp

# Or download pre-compiled binary (Linux)
wget https://github.com/OpenGene/fastp/releases/download/v0.24.0/fastp
chmod +x fastp
./fastp --version
# fastp 0.24.0

# Verify
fastp --version
```

## Pre-flight Interview

Settle these with the user before writing any analysis code.

```yaml
decisions:
  - id: D1
    param: -i / -I
    kind: required
    source: data
    ask: "Were these reads sequenced from single-end or paired-ends of each fragment?"
    default: "read off the file inventory - paired when R1/R2 mates are present"

  - id: D2
    param: library_prep
    kind: required
    source: user
    ask: "Which library prep produced these reads - 5'-, 3'-, small RNA, lnRNA or amplicon?"
    default: null

  - id: D3
    param: --trim_poly_x
    kind: optional
    source: user
    depends_on: [D2]
    ask: "Trim polyA/polyT tails left by the capture step?"
    default: "on for 3'-enriched RNA-seq, off otherwise"

  - id: D4
    param: --adapter_sequence / --detect_adapter_for_pe
    kind: required
    source: user
    depends_on: [D1]
    ask: "Auto-detect the adapter, or supply the kit's sequence explicitly?"
    default: "auto-detect"

  - id: D5
    param: --length_required
    kind: required
    source: user
    depends_on: [D2]
    ask: "Reads shorter than this after trimming are discarded - how short is too short to map?"
    default: "15 (raise to ~36 for standard RNA-seq; keep low for small RNA)"

  - id: D6
    param: --qualified_quality_phred
    kind: optional
    source: user
    ask: "How confident must a base call be before it counts as good quality?"
    default: "15 (20 = 1% error)"

  - id: D7
    param: --correction
    kind: optional
    source: user
    depends_on: [D1]
    ask: "Correct mismatched bases where the two mates overlap?"
    default: "off"
    skip_if: "single-end input - there is no overlap to compare"

  - id: D8
    param: --low_complexity_filter
    kind: optional_conditional
    source: data
    ask: "Drop low-complexity reads (homopolymer and short-repeat runs)?"
    default: "off"

  - id: D9
    param: --thread, --split
    kind: never_ask
    source: data
    reason: "Parallelism and output sharding; affect runtime and file layout, not the reads kept"
    default: "8 threads, no split"
```

D2 is asked outright rather than guessed, because the prep drives two later
answers: it decides whether polyA trimming is correct (D3, a 3'-enriched library
ends in a capture tail that is not biological sequence) and where the length
floor belongs (D5 — a 36 bp minimum that is right for RNA-seq discards the
entire read in a small-RNA run). D4 and D7 hang on D1 because both describe what
to do with a mate pair.

## Quick Start

```bash
# Paired-end adapter trimming with QC report
fastp \
    -i sample_R1.fastq.gz \
    -I sample_R2.fastq.gz \
    -o sample_R1.trimmed.fastq.gz \
    -O sample_R2.trimmed.fastq.gz \
    -h sample_qc.html \
    -j sample_qc.json \
    --thread 8

echo "Trimmed reads in: sample_R1.trimmed.fastq.gz"
```

## Workflow

### Step 1: Single-End Adapter Trimming

Run fastp on single-end FASTQ with automatic adapter detection.

```bash
# Single-end with auto adapter detection
fastp \
    -i sample.fastq.gz \
    -o sample.trimmed.fastq.gz \
    -h sample_qc.html \
    -j sample_qc.json \
    --thread 8 \
    --qualified_quality_phred 20 \
    --length_required 36

echo "Input reads:   $(zcat sample.fastq.gz | wc -l | awk '{print $1/4}')"
echo "Output reads:  $(zcat sample.trimmed.fastq.gz | wc -l | awk '{print $1/4}')"
```

### Step 2: Paired-End Adapter Trimming

Process paired-end FASTQ files with overlap-based adapter detection and correction.

```bash
# Paired-end with overlap-based adapter auto-detection
fastp \
    -i sample_R1.fastq.gz \
    -I sample_R2.fastq.gz \
    -o sample_R1.trimmed.fastq.gz \
    -O sample_R2.trimmed.fastq.gz \
    -h sample_qc.html \
    -j sample_qc.json \
    --thread 8 \
    --correction \
    --detect_adapter_for_pe \
    --qualified_quality_phred 20 \
    --length_required 36

# Specify adapters explicitly (if auto-detection fails)
# fastp -i R1.fq.gz -I R2.fq.gz \
#   --adapter_sequence AGATCGGAAGAGCACACGTCTGAACTCCAGTCA \
#   --adapter_sequence_r2 AGATCGGAAGAGCGTCGTGTAGGGAAAGAGTGT \
#   -o R1.out.fq.gz -O R2.out.fq.gz
```

### Step 3: Quality Filtering and Read Length Trimming

Configure quality and length thresholds for stricter or more lenient filtering.

```bash
# Strict quality filtering (e.g., for variant calling)
fastp \
    -i sample_R1.fastq.gz \
    -I sample_R2.fastq.gz \
    -o sample_R1.filtered.fastq.gz \
    -O sample_R2.filtered.fastq.gz \
    -h sample_qc.html \
    -j sample_qc.json \
    --thread 8 \
    --qualified_quality_phred 25 \
    --unqualified_percent_limit 20 \
    --length_required 50 \
    --max_len1 150 \
    --max_len2 150 \
    --low_complexity_filter \
    --complexity_threshold 30

echo "Filtering complete. Check sample_qc.html for pass/fail rates."
```

### Step 4: RNA-seq polyA Tail Removal

Remove polyA tails from 3′-enriched RNA-seq protocols before alignment.

```bash
# Remove polyA tails (QuantSeq 3′ mRNA-seq)
fastp \
    -i quantseq_R1.fastq.gz \
    -o quantseq_R1.trimmed.fastq.gz \
    -h quantseq_qc.html \
    -j quantseq_qc.json \
    --thread 8 \
    --trim_poly_x \
    --poly_x_min_len 10 \
    --qualified_quality_phred 20 \
    --length_required 25

# For Smart-seq2 paired-end with polyA
fastp \
    -i smartseq_R1.fastq.gz \
    -I smartseq_R2.fastq.gz \
    -o smartseq_R1.trimmed.fastq.gz \
    -O smartseq_R2.trimmed.fastq.gz \
    --trim_poly_x --poly_x_min_len 10 \
    --thread 8 \
    -h smartseq_qc.html -j smartseq_qc.json
```

### Step 5: Parse QC Report JSON for Pipeline Monitoring

Extract key QC metrics from fastp's JSON output for automated quality gates.

```python
import json
from pathlib import Path

def parse_fastp_json(json_path: str) -> dict:
    with open(json_path) as f:
        data = json.load(f)
    
    before = data["summary"]["before_filtering"]
    after = data["summary"]["after_filtering"]
    
    return {
        "total_reads_in":  before["total_reads"],
        "total_reads_out": after["total_reads"],
        "pct_passed":      after["total_reads"] / before["total_reads"] * 100,
        "q30_rate_before": before["q30_rate"] * 100,
        "q30_rate_after":  after["q30_rate"] * 100,
        "mean_len_before": before["read1_mean_length"],
        "mean_len_after":  after["read1_mean_length"],
        "adapter_trimmed": data["filtering_result"]["adapter_trimmed"],
    }

metrics = parse_fastp_json("sample_qc.json")
for key, val in metrics.items():
    print(f"{key:25s}: {val:.1f}" if isinstance(val, float) else f"{key:25s}: {val:,}")

# Quality gate: fail if < 70% reads pass filter
if metrics["pct_passed"] < 70:
    print("WARNING: Low pass rate — check raw data quality")
```

### Step 6: Batch Preprocessing Pipeline

Process multiple samples sequentially with per-sample QC summaries.

```bash
#!/bin/bash
# Batch paired-end preprocessing for multiple samples
SAMPLES=(ctrl_1 ctrl_2 treat_1 treat_2)
DATA="data"
OUT="trimmed"
QC="qc/fastp"
THREADS=8

mkdir -p "$OUT" "$QC"

for sample in "${SAMPLES[@]}"; do
    echo "=== Processing $sample ==="
    fastp \
        -i "$DATA/${sample}_R1.fastq.gz" \
        -I "$DATA/${sample}_R2.fastq.gz" \
        -o "$OUT/${sample}_R1.fastq.gz" \
        -O "$OUT/${sample}_R2.fastq.gz" \
        -h "$QC/${sample}.html" \
        -j "$QC/${sample}.json" \
        --thread $THREADS \
        --correction \
        --detect_adapter_for_pe \
        --qualified_quality_phred 20 \
        --length_required 36 \
        2>&1 | grep -E "Read[12]|Filtering|Adapter|passed"
done

# Aggregate QC metrics
python3 - << 'EOF'
import json, pandas as pd
from pathlib import Path

rows = []
for jf in sorted(Path("qc/fastp").glob("*.json")):
    with open(jf) as f: data = json.load(f)
    after = data["summary"]["after_filtering"]
    before = data["summary"]["before_filtering"]
    rows.append({
        "sample": jf.stem,
        "reads_in": before["total_reads"],
        "reads_out": after["total_reads"],
        "pct_passed": round(after["total_reads"]/before["total_reads"]*100, 1),
        "q30_after": round(after["q30_rate"]*100, 1),
    })
df = pd.DataFrame(rows)
print(df.to_string(index=False))
df.to_csv("fastp_summary.tsv", sep="\t", index=False)
EOF

# Run MultiQC to aggregate all fastp JSON reports
multiqc qc/fastp/ -o qc/ -n fastp_multiqc_report
```

## Key Parameters

| Parameter | Default | Range/Options | Effect |
|-----------|---------|---------------|--------|
| `-i / -I` | required | file path | Input FASTQ (R1 and R2 for paired-end) |
| `-o / -O` | required | file path | Output trimmed FASTQ (R1 and R2) |
| `-h / -j` | — | file path | HTML and JSON QC report output paths |
| `--thread` | `3` | 1–16 | CPU threads; 8 is a good balance |
| `--qualified_quality_phred` | `15` | 0–40 | Minimum base quality (Phred); 20 = 1% error |
| `--length_required` | `15` | 1–1000 | Minimum read length after trimming; discard shorter reads |
| `--correction` | off | flag | Correct mismatches in PE overlap region |
| `--detect_adapter_for_pe` | off | flag | Enable overlap-based adapter auto-detection for PE data |
| `--adapter_sequence` | auto | string | Explicit R1 adapter; overrides auto-detection |
| `--trim_poly_x` | off | flag | Trim polyX (polyA/polyT) tails; use for 3′-enriched RNA-seq |
| `--low_complexity_filter` | off | flag | Filter reads with low complexity (< 30% complexity by default) |
| `--split` | off | integer | Split output into N files per direction (for parallelism) |

## Common Recipes

### Recipe 1: Integrate fastp into a Snakemake Pipeline

```python
# Snakefile — fastp trimming rule
configfile: "config.yaml"
SAMPLES = config["samples"]

rule fastp_pe:
    input:
        r1 = "data/{sample}_R1.fastq.gz",
        r2 = "data/{sample}_R2.fastq.gz"
    output:
        r1 = "trimmed/{sample}_R1.fastq.gz",
        r2 = "trimmed/{sample}_R2.fastq.gz",
        html = "qc/{sample}_fastp.html",
        json = "qc/{sample}_fastp.json"
    threads: 8
    shell:
        """
        fastp -i {input.r1} -I {input.r2} \
              -o {output.r1} -O {output.r2} \
              -h {output.html} -j {output.json} \
              --thread {threads} \
              --correction --detect_adapter_for_pe \
              --qualified_quality_phred 20 \
              --length_required 36
        """
```

### Recipe 2: Aggregate fastp JSON Reports with Python

```python
import json
import pandas as pd
from pathlib import Path

qc_dir = Path("qc/fastp")
records = []

for jf in sorted(qc_dir.glob("*.json")):
    with open(jf) as f:
        d = json.load(f)
    b = d["summary"]["before_filtering"]
    a = d["summary"]["after_filtering"]
    records.append({
        "sample": jf.stem.replace("_fastp", ""),
        "reads_in_M": b["total_reads"] / 1e6,
        "reads_out_M": a["total_reads"] / 1e6,
        "pct_passed": a["total_reads"] / b["total_reads"] * 100,
        "q30_pct": a["q30_rate"] * 100,
        "mean_len_bp": a["read1_mean_length"],
        "adapter_pct": d["filtering_result"]["adapter_trimmed"] / b["total_reads"] * 100,
    })

df = pd.DataFrame(records).round(2)
print(df.to_string(index=False))

# Flag low-quality samples
low_q = df[df["pct_passed"] < 80]
if not low_q.empty:
    print(f"\nSamples with < 80% reads passing: {list(low_q['sample'])}")
```

## Expected Outputs

| Output | Format | Description |
|--------|--------|-------------|
| `*_R1.trimmed.fastq.gz` | FASTQ.gz | Trimmed R1 reads (adapters and low-quality bases removed) |
| `*_R2.trimmed.fastq.gz` | FASTQ.gz | Trimmed R2 reads (paired-end only) |
| `*.html` | HTML | Interactive QC report with per-base quality, GC content, adapter plots |
| `*.json` | JSON | Machine-readable QC metrics for automation and MultiQC parsing |
| `fastp.log` | Text | stderr summary with pass/fail read counts and filtering statistics |

## Troubleshooting

| Problem | Cause | Solution |
|---------|-------|----------|
| Adapter not detected in SE mode | SE reads require explicit adapter or `--adapter_sequence` | Use `--detect_adapter_for_pe` only for PE; specify adapter for SE: `--adapter_sequence AGATCGGAAGAGC` |
| Very high adapter content (> 50%) | Short inserts (small RNA, miRNA) or poor library prep | Check library protocol; use `--overlap_len_require 10` to adjust overlap sensitivity |
| Too many reads filtered (< 60% pass) | Over-strict quality thresholds or low-quality sequencing run | Relax `--qualified_quality_phred` to 15; lower `--length_required` to 25 |
| JSON output missing fields | Old fastp version | Upgrade: `conda update fastp` or download latest binary from GitHub |
| MultiQC not parsing fastp JSON | JSON file not in the scanned directory | Run `multiqc qc/` not `multiqc .`; verify JSON files exist with `ls qc/*.json` |
| Output FASTQ is empty | All reads filtered (wrong input or extreme thresholds) | Verify input FASTQ with `zcat sample.fq.gz \| head -8`; run without `--low_complexity_filter` first |
| Slow performance on large files | Low thread count | Increase `--thread` to 8–12; ensure input is on fast storage (SSD) |
| polyA not removed | `--trim_poly_x` not set | Add `--trim_poly_x --poly_x_min_len 10` for 3′-enriched protocols |

## References

- [fastp GitHub: OpenGene/fastp](https://github.com/OpenGene/fastp) — source code, changelog, and usage guide
- Chen S et al. (2018) "fastp: an ultra-fast all-in-one FASTQ preprocessor" — *Bioinformatics* 34(17):i884-i890. [DOI:10.1093/bioinformatics/bty560](https://doi.org/10.1093/bioinformatics/bty560)
- [fastp documentation](https://github.com/OpenGene/fastp#readme) — full parameter reference and recipes
- [MultiQC fastp module](https://multiqc.info/modules/fastp/) — aggregating fastp JSON reports across samples

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.