Configuration profiling
Inspect your application's resolved configuration values in the Config panel, with sensitive key masking.
This tutorial shows how to add the config collector to display resolved configuration values in the profiler's Config panel.
Prerequisites
@eleven-labs/nest-profilerinstalled and configured@nestjs/configinstalled with load factories (required - see note below)
Load factories are required
The config collector reads ConfigService's internal config map. This map is only populated when
you use the load option with factory functions (the registerAs pattern). If you only pass
envFilePath or ignoreEnvFile, the internal config will be empty and the Config tab will show
no entries.
Step 1 - Install the package
pnpm add @eleven-labs/nest-profiler-config@alphaStep 2 - Define config factories with registerAs
Create one factory per configuration domain:
import { registerAs } from '@nestjs/config';
export const databaseConfig = registerAs('database', () => ({
host: process.env.DB_HOST ?? 'localhost',
port: parseInt(process.env.DB_PORT ?? '5432', 10),
name: process.env.DB_NAME ?? 'myapp',
username: process.env.DB_USER ?? 'postgres',
password: process.env.DB_PASSWORD ?? '',
}));import { registerAs } from '@nestjs/config';
export const appConfig = registerAs('app', () => ({
port: parseInt(process.env.PORT ?? '3000', 10),
env: process.env.NODE_ENV ?? 'development',
debug: process.env.DEBUG === 'true',
}));Step 3 - Register the modules
import { Module } from '@nestjs/common';
import { ConfigModule, ConditionalModule } from '@nestjs/config';
import { ProfilerModule } from '@eleven-labs/nest-profiler';
import { ConfigCollectorModule } from '@eleven-labs/nest-profiler-config';
import { appConfig } from './config/app.config';
import { databaseConfig } from './config/database.config';
const isProfilerEnabled = (env: NodeJS.ProcessEnv) => env['PROFILER_ENABLED'] === 'true';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [appConfig, databaseConfig],
}),
ConditionalModule.registerWhen(ProfilerModule.forRoot({ isGlobal: true }), isProfilerEnabled),
ConditionalModule.registerWhen(
ConfigCollectorModule.forRoot({ maskKeys: ['database.password'] }),
isProfilerEnabled,
),
],
})
export class AppModule {}The maskKeys option accepts dot-notation paths. Matched values are replaced with *** in the profiler UI.
Step 4 - Test it
Start your application and open any profile:
curl -i http://localhost:3000/healthOpen /_profiler/{token} and click the Config tab.
You will see the configuration split into one collapsible section per namespace, with keys shown relative to their namespace:
app — 3 keys
| Key | Value |
|---|---|
port | 3000 |
env | development |
debug | false |
database — 5 keys
| Key | Value |
|---|---|
host | localhost |
port | 5432 |
name | myapp |
username | postgres |
password | *** |
Top-level scalar values that don't belong to a namespace appear under a General section. The toolbar badge shows the total number of resolved configuration keys (e.g., 8 keys).
forFeature is supported too
A namespace loaded lazily with ConfigModule.forFeature(databaseConfig) in a feature module is
captured exactly like one passed to forRoot({load}) — both merge into the same config store.
Grouping is by namespace, not by how it was loaded.
Masking additional keys
Any key matching the maskKeys array is masked. Keys are matched by their full dot-notation path:
ConfigCollectorModule.forRoot({
maskKeys: ['database.password', 'redis.password', 'jwt.secret'],
});Values matching /password|secret|key|token|credential/i are also masked automatically regardless of maskKeys.
Driving options from ConfigService
Use forRootAsync() to resolve maskKeys from ConfigService (or any provider), gated per environment with ConditionalModule — the recommended way (for the enabled flag, see Enabling and disabling the profiler):
import { ConditionalModule, ConfigService } from '@nestjs/config';
ConditionalModule.registerWhen(
ConfigCollectorModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
maskKeys: config.get<string[]>('profiler.maskKeys') ?? ['database.password', 'jwt.secret'],
}),
}),
isProfilerEnabled,
);How it works
The collector reads ConfigService's internal config once at application bootstrap, groups it by namespace (with keys flattened relative to each namespace, e.g. pool.max) and applies the masking rules; every profile then shows that same snapshot. Masking still matches the fully-qualified path, so maskKeys: ['database.password'] works even though the key is displayed as password. See How it works on the package page.