Feature Request: Allow `provideVariables` Hooks to Access Dotenv Variables
Summary
I'd like to enhance the provideVariables hook system to support chaining, where hooks can access and build upon variables provided by previous hooks in the series. Specifically, this would allow custom hooks to use dotenv-loaded variables for lookups and derived variable injection before HTTP file substitution occurs.
What I'm Trying to Do
I'm building a custom .httpyac.js provideVariables hook that:
- Reads values from dotenv files (e.g.,
.env) - Uses those values to perform lookups (e.g., against a database or API)
- Injects the resulting derived variables into the context
- Ensures these variables are available for substitution in
.httpfiles
This enables dynamic variable resolution based on environment-specific config.
Current Attempts and Issues
Attempt 1: Using .httpyac.json
- Created
.httpyac.jsonwith environment variables - Added a
provideVariableshook in.httpyac.jsto accesscontext.config.variables -
Issue:
.httpyac.jsonand.httpyac.jscan't coexist (.httpyac.jstakes precedence), so I couldn't use both for config and hooks
Attempt 2: Moving to .httpyac.js Only
- Consolidated everything into
.httpyac.js(environments + hook) -
Issue: Dotenv variables are still not available in
context.config.variablesduring hook execution -
Root cause: Dotenv loading happens via the
provideVariableshook itself, butcontext.config.variablesonly contains.httpyac.jsvariables at that time
Attempt 3: Manual Dotenv Loading in Hook
- Modified my hook to directly call the internal
provideDotenvVariablesfunction - Issue: This duplicates loading logic, creates maintenance burden, and could break if internal implementations change
Why It Doesn't Work Cleanly
The current provideVariables implementation uses a SeriesHook where:
- Each hook receives identical input (
environments,context) - Hooks run sequentially but independently
- Outputs are collected and merged at the end
This prevents chaining: my hook can't access dotenv variables loaded by the dotenv plugin's hook, even though they run in the same series.
Dotenv variables are loaded during hook execution, not pre-loaded into context.config.variables, so they're unavailable for cross-hook dependencies.
Proposed Feature Improvement
Change provideVariables from a SeriesHook to a WaterfallHook that:
- Passes accumulated variables through each hook in sequence
- Allows downstream hooks to access and build upon upstream results
- Maintains backward compatibility by still merging all outputs
Alternatively, pre-load dotenv variables into context.config.variables before provideVariables hooks execute, making them available for all hooks.
Benefits
- Enables plugin chaining and complex variable resolution workflows
- Reduces code duplication in custom hooks
- Improves extensibility for dynamic configuration scenarios
- Maintains the existing API while adding power
Example Use Case
module.exports = {
configureHooks: (api) => {
api.hooks.provideVariables.addHook(async (environments, context, accumulatedVars) => {
// With waterfall: accumulatedVars would include dotenv vars from previous hooks
const apiKey = accumulatedVars.API_KEY; // From .env
const derivedToken = await lookupToken(apiKey); // Dynamic lookup
return { authToken: derivedToken };
});
}
};
This would allow seamless integration between built-in dotenv loading and custom variable processing.
Environment
- httpyac version: 6.16.7
- Use case: Custom plugin for dynamic variable resolution based on dotenv + external lookups
Thank you for considering this enhancement! I'm happy to provide more details or help test implementations.
Hi, I'm surprised at how deeply you know my code. But I don't think WaterfallHook will work, since it only returns the first result. You could use an interceptor to capture the results. In the context, there would be a results property, and you could access it using beforeLoop.
class CustomJdbcVariables {
private _results?: Array<Variables>;
private id = "custom_jdbc"
public provideDotenvVariables(
env: string[] | undefined,
context: VariableProviderContext
): Promise<Variables> {
....
// use results
}
public async beforeLoop(
hookContext: HookTriggerContext<unknown>
): Promise<boolean | undefined> {
this._results = context.results;
return undefined;
}
export function registerJdbc(api: models.HttpyacHooksApi) {
const foo = new CustomJdbcVariables ();
api.hooks.provideVariables.addInterceptor(foo );
api.hooks.provideVariables.addHook('jdbc', foo.provideVariables.bind(foo ) );
}