@neoxr/zapo
v1.2.13
Published
TypeScript connector for building WhatsApp Bot (ZapoJS)
Maintainers
Readme
@neoxr/zapo
@neoxr/zapo is a TypeScript connector for building WhatsApp bots on top of zapo-js. It creates and manages the WhatsApp connection, forwards client events, serializes incoming and outgoing messages, loads optional plugins, and adds high-level helpers for common message types.
Table of Contents
- Quick Start
- Connection Options
- Session Storage
- Events
- Message Object
- Messaging Functions
- Running the Full Bot Base (neoxr-bot)
- Troubleshooting
Quick Start
Get a working bot that replies to ping in about five minutes.
1. Requirements
- Node.js 20 or newer (LTS recommended)
- FFmpeg installed and available in your
PATH(needed for stickers, voice notes and video helpers) - A WhatsApp account to use as the bot number
2. Create a project and install
mkdir my-bot && cd my-bot
npm init -y
npm install @neoxr/zapoThe examples below use ES modules, so add this to your package.json:
{
"type": "module"
}3. Create index.js
import { Client } from '@neoxr/zapo'
const waSocket = new Client({
plugsdir: 'plugins',
presence: true,
online: true,
pairing: {
state: true, // set to false to log in with a QR code instead
number: '6285xxxxxxx' // bot number with country code, no "+" or spaces
},
create_session: { type: 'sqlite', session: 'bot-session' },
custom_id: 'neoxr',
engines: [], // optional: add store / voip engines here
debug: false
})
waSocket.on('connect', () => console.log('Connecting to WhatsApp...'))
waSocket.on('ready', () => console.log('Bot is ready!'))
waSocket.on('error', error => console.error(error))
waSocket.on('message', async ctx => {
if (ctx.fromMe) return
if (ctx.text?.toLowerCase() === 'ping') {
await waSocket.sock.reply(ctx.chat, 'Pong!', ctx)
}
})4. Run
node index.js5. Link your WhatsApp account
- Pairing code (
pairing.state: true): the console prints an 8-character code. On your phone open WhatsApp > Linked devices > Link a device > Link with phone number instead and enter it. You can choose your own code by settingpairing.codeto 8 alphanumeric characters (for exampleNEOXRBOT). - QR code (
pairing.state: false): scan the QR code printed in the console from WhatsApp > Linked devices > Link a device.
After linking, send ping to the bot number. It should answer Pong!.
The session is saved by
create_session. As long as the session file or database is kept, you do not have to pair again on the next start. Delete it to log in with a different number.
6. Keep it running (optional)
npm install -g pm2
pm2 start index.js --name neoxr-bot
pm2 save
pm2 logs neoxr-botNext steps
- Add commands as plugins in the
pluginsfolder (see theplugsdiroption). - Browse the Messaging Functions to send images, stickers, buttons and more.
- Want a ready-made bot with downloader, group management and utilities? See Running the Full Bot Base.
Connection Options
The following is the default configuration used when initializing a new Zapo connection. It covers session management, plugin loading and bot-specific behavior.
type SessionType = 'mongo' | 'postgres' | 'local' | 'mysql' | 'sqlite' | 'redis'
export interface ConnectionOpts {
online?: boolean,
presence?: boolean,
stealth?: string,
custom_id: string,
pairing?: {
state: boolean,
number: string,
code?: string
},
multiple?: boolean,
create_session?: {
type: SessionType,
session: string,
config?: any,
number?: string | number,
owner?: string | number
},
setting?: any,
engines: any[],
debug: boolean,
}| Option | Type | Description |
| --- | --- | --- |
| plugsdir | string | Folder that contains your plugins. |
| online | boolean | Mark the bot as online after connecting. |
| presence | boolean | Receive and emit presence updates. |
| stealth | string | Pretend to be another device to avoid bot detection. One of ios, android, web, desktop. |
| custom_id | string | Identifier for this bot instance. Required. |
| pairing.state | boolean | true for pairing code, false for QR scan. |
| pairing.number | string | Bot number with country code. |
| pairing.code | string | Optional custom pairing code (8 alphanumeric characters). |
| multiple | boolean | Allow several bot sessions in one process. |
| create_session | object | Where and how the session is stored. See Session Storage. |
| setting | any | Extra settings passed to your bot. |
| engines | any[] | Optional engines such as store or voip. Use [] if you do not need them. |
| debug | boolean | Print internal logs. Set to true to see how the module works. |
Full example with all common options:
import { Client, Utils, Config } from '@neoxr/zapo'
const waSocket = new Client({
plugsdir: 'plugins',
presence: true,
online: true,
pairing: {
state: true,
number: '6285xxxxxxx',
code: 'NEOXRBOT'
},
create_session: { type: 'sqlite', session: 'bot-session' },
// stealth: 'ios',
custom_id: 'neoxr',
engines: [store, voip], // only if you have initialized them
debug: false
})Session Storage
create_session.type decides where the login session is kept:
| Type | Typical use |
| --- | --- |
| local | Plain files in a local folder. Simplest option for development. |
| sqlite | Single-file database. Good default for one bot on one server. |
| mysql / postgres | Shared SQL database, useful for several instances. |
| mongo | MongoDB, for example a hosted Atlas cluster. |
| redis | Fast in-memory storage, for example on ephemeral hosting. |
For anything other than local and sqlite, pass the connection details in create_session.config.
Events
Built-in events
waSocket.on('connect', () => console.log('connecting'))
waSocket.on('error', error => console.log(error))
waSocket.on('ready', () => console.log('ready'))
waSocket.on('stories', ctx => console.log(ctx))
waSocket.on('message', ctx => console.log(ctx))
waSocket.on('message.delete', ctx => console.log(ctx))
waSocket.on('group.add', ctx => console.log(ctx))
waSocket.on('group.remove', ctx => console.log(ctx))
waSocket.on('group.promote', ctx => console.log(ctx))
waSocket.on('group.demote', ctx => console.log(ctx))
waSocket.on('group.request', ctx => console.log(ctx))
waSocket.on('presence', ctx => console.log(ctx))Event piping
You can also listen to the raw events emitted by zapo-js through waSocket.ev:
waSocket.ev.on('voip_call_incoming', update => console.log(update))
waSocket.ev.on('voip_call_state', update => console.log(update))
waSocket.ev.on('voip_call_ended', update => console.log(update))
waSocket.ev.on('voip_call_error', update => console.log(update))
waSocket.ev.on('voip_call_outbound_audio_finished', update => console.log(update))
waSocket.ev.on('voip_call_inbound_video', update => console.log(update))
waSocket.ev.on('voip_call_inbound_video_rtp', update => console.log(update))
waSocket.ev.on('message', update => console.log(update))
waSocket.ev.on('message_send', update => console.log(update))
waSocket.ev.on('message_addon', update => console.log(update))
waSocket.ev.on('message_protocol', update => console.log(update))
waSocket.ev.on('message_bot_chunk', update => console.log(update))
waSocket.ev.on('message_unavailable', update => console.log(update))
waSocket.ev.on('receipt', update => console.log(update))
waSocket.ev.on('presence', update => console.log(update))
waSocket.ev.on('chatstate', update => console.log(update))
waSocket.ev.on('call', update => console.log(update))
waSocket.ev.on('group', update => console.log(update))
waSocket.ev.on('newsletter', update => console.log(update))
waSocket.ev.on('newsletter_message_update', update => console.log(update))
waSocket.ev.on('business', update => console.log(update))
waSocket.ev.on('picture', update => console.log(update))
waSocket.ev.on('privacy', update => console.log(update))
waSocket.ev.on('blocklist', update => console.log(update))
waSocket.ev.on('own_username', update => console.log(update))
waSocket.ev.on('mutation', update => console.log(update))
waSocket.ev.on('mutation_send', update => console.log(update))
waSocket.ev.on('history_sync_chunk', update => console.log(update))
waSocket.ev.on('group_history_bundle', update => console.log(update))
waSocket.ev.on('offline_resume', update => console.log(update))
waSocket.ev.on('offline_thread_metadata', update => console.log(update))
waSocket.ev.on('mex_notification', update => console.log(update))
waSocket.ev.on('stream_failure', update => console.log(update))
waSocket.ev.on('stanza_error', update => console.log(update))Message Object
Every message event gives you a serialized message (m or ctx). Commonly used fields:
| Field | Description |
| --- | --- |
| m.chat | Chat JID where the message was sent. |
| m.sender | Sender JID (phone-number form). |
| m.text | Text content of the message. |
| m.mtype | Message type, for example extendedTextMessage. |
| m.isGroup | true when sent in a group. |
| m.fromMe | true when sent by the bot itself. |
| m.quoted | The quoted message, or null. |
| m.mentionedJid | List of mentioned JIDs. |
| m.key | Message key (remoteJid, id, fromMe, participant, ...). |
| m.reply(...) | Reply to the message. |
| m.react(...) | React to the message. |
| m.download() | Download attached media. |
Example of a full object (group text message):
{
rawNode: {
tag: 'message',
attrs: {
from: '[email protected]',
type: 'text',
id: 'A512151D2087CBD787C23C1129D69EE5',
participant: '8792385306797@lid',
sts: '1790580592042831',
offline: '0',
verified_level: 'high',
notify: 'Neoxr Creative',
addressing_mode: 'lid',
expiration: '86400',
verified_name: '4892374714914454331',
participant_pn: '[email protected]',
t: '1790580591'
},
content: [[Object], [Object], [Object], [Object]]
},
key: {
remoteJid: '[email protected]',
id: 'A512151D2087CBD787C23C1129D69EE5',
fromMe: false,
isGroup: true,
isBroadcast: false,
isNewsletter: false,
participantAlt: '[email protected]',
senderDevice: 0,
participant: '8792385306797@lid'
},
stanzaType: 'text',
offline: true,
timestampSeconds: 1790580591,
expirationSeconds: 86400,
pushName: 'Neoxr Creative',
message: {
extendedTextMessage: {
endCardTiles: [],
text: '=> m',
previewType: 0,
contextInfo: [Object],
inviteLinkGroupTypeV2: 0
},
messageContextInfo: {
threadId: [],
messageSecret: [Uint8Array],
botMetadata: [Object]
}
},
id: 'A512151D2087CBD787C23C1129D69EE5',
isBot: false,
fromMe: false,
chat: '[email protected]',
sender: '[email protected]',
isGroup: true,
mtype: 'extendedTextMessage',
quoted: null,
mentionedJid: [],
expiration: 86400,
text: '=> m',
reply: [Function],
react: [AsyncFunction],
download: [AsyncFunction]
}Messaging Functions
All helpers live on the socket client:
const client = waSocket.sockText and reactions
// relay a raw message (like Baileys)
client.relayMessage(m.chat, {
extendedTextMessage: { text: 'Hi' }
}, {
additionalAttributes: {},
additionalNodes: [],
...options
})
// send a text message (auto tagged)
client.reply(m.chat, 'Test!', m)
// send a reaction
client.sendReact(m.chat, '💀', m.key)
// send a text message with a custom thumbnail
client.sendMessageModify(m.chat, 'Test!', m, {
title: '© neoxr-bot',
largeThumb: true,
ads: false,
thumbnail: 'https://iili.io/HP3ODj2.jpg', // Buffer or URL
url: 'https://chat.whatsapp.com/HYknAquOTrECm9KPJJQO1V'
})
// same, as a link preview
client.sendMessageModify(m.chat, 'Test!', m, {
title: '© neoxr-bot',
largeThumb: true,
type: 'preview-link',
ratio: 'landscape', // landscape (default), portrait, square
thumbnail: 'https://iili.io/HP3ODj2.jpg', // Buffer or URL
url: 'https://chat.whatsapp.com/HYknAquOTrECm9KPJJQO1V'
})Files and media
All file helpers accept a path, URL or Buffer, and detect the extension automatically.
// send a file
client.sendFile(m.chat, 'https://iili.io/HP3ODj2.jpg', 'image.jpg', 'Test!', m)
// send as a document
client.sendFile(m.chat, 'https://iili.io/HP3ODj2.jpg', 'image.jpg', 'Test!', m, {
document: true
})
// send a live photo (video source)
client.sendFile(m.chat, 'https://cdn.videy.co/7QUk0zRO1.mp4', 'video.mp4', 'Test!', m, {
photo_live: true,
// thumbnail: Buffer | URL | Path
})
// send a voice note
client.sendFile(m.chat, './media/audio/ah.mp3', '', '', m, {
ptt: true
})
// send audio with a cover image in the audio tag
client.sendFile(m.chat, './media/audio/ah.mp3', '', '', m, {
audio_tag: {
APIC: coverBuffer // Buffer
}
})
// send a PTV (round video) message, 10 seconds max
client.sendPtv(m.chat, './media/video/yemete.mp4')
// send an album
client.sendAlbumMessage(m.chat, [{
url: 'https://i.pinimg.com/736x/6f/a3/6a/6fa36aa2c367da06b2a4c8ae1cf9ee02.jpg',
caption: 'Content 1st', // optional
type: 'image' // optional
}, {
url: 'https://i.pinimg.com/736x/0b/97/6f/0b976f0a7aa1aa43870e1812eee5a55d.jpg',
caption: 'Content 2nd',
type: 'image'
}, {
url: 'https://i.pinimg.com/736x/8c/6d/db/8c6ddb5fe6600fcc4b183cb2ee228eb7.jpg',
caption: 'Content 3rd',
type: 'image'
}], m)Stickers
Source can be a URL or Buffer.
const sticker = {
packname: 'Sticker by',
author: '© neoxr.js'
}
client.sendSticker(m.chat, 'https://iili.io/HP3ODj2.jpg', m, sticker) // normal
client.sendSticker(m.chat, 'https://iili.io/HP3ODj2.jpg', m, { ...sticker, meta: true }) // AI sticker
client.sendSticker(m.chat, 'https://iili.io/HP3ODj2.jpg', m, { ...sticker, lock: true }) // lock sticker
client.sendSticker(m.chat, 'https://iili.io/HP3ODj2.jpg', m, { ...sticker, premium: true }) // premium stickerPolls and contacts
// poll result
client.pollResult(m.chat, {
name: 'Demo Poll Result',
votes: [{
name: 'Option A',
count: 1500
}, {
name: 'Option B',
count: 200
}]
}, m)
// contact card
client.sendContact(m.chat, [{
name: 'Wildan Izzudin',
number: '6285887776722',
about: 'Owner & Creator'
}], m, {
org: 'Neoxr Network',
website: 'https://api.neoxr.my.id',
email: '[email protected]'
})Interactive buttons
Interactive buttons depend on WhatsApp client behavior and may stop working at any time. Use at your own risk.
const buttons = [{
name: 'quick_reply',
buttonParamsJson: JSON.stringify({
display_text: 'OWNER',
id: '.owner'
}),
}, {
name: 'cta_url',
buttonParamsJson: JSON.stringify({
display_text: 'Rest API',
url: 'https://api.neoxr.my.id',
merchant_url: 'https://api.neoxr.my.id'
})
}, {
name: 'cta_copy',
buttonParamsJson: JSON.stringify({
display_text: 'Copy',
copy_code: '123456'
})
}, {
name: 'cta_call',
buttonParamsJson: JSON.stringify({
display_text: 'Call',
phone_number: '6285887776722'
})
}, {
name: 'single_select',
buttonParamsJson: JSON.stringify({
title: 'Tap!',
sections: [{
rows: [{
title: 'Owner',
description: 'X',
id: '.owner'
}, {
title: 'Runtime',
description: 'Y',
id: '.run'
}]
}]
})
}]
// buttons and list
client.replyButton(m.chat, buttons, m, {
header: '',
content: 'Hi!',
// v2: true, // product style with image
type: 'interactive',
footer: '',
media: global.db.setting.cover // video or image link
})
// buttons and list (grouped in a single select menu)
client.replyButton(m.chat, buttons, m, {
header: '',
content: 'Hi!',
footer: '',
media: global.db.setting.cover,
multiple: {
name: 'オートメーション',
code: 'neoxr-bot',
list_title: 'Select Menu',
button_title: 'Tap Here!'
},
type: 'interactive'
})
// carousel
const cards = [{
header: {
image: global.db.setting.cover,
hasMediaAttachment: true,
},
body: { text: 'P' },
nativeFlowMessage: {
buttons: [{
name: 'cta_url',
buttonParamsJson: JSON.stringify({
display_text: 'Contact Owner',
url: 'https://api.neoxr.eu',
webview_presentation: null
})
}]
}
}, {
header: {
image: global.db.setting.cover,
hasMediaAttachment: true,
},
body: { text: 'P' },
nativeFlowMessage: {
buttons: [{
name: 'cta_url',
buttonParamsJson: JSON.stringify({
display_text: 'Contact Owner',
url: 'https://api.neoxr.eu',
webview_presentation: null
})
}]
}
}]
client.replyButton(m.chat, [], m, {
title: global.header,
type: 'interactive',
content: 'Hi! @0',
cards
})Legacy buttons
text and footer must not be empty.
const buttons = [{
text: 'Runtime',
command: '.runtime'
}, {
text: 'Statistic',
command: '.stat'
}]
// text
client.replyButton(m.chat, buttons, m, {
text: 'Hi @0',
footer: global.footer
})
// image or video
client.replyButton(m.chat, buttons, m, {
text: 'Hi @0',
footer: global.footer,
media: global.db.setting.cover
})
// document
client.replyButton(m.chat, buttons, m, {
text: 'Hi @0',
footer: global.footer,
media: global.db.setting.cover, // any file link
document: {
filename: 'neoxr.jpg'
}
})
// location
client.replyButton(m.chat, buttons, m, {
text: 'Hi @0',
footer: global.footer,
media: global.db.setting.cover,
location: {
name: global.header,
description: 'オートメーション'
}
})
// legacy buttons + native flow
client.replyButton(m.chat, [{
text: 'Runtime',
command: '.runtime'
}, {
name: 'single_select',
params: {
title: 'Tap!',
sections: [{
rows: [{
title: 'Runtime',
description: '---',
id: '.runtime'
}, {
title: 'Statistic',
description: '---',
id: '.stat'
}]
}]
}
}], m, {
text: 'Hi @0',
footer: global.footer,
media: global.db.setting.cover
})Group status
// image or video
client.groupStatus(m.chat, {
media: 'https://i.pinimg.com/736x/0b/97/6f/0b976f0a7aa1aa43870e1812eee5a55d.jpg', // URL or Buffer
caption: 'Hi!'
})
// audio
client.groupStatus(m.chat, {
media: 'https://example.com/audio.mp3', // URL or Buffer
background: '#FF0000'
})
// text
client.groupStatus(m.chat, {
text: 'Hi!',
background: '#FF0000',
color: '#222222'
})
// close friends: add the `private` option to any of the above
client.groupStatus(m.chat, {
text: 'Hi!',
background: '#FF0000',
color: '#222222'
}, {
private: {
name: 'Neoxr Creative',
emoji: '🔥'
}
})
// instant: forward a quoted message as status
client.groupStatus(m.chat, m.quoted.fakeObj, {
private: {
name: 'Neoxr Creative',
emoji: '🔥'
}
})Meta (rich) messages
sendMetaMsg takes a list of blocks. Which blocks work depends on the version.
v1: text with mentions, tables and code
client.sendMetaMsg(m.chat, [{
text: `Hi @${m.sender.replace(/@.+/, '')} ✨, this is a simple meta message that supports *mentions*, *tables* and *code format*.`
}, {
code: {
language: 'javascript',
code: fs.readFileSync('./error.js', 'utf-8')
}
}, {
text: 'And this is an example of a table.'
}, {
table: {
title: 'Data',
headers: ['Code', 'Artist'],
rows: [
['SSID-738', 'Artist A'],
['RTXU-849', `@${m.sender.replace(/@.+/, '')}`]
]
}
}, {
text: 'You can add text, tables and code blocks as you like. 😎'
}], m, {
title: global.header,
mentions: [m.sender]
})v2: citations, links, muted text, suggestions and sources (no mentions or tables)
client.sendMetaMsg(m.chat, [{
text: 'This meta message supports citations [](https://api.neoxr.eu) and links [Neoxr API](https://api.neoxr.eu).\n\nCode formatting still works:'
}, {
code: {
language: 'javascript',
code: fs.readFileSync('./error.js', 'utf-8')
}
}, {
muted: 'There is also muted text like this.'
}, {
suggestions: ['N', 'E', 'O', 'X', 'R', 'B', 'O', 'T']
}, {
sources: [{
icon: 'https://i.pinimg.com/736x/b4/e0/12/b4e012b9e55bc101eb2c89655888e2f1.jpg',
title: 'Github',
url: 'https://github.com/neoxr/neoxr-bot'
}]
}], m, {
title: global.header
})v3: reels, posts and products (no mentions or tables)
client.sendMetaMsg(m.chat, [{
text: 'Photo and video blocks are supported.\n\n---\n\nHere is an example of reels:'
}, {
reels: [
'https://i.pinimg.com/736x/48/58/17/485817189f76066e8b22637d1310d3b4.jpg',
'https://i.pinimg.com/736x/0e/32/e3/0e32e35b0324a541ba7fe705330febde.jpg',
'https://i.pinimg.com/736x/88/78/b1/8878b18952e3587dd5c7502f35155731.jpg'
].map(image => ({
creator: 'Neoxr Creative',
avatar: 'https://avatars.githubusercontent.com/u/52621597?v=4',
verified: true,
thumbnail: image,
url: 'https://api.neoxr.eu',
source: 'IG'
}))
}, {
text: '\n\n---\n\nSocial media posts:'
}, {
posts: [{
media: 'https://i.pinimg.com/736x/ba/fd/92/bafd92cf5c8fbd802cf59c51286fc13f.jpg',
caption: 'Caption one',
source: 'FACEBOOK'
}, {
media: 'https://i.pinimg.com/1200x/ec/0a/fd/ec0afd363645fa73d9d17df1136fddd4.jpg',
caption: 'Caption two',
source: 'THREADS'
}, {
media: 'https://i.pinimg.com/736x/53/dd/f9/53ddf9fcf881b704548f2b6c676b07a6.jpg',
caption: 'Caption three',
source: 'INSTAGRAM'
}].map(v => ({
username: 'Neoxr Creative',
avatar: 'https://avatars.githubusercontent.com/u/52621597?v=4',
verified: true,
caption: v.caption,
url: 'https://api.neoxr.eu',
thumbnail: v.media,
source: v.source,
post_type: 'PHOTO'
}))
}, {
text: '\n\n---\n\nA single product (no slide):\n\n'
}, {
products: {
title: 'Script Selfbot (WhatsApp Bot) [Free Update]',
image: 'https://imgkub.com/images/2026/01/14/image586735a7ca2a894a.jpg',
sale_price: 'Rp. 65.000',
brand: 'Neoxr Creative',
url: `https://wa.me/${Config.owner}`
}
}, {
text: '\n\n---\n\nProducts as a slide:'
}, {
products: [{
title: 'Script Premium (WhatsApp Bot) V5.1-Optima',
image: 'https://imgkub.com/images/2025/12/07/image6e1f9b94ba9ced64.jpg',
sale_price: 'Rp. 150.000'
}, {
title: 'Script E-Commerce (NeoCommerce)',
image: 'https://imgkub.com/images/2025/12/07/image42981ad8a7441ff7.jpg',
price: 'Rp. 175.000',
sale_price: 'Rp. 150.000'
}, {
title: 'Script Selfbot (WhatsApp Bot)',
image: 'https://imgkub.com/images/2026/01/14/image3b73a38fbc0a64c6.jpg',
sale_price: 'Rp. 55.000'
}].map(v => ({
...v,
brand: 'Neoxr Creative',
url: `https://wa.me/${Config.owner}`
}))
}], m, {
title: global.header
})Running the Full Bot Base (neoxr-bot)
If you prefer a ready-made bot (downloader, group management and utilities) instead of starting from scratch, use the neoxr-bot 5.0-ZAPO branch, which is built on this library.
git clone -b 5.0-ZAPO https://github.com/neoxr/neoxr-bot
cd neoxr-bot
npm installFill in your bot number and settings in the configuration files, then start it:
# direct
npm start
# or with pm2
pm2 start pm2.config.cjs && pm2 logs neoxrDocker:
docker build -t bot .
docker run -d --name neoxr bot && docker logs -f neoxr
# stop
docker stop neoxrSome features (such as downloaders) need an API key from api.neoxr.my.id. Check the repository's own README for the exact configuration keys of this branch.
Troubleshooting
| Problem | What to try |
| --- | --- |
| Pairing code never appears | Make sure pairing.state is true and pairing.number has the country code with digits only. |
| Asked to pair on every start | The session is not persisted. Check that create_session points to a writable location. |
| Stickers, voice notes or video fail | Install FFmpeg and confirm ffmpeg -version works in the same shell. |
| Cannot use import statement outside a module | Add "type": "module" to package.json or rename the file to .mjs. |
| Bot is logged out or blocked | Avoid spamming and unofficial interactive messages; consider the stealth option. |
| Need more detail | Set debug: true to see what the library is doing. |
