--- title: Typed workflows description: Use typed workflows in Nextflow to define statically typed inputs and outputs for workflows. --- # Typed workflows Typed workflows let you declare the types of a named workflow's inputs and outputs in its `take:` and `emit:` sections. Nextflow uses the annotations to validate the values that enter and leave the workflow against their declared types. ```nextflow nextflow.enable.types = true workflow hello_bye { take: samples: Channel main: ch_hello = hello(samples) val_bye = bye(ch_hello.collect()) emit: result: Value = val_bye } ``` In the above example, `hello_bye` takes a channel of files (`Channel`) and emits a dataflow value with a single file (`Value`). Typed workflows require the `nextflow.enable.types` feature flag. For an overview of static typing and how to enable it, see [Static typing][static-typing-page]. For the complete syntax reference, see [Workflow (typed)][syntax-workflow-typed]. ## Supported types Channels and values are modeled using the dataflow types: - Use `Channel` for a channel of values. - Use `Value` for a single dataflow value. The element type `V` can be any [standard type][stdlib-types], such as `Path`, `String`, or a record. Workflow inputs can be channels, dataflow values, or plain values. Workflow outputs can be channels or dataflow values. ## Restricted syntax The following syntax patterns are no longer supported in typed workflows: - Using `Channel` to access channel factories (use `channel` instead) - Using implicit closure parameters (declare parameters explicitly instead) - Using `set` or `tap` to assign channels (use standard assignments instead) - Composing dataflow logic with `|` and `&` (use standard method calls instead) - Accessing process and workflow outputs via `.out` (use standard assignments instead) For more information about preparing existing code, see [Preparing for static typing][preparing-static-types]. ## Operators The operator library supports static typing and records. All operators work in both typed and legacy workflows, but only a [core subset][channel-type] of operators is recommended for static typing. For more information about best practices when migrating existing code, see [Using operators with static typing][migrating-static-types-operators]. ## Pipeline composition :::warning{title="Experimental: may change in a future release."} ::: An entire pipeline -- the `params` block, entry workflow, and `output` block of a script -- can be included as a named workflow and called like any other workflow. The params block acts as the `take:` section and the output block acts as the `emit:` section. Given the following pipeline: ```nextflow // pipelines/rnaseq.nf nextflow.enable.types = true params { input: Channel aligner: String = 'star_salmon' fasta: Path } workflow { main: // ... publish: bams = ch_bams multiqc = val_multiqc } output { bams: Channel { path 'bams' } multiqc: Path { path 'multiqc' } } ``` It can be included and called as follows: ```nextflow nextflow.enable.types = true include { workflow as RNASEQ } from './pipelines/rnaseq.nf' workflow { main: rnaseq = RNASEQ(record( input: samples, fasta: file('index.fasta') )) rnaseq.bams.view() // Channel rnaseq.multiqc.view() // Value } ``` The included pipeline must be aliased to a specific name (`RNASEQ`), which is also used to scope its processes in the config: ```nextflow process { withName: 'RNASEQ:STAR_ALIGN' { cpus = 12 memory = 72.GB } } ``` Because the included pipeline is part of the calling pipeline's dataflow graph, it can consume a channel produced by another pipeline, and begin working on each item as soon as it is emitted. Note the following: - Both the calling script and the included pipeline must enable static typing (`nextflow.enable.types = true`). - The pipeline is called with a single record, with one field for each param. Params with a default value can be omitted. - The outputs published by the included pipeline are emitted to the calling workflow instead of being published. Only the calling pipeline decides what is published, by declaring its own `output` block. - Params and outputs are not inherited. The calling pipeline declares its own params and outputs and passes them explicitly to the included pipeline. ### Importing the params block Redeclaring every param of an included pipeline gets tedious. The `params` block can be imported as a record type instead: ```nextflow nextflow.enable.types = true include { params as RnaseqParams ; workflow as NFCORE_RNASEQ } from './pipelines/rnaseq.nf' params { strandedness: String = 'auto' // unique to this pipeline rnaseq: RnaseqParams // input, aligner, fasta } workflow { main: rnaseq = NFCORE_RNASEQ( params.rnaseq + record(input: samples) ) } ``` `RnaseqParams` is a *partial* record type: every field is nullable, so a user can provide any rnaseq param as `--rnaseq.`, the calling pipeline can override specific params with `params.rnaseq + record(input: samples)`, and the `NFCORE_RNASEQ()` call reports any param that is still missing. Note the following: - A param that the pipeline defaults (`aligner`) can be omitted from the record. The pipeline applies its own default when it is called. - `rnaseq.input` is supplied by the dataflow, which overrides any value given by the user. - `rnaseq.fasta` must still be provided, but the error surfaces at the `NFCORE_RNASEQ()` call rather than at launch. A pipeline receives its params when it is called, so `params` refers to a single execution of the pipeline. A pipeline can be included under any number of aliases, and each alias resolves its own params. As with a named workflow, a pipeline can be called only once per alias -- include it again under a different alias to call it again. ### Best practices Pipeline inclusion only captures the pipeline script and the modules it includes. It does not capture external context such as the config or the `lib` directory. As a result, an included pipeline should be written so that it works when included by another pipeline: - Pipeline parameters should be declared in the `params` block. The config should only declare *config params*, i.e. params that only affect config settings. - Project-level assets (`projectDir`, `bin`, `lib`) should not be used, since the calling pipeline has a different project root. Module-level assets can be safely used through the module `resources/` bundle and `moduleDir`. - Params should be referred to only in the entry workflow and output block. A process or workflow that reads `params` directly should declare an explicit input instead. - Process configuration (`container`, `conda`, `ext`) should be specified in the process definition or avoided in favor of process inputs. - Workflow outputs should be published using the `output` block, not `publishDir`. None of these constraints are absolute. Each of them can be circumvented by replicating the external context in the calling pipeline. Following them simply makes it easier to include a pipeline with minimal extra work. ## Validation Nextflow validates each workflow input and output against its declared type. Calling a workflow with an argument whose type does not match its `take:` declaration, or emitting a value that does not match its `emit:` declaration, is reported as a type error. ## See also - [Static typing][static-typing-page]: Overview of static typing and how to enable it. - [Standard types][stdlib-types]: The standard types available for annotations. - [Operators][channel-type]: The core operators recommended for static typing. [channel-type]: ./reference/stdlib-types/channel [migrating-static-types-operators]: ./tutorials/static-types-operators [preparing-static-types]: ./static-typing-prep [static-typing-page]: ./static-typing [stdlib-types]: ./reference/stdlib-types [syntax-workflow-typed]: ./reference/syntax#workflow-typed