Skip to content

Use multiple accounts

TwilioModule.forRoot() configures the default client and the options every named client inherits. TwilioModule.registerClient() adds further clients (typically Twilio subaccounts) which inherit those options and override only what differs.

This mirrors BullModule.forRoot() / BullModule.registerQueue() in @nestjs/bullmq, where the root registration holds shared configuration and each named registration inherits it.

src/app.module.ts
import { Module } from '@nestjs/common';
import { TwilioModule } from 'nestjs-twilio';
@Module({
imports: [
TwilioModule.forRoot({
accountSid: process.env.TWILIO_ACCOUNT_SID,
authToken: process.env.TWILIO_AUTH_TOKEN,
region: 'ie1',
edge: 'dublin',
}),
TwilioModule.registerClient({
name: 'billing',
accountSid: process.env.TWILIO_BILLING_ACCOUNT_SID,
authToken: process.env.TWILIO_BILLING_AUTH_TOKEN,
}),
],
})
export class AppModule {}

The billing client uses its own credentials but inherits region: 'ie1' and edge: 'dublin' from the root registration.

import { Injectable } from '@nestjs/common';
import { InjectTwilio, type TwilioClient } from 'nestjs-twilio';
@Injectable()
export class BillingService {
constructor(@InjectTwilio('billing') private readonly twilio: TwilioClient) {}
}

Names are matched case-insensitively, so 'Billing' and 'billing' resolve to the same client. @InjectTwilio() with no argument resolves the default client from forRoot().

To resolve the token directly (when wiring a provider by hand, for example), use getTwilioClientToken('billing').

Every option from forRoot() is inherited unless the named client overrides it with a defined value.

Registration Result
Key omitted Inherited from forRoot()
Key set to undefined Inherited from forRoot()
Key set to a value Overrides the inherited value
// Inherits region 'ie1' even though the key is present.
TwilioModule.registerClient({
name: 'billing',
accountSid,
authToken,
region: process.env.BILLING_REGION, // undefined when unset
});
// Deliberately overrides, routing this client through Australia.
TwilioModule.registerClient({ name: 'au', accountSid, authToken, region: 'au1' });

authToken and apiKey / apiSecret are alternative authentication modes, not independent settings, so they are inherited together. A client that supplies either mode drops the inherited other one:

TwilioModule.forRoot({ accountSid, authToken });
// Authenticates with the API key. The root's authToken is dropped, not
// merged alongside it.
TwilioModule.registerClient({ name: 'realtime', apiKey, apiSecret });

Merging them field-by-field would let the inherited authToken outrank the explicitly configured apiKey, making it impossible to register an API-key client (the kind Access Tokens require) beneath an auth-token root.

Everything that is not a credential, including region and edge, keeps inheriting across a mode change.

registerClientAsync() accepts the same shape as Nest’s own generated *Async methods. Use exactly one of useFactory, useClass or useExisting.

TwilioModule.registerClientAsync({
name: 'billing',
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
accountSid: config.getOrThrow('TWILIO_BILLING_ACCOUNT_SID'),
authToken: config.getOrThrow('TWILIO_BILLING_AUTH_TOKEN'),
}),
inject: [ConfigService],
});

With a factory class, implement TwilioClientOptionsFactory:

src/billing-twilio.config.ts
import { Injectable } from '@nestjs/common';
import type { TwilioClientOptionsFactory, TwilioModuleOptions } from 'nestjs-twilio';
@Injectable()
export class BillingTwilioConfig implements TwilioClientOptionsFactory {
constructor(private readonly config: ConfigService) {}
createTwilioClientOptions(): Partial<TwilioModuleOptions> {
return {
accountSid: this.config.getOrThrow('TWILIO_BILLING_ACCOUNT_SID'),
authToken: this.config.getOrThrow('TWILIO_BILLING_AUTH_TOKEN'),
};
}
}
TwilioModule.registerClientAsync({ name: 'billing', useClass: BillingTwilioConfig });

Options resolved asynchronously inherit from forRoot() exactly as synchronous ones do.

registerClient() works on its own, which suits a platform where every tenant is a subaccount and there is no meaningful primary account. Each registration must then carry full credentials, since there is nothing to inherit.

@Module({
imports: [
TwilioModule.registerClient({ name: 'tenant-a', accountSid: '…', authToken: '…' }),
TwilioModule.registerClient({ name: 'tenant-b', accountSid: '…', authToken: '…' }),
],
})
export class AppModule {}

forRoot() always registers globally, as TypeOrmCoreModule, Mongoose’s core module and BullModule.forRoot() all do. Import it once in your root module and every client is available application-wide.

This is also what makes inheritance work: a named client is registered as its own module, and a non-global root would be invisible to it.

See TwilioModule for the full method signatures, getTwilioClientToken for the DI token helper, and InjectTwilio for the injection decorator.