Part 6: Configuration¶
In this section, you'll make your plugin configurable by reading settings from nextflow.config.
Users will be able to customize plugin behavior without modifying code.
Starting from here?
If you're joining at this part, copy the solution from Part 5 to use as your starting point:
Nextflow provides two approaches for plugin configuration:
| Approach | Best for | Trade-offs |
|---|---|---|
session.config.navigate() |
Quick prototyping, simple plugins | No IDE support, manual type conversion |
@ConfigScope classes |
Production plugins, complex config | More code, but type-safe and documented |
We'll start with the simple approach, then upgrade to the formal approach.
1. Simple configuration with navigate()¶
The session.config.navigate() method reads nested configuration values:
// Read 'greeting.enabled' from nextflow.config, defaulting to true
final enabled = session.config.navigate('greeting.enabled', true)
This lets users control plugin behavior:
This approach works well for quick prototyping and simple plugins.
2. Try it: Make the task counter configurable¶
This exercise adds configuration options to:
- Enable/disable the entire greeting plugin
- Control whether per-task counter messages are shown
2.1. Update TaskCounterObserver¶
First, edit TaskCounterObserver.groovy to accept a configuration flag:
The key changes:
- Line 14: Add a
verboseflag to control whether per-task messages are printed - Lines 17-19: Constructor that accepts the verbose setting
- Lines 24-26: Only print per-task messages if
verboseis true
2.2. Update the Factory¶
Now update GreetingFactory.groovy to read the configuration and pass it to the observer:
The factory now:
- Lines 33-34: Reads the
greeting.enabledconfig and returns early if disabled - Line 36: Reads the
greeting.taskCounter.verboseconfig (defaulting totrue) - Line 39: Passes the verbose setting to the
TaskCounterObserverconstructor
2.3. Build and test¶
Rebuild and reinstall the plugin:
Now update nextflow.config to disable the per-task messages:
Run the pipeline and observe that only the final count appears:
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [stoic_wegener] DSL2 - revision: 63f3119fbc
Pipeline is starting! 🚀
Reversed: olleH
Reversed: ruojnoB
Reversed: àloH
Reversed: oaiC
Reversed: ollaH
[5e/9c1f21] Submitted process > SAY_HELLO (2)
[20/8f6f91] Submitted process > SAY_HELLO (1)
[6d/496bae] Submitted process > SAY_HELLO (4)
[5c/a7fe10] Submitted process > SAY_HELLO (3)
[48/18199f] Submitted process > SAY_HELLO (5)
Decorated: *** Hello ***
Decorated: *** Bonjour ***
Decorated: *** Holà ***
Decorated: *** Ciao ***
Decorated: *** Hallo ***
Pipeline complete! 👋
📈 Final task count: 5
3. Try it: Make the decorator configurable¶
This exercise makes the decorateGreeting function use configurable prefix/suffix.
We'll intentionally make a common mistake to understand how Groovy/Java handles variables.
3.1. Add the configuration reading (this will fail!)¶
Edit GreetingExtension.groovy to read configuration in init() and use it in decorateGreeting():
Now try to build:
3.2. Observe the error¶
The build fails with an error like:
> Task :compileGroovy FAILED
GreetingExtension.groovy: 30: [Static type checking] - The variable [prefix] is undeclared.
@ line 30, column 9.
prefix = session.config.navigate('greeting.prefix', '***') as String
^
GreetingExtension.groovy: 31: [Static type checking] - The variable [suffix] is undeclared.
What went wrong? In Groovy (and Java), you can't just use a variable.
You must declare it first.
We're trying to assign values to prefix and suffix, but we never told the class that these variables exist.
3.3. Fix by declaring instance variables¶
Add the variable declarations at the top of the class, right after the opening brace:
The private String prefix = '***' line does two things:
- Declares a variable named
prefixthat can hold a String - Initializes it with a default value of
'***'
Now the init() method can assign new values to these variables, and decorateGreeting() can read them.
3.4. Build again¶
This time it should succeed with "BUILD SUCCESSFUL".
Learning from errors
This "declare before use" pattern is fundamental to Java/Groovy but unfamiliar if you come from Python or R where variables spring into existence when you first assign them. Experiencing this error once helps you recognize and fix it quickly in the future.
3.5. Test the configurable decorator¶
Update nextflow.config to customize the decoration:
Run the pipeline:
The decorator now uses our custom prefix and suffix.
4. Formal configuration with ConfigScope¶
The session.config.navigate() approach works, but has limitations:
- No IDE autocompletion for users writing
nextflow.config - Configuration options aren't self-documenting
- Manual type conversion with
as String,as boolean, etc.
For production plugins, Nextflow provides a formal configuration system using annotations. This creates a schema that documents available options.
4.1. Understanding the annotations¶
| Annotation | Purpose |
|---|---|
@ScopeName('name') |
Declares a configuration block (e.g., greeting { }) |
@ConfigOption |
Marks a field as a configuration option |
ConfigScope interface |
Must be implemented by config classes |
4.2. Create a configuration class¶
Create a new file GreetingConfig.groovy:
Add the configuration class:
Key points:
@ScopeName('greeting'): Maps to thegreeting { }block in configimplements ConfigScope: Required interface for config classes@ConfigOption: Each field becomes a configuration option- Nested class: For nested paths like
taskCounter.verbose, use a nested class - Constructors: Both no-arg and Map constructors are needed
- Default values: Set directly on the fields
4.3. Register the config class¶
Update build.gradle to register the config class as an extension point:
4.4. Build and test¶
The config class provides documentation and schema validation.
Your code continues using session.config.navigate() for reading values:
The behavior is identical, but now your configuration is:
- Self-documenting: The config class shows all available options
- Structured: Nested configuration is explicit
Config class vs runtime access
The config class primarily serves as documentation and schema definition.
Runtime value access still uses session.config.navigate().
This is the pattern used by most Nextflow plugins including nf-validation.
5. Summary¶
| Use case | Recommended approach |
|---|---|
| Quick prototype or simple plugin | session.config.navigate() only |
| Production plugin with many options | Add ConfigScope class for documentation |
| Plugin you'll share publicly | Add ConfigScope class for documentation |
For this training, the navigate() approach is sufficient.
Adding a config class helps users understand available options.
Takeaway¶
You learned that:
session.config.navigate()provides simple, quick configuration reading@ScopeNameand@ConfigOptionannotations create self-documenting configuration- Configuration can be applied to both observers and extension functions
- Variables must be declared before use in Groovy/Java
What's next?¶
The next section covers how to share your plugin with others.