Nextflow Subworkflows
Subworkflows
A subworkflow is a named workflow that can be called from another workflow, just like a process. Where a process wraps a single tool or command, a subworkflow wraps a sequence of processes that you want to treat as a unit.
You have already seen workflows in main.nf. A subworkflow is the same
concept, but it is defined in its own file and imported with include rather
than living in main.nf directly.
When to Extract a Subworkflow
The main motivation for a subworkflow is eliminating duplicated logic across projects. In this course, several analysis steps appear in multiple projects with nearly identical code:
- FastQC before and after trimming
- Samtools sort → index → flagstat on aligned BAMs
- Collecting QC files and running MultiQC
Writing this logic once as a subworkflow and importing it means that a correction or improvement propagates to every project automatically rather than requiring the same change to be made in multiple places.
The other motivation is readability. A main.nf that calls READS_QC,
ALIGN, and BAM_QC is easier to understand at a glance than one that
inlines thirty lines of channel wiring for each of those steps.
Subworkflow File Structure
By convention, subworkflows for this course live in projects/subworkflows/.
Each subworkflow is its own .nf file:
projects/
├── subworkflows/
│ ├── reads_qc.nf
│ ├── bam_qc.nf
│ └── multiqc_report.nf
├── project-2-nextflow-solutions/
│ ├── main.nf
│ └── modules/
└── project-3-nextflow-solutions/
├── main.nf
└── modules/
Defining a Subworkflow
A subworkflow file looks like a normal module file, but contains a workflow
block instead of a process block. It also needs nextflow.preview.types = true
if the take: and emit: sections use type annotations.
Here is the READS_QC subworkflow as a full example:
nextflow.preview.types = true
include { FASTQC } from '../project-2-nextflow-solutions/modules/fastqc'
include { TRIM } from '../project-2-nextflow-solutions/modules/trimmomatic'
record Sample {
id: String
reads: List<Path>
}
record TrimmedSample {
id: String
reads: List<Path>
trim_log: Path
}
workflow READS_QC {
take:
samples_ch: Channel<Sample>
adapters: Path
main:
pre_trim_qc = FASTQC(samples_ch)
trimmed_ch = TRIM(samples_ch, adapters)
post_trim_qc = FASTQC(trimmed_ch.map { s -> record(id: s.id, reads: s.reads) })
emit:
trimmed: Channel<TrimmedSample> = trimmed_ch
fastqc_pre: Channel<Record> = pre_trim_qc
fastqc_post: Channel<Record> = post_trim_qc
}
The structure of a subworkflow mirrors that of a regular workflow:
take:— the inputs the subworkflow expects, with type annotationsmain:— the processing logicemit:— the named outputs the subworkflow produces, with type annotations
Typed take: and emit:
Type annotations in take: and emit: serve two purposes. First, they
document the contract: anyone reading the file can immediately see what type of
data the subworkflow expects and produces without tracing through the logic.
Second, the language server uses the annotations to validate that the channels
you pass in are compatible and that the channels you receive back are used
correctly.
The type annotation syntax is the same as in process inputs and workflow parameters:
take:
samples_ch: Channel<Sample> // a channel of Sample records
adapters: Path // a single file path (dataflow value)
Emit annotations use an assignment form:
emit:
trimmed: Channel<TrimmedSample> = trimmed_ch
report: Value<Path> = report_file
If the declared type does not match the actual type of the assigned value, the language server will report an error.
Importing a Subworkflow
Subworkflows are imported with include, exactly like processes:
include { READS_QC } from '../../subworkflows/reads_qc'
include { BAM_QC } from '../../subworkflows/bam_qc'
The path is relative to the file doing the importing. Because subworkflows
live one level above the project directories, you need to go up two levels from
a main.nf inside a project folder.
Calling a Subworkflow
A subworkflow is called exactly like a process — by name, with its inputs as arguments. The return value is the combined record of all emitted outputs:
workflow {
read_pairs_ch = channel.fromList(params.reads)
qc_out = READS_QC(read_pairs_ch, params.adapters)
align_out = ALIGN(qc_out.trimmed, params.genome)
bam_out = BAM_QC(align_out.bam)
MULTIQC_REPORT(
qc_out.fastqc_pre
.mix(qc_out.fastqc_post)
.mix(bam_out.flagstat)
.flatMap { r -> r.files }
.collect()
)
}
Each emitted output is accessed as a field on the return value, using the name
given in the emit: section of the subworkflow. This is the same approach used
for process outputs with static typing — no .out property, just standard field
access on the return value.
Record Types Across Files
When multiple files need to share the same record type — for example, both
reads_qc.nf and main.nf need to know what a Sample record looks like —
define the type in the subworkflow file and re-export it with include:
In reads_qc.nf:
record Sample {
id: String
reads: List<Path>
}
In main.nf:
include { READS_QC, Sample } from '../../subworkflows/reads_qc'
This ensures there is a single definition of Sample that both files agree on,
rather than two independent definitions that could silently diverge.
Designing Subworkflow Inputs for Reuse
A subworkflow is more reusable when its take: types require only the fields
it actually needs. If READS_QC declares its input as Channel<Sample> where
Sample has only id and reads, then any richer sample record — one that
also carries condition, replicate, or any other project-specific fields —
will still be accepted due to duck-typing.
This means you can write a subworkflow once and call it from projects that have
different samplesheet columns, as long as all of them provide at least id and
reads.
Avoid declaring a take: type that requires fields the subworkflow does not
actually use. Doing so will make the subworkflow unnecessarily restrictive and
will break callers that have not included those fields.
Subworkflows Available in This Course
The following shared subworkflows are provided under projects/subworkflows/:
| File | Workflow name | What it does | Used by |
|---|---|---|---|
reads_qc.nf |
READS_QC |
Pre-trim FastQC → Trimmomatic → post-trim FastQC | Projects 2, 3 |
bam_qc.nf |
BAM_QC |
Samtools sort → index → flagstat | Project 3 |
multiqc_report.nf |
MULTIQC_REPORT |
Collect QC files and run MultiQC | Projects 2, 3 |