Awesome Open Source
Awesome Open Source


npm GitHub repo size in bytes Build Status

A JavaScript wrapper for TDLib (Telegram Database library), version 1.4.0 or newer.

Table of Contents

Getting started

  1. Build the binary (
  2. npm install tdl tdl-tdlib-ffi (install both)


  • The libtdjson library ( on Linux, libtdjson.dylib on macOS, tdjson.dll on Windows)
  • Node.js v10+

You can also use third-party prebuilt binaries:


new Client(tdlibInstance, options) => Client
// Example in Node.js:
const { Client } = require('tdl')
const { TDLib } = require('tdl-tdlib-ffi')

const client = new Client(new TDLib(), {
  apiId: 2222, // Your api_id
  apiHash: '0123456789abcdef0123456789abcdef', // Your api_hash

api_id and api_hash can be obtained at

You can specify the path to libtdjson in the TDLib constructor's argument. It is (almost) directly passed to dlopen.

client.connect() => Promise<undefined>

You can use this method to initialize and connect your client with Telegram. Returns a promise.

await client.connect()
client.login(fn?: () => LoginDetails) => Promise<undefined>
await client.login()

By default, tdl asks the user for the phone number, auth code, and password (if specified) in the console. You can pass your functions:

// Example
await client.login(() => ({
  getPhoneNumber: retry => retry
    ? Promise.reject('Invalid phone number')
    : Promise.resolve('+9996620001'),
  getAuthCode: retry => retry
    ? Promise.reject('Invalid auth code')
    : Promise.resolve('22222'),
  getPassword: (passwordHint, retry) => retry
    ? Promise.reject('Invalid password')
    : Promise.resolve('abcdef'),
  getName: () =>
    Promise.resolve({ firstName: 'John', lastName: 'Doe' })

The getName function is called if user is not registered.

Also see LoginDetails interface in Options section.

It is possible to not use the client.login helper and implement login process manually.

This function requires TDLib v1.5.0+ to work.

Warning: The login function may not fully follow SemVer, just as TDLib itself doesn't follow SemVer.

client.connectAndLogin(fn?: () => LoginDetails) => Promise<undefined>

Same as client.connect().then(() => client.login(fn)).

client.on(event: string, callback: Function) => Client
client.addListener(event: string, callback: Function) => Client

Attach an event listener for iterating updates.

client.on('update', console.log)
client.on('error', console.error)

Ideally you should always have a listener on client.on('error').

client.once(event: string, callback: Function) => Client

Add a one-time listener. string, listener: Function, once?: boolean) => Client
client.removeListener(event: string, listener: Function, once?: boolean) => Client

Remove an event listener.

const listener = v => {
  console.log('New update.', v)'update', listener)
client.on('update', listener)

You can consider using reactive libraries like RxJS or most for convenient event processing.

client.invoke(query: Object) => Promise<Object>

Send an asynchronous message to Telegram and receive a response.
Resolves with response, or rejects with an error.

The API list can be found on Note that tdl renames @type to _.

const chats = await client.invoke({
  _: 'getChats',
  offset_order: '9223372036854775807',
  offset_chat_id: 0,
  limit: 100
await client.invoke({
  _: 'sendMessage',
  chat_id: 123456789,
  input_message_content: {
    _: 'inputMessageText',
    text: {
      _: 'formattedText',
      text: '👻'
client.execute(query: Object) => (Object | null)

Send a synchronous message to Telegram and receive response.

const res = client.execute({
  _: 'getTextEntities',
  text: '@telegram /test_command'
client.destroy() => undefined

Destroy the client.

client.setLogFatalErrorCallback(fn: (null | Function)) => undefined

Sets the callback that will be called when a fatal error happens. None of the TDLib methods can be called from the callback. The TDLib will crash as soon as callback returns. By default the callback is not set.

  errorMessage => console.error('Fatal error:', errorMessage)

See docs.

client.invokeFuture(query: Object) => Future

Same as client.invoke, but returns Future instead of Promise.

Note: You need to install fluture (npm install fluture) to use invokeFuture.

Low-level TDLib API


Login as a bot

const client = new Client(new TDLib(), {
  apiId: 2222, // Your api_id
  apiHash: '0123456789abcdef0123456789abcdef' // Your api_hash

await client.connectAndLogin(() => ({
  type: 'bot',
  getToken: retry => retry
    ? Promise.reject('Token is not valid')
    : Promise.resolve('YOUR_BOT_TOKEN') // Token from @BotFather


type Options = {
  apiId: number, // Can be obtained at
  apiHash: string, // Can be obtained at
  databaseDirectory: string, // Relative path
  filesDirectory: string, // Relative path
  databaseEncryptionKey: string, // Optional key for database encryption
  verbosityLevel: number,
  useTestDc: boolean, // Use telegram dev server
  // Advanced options:
  receiveTimeout: number,
  skipOldUpdates: boolean, // Don't emit old updates
  useMutableRename: boolean,
  useDefaultVerbosityLevel: boolean,
  disableAuth: boolean,
  tdlibParameters: Object

type LoginDetails = {
  type: 'user',
  getPhoneNumber: (retry?: boolean) => Promise<string>,
  getAuthCode: (retry?: boolean) => Promise<string>,
  getPassword: (passwordHint: string, retry?: boolean) => Promise<string>,
  getName: () => Promise<{ firstName: string, lastName?: string }>
} | {
  type: 'bot',
  getToken: (retry?: boolean) => Promise<string>

Only apiId and apiHash are required fields. Any other field can just be not specified.

tdlibParameters option: See

options = {
  databaseDirectory: '_td_database',
  filesDirectory: '_td_files',
  verbosityLevel: 2,
  receiveTimeout: 10,
  skipOldUpdates: false,
  useTestDc: false,
  useMutableRename: false,
  disableAuth: false,
  tdlibParameters: {
    use_message_database: true,
    use_secret_chats: false,
    system_language_code: 'en',
    application_version: '1.0',
    device_model: 'Unknown device',
    system_version: 'Unknown',
    enable_storage_optimizer: true


tdl fully supports Flow and TypeScript out of the box.
Typings are generated from scheme.

You can import TDLib types:

// TypeScript
import { updateMessageViews, messageInvoice /* ... */ } from 'tdl/types/tdlib'

// Flow
import type { updateMessageViews, messageInvoice /* ... */ } from 'tdl/types/tdlib'

Current built-in typings are for TDLib v1.6.0.

Warning: TDLib typings do not follow SemVer, just as TDLib itself doesn't. You can consider using ~ instead of ^ in your package.json dependencies.

The typings can be generated for the appropriate TDLib version using tdlib-typings in packages/tdlib-typings/


tdl also has experimental wrapper for tdlib in wasm, see tdl-tdlib-wasm/.

Architecture notes

The library is designed to work with different "backends", their interface is described in file. By this the same main wrapper can be used with node ffi, with node addons, and in browser with WebAssembly.

Available packages in the tdl repository:

You can easily substitute one with another.


See the examples/ directory.



Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
javascript (66,008
webassembly (304
telegram (288
wasm (229
wrapper (208
bindings (59
telegram-api (34
tdlib (14

Find Open Source By Browsing 7,000 Topics Across 59 Categories