Node.js bindings to TDLib. 🥒
Alternatives To Tdl
Project NameStarsDownloadsRepos Using ThisPackages Using ThisMost Recent CommitTotal ReleasesLatest ReleaseOpen IssuesLicenseLanguage
Go Tdlib32452 years ago12January 29, 202238gpl-3.0Go
Golang Telegram TdLib JSON bindings
Tdl309544 days ago49October 11, 20228mitJavaScript
Node.js bindings to TDLib. 🥒
8 days ago2mitC#
C#/.NET Core bindings for TDLib (Telegram MTProto API)
Awesome Tarantool57
a year agocc-by-4.0
A curated list of delightful Tarantool modules, connectors and other resources
Telegram Native47
3 years agobsl-1.0C++
TDLib bindings for 1C:Enterprise
Node Tdlib28
4 years agounlicenseJavaScript
TDLib Binding with Telegram Bot API Reimplemention for Node.js
Go Tdjson22
4 years ago1September 13, 20193mitGo
Golang bindings for TDLib (Telegram MTProto library)
Go Telegram15
8 years agoMay 26, 20211otherGo
Go binding for Telegram
2 years ago1April 19, 20181mitGo
📚 Golang bindings for Telegram API
Lua Telegram Bot Api12
4 years ago2mitLua
Telegram Bot API bindings and utilities
Alternatives To Tdl
Select To Compare

Alternative Project Comparisons

tdl   npm CI

tdl is a fairly simple JavaScript wrapper for TDLib (Telegram Database library), a library to create Telegram clients or bots.

TDLib version 1.5.0 or newer is required.


  • Node.js v12.0.0 or newer
  • The tdjson shared library ( on Linux, libtdjson.dylib on macOS, tdjson.dll on Windows)
  • In some cases, a C++ compiler and Python installed to build the node addon[^1]

[^1]: tdl is packaged with pre-built addons for Windows (x86_64), GNU/Linux (x86_64, glibc >= 2.17), and macOS (x86_64, aarch64). If a pre-built binary is not available for your system, then the node addon will be built using node-gyp, requiring Python and a C++ toolchain to be installed (on Windows, MSVS or Build Tools). Pass --build-from-source to never use the pre-built binaries. Note that macOS aarch64 binaries aren't tested.


  1. Build TDLib (tdlib/td) or install pre-built libraries
  2. Run npm install tdl
  3. (optional) If you use TypeScript or Flow, types for TDLib can be installed via the tdlib-types package, see the Types section below

To use tdl, you need to get TDLib first. The tdjson shared library should be present in the system search paths (otherwise the path to libtdjson can be specified manually).

Note: When building TDLib, the libraries can be installed into the system using cmake --install . (optionally specify the --prefix option, the default is /usr/local) after TDLib has been built successfully. This command may require sudo.

Instead of building TDLib from source, you can possibly install pre-built TDLib libraries distributed through the prebuilt-tdlib npm package. To install prebuilt-tdlib for e.g. TDLib v1.8.14, run npm install [email protected] (the available versions of prebuilt-tdlib can be found by running npm info prebuilt-tdlib dist-tags). An example of using libraries from prebuilt-tdlib is present in the section below. See the README of prebuilt-tdlib for additional information (the supported systems are listed there).

Getting started

const tdl = require('tdl')

// If libtdjson is not present in the system search paths, the path to the
// libtdjson shared library can be set manually, e.g.:
//   tdl.configure({ tdjson: '/usr/local/lib/libtdjson.dylib'})
// The library prefix can be set separate from the library name,
// example to search for libtdjson in the directory of the current script:
//   tdl.configure({ libdir: __dirname })

// Instead of building TDLib yourself, the aforementioned prebuilt-tdlib can be used as follows:
//   const { getTdjson } = require('prebuilt-tdlib')
//   tdl.configure({ tdjson: getTdjson() })

