How to Extend Rocket.Chat Functionality Using the Apps-Engine Framework

Developers extend Rocket.Chat functionality by creating sandboxed Apps that interact with the core server through the Apps-Engine framework, utilizing controlled accessors to modify messages, register slash commands, add UI buttons, and expose HTTP endpoints without modifying core code.

Rocket.Chat is an open-source communication platform designed for extensibility through its proprietary Apps-Engine architecture. Rather than modifying core server files, developers package custom behaviors as standalone Rocket.Chat Apps that run in isolated sandboxes. This approach ensures upgrade compatibility while providing deep integration capabilities through well-defined APIs.

Understanding the Apps-Engine Architecture

The extension system centers on the @rocket.chat/apps-engine package, which implements a strict plugin architecture. When the server initializes, it instantiates a singleton AppManager (packages/apps/src/server/AppManager.ts) that orchestrates the entire app lifecycle.

The AppManager performs four critical operations:

  1. Loads installed app packages from the storage layer
  2. Compiles each app into a sandboxed runtime via AppRuntimeManager (Node VM)
  3. Registers listeners, commands, and endpoints through specialized managers (AppListenerManager, AppSlashCommandManager, AppApiManager, UIActionButtonManager)
  4. Handles lifecycle events (onInstall, onEnable, onDisable, onUninstall, onUpdate) by delegating to the user-defined class extending the abstract App base class (packages/apps-engine/src/definition/App.ts)

Core Extension Points for Rocket.Chat Apps

Event-Driven Listeners

Apps register for system events to intercept or react to platform activities. Available events include IPreMessageSentPrevent, IPostMessageSent, and IPreRoomCreate. When the core emits these events, the AppListenerManager invokes the corresponding handler within the app's sandbox.

Slash Commands

Custom commands are registered through configuration.slashCommands.provideSlashCommand(), allowing apps to expose functionality via the message input box.

UI Action Buttons

Developers inject interactive elements into the client interface using configuration.uiActionButtons.provideButton(), handled server-side by UIActionButtonManager (packages/apps/src/server/managers/UIActionButtonManager.ts).

Custom HTTP Endpoints

Apps expose REST APIs through configuration.api.provideApi(), managed by AppApiManager (packages/apps/src/server/managers/AppApiManager.ts), with configurable visibility (public or private).

Data Persistence

The IPersistence accessor allows apps to store private data using persistence.create() and persistence.update(), ensuring isolated storage per app.

Building a Minimal Rocket.Chat App

Every app extends the abstract App class and implements the initialize method to register capabilities. Here is a complete "Hello World" example that demonstrates slash commands and message listeners:

// src/App.ts
import {
  IAppAccessors,
  IConfigurationExtend,
  IEnvironmentRead,
} from '@rocket.chat/apps-engine/definition/accessors';
import { App } from '@rocket.chat/apps-engine/definition/App';
import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';

export class HelloWorldApp extends App {
  constructor(info: IAppInfo, logger: any, accessors?: IAppAccessors) {
    super(info, logger, accessors);
  }

  public async initialize(configuration: IConfigurationExtend, environment: IEnvironmentRead) {
    await this.registerCommand(configuration);
    await this.registerListener(configuration);
  }

  private async registerCommand(configuration: IConfigurationExtend) {
    await configuration.slashCommands.provideSlashCommand({
      command: 'hello',
      i18nDescription: 'Say hello',
      providesPreview: false,
      executor: async (context, read, modify, http, pers) => {
        const { user } = context;
        const message = modify.getCreator().startMessage()
          .setSender(user)
          .setRoom(context.getRoom())
          .setText(`👋 Hello, ${user.username}!`);
        await modify.getCreator().finish(message);
      },
    });
  }

  private async registerListener(configuration: IConfigurationExtend) {
    await configuration.messageListeners.provideMessageListener({
      eventName: 'IPostMessageSent',
      execute: async (context, read, modify) => {
        const { message } = context;
        if (/thanks/i.test(message.text || '')) {
          await modify.getCreator().addReaction(message.id, '👍');
        }
      },
    });
  }
}

This implementation leverages the IModify accessor to create messages and reactions, while the IRead accessor (available in parameters) provides read-only access to rooms and users.

Advanced Extension Patterns

Adding UI Action Buttons

To extend Rocket.Chat functionality with client-side interactions, apps register UI buttons that appear in message contexts:

await configuration.uiActionButtons.provideButton({
  appId: this.getID(),
  id: 'open-ticket',
  label: { en: 'Open Ticket' },
  context: ['message'],
  action: async (context, read, modification, http, pers) => {
    const { message } = context;
    const ticketRoom = await modification.getCreator().startRoom()
      .setType('c')
      .setDisplayName(`Ticket for ${message.sender.username}`)
      .setCreator(message.sender)
      .finish();

    await modification.getNotifier().notifyUser(message.sender, {
      rid: ticketRoom.id,
      msg: 'Your ticket has been created!',
    });
  },
});

The UIActionButtonManager processes these registrations and coordinates with the client-side orchestrator (apps/meteor/client/apps/orchestrator.ts) to render the components.

Exposing Custom HTTP Endpoints

Apps can create private or public REST endpoints to integrate external services:

await configuration.api.provideApi({
  path: '/external-data',
  visibility: ApiVisibility.PRIVATE,
  endpoints: [
    {
      path: '/fetch',
      method: HttpMethod.GET,
      auth: {
        requiredPermissions: [],
      },
      endpoint: async (request, read, modify, http, pers) => {
        const external = await http.get('https://api.example.com/data');
        return {
          status: HttpStatusCode.OK,
          headers: { 'Content-Type': 'application/json' },
          content: JSON.stringify(external.data),
        };
      },
    },
  ],
});

The AppApiManager routes incoming requests to the appropriate sandboxed endpoint, enforcing the specified authentication and permissions.

Key Source Files in the Apps-Engine

Understanding the following source files is essential for developers looking to extend Rocket.Chat functionality:

Summary

  • Rocket.Chat Apps provide the official mechanism to extend Rocket.Chat functionality without modifying core server code.
  • The Apps-Engine architecture uses a sandboxed runtime (Node VM) to ensure security and stability.
  • Developers interact with the platform through accessors (IRead, IModify, IHttp, IPersistence) injected into lifecycle methods.
  • Extension points include event listeners, slash commands, UI action buttons, and custom HTTP endpoints.
  • The AppManager singleton orchestrates app lifecycles while specialized managers handle specific capability registrations.

Frequently Asked Questions

What programming language is used to extend Rocket.Chat functionality?

Rocket.Chat Apps are written in TypeScript (compiled to JavaScript). The Apps-Engine provides type definitions in the @rocket.chat/apps-engine package, ensuring type-safe access to platform APIs and accessors.

How do Rocket.Chat Apps differ from traditional server modifications?

Unlike direct server modifications that alter core files and break during upgrades, Apps run in isolated sandboxes managed by AppRuntimeManager. They interact with the host exclusively through well-defined accessors, ensuring the core server remains stable and upgrade-compatible.

Can Apps interact with external APIs and services?

Yes. The IHttp accessor allows apps to make outbound requests to external services. Additionally, apps can expose their own endpoints via provideApi(), managed by AppApiManager, to receive inbound webhooks or serve data to external systems.

Where are Apps stored and how are they lifecycle-managed?

Apps are stored in the server's database and loaded at startup by AppManager (packages/apps/src/server/AppManager.ts). The manager handles installation, enabling, disabling, updating, and uninstallation by calling the corresponding lifecycle methods (onInstall, onEnable, onDisable, onUninstall, onUpdate) defined in the app's main class.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →