LogoRobo.js

Upgrading to v0.11

Complete migration guide for upgrading Robo.js projects from v0.10 to v0.11.

v0.11 is a major architectural overhaul that decouples Discord.js into a plugin, introduces a structured lifecycle system, and ships a full database engine in Flashcore. Despite the scope of these changes, most projects need only 3-5 updates to migrate.

This page is the single entry point for all breaking changes. Each section links to a detailed guide for deeper dives.

The Flashcore KV API (Flashcore.get, Flashcore.set, Flashcore.delete) is completely unchanged. Existing KV code works without modification.

What's New

Beyond the architectural changes, v0.11 ships several major new features:

Hot Module Replacement

v0.11 introduces true HMR for the first time. Instead of restarting the entire process when code changes during development, Robo.js now reloads only the affected handler:

  • Per-handler reloads — Editing a command or event handler re-imports just that one module, not the whole project
  • Dependency graph tracking — When a utility file changes, the HMR system traces the import graph to find exactly which handlers are affected and reloads only those
  • Incremental manifest updates — Adding or removing handler files patches the manifest in-place without a full route scan
  • Plugin HMR hooks — Plugins can react to HMR events via src/robo/hmr.ts with namespace/route filtering
  • Module cleanup APIhmr.module(import.meta.url).dispose() lets handlers clean up timers, sockets, and other resources before being replaced
  • Runtime subscriptionshmr.subscribe() lets code react to changes at runtime with namespace/route filters

HMR is experimental in v0.11 and can be enabled with robo dev --hmr. All HMR APIs are no-ops in production.

Quick Migration Checklist

Install @robojs/discordjs

Discord.js is no longer bundled with the core framework. Add it as a plugin:

npx robo add @robojs/discordjs@next

Update imports

Discord-specific types and utilities have moved from robo.js to @robojs/discordjs. Update your import statements accordingly.

Move Discord config

Move clientOptions, sage, and other Discord settings from config/robo.ts to config/plugins/robojs/discordjs.ts.

Migrate lifecycle hooks

Move src/events/_start.ts to src/robo/start.ts and src/events/_stop.ts to src/robo/stop.ts. The new hooks receive a context object instead of bare arguments.

Update Manifest access

Replace getManifest() calls with the new Manifest singleton API.

Update Portal usage

The portal is now namespace-based. Replace portal.commands.get('x') with portal.getRecord('discordjs', 'commands', 'x') and add () to all portal.module('x').isEnabled calls.

Install @robojs/flashcore-extras (if needed)

Advanced Flashcore features like migrations, transactions, WAL, and integrity tools are available through a separate plugin.

Rebuild

npx robo build

Discord.js is Now a Plugin

Discord.js integration has been extracted from the core framework into @robojs/discordjs. This means Robo.js can now run as a pure web server, cron runner, or any other shape without carrying Discord dependencies.

npx robo add @robojs/discordjs@next

Import Changes

Before (robo.js)After (@robojs/discordjs)
import { client } from 'robo.js'import { getClient } from '@robojs/discordjs'
import { createCommandConfig } from 'robo.js'import { createCommandConfig } from '@robojs/discordjs'
import type { CommandConfig } from 'robo.js'import type { CommandConfig } from '@robojs/discordjs'
import type { CommandOptions } from 'robo.js'import type { CommandOptions } from '@robojs/discordjs'
import type { CommandResult } from 'robo.js'import type { CommandResult } from '@robojs/discordjs'
import type { EventConfig } from 'robo.js'import type { EventConfig } from '@robojs/discordjs'

The client export no longer exists. Use the getClient() function instead:

// Before
import { client } from 'robo.js'
client.user?.setPresence({ status: 'idle' })

// After
import { getClient } from '@robojs/discordjs'
const client = getClient()
client.user?.setPresence({ status: 'idle' })

Config Migration

Discord-specific configuration moves from config/robo.ts to config/plugins/robojs/discordjs.ts:

config/robo.ts
export default {
	clientOptions: {
		intents: ['Guilds', 'GuildMessages']
	},
	sage: {
		defer: true
	}
}
config/plugins/robojs/discordjs.ts
import type { DiscordConfig } from '@robojs/discordjs'

export default {
	clientOptions: {
		intents: ['Guilds', 'GuildMessages']
	},
	sage: {
		defer: true
	}
} satisfies DiscordConfig

For the full Discord.js migration guide including command migration, event migration, and intent syntax changes, see the dedicated page.

New Lifecycle System

Lifecycle hooks have moved from magic _start/_stop events in src/events/ to dedicated files in src/robo/. The new system supports a full startup sequence with distinct phases and provides structured context to every hook.

File Location

BeforeAfter
src/events/_start.tssrc/robo/start.ts
src/events/_stop.tssrc/robo/stop.ts
src/events/_restart.tsDeprecated (use stop with context.reason === 'restart')

New Directory Structure

init.tsEarly initialization
prepare.tsResource preparation
start.tsStartup hook
stop.tsShutdown hook
error.tsError handling
hmr.tsHot reload hook
start.tsPre-build hook
transform.tsEntry transformation
complete.tsPost-build hook

