NxCreateDocs

How It Works

Understand how NxCreator runs your bot code, what tools are available, and how to set up automated tasks.

This page explains what happens behind the scenes when you write and save bot code on NxCreator — and how to use the built-in tools that come with every bot.

What is NxCreator doing?

When you create a bot on NxCreator, the platform takes care of everything you would normally have to set up yourself: renting a server, installing software, keeping the bot process running 24/7, and connecting to Telegram. You just write the logic — what should happen when a user sends a message — and NxCreator handles the rest.

Think of it like this: NxCreator is the kitchen, and your code is the recipe. You write the recipe, and the kitchen handles the oven, the electricity, and making sure everything stays hot.

How your code runs

Each bot runs in its own private environment, completely separate from other bots on the platform. Your code sections are combined into one unified bot program, and the platform connects that program to Telegram on your behalf.

NxCreator uses a popular bot library called Telegraf under the hood. You do not need to set it up or configure it — it is already running. Your job is simply to tell it what to do by registering handlers on the pre-existing bot object. Never create your own bot instance or import Telegraf yourself.

Saving and testing

When you hit save in the editor, NxCreator immediately applies your changes to the live running bot — no deployment steps, no waiting. Just save, switch to Telegram, and test your change. If something is wrong, fix it and save again.

This tight loop — write, save, test in Telegram — is one of the biggest advantages of using NxCreator over self-hosted bots.

Built-in tools

NxCreator provides a set of ready-to-use tools inside every bot. These are called globals — they are available automatically without needing to install or import anything. Just use them by name.

Do not use require(...) to load packages. If you need something, check the table below — it is almost certainly already available.

What is available

What you want to doGlobal to useNotes
Respond to messages and commandsbot`, `Telegraf`, `Scenes`, `MarkupThe main bot object is already set up. Register handlers directly on `bot`.
Use the Grammy bot framework insteadGrammyBot`, `Grammy`, `InlineKeyboard`, `Keyboard`, `grammySessionOnly available if your bot is configured to use the Grammy framework.
Use node-telegram-bot-api insteadTelegramBotOnly available if your bot is configured to use node-telegram-bot-api.
Make HTTP requests to external APIsaxiosA standard HTTP client. Use this to fetch data from any web API.
Store and retrieve datadbA built-in database helper. No configuration needed.
Run tasks on a schedulecronFor repeating tasks. See the Scheduled Tasks section below for details.
Send emailsnodemailerAvailable with some restrictions on file attachments.
Generate text-to-speech audio URLsgoogleTTSUseful for voice bots or audio responses.
Draw images or graphicscreateCanvas`, `loadImageImage creation with size limits. File and URL-based image loading is restricted.
Resize or process imagessharpImage transformation with limits. Saving files directly is not allowed.
Generate unique IDsuuidUseful for creating unique keys or identifiers.
Hash data or generate tokenscryptoStandard cryptographic utilities.
Send a message to all your bot usersbroadcast(payload)Queues a background broadcast job. Supports text, photo, video, audio, document, and more.
Broadcast from a different bot you ownbroadcastAnotherBot(payload)Same as broadcast() but targets a different bot using its botId and your account apiKey.
Copy this bot to another NxCreator accountbotTransfer({ recipientEmail })Clones the bot logic and sections to a recipient. Optionally attaches a new token and auto-starts.

Calling external APIs

Your bot can reach out to the internet and fetch data from any external service. Use the built-in axios global to make these requests. This is how you would connect your bot to a weather service, a payment API, a news feed, or anything else.

bot.command('news', async (ctx) => {
  try {
    const response = await axios.get('https://api.example.com/news');
    await ctx.reply('Latest news: ' + response.data.headline);
  } catch (err) {
    console.error('API request failed:', err);
    await ctx.reply('Could not fetch the news right now. Please try again later.');
  }
});

The try/catch block is important here — external APIs can fail or go down, and you want your bot to handle that gracefully instead of crashing.

Delays and repeating actions

Sometimes you want your bot to do something after a delay, or on a repeating schedule. JavaScript provides two built-in tools for this:

  • setTimeout(fn, ms) — runs something once after a delay. For example, send a follow-up message 5 seconds after a user signs up.
  • setInterval(fn, ms) — runs something repeatedly on a fixed interval. For example, check a feed every 60 seconds.
