Part 3: Custom Functions¶
By the end of this section, you'll have custom functions in your plugin, built and installed locally, running in a real workflow.
Starting from here?
If you're joining at this part, copy the solution from Part 2 to use as your starting point:
1. See what the template generated¶
Before writing your own functions, look at the example function the template created to understand the pattern.
Change into the plugin directory:
The template created a file called GreetingExtension.groovy where plugin functions are defined.
Open it to see the starting point:
/*
* Copyright 2025, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package training.plugin
import groovy.transform.CompileStatic
import nextflow.Session
import nextflow.plugin.extension.Function
import nextflow.plugin.extension.PluginExtensionPoint
/**
* Implements a custom function which can be imported by
* Nextflow scripts.
*/
@CompileStatic
class GreetingExtension extends PluginExtensionPoint { // (1)!
@Override
protected void init(Session session) { // (2)!
}
/**
* Say hello to the given target.
*
* @param target
*/
@Function // (3)!
void sayHello(String target) {
println "Hello, ${target}!"
}
}
- The class your extension builds on. Nextflow requires this to recognize your functions.
- Called when the plugin loads; use for initialization
- Makes this method callable from workflows via
include
The template includes a sample sayHello function.
The @Function annotation is what makes a method callable from Nextflow workflows.
Without it, the method exists only inside the plugin code.
In Groovy (and Java), methods declare what type they return and what types their parameters are.
For example, String reverseGreeting(String greeting) declares a method that takes a String parameter and returns a String.
The keyword void means the method returns nothing, as with sayHello above.
This is different from Python or R, where types do not need to be declared explicitly.
2. Replace sayHello with reverseGreeting¶
The template's sayHello function is a placeholder.
Replace it with your own function to see the full cycle of writing, building, and using a plugin function.
Edit src/main/groovy/training/plugin/GreetingExtension.groovy to replace the sayHello method:
| GreetingExtension.groovy | |
|---|---|
- Makes the method callable from Nextflow workflows
- Takes a String, returns a String
- Groovy's built-in string reversal method
Key parts of this function:
@Function: Makes the method callable from Nextflow workflowsString reverseGreeting(String greeting): Takes a String, returns a Stringgreeting.reverse(): Groovy's built-in string reversal method
Public and private methods
Methods without @Function are not exposed to Nextflow workflows.
You can add helper methods to your class without worrying about them leaking into the workflow namespace.
3. Build and install your plugin¶
Build and install the plugin:
If the build fails
Read the error message carefully; it usually includes a line number and describes the problem. Common causes are syntax errors (missing bracket or quote), misspelled class names, and type mismatches. If you are stuck, compare your code character-by-character with the examples.
4. Use your function in a workflow¶
The plugin is built and installed.
The next step is to use reverseGreeting in a workflow to verify it works end-to-end.
Go back to the pipeline directory:
Edit greet.nf to import and use reverseGreeting:
Run the pipeline:
Output
N E X T F L O W ~ version 25.10.2
Launching `greet.nf` [elated_marconi] DSL2 - revision: cd8d52c97c
Pipeline is starting! 🚀
executor > local (5)
[fe/109754] process > SAY_HELLO (5) [100%] 5 of 5 ✔
Reversed: olleH
Reversed: ruojnoB
Reversed: àloH
Reversed: oaiC
Reversed: ollaH
Output: Hello
Output: Bonjour
Output: Holà
Output: Ciao
Output: Hallo
Pipeline complete! 👋
Your first custom plugin function is working in a real workflow.
The same include { ... } from 'plugin/...' pattern you used with nf-hello and nf-schema in Part 1 works with your own plugin.
5. Add decorateGreeting¶
A plugin can provide multiple functions. Add a second one that wraps a greeting with decorative markers; you'll make it configurable in Part 6.
Edit GreetingExtension.groovy to add decorateGreeting after reverseGreeting, before the closing brace of the class:
- Groovy string interpolation:
${...}inserts the variable's value into the string
| GreetingExtension.groovy | |
|---|---|
This function uses Groovy string interpolation ("*** ${greeting} ***") to embed the greeting variable inside a string.
Build, install, and update the workflow:
Update greet.nf to also import and use decorateGreeting:
- Multiple functions from the same plugin need separate
includestatements - Plugin functions work inside process
script:blocks too
Output
N E X T F L O W ~ version 25.10.2
Launching `greet.nf` [elated_marconi] DSL2 - revision: cd8d52c97c
Pipeline is starting! 🚀
executor > local (5)
[fe/109754] process > SAY_HELLO (5) [100%] 5 of 5 ✔
Reversed: olleH
Reversed: ruojnoB
Reversed: àloH
Reversed: oaiC
Reversed: ollaH
Decorated: *** Hello ***
Decorated: *** Bonjour ***
Decorated: *** Holà ***
Decorated: *** Ciao ***
Decorated: *** Hallo ***
Pipeline complete! 👋
Plugin functions work in both process scripts (like decorateGreeting inside SAY_HELLO) and workflow operations (like reverseGreeting in a map).
Takeaway¶
You learned that:
- Functions are defined with the
@Functionannotation inPluginExtensionPointsubclasses - Plugin functions imported with
includework identically whether from your own plugin or an existing one - Plugin functions work in both process scripts and workflow operations
What's next?¶
Your functions work, but so far you've only verified that by running the full pipeline and checking the output by eye. That approach doesn't scale: as you add more functions, you need a faster way to check that each one behaves correctly, especially after making changes. The next section introduces unit tests, which let you verify individual functions automatically without running a pipeline.