Signature Change

Lifecycle hooks now receive a context object including mode, projectConfig, pluginConfig, state, logger, env, and meta fields. The old pattern of no arguments or bare arguments is replaced by a single typed context parameter.

src/events/_start.ts
export default () => {
	console.log('Bot started!')
}
src/robo/start.ts
import type { StartContext } from 'robo.js'

export default (context: StartContext) => {
	context.logger.info('Bot started!')
}

Handling Restarts

The _restart event is deprecated. Instead, check the reason field on the StopContext:

src/robo/stop.ts
import type { StopContext } from 'robo.js'

export default (context: StopContext) => {
	if (context.reason === 'restart') {
		context.logger.info('Restarting...')
		return
	}
	// Normal shutdown logic
	context.logger.info('Shutting down...')
}

Legacy _start, _stop, and _restart events still work but are deprecated. Migrate them to src/robo/ files when convenient.

Config Field Changes

Several fields have been removed from config/robo.ts and moved to plugin-specific configs:

FieldBefore (config/robo.ts)After
clientOptionsconfig/robo.tsconfig/plugins/robojs/discordjs.ts
sageconfig/robo.tsconfig/plugins/robojs/discordjs.ts
heartbeatconfig/robo.tsRemoved
inviteconfig/robo.tsRemoved
defaultsconfig/robo.tsconfig/plugins/robojs/discordjs.ts
config/robo.ts
export default {
	clientOptions: {
		intents: ['Guilds']
	},
	sage: { defer: true },
	logger: { level: 'info' },
	plugins: []
}
config/robo.ts
export default {
	logger: { level: 'info' },
	plugins: []
}
config/plugins/robojs/discordjs.ts
import type { DiscordConfig } from '@robojs/discordjs'

export default {
	clientOptions: {
		intents: ['Guilds']
	},
	sage: { defer: true }
} satisfies DiscordConfig

Type and Export Moves

Types and utilities have been reorganized across packages. Here is a summary grouped by destination:

Moved to @robojs/discordjs

ExportKind
CommandConfigtype
CommandOptionstype
CommandResulttype
CommandContexttype
CommandEntrytype
EventConfigtype
EventEntrytype
ContextConfigtype
MiddlewareConfigtype
MiddlewareDatatype
MiddlewareResulttype
SageOptionstype
DiscordConfigtype
createCommandConfigfunction
createContextConfigfunction
getClientfunction
hasClientfunction
registerSlashCommandsfunction

New in @robojs/flashcore-extras

These are new v0.11 features provided through an optional plugin, not things that existed in v0.10:

ExportKind
AdapterBuilderclass
defineMigrationfunction
Transaction utilitiesfunctions
WAL recovery toolsfunctions
Integrity check/repairfunctions

Remain in robo.js

ExportKind
Flashcoreclass (KV API)
Manifestsingleton
Robosingleton
State, getState, setStatestate management
Logger, loggerlogging
Env, envenvironment
Moderuntime mode
getConfigfunction
getPluginOptionsfunction
portalportal access
All lifecycle context typestypes

Manifest API

The old getManifest() function loaded one monolithic manifest.json file into memory with all commands, events, routes, and plugin data at once. v0.11 replaces this with a Manifest singleton backed by a directory of small JSON files that are lazy-loaded and cached on first access.

This means faster startup (only load what you need), precise HMR invalidation (reload a single route instead of the entire manifest), and an extensible namespace system where any plugin can define its own route types without modifying core code.

import { getManifest } from 'robo.js'

const manifest = getManifest()
const commands = manifest.commands
import { Manifest } from 'robo.js'

const commands = await Manifest.routes('discordjs', 'commands')
const hooks = Manifest.hooks('start')
const plugins = Manifest.plugins()
const config = Manifest.config()

The namespace + route pattern (Manifest.routes('discordjs', 'commands')) replaces hardcoded handler types. Each plugin owns its namespace — @robojs/discordjs uses 'discordjs', @robojs/server uses 'server', and so on.

Key Manifest methods:

MethodDescription
Manifest.routes(namespace, route)Get route handler entries (async, lazy-loaded)
Manifest.routesSync(namespace, route)Get route handler entries (sync, must be pre-loaded)
Manifest.routeSummaries(namespace, route)Get lightweight handler summaries (async)
Manifest.hooks(hook)Get lifecycle hook entries
Manifest.plugins()Get all registered plugin info
Manifest.plugin(name)Get info for a specific plugin
Manifest.config()Get merged project configuration
Manifest.pluginConfig(plugin)Get config for a specific plugin
Manifest.metadata(namespace)Get aggregated metadata for a namespace
Manifest.project()Get project metadata

Flashcore

The KV API is completely unchanged. v0.11 adds a full ORM-like model system on top of it, and production-grade tooling is available through an optional plugin.

KV API: Unchanged

import { Flashcore } from 'robo.js'

// All of these work exactly as before
await Flashcore.set('key', 'value')
const value = await Flashcore.get<string>('key')
await Flashcore.delete('key')

