Storing data for your bot

Three honest options for remembering things between restarts — a JSON file, SQLite on the server, or a managed database that outlives reinstalls — and how to choose.

The moment your bot needs to remember something between restarts — a running score, a warning count, per-server settings — it needs somewhere to put it. Plain variables reset every time the server stops. This guide covers three real options, from a text file to a managed database, and when each one is the right call. The code examples are for the Node.js application; the trade-offs apply to any language.

At a glance
You need a working bot (see the discord.js or discord.py guide)
Time about twenty minutes

Option 1: a JSON file

The simplest thing that works. Read a .json file on start, keep the data in memory, write it back when it changes:

const fs = require('fs');

let data = {};
try {
  data = JSON.parse(fs.readFileSync('data.json', 'utf8'));
} catch {
  data = {}; // first run, no file yet
}

function save() {
  fs.writeFileSync('data.json', JSON.stringify(data, null, 2));
}

This is genuinely fine for a handful of values that rarely change — a config, a couple of counters, a prototype. But it stops being fine as you grow. There's no protection against two events writing at once, and if the process is killed mid-write (an out-of-memory kill, a timer expiry) you can end up with a truncated, unreadable file. Treat JSON as a starting point, not a foundation.

Option 2: SQLite, on the server

When you outgrow a JSON blob but don't want a separate service, SQLite is the sweet spot: a real database that lives in a single file next to your bot, with proper queries and safe concurrent writes. On the Node.js application the better-sqlite3 package installs cleanly — it ships prebuilt binaries, so there's no compiler to worry about. Add it from the Packages page, then:

const Database = require('better-sqlite3');
const db = new Database('bot.db');

// Create the table once; the IF NOT EXISTS makes this safe every start.
db.exec(`
  CREATE TABLE IF NOT EXISTS points (
    user_id TEXT PRIMARY KEY,
    score   INTEGER NOT NULL DEFAULT 0
  )
`);

// Prepared statements: reusable, fast, and safe against injection.
const addPoint = db.prepare(
  `INSERT INTO points (user_id, score) VALUES (?, 1)
   ON CONFLICT(user_id) DO UPDATE SET score = score + 1`
);
const getScore = db.prepare('SELECT score FROM points WHERE user_id = ?');

// ...inside a command handler:
addPoint.run(interaction.user.id);
const row = getScore.get(interaction.user.id);
await interaction.reply(`You have ${row.score} points.`);

💡 Tip: Always pass user input as ? parameters like this, never by building SQL strings — that's what keeps a mischievous username from becoming a database command.

⚠️ Heads up: The bot.db file lives in /home/container, on the server itself. It survives restarts, but a reinstall wipes it — and switching your server to a different application (or deploying a template that switches it) is a reinstall.

For a single bot that stays on one runtime, SQLite is an excellent default. If your data must outlive that, use option 3.

Option 3: a managed database

For data that has to survive anything — reinstalls, application switches, or being shared between two servers — use a managed MySQL database from the server's Databases page. It runs on a shared database host that Falix keeps online for you; nothing to start or babysit, and it outlives your server's own lifecycle. Create one following Add a database, and the page shows a connection string like:

mysql://user:pass@host:port/dbname

Put that string in your .env (call it DATABASE_URL) so it stays out of your code and out of Git, add the mysql2 package from the Packages page, and connect:

const mysql = require('mysql2/promise');

const pool = mysql.createPool(process.env.DATABASE_URL);

await pool.execute(
  `CREATE TABLE IF NOT EXISTS points (
     user_id VARCHAR(32) PRIMARY KEY,
     score   INT NOT NULL DEFAULT 0
   )`
);

// ...inside a command handler:
await pool.execute(
  `INSERT INTO points (user_id, score) VALUES (?, 1)
   ON DUPLICATE KEY UPDATE score = score + 1`,
  [interaction.user.id]
);
const [rows] = await pool.execute(
  'SELECT score FROM points WHERE user_id = ?',
  [interaction.user.id]
);
await interaction.reply(`You have ${rows[0].score} points.`);

Same ?-parameter discipline as SQLite. For the full connection walkthrough (and PostgreSQL/MongoDB, which the Databases page also offers), see Connect your app to a database.

Which one?

JSON SQLite Managed DB
Best for a few values, one writer, a prototype a single bot on one runtime data shared or that must outlive reinstalls
Queries none real SQL real SQL
Extra service none none managed host
Survives a reinstall no no yes

JSON is easiest to start and first to break under load; SQLite is the default for a single bot, just remember its file is local; a managed database is the pick when the data must outlive reinstalls, or you switch runtimes, or several servers share it.

Keep it safe

  • Keep data files out of Git. .env, bot.db, data.json — none of them belong in a repository. The starter .gitignore already excludes .env; add your data files too. See Keep secrets out of Git.
  • Back up what matters. bot.db and data.json sit in /home/container like any other server file, so a server backup captures them — take one from the Backups page before risky changes, and restore it if something goes wrong. See Backups. A managed database already lives independently of your server, so it isn't part of these file backups.

Next steps

Was this guide helpful?