Loading...
Loading...
Use when writing or modifying Nextflow (.nf) scripts on Nextflow 25.10+ / 26.04+, especially when output may default to legacy untyped DSL2 patterns (tuple inputs, `publishDir`, `Channel.from`, `|` pipes, implicit `it`, `set`/`tap`, `splitCsv` as operator). Reference for strict syntax, typed processes, typed workflows, records, typed params, workflow output blocks, and typed operator equivalents.
npx skill4agent add d-laub/dlaub-togo typed-nextflowtuple val(id), path(reads)publishDirChannel.fromitsettap|splitCsv.nftuple(id: String, …)output {}publishDirChannel.fromitsettapmix(id, fastq_1, fastq_2): Tuple<String, Path, Path>tuple(id: String, fastq_1: Path, fastq_2: Path)(name1, name2): Tuple<A, B>input:Tuple<A, B>Channel<Tuple<String, Path>>groupBy(key, value)(key, size, value)map// wrong
pairs.groupBy { p -> p[0] }
// right
pairs.map { id, file -> tuple(id, file) }.groupBy()
// → emits (id, Bag<files>)(id, [files]).mapprocess MERGE_FILES {
input:
tuple(id: String, files: List<Path>)
// ...
}
workflow {
grouped = pairs.groupBy() // Channel<Tuple<String, Bag<Path>>>
MERGE_FILES(grouped) // single channel argument
}input:stage:output:topic:script:exec:stub:output:script:output:stdout()file('x.txt')record(...)name: Type = value// legacy
output:
path results
// wrong typed-style guess (model regression)
output:
results
// correct typed output
output:
results: Path = file("${id}_results.csv") // literal-ish name
// or, if the script-level variable `results` is a Path/String:
results: Path = file("${results}")
// or, for a single unnamed output:
file('results.csv')tuple(...)record(...)metarecord(meta: sample, logs: ...)// anti-pattern (nf-core meta habit)
record(meta: sample, logs: files('*_fastqc.zip'))
// idiomatic
record(id: sample.id, single_end: sample.single_end, logs: files('*_fastqc.zip'))by: 'id'by: 'meta.id'nextflow.enable.types = true
// (strict syntax is on by default in 26.04; for 25.10 export NXF_SYNTAX_PARSER=v2)params {}output {}nextflow.enable.typestake:emit:params {}params.foo = ...params {
// Samplesheet path
reads: Path = "${projectDir}/data/samples.csv"
// Optional reference; required if no default given
transcriptome: Path
// Default-false boolean
save_intermeds: Boolean
outdir: Path = 'results'
}paramsoutput {}falserecord(...)record Name { ... }record Sample {
id: String
fastq_1: Path
fastq_2: Path? // `?` = nullable
}
def s = record(id: 'A', fastq_1: file('a_1.fq'), fastq_2: file('a_2.fq'))
s.id // access by name, not indexmeta(meta, files)meta// legacy nf-core
process FASTQC {
tag "${meta.id}"
input:
tuple val(meta), path(reads)
output:
tuple val(meta), path('*_fastqc.zip'), emit: zip
// ...
}
workflow {
ch_reads = Channel
.fromPath(params.samplesheet)
.splitCsv(header: true)
.map { row ->
def meta = [id: row.sample, single_end: row.single_end.toBoolean()]
def reads = meta.single_end ? [file(row.fastq_1)] : [file(row.fastq_1), file(row.fastq_2)]
tuple(meta, reads)
}
FASTQC(ch_reads)
}nextflow.enable.types = true
record Sample {
id: String
single_end: Boolean
strandedness: String?
reads: List<Path>
}
process FASTQC {
tag sample.id
input:
sample: Sample
output:
record(
id: sample.id,
single_end: sample.single_end,
zip: files('*_fastqc.zip'),
html: files('*_fastqc.html')
)
script:
"""
fastqc --threads ${task.cpus} ${sample.reads.join(' ')}
"""
}
workflow {
main:
samples = channel.of(params.samplesheet)
.flatMap { csv -> csv.splitCsv(header: true) }
.map { row ->
def single = row.single_end.toBoolean()
record(
id: row.sample,
single_end: single,
strandedness: row.strandedness ?: null,
reads: single
? [file(row.fastq_1, checkIfExists: true)]
: [file(row.fastq_1, checkIfExists: true), file(row.fastq_2, checkIfExists: true)]
)
}
fastqc = FASTQC(samples)
publish:
fastqc = fastqc
}
output {
fastqc: Channel<Record> {
// per-sample subdir, by `id` directly (no `meta.id`)
path { r -> "fastqc/${r.id}" }
index { path 'fastqc.csv'; header true }
}
}meta + [type: 'long']s + record(type: 'long')meta.ids.idtag "${meta.id}"tag sample.idmeta.single_end ? [r1] : [r1, r2]List<Path>publishDir { "results/fastqc/${meta.id}" }output { fastqc { path { r -> "fastqc/${r.id}" } } }task.ext.argstask.ext.prefixnextflow.enable.types = true
process FASTQC {
tag sample.id
conda 'bioconda::fastqc=0.12.1'
input:
sample: Sample // by record type
// or destructured:
// record(id: String, fastq_1: Path, fastq_2: Path)
// or tuple destructure:
// tuple(id: String, fastq: Path)
// or scalar:
// index: Path
output:
record(
id: sample.id,
fastqc: file("fastqc_${sample.id}_logs")
)
script:
"""
fastqc.sh ${sample.id} ${sample.fastq_1} ${sample.fastq_2}
"""
}name: Typerecord(...)tuple(...)pathPathvalpath '*'Set<Path>List<Path>PathPath'*'input: Path?nullfile()files()stdout()env('VAR')record(...)tuple(...)file('x', optional: true)nulleachcombinestage:stage:process grep {
input:
id: String
fasta: Path
stage:
stageAs fasta, "${id}.fa" // note: (value, pattern) order in 26.04
env 'SAMPLE_ID', id
stdin id
script:
"cat ${id}.fa | grep '>'"
}stageAs(value, pattern)nextflow.enable.types = true
workflow RNASEQ {
take:
samples: Channel<Sample>
transcriptome: Path
main:
index = INDEX(transcriptome)
fastqc = FASTQC(samples)
quant = QUANT(samples, index)
joined = fastqc.join(quant, by: 'id')
emit:
samples: Channel<AlignedSample> = joined
}
record AlignedSample {
id: String
fastqc: Path
quant: Path
}Channel<T>Value<T>TChannel.itsettap|&.outpublishDirpublishDirworkflow {
main:
samples_ch = channel.of(params.reads)
.flatMap { csv -> csv.splitCsv(header: true) }
.map { row -> record(id: row.id, fastq_1: file(row.fastq_1), fastq_2: file(row.fastq_2)) }
aligned_ch = RNASEQ(samples_ch, params.transcriptome)
report = MULTIQC(aligned_ch.flatMap { s -> [s.fastqc, s.quant] }.collect(), params.multiqc)
publish:
samples = aligned_ch
multiqc_report = report
}
output {
samples: Channel<AlignedSample> {
path { s ->
s.fastqc >> "fastqc/${s.id}"
s.quant >> "quant/${s.id}"
}
index {
path 'samples.csv'
header true
}
}
multiqc_report: Path {
path 'multiqc_report.html'
}
}workflow.output.mode = 'copy'
outputDir = 'results' // or use -output-dir on CLImanifest.defaultBranch| Banned / deprecated | Use instead |
|---|---|
| |
| |
| top-level statements mixed with declarations | put statements inside |
| |
| |
spread | enumerate or destructure |
| |
| pass as explicit inputs |
| |
slashy strings with | double-quoted / triple-quoted |
| |
| |
| |
| |
implicit | |
| put inside entry workflow as |
process | filter at the call site |
| take as explicit input |
nextflow.enable.types| Legacy | Replacement |
|---|---|
| |
| assign before/after the next op |
| one |
| one |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| List methods on collected values (operator versions are non-deterministic) |
| |
| |
| implement explicitly; don't use (non-deterministic) |
| |
| a workflow output index file, or an |
| |
| |
filterjoinby:failOnDuplicatefailOnMismatchremainder:flatMapmapnull.filter { v -> v != null }mixnextflow.enable.types = true
params {
samplesheet: Path
reference: Path
outdir: Path = 'results'
}
record Sample {
id: String
fastq_1: Path
fastq_2: Path?
}
include { ALIGN } from './modules/align'
workflow {
main:
samples = channel.of(params.samplesheet)
.flatMap { csv -> csv.splitCsv(header: true) }
.map { row ->
record(
id: row.id,
fastq_1: file(row.fastq_1),
fastq_2: row.fastq_2 ? file(row.fastq_2) : null
)
}
aligned = ALIGN(samples, params.reference)
publish:
aligned = aligned
}
output {
aligned: Channel<Record> {
path { s -> "aligned/${s.id}" }
index { path 'aligned.csv'; header true }
}
}nextflow.enable.types = trueinput:output:take:emit:tuple val(id), path(reads)record(id: String, reads: Path)tuple(id: String, reads: Path)pathSet<Path>List<Path>splitCsvflatMap { f -> f.splitCsv(...) }Channel.from(...)channel.of(...)channel.fromList(...)it{ v -> ... }MY_WF.out.fooout = MY_WF(); out.foomix(a, b, c).mix(a).mix(b)branchmultiMappublishDiroutput {}stageAs '*', valuestageAs value, '*'| Scenario | Result |
|---|---|
| Generic FastQC process from CSV samplesheet | clean on first pass |
nf-core | first pass nested |
Edit a legacy partially-typed file (this repo's | first pass used 25.10 destructure |
Group | first pass used |