Automatic Data Migration

On first startup with v0.11, Flashcore automatically backs up your legacy data from .robo/data/ and begins lazy-migrating keys to the new .robo/flashcore/ format as they are read. No action is required.

New: Model System

v0.11 ships a Prisma-like model system built on top of the KV layer. Define schemas with typed fields, relations, indexes, and use a full CRUD query API:

import { createModel, f } from 'robo.js/flashcore'

const User = createModel('user', {
	id: f.id(),
	name: f.string(),
	email: f.string().unique(),
	role: f.enum(['admin', 'user']).default('user'),
	createdAt: f.date().default(() => new Date())
})

// Prisma-like CRUD
const user = await User.create({ data: { name: 'Alice', email: 'alice@example.com' } })
const found = await User.findMany({ where: { role: 'admin' }, orderBy: { name: 'asc' } })

The model system includes chunked storage, query planning with index usage, relations (belongsTo, hasOne, hasMany, manyToMany), cascade policies, bulk operations, upsert, and a plugin/middleware system. This is entirely new — it did not exist in v0.10.

Flashcore Extras

Production-grade features are available through the optional @robojs/flashcore-extras plugin to keep the core package lean:

FeatureDescription
Adapter wrappersCache, compression, encryption, resilience wrappers and AdapterBuilder
MigrationsdefineMigration(), schema diff, migration history, distributed locking
TransactionsACID-like transaction context with serial execution
Write-Ahead LogWAL manager with crash recovery
Integrity toolsIndex/catalog integrity checking and repair
/db terminal commands/db status, /db check, /db repair, /db migrate, /db export, /db clear, /db diff, /db history, /db rebuild-indexes
npx robo add @robojs/flashcore-extras@next

The /db terminal commands run inside the interactive robo dev terminal and are provided by @robojs/flashcore-extras. These are new in v0.11 — there were no robo db CLI commands in v0.10.

Portal API

The portal has been rewritten from a Discord-centric class with hardcoded commands, events, and middleware properties into a namespace-based, lazy-loading system where any plugin can register its own handler types.

Direct Properties Removed

The old portal.commands, portal.events, portal.context, portal.middleware, and portal.apis properties no longer exist. Access handlers through the namespace API instead:

import { portal } from 'robo.js'

// Old: Discord.js Collection instances
const pingHandler = portal.commands.get('ping')
const allEvents = portal.events.keys()
import { portal } from 'robo.js'

// New: namespace-based access
const record = portal.getRecord('discordjs', 'commands', 'ping')
const handler = await portal.getHandler('discordjs', 'commands', 'ping')

// Or via namespace proxy (with @robojs/discordjs installed)
const commands = portal.discordjs.commands

isEnabled is Now a Method

// Before (getter property — no parentheses)
portal.module('admin').isEnabled

// After (method — requires parentheses)
portal.module('admin').isEnabled()

Code using portal.module('admin').isEnabled without parentheses will silently return the function reference (always truthy) instead of the actual boolean value. Add () to all isEnabled calls.

Lazy Handler Loading

Handlers are now lazy-loaded by default in development mode. The handler field on HandlerRecord can be null until imported. Use portal.getHandler() or portal.importHandler() to ensure a handler is loaded before accessing it. Production mode defaults to eager loading.

HandlerRecord Shape Changed

FieldBeforeAfter
handlerAlways populatednull until lazy-loaded
type'command', 'event', etc.'discordjs:commands', 'server:api', etc.
plugin.pathPlugin path stringRemoved (use plugin.version)
descriptionTop-level fieldMoved to record.metadata.description
enabledN/ANew boolean for runtime toggling

New: Controller Pattern

v0.11 introduces controllers in @robojs/discordjs for runtime management of Discord handlers.

Per-Handler Controllers

Access individual handlers through the portal to enable/disable them at runtime or restrict to specific servers:

import { portal } from 'robo.js'

// Disable a command at runtime
portal.discordjs.command('ping').setEnabled(false)

// Restrict to specific servers
portal.discordjs.command('admin-reset').setServerOnly(['guild-id-1', 'guild-id-2'])

// Reorder middleware
portal.discordjs.middlewareItem('rate-limit').setOrder(5)

Namespace Controllers

Pre-instantiated controllers let you list, look up, and programmatically execute handlers:

import { commands, events } from '@robojs/discordjs'

// List all registered commands
const allCommands = commands.list()

// Execute a command programmatically
await commands.execute('ping', interaction)

// Emit an event
await events.emit('guildMemberAdd', member)

Five namespace controllers are available: commands, events, context, middleware, and prefixCommands.

CLI Changes

New Commands

CommandDescription
robo inspectInspect project state, manifest, and configuration (--json for structured output)
robo logsView and filter log files (--json for NDJSON output)
robo skillsManage AI coding tool skills

New Terminal Commands

v0.11 introduces an interactive terminal in robo dev with built-in commands (/help, /state, /flashcore, /env, /status, /plugins) and plugin-provided commands. See AI Native for details.

On this page