bot.command('remindme', async (ctx) => {
  await ctx.reply('Got it! I will remind you in 5 seconds...');

  setTimeout(() => {
    ctx.reply('Reminder: 5 seconds have passed!');
  }, 5000); // 5000 milliseconds = 5 seconds
});
These in-memory timers only last as long as the bot is running. If the bot restarts or your code is re-saved, they disappear. For tasks that must survive restarts, see the Scheduled Tasks section below.

Scheduled tasks that last

Imagine you want your bot to send a daily summary to a user every morning at 9am. A simple timer like setTimeout would not work for this — if the bot ever restarts, the timer is gone and the task never runs again.

NxCreator solves this with persistent scheduled tasks. These are jobs that get stored safely outside the bot process, so they survive restarts, re-saves, and updates. Even if your bot goes down and comes back up, the scheduled task will still run at the right time.

How to think about it

Setting up a persistent task is a two-step process:

  • Step 1: Define what should happen. You write the function and give it a name — for example daily-report. This is your task definition.
  • Step 2: Set when it should run. You use the scheduler to tell NxCreator when to trigger that task — once, or on a repeating schedule.

The key insight is that NxCreator stores the name of your task, not the code itself. When the time comes to run it, it looks up the registered task by name and executes it. This is why the name you use in Step 1 must exactly match the name you use in Step 2.

Choosing the right tool

ToolBest forSurvives bot restarts?
setTimeoutA short one-time delay (e.g. wait 10 seconds)No
setIntervalA simple repeating action (e.g. ping every minute)No
cronTime-based repeating actions while the bot is runningNo
persistentScheduler.setTimeoutA one-time future action that must not be lostYes
persistentCron.scheduleA repeating time-based task that must keep runningYes
If a task is important — like sending a daily report or expiring a user subscription — always use the persistent versions. Regular timers are fine for temporary, low-stakes behavior.

Example: send a daily report every morning

This example shows how to send a message every morning at 9:00 AM. Read it from top to bottom — first the task is defined, then a bot command schedules it.

// Step 1: Define what happens when the task runs
registerPersistentTask('daily-report', async ({ payload }) => {
  await axios.post('https://api.example.com/report', {
    chatId: payload.chatId,
    period: 'daily',
  });
});

// Step 2: A command that schedules the task for the user
bot.command('schedule_report', async (ctx) => {
  // This time pattern means: at minute 0, hour 9, every day
  const timePattern = '0 9 * * *';

  if (!persistentCron.validate(timePattern)) {
    await ctx.reply('Something went wrong with the schedule.');
    return;
  }

  await persistentCron.schedule(
    'daily-report',        // must match the name from registerPersistentTask
    timePattern,
    { chatId: ctx.chat.id }, // data passed to the task when it runs
    { key: 'daily-report-main', timezone: 'UTC' }
  );

  await ctx.reply('Done! You will get a daily report at 09:00 UTC every morning.');
});

Breaking down what each part does:

  • registerPersistentTask('daily-report', ...): registers the job logic under the name daily-report. This is the work that will run on schedule.
  • payload: a small piece of data that gets saved alongside the schedule and passed back to your task when it runs. Here it carries the chat ID so the bot knows who to message.
  • persistentCron.validate(timePattern): checks that your time pattern is valid before saving it.
  • persistentCron.schedule(...): saves the task into the persistent scheduler so it will keep running even after restarts.
  • key: 'daily-report-main': a unique name for this particular scheduled job. If a user runs the command again, it updates the existing schedule instead of creating a duplicate.

Example: send a message after a delay

Sometimes you do not need a repeating schedule — you just need something to happen once, later. For example, sending a reminder one hour after a user signs up, even if the bot restarts in between.

// Define what happens when the delayed task fires
registerPersistentTask('follow-up-message', async ({ payload }) => {
  await bot.telegram.sendMessage(
    payload.chatId,
    'Just a reminder: your free trial ends in 1 hour.'
  );
});

// Schedule it to run once, 1 hour from now
bot.command('remind_later', async (ctx) => {
  const oneHour = 60 * 60 * 1000; // milliseconds

  const jobId = await persistentScheduler.setTimeout(
    'follow-up-message',
    oneHour,
    { chatId: ctx.chat.id }
  );

  await ctx.reply('Got it! You will hear from me in an hour. (Job ID: ' + jobId + ')');
});

This pattern works well for reminders, delayed follow-ups, expiring access codes, or anything else that should fire once at a specific time in the future.