const client = tdl.createClient({
  apiId: 2222, // Your api_id
  apiHash: '0123456789abcdef0123456789abcdef' // Your api_hash
// Passing apiId and apiHash is mandatory, these values can be obtained at

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

// Aside of receiving responses to your requests, the server can push to you
// events called "updates" which ar received as follows:
client.on('update', update => {
  console.log('Got update:', update)

async function main () {
  // Log in to a Telegram account. By default, with no arguments, this function will ask
  // for phone number etc. in the console. Instead of logging in as a normal user,
  // it's also possible to log in as a bot using `client.loginAsBot('<TOKEN>')`.
  await client.login()

  // Invoke a TDLib method. The information regarding TDLib method list and
  // documentation is below this code block.
  const me = await client.invoke({ _: 'getMe' })
  console.log('My user:', me)

  // Invoke some other TDLib method.
  const chats = await client.invoke({
    _: 'getChats',
    chat_list: { _: 'chatListMain' },
    limit: 10
  console.log('A part of my chat list:', chats)

  // Close the instance so that TDLib exits gracefully and the JS runtime can finish the process.
  await client.close()


The API list of TDLib methods, which are called using client.invoke, can be found at, e.g.:

In that TDLib documentation, the bytes type means a base64-encoded string. int64 accepts either a number or a string, pass string for large numbers. Other types are self-evident. If tdlib-types is installed, the TDLib types are also annotated with the documentation comments (i.e. the documentation can be browsed in the .d.ts file).

See also for some basic information on how to use TDLib (tdl handles the authorization part with client.login). Note that the TDLib JSON interface actually sends a @type field, but tdl renames it to _.

Some short examples are available in the examples/ directory.


Note: A more exhaustive documentation is available in the TypeScript typings file.

tdl.configure(options: TDLibConfiguration) => void

Configure several parameters such as libtdjson name or verbosity level. This function should be called before tdl.createClient or tdl.execute.

  // Path to the library. By default, it is 'tdjson.dll' on Windows,
  // 'libtdjson.dylib' on macOS, or '' otherwise.
  tdjson: '',
  // Path to the library directory. By default, it is empty string.
  libdir: '...',
  // Verbosity level of TDLib. By default, it is 2.
  verbosityLevel: 3

Some examples:

  • tdl.configure({ tdjson: '/root/', verbosityLevel: 5 })
  • tdl.configure({ tdjson: 'libtdjson.dylib.1.8.6', libdir: '/usr/local/lib' })
  • tdl.configure({ libdir: __dirname })
  • tdl.configure({ tdjson: require('prebuilt-tdlib').getTdjson() })

The path concatenation of libdir + tdjson is directly passed to dlopen (Unix) or LoadLibrary (Windows). Check your OS documentation to find out where the shared library will be searched for.

tdl.createClient(options: ClientOptions) => Client

Create a TDLib client.

const client = tdl.createClient({
  apiId: 2222, // Your api_id
  apiHash: '0123456789abcdef0123456789abcdef' // Your api_hash
  // ... other options ...

The interface of the options that can be passed here:

type ClientOptions = {
  apiId: number, // Can be obtained at
  apiHash: string, // Can be obtained at
  databaseDirectory: string, // Relative path (default is '_td_database')
  filesDirectory: string, // Relative path (default is '_td_files')
  databaseEncryptionKey: string, // Optional key for database encryption
  useTestDc: boolean, // Use test telegram server (default is false)
  tdlibParameters: Object, // Raw TDLib parameters
  // Advanced options:
  skipOldUpdates: boolean,
  bare: boolean,
  receiveTimeout: number

Of these fields, only apiId and apiHash are required. Any other field can be omitted.

The tdlibParameters option is described in

By default, in tdl, tdlibParameters is set to:

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,
  api_id: options.apiId,
  api_hash: options.apiHash,
  database_directory: options.databaseDirectory,
  files_directory: options.filesDirectory,
  use_test_dc: options.useTestDc

In a real application, you probably want to change device_model and other parameters.

client.login(fn?: () => LoginDetails) => Promise<void>

Log in to your Telegram account.

await client.login()

By default, tdl asks the user for the phone number, auth code, and 2FA password (if needed) in the console. You can override the defaults with custom functions, for example:

// 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 the user is not signed up.

client.login supports only a subset of authentication methods available on Telegram. It is possible (and advisable for larger apps) not to use the client.login helper and implement the authorization process manually, handling authorizationStateWaitPhoneNumber and other updates.

This function accepts the following interface:

type LoginDetails = {
  type?: 'user',
  getPhoneNumber?: (retry?: boolean) => Promise<string>,
  getEmailAddress?: () => Promise<string>,
  getEmailCode?: () => Promise<string>,
  confirmOnAnotherDevice?: (link: string) => void,
  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>
// Note that client.login accepts a function that returns the object, not the
// object directly. The function will not be called if the client is already
// authorized.
declare function login (fn?: () => LoginDetails): Promise<void>

getEmailAddress and getEmailCode are called in TDLib >= v1.8.6 only.

client.loginAsBot(token: string | (() => string | Promise<string>)) => Promise<void>

Instead of logging in as a user, you can log in as a bot by token.

await client.loginAsBot('YOUR_BOT_TOKEN') // Enter your token from @BotFather

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

Attach an event listener to receive updates.

function onUpdate (update) {
  console.log('New update:', update)
client.on('update', onUpdate)
client.on('error', console.error)

Ideally, you should always have a listener on client.on('error'). There is no default listener, all errors will be ignored otherwise.

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

Some other rarely-used events also exist and are described in the TypeScript interface.

client.addListener is an alias for client.on.

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

Attach a one-time listener. string, listener: Function, once?: boolean) => Client

Remove an event listener.

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

client.removeListener is an alias for

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

Call a TDLib method asynchronously. The promise can be rejected with a TDLib object of type _: 'error'.

For the information regarding TDLib API list, see the "Getting started" section of this README.

const chats = await client.invoke({
  _: 'getChats',
  chat_list: { _: 'chatListMain' },
  limit: 4000
await client.invoke({
  _: 'sendMessage',
  chat_id: 123456789,
  input_message_content: {
    _: 'inputMessageText',
    text: {
      _: 'formattedText',
      text: '👻'

tdl.execute(query: Object) => (Object | null)

Call a TDLib method synchronously. This function can be used only with the methods marked as "can be called synchronously" in the TDLib documentation.

const res = tdl.execute({
  _: 'getTextEntities',
  text: '@telegram /test_command'

client.execute(query: Object) => (Object | null)

Same as tdl.execute.

client.close() => Promise<void>

Close the TDLib client.

await client.close()

For the full API, see the index.d.ts file.


tdl fully supports TypeScript and Flow. However, to get the types for TDLib, tdlib-types should be additionally installed.

While tdl works with any TDLib version (above the requirement), the types have to be installed specifically for the TDLib version you use.

tdlib-types is installed by running npm i -D tdlib-types@td-<TDLIB_VERSION>. For example, to install tdlib-types for TDLib v1.8.14, run npm i -D [email protected] To get the list of available versions of tdlib-types, run npm info tdlib-types dist-tags.

The latest supported version of TDLib in tdlib-types is v1.8.14 (the prebuilt-tdlib@stable tag installs types for TDLib v1.8.0).

The types can be imported:

import type { message as Td$message, user /* ... */ } from 'tdlib-types'
// Or import all the types at once:
import type * as Td from 'tdlib-types'
// And use as: Td.message, Td.user, ...

It is considerably more convenient to use tdl in TypeScript, which enables full autocompletion for the TDLib methods and objects along with the documentation.

For more information, see the tdlib-types' README.

The process to manually install types beyond supported versions of tdlib-types currently isn't fleshed out. You can clone the tdl repository, run the tdlib-types generator, and put the generated typings in a .d.ts file within declare module 'tdlib-types' { ... }. In the future, tdl could possibly provide a cli utility to automatically download and install the types.

Creating multiple clients

The current limitation is that the number of created clients should not exceed UV_THREADPOOL_SIZE (as for now, the default is 4, max is 1024).

Possible errors


Update TDLib to v1.7.9 (v1.8.0) or newer. It is no longer possible to log in by phone number in older versions of TDLib.

  • Dynamic Loading Error: Win32 error 126 (Windows)
  • Dynamic Loading Error: dlopen(…) image not found (macOS)
  • …cannot open shared object file: No such file or directory (Linux)

The tdjson shared library or one of its dependencies (for example, libssl) cannot be found. To troubleshoot dependency issues, try to run ldd on Linux or otool -L libtdjson.dylib on macOS. On Windows, you can use an app like Dependency Walker.

Recheck the documentation of dlopen (Linux), dlopen (macOS), Dynamic-Link Library Search Order (Windows) to make sure the shared library is present in the search paths. By default, Linux does not search in the current working directory, while macOS does.

  • fatal error: napi.h: no such file or directory
  • error: no such file or directory: …/node-modules/node-addon-api

The path to the directory where you execute npm install likely contains spaces, which is not supported by gyp:

  • Error while reading RSA public key

You can get this error if libtdjson is dynamically linked against OpenSSL and some of the symbols got resolved to Node.js instead of the system OpenSSL.

Note that Node.js also uses OpenSSL (the distributed binaries are statically linked against it) and exports the OpenSSL symbols. In the result, there are two versions of OpenSSL in the same application. Then, using standard dlopen, especially on Linux, most of the symbols will be resolved into libcrypto inside the Node.js binary, not into the system libcrypto. It still can work correctly if the versions are ABI-compatible, i.e. if TDLib is linked against an OpenSSL version sufficiently similar to the version that Node.js uses (node -p "process.versions.openssl").

tdl tries to get around the symbol conflict issues by using RTLD_DEEPBIND when available, so these issues should be rare in practice.

You can use lldb or gdb to check whether the symbols get resolved into Node.js. For example, open lldb -- node index.js and set these breakpoints:

break set -r EVP_ -s node
break set -r AES_ -s node
break set -r BIO_ -s node
break set -r RSA_ -s node
break set -r CRYPTO_ -s node

It's also possible to set breakpoints inside the system OpenSSL:

break set -r . -s
break set -r . -s

To solve this issue, try to link TDLib statically against OpenSSL (the OPENSSL_USE_STATIC_LIBS option in cmake) or link it against the OpenSSL version that Node.js uses.

Another possible option is to rebuild Node.js from source, linking it dynamically against the same system OpenSSL. That way, there is only one instance of OpenSSL in the application. For example, using nvm, you can install Node.js v16 from source on GNU/Linux via this command:

$ nvm install -s 16 --shared-openssl --shared-openssl-includes=/usr/include/ --shared-openssl-libpath=/usr/lib/x86_64-linux-gnu/

However, it's inconvenient for most users to rebuild Node.js.

Another hypothetical solution is to rebuild TDLib with the OpenSSL headers distributed in Node.js (<path-to-node>/include/node/) without linking it to anything, simply leaving the undefined symbols. Using this option, there is also only one OpenSSL. I haven't checked that this works or that Node exports all the symbols needed for TDLib. With this option, TDLib also should be rebuilt every time Node.js updates the OpenSSL dependency.

This issue doesn't apply to Electron because it doesn't export the OpenSSL symbols.

  • Segmentation fault

Most likely, the cause of the segfault is the same as above.

Popular Bindings Projects
Popular Telegram Projects
Popular Libraries Categories
Related Searches

Get A Weekly Email With Trending Projects For These Categories
No Spam. Unsubscribe easily at any time.
Telegram Api