Understanding the time pattern format

Persistent repeating schedules use a time pattern with five parts, separated by spaces. Each part controls one unit of time — minute, hour, day, month, and day of the week. A * means "every".

* * * * *
│ │ │ │ │
│ │ │ │ └── day of week  (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
│ │ │ └──── month        (1–12)
│ │ └────── day of month (1–31)
│ └──────── hour         (0–23)
└────────── minute       (0–59)

Some practical examples to get you started:

  • 0 9 * * * — every day at 9:00 AM.
  • */15 * * * * — every 15 minutes, all day long.
  • 0 0 * * 1 — every Monday at midnight.
  • 30 18 * * 5 — every Friday at 6:30 PM.

Quick reference

  • registerPersistentTask(name, handler) — defines a named task that the scheduler can call later.
  • persistentCron.validate(pattern) — returns true if the time pattern is valid, false otherwise.
  • persistentCron.schedule(name, pattern, payload, options) — saves a repeating scheduled task.
  • persistentScheduler.setTimeout(name, ms, payload) — saves a one-time delayed task.
  • persistentCron.cancel(jobId) or persistentScheduler.cancel(jobId) — removes a saved task.

Common mistakes to avoid

  • Scheduling before registering: Always call registerPersistentTask first, then schedule it. The scheduler needs the task to already exist.
  • Mismatched names: The name in registerPersistentTask and the name in persistentCron.schedule must be exactly the same.
  • No key set: If a user can trigger the scheduling command more than once, always pass a key in options. Without it, each run of the command creates a new copy of the job.
  • Too much data in payload: Only put small identifiers in the payload (like a user ID or chat ID). Store everything else in the database.
A solid beginner pattern: store your app data in db, register a simple named task, and schedule it with a stable key. That covers most real use cases without overcomplicating things.

Broadcasting messages

The broadcast function sends a message to every user who has ever interacted with your bot. It runs entirely in the background — your bot code does not wait for all messages to be sent. Instead, it queues the job and returns a jobId immediately.

Broadcasting respects Telegram's rate limits automatically (30–40 messages per second). If Telegram returns a rate-limit error (429), the broadcaster pauses and retries from the exact same user without losing progress.

Basic usage

// Text message
const result = await broadcast({ type: 'text', text: 'Hello everyone! 👋' })
console.log(result.jobId) // track progress later

// Shorthand for text
const result = await broadcast('Hello everyone!')

Sending media

Pass a media field containing a Telegram file_id or a public URL. Use caption to add text beneath the media.

// Photo with caption
await broadcast({
  type: 'photo',
  media: '<file_id or URL>',
  caption: '🔥 Check out our new update!',
  parseMode: 'HTML',
})

// Video
await broadcast({
  type: 'video',
  media: '<file_id>',
  caption: 'Watch this!',
  extra: { reply_markup: { inline_keyboard: [[{ text: 'Open', url: 'https://example.com' }]] } },
})

Supported types

typeWhat it sendsRequired fields
textPlain or formatted text messagetext
photoImage`media` (file_id or URL)
videoVideo filemedia
audioAudio filemedia
documentAny file (PDF, ZIP, etc.)media
animationGIF or silent MP4media
voiceVoice message (OGG)media
video_noteRound video messagemedia
stickerStickermedia
locationMap pinextra.latitude`, `extra.longitude
contactPhone contact cardextra.phone_number`, `extra.first_name
pollPoll with options`extra.question`, `extra.options` (array, min 2)

Targeting a language

Use the optional filter field to send only to users whose Telegram language matches a given code. This is useful for sending announcements in the right language.

await broadcast({
  type: 'text',
  text: 'Hello English speakers!',
  filter: { lang: 'en' },
})

await broadcast({
  type: 'text',
  text: 'नमस्ते!',
  filter: { lang: 'hi' },
})

Return value

const result = await broadcast({ type: 'text', text: 'Hi!' })
// result: { ok: true, jobId: 'a1b2c3d4', status: 'queued', note: '...' }
Up to 20 bots can broadcast simultaneously. If your bot already has an active broadcast running, the new job is queued and will start automatically when the current one finishes.

Telegram send parameters

Every broadcast call accepts a top-level parseMode field and an extra object. Anything inside extra is forwarded directly to the Telegram Bot API, so you can use any parameter the API supports for that message type.

Parse mode

Control how Telegram renders the message text or caption. Pass parseMode at the top level — no need to nest it inside extra.

ValueWhat it does
'HTML'Enables `<b>`, `<i>`, `<code>`, `<a href="...">`, `<pre>`, `<s>`, `<u>`, `<tg-spoiler>` tags.
'MarkdownV2'Enables **bold**, _italic_, `code`, [links](url), ||spoiler||. Special chars must be escaped with `\`.
'Markdown'Legacy Markdown — avoid for new code. Use MarkdownV2 instead.
`''` (empty)Plain text, no formatting. Default.
await broadcast({
  type:      'text',
  text:      '<b>🚀 New update!</b>\nVersion <code>2.0</code> is live.',
  parseMode: 'HTML',
})

await broadcast({
  type:      'photo',
  media:     '<file_id>',
  caption:   '**Bold caption** and _italic_',
  parseMode: 'MarkdownV2',
})

Extra Telegram parameters

Pass any additional Telegram Bot API field inside the extra object. Common ones:

ParameterTypeApplies toDescription
disable_web_page_previewbooleantextPrevents Telegram from generating a link preview below the message.
disable_notificationbooleanall typesSends the message silently — users receive it with no notification sound.
protect_contentbooleanall typesPrevents recipients from forwarding or saving the message.
reply_markupobjectall typesAttach an inline keyboard or custom reply keyboard to the message.
durationnumberaudio, video, voiceDuration of the media in seconds.
width` / `heightnumbervideo, animationVideo or animation dimensions.
supports_streamingbooleanvideoPass `true` for videos suitable for streaming.
thumbnailstringvideo, audio, doc`file_id` or URL of a custom thumbnail.
// Disable link preview + silent delivery
await broadcast({
  type:      'text',
  text:      'Check https://example.com',
  parseMode: 'HTML',
  extra: {
    disable_web_page_preview: true,
    disable_notification:     true,
  },
})

// Inline keyboard button on a photo
await broadcast({
  type:    'photo',
  media:   '<file_id>',
  caption: 'Tap below to learn more',
  extra: {
    reply_markup: {
      inline_keyboard: [[
        { text: '🔗 Open', url: 'https://example.com' },
      ]],
    },
  },
})

// Protected video — cannot be forwarded
await broadcast({
  type:  'video',
  media: '<file_id>',
  extra: {
    protect_content:     true,
    supports_streaming:  true,
    duration:            120,
  },
})

Broadcast from another bot

broadcastAnotherBot works exactly like broadcast but targets a different bot that you own. Provide the target bot's botId and your NxCreate account apiKey.

This is useful when you run multiple bots and want one bot's logic to trigger a broadcast on behalf of another — for example, an admin bot kicking off an announcement through a channel-specific bot.

Basic usage

const result = await broadcastAnotherBot({
  botId:  'abcd1234',          // target bot's NxCreate bot ID
  apiKey: 'your-account-key',  // your NxCreate account API key (from dashboard)
  type:   'text',
  text:   '📢 Hello from a sibling bot!',
  parseMode: 'HTML',
})
console.log(result.jobId)

Sending media

await broadcastAnotherBot({
  botId:  'abcd1234',
  apiKey: 'your-account-key',
  type:   'photo',
  media:  '<file_id>',
  caption: 'Sent via another bot 🤖',
  extra: {
    disable_notification: true,
    reply_markup: {
      inline_keyboard: [[{ text: 'Learn more', url: 'https://example.com' }]],
    },
  },
})

Parameters

ParameterTypeRequiredDescription
botIdstringYesThe NxCreate bot ID of the target bot (visible in the dashboard).
apiKeystringYesYour NxCreate account API key. Throws if omitted.
typestringNoMessage type. Same values as `broadcast()`. Defaults to `'text'`.
textstringCondRequired when `type` is `'text'`.
mediastringCondRequired for photo, video, audio, document, animation, voice, video_note, sticker.
captionstringNoOptional caption for media types.
parseModestringNo`'HTML'` | `'MarkdownV2'` | `'Markdown'`. Applies to text and captions.
filterobjectNoScope the broadcast to users matching `{ lang: 'en' }`. Omit to target everyone.
extraobjectNoAny additional Telegram Bot API params forwarded verbatim.
The botId must belong to your account. Always wrap calls in try/catch.

Tracking a broadcast job

Because broadcast queues a background job, your bot code gets a jobId back immediately. Use broadcastStatus(jobId) and broadcastCancel(jobId) — both are injected globals available in every bot, just like broadcast itself.

Checking job status

bot.command('blast_status', async (ctx) => {
  // retrieve the stored jobId (you saved it earlier)
  const record = await db.operation.findOne('broadcast_jobs', { chatId: ctx.chat.id })
  if (!record) return ctx.reply('No broadcast found.')

  const result = await broadcastStatus(record.jobId)
  const job = result.job

  await ctx.reply(
    `📡 Status: ${job.status}\n` +
    `✅ Sent: ${job.sent} / ${job.total}\n` +
    `❌ Failed: ${job.failed}`
  )
})

Status fields

FieldTypeDescription
jobIdstringUnique ID of this broadcast job.
statusstringqueued` → `running` → `done` / `failed` / `cancelled
totalnumberTotal number of users targeted.
sentnumberMessages successfully delivered so far.
failednumberUsers skipped (blocked bot, deactivated account, etc.).
createdAtnumberUnix timestamp (ms) when the job was queued.
startedAtnumberUnix timestamp (ms) when sending began. 0 if still queued.
finishedAtnumberUnix timestamp (ms) when the job completed. 0 if still running.

Polling until done

For automated flows you can poll the status in a loop. Keep the interval at least 5 seconds to avoid hammering the service.

async function waitForBroadcast(jobId) {
  while (true) {
    const result = await broadcastStatus(jobId)
    const job = result.job
    if (['done', 'failed', 'cancelled'].includes(job.status)) return job
    await new Promise(r => setTimeout(r, 5000))
  }
}

bot.command('blast', async (ctx) => {
  const { jobId } = await broadcast({ type: 'text', text: 'Big announcement!' })
  await db.operation.updateOne('broadcast_jobs', { chatId: ctx.chat.id }, { $set: { jobId } }, { upsert: true })
  await ctx.reply('Broadcast started!')

  const job = await waitForBroadcast(jobId)
  await ctx.reply(`Done! Sent ${job.sent}, failed ${job.failed}.`)
})
Only poll inside admin-only commands or background tasks. For large audiences a broadcast can take several minutes — never await it inline in a regular user-facing handler.

Cancelling a running broadcast

bot.command('stop_blast', async (ctx) => {
  const jobId = ctx.message.text.split(' ')[1] // /stop_blast <jobId>
  if (!jobId) return ctx.reply('Usage: /stop_blast <jobId>')

  const result = await broadcastCancel(jobId)
  await ctx.reply(result.ok ? 'Cancellation requested.' : result.message)
})

Transferring a bot

The botTransfer function copies this bot — its logic and all its sections — to another NxCreator account. The original bot is not affected. The recipient gets a new bot under their account that they can attach their own Telegram token to.

This is useful for building bot-as-a-product flows: a user pays or completes a step, and your bot automatically provisions a ready-to-use copy for them.

Basic usage

// Transfer to a recipient — they connect their own token from the dashboard
const result = await botTransfer({ recipientEmail: '[email protected]' })
console.log(result.data.newBotId) // the new bot ID on their account

Transfer and auto-start

If you already have the recipient's Telegram bot token (for example, they submitted it via a form), you can attach it and start the bot immediately.

const result = await botTransfer({
  recipientEmail: '[email protected]',
  newBotToken:    '123456789:AABBcc-theirtoken',
  runNow:         true,
})

if (result.ok) {
  await ctx.reply('Your bot is live! Bot ID: ' + result.data.newBotId)
} else {
  await ctx.reply('Transfer failed: ' + result.msg)
}

Parameters

ParameterTypeRequiredDescription
recipientEmailstringYesEmail address of the NxCreator account to receive the bot. Throws an error if omitted.
newBotTokenstringNoTelegram bot token to attach to the new bot on the recipient account.
runNowbooleanNoIf true, starts the bot immediately after transfer. Requires `newBotToken`.

Return value

{
  ok:  true,
  msg: "Bot transferred successfully.",
  data: {
    oldBotId:    "abc123",   // original bot ID (this bot)
    newBotId:    "def456",   // new bot ID on recipient's account
    logicCopied: true,       // whether the .js logic file was copied
    startup:     null,       // activation result if runNow was true
  }
}
If recipientEmail is not provided, botTransfer throws an error with { ok: false, message: "recipientEmail is required" } on the error object. Always wrap calls in a try/catch.
Last updated March 22, 2026
Was this page helpful?