Build Your First Feature

This tutorial walks through the first meaningful customization most developers need: add a new bot command and a matching Mini App screen.

It uses the scaffolded app shape generated by create-teleforge-app.

Goal

Add:

  • a new /orders bot command
  • a new /orders Mini App route
  • a simple screen that proves the route is wired correctly

1. Update the Manifest

Open teleforge.app.json.

Add a new command entry:

{
  "command": "orders",
  "description": "Open recent orders",
  "handler": "commands/orders"
}

Add a new route entry:

{
  "path": "/orders",
  "component": "pages/Orders",
  "launchModes": ["compact", "fullscreen"]
}

Important: in current Teleforge V1, these strings are conventions and metadata, not magic auto-imports.

They should line up with the files you create:

  • handler: "commands/orders" -> apps/bot/src/commands/orders.ts
  • component: "pages/Orders" -> apps/web/src/pages/Orders.tsx

teleforge doctor uses the component convention when checking route files, and the manifest remains the clearest place to document intended app structure.

2. Create the Bot Command

Create apps/bot/src/commands/orders.ts:

import type { BotCommandDefinition } from "@teleforgex/bot";

export function createOrdersCommand(miniAppUrl: string): BotCommandDefinition {
  return {
    command: "orders",
    description: "Open recent orders",
    async handler(context) {
      await context.replyWithWebApp(
        "Open the Orders screen in the Mini App.",
        "Open Orders",
        `${miniAppUrl.replace(/\/$/, "")}/orders`
      );
    }
  };
}

Then register it in apps/bot/src/runtime.ts next to the generated /start command:

import { createOrdersCommand } from "./commands/orders.ts";

runtime.registerCommands([
  createStartCommand(config.miniAppUrl),
  createOrdersCommand(config.miniAppUrl)
]);

Now the bot knows about /orders.

3. Create the Mini App Screen

Create apps/web/src/pages/Orders.tsx:

export function OrdersPage() {
  return (
    <div className="stack">
      <p className="badge">Route: /orders</p>
      <h2>Recent Orders</h2>
      <p>This is the first custom screen added to the scaffold.</p>
    </div>
  );
}

4. Wire the Route in the Web App

SPA Scaffold

If your generated app uses spa mode, update apps/web/src/App.tsx:

  • import OrdersPage
  • extend the route resolver
  • add navigation to /orders
  • render OrdersPage when the current route is /orders

The generated SPA app uses a small in-app router built from window.history, so adding a route is just a React state change plus a new page component.

BFF Scaffold

If your generated app uses bff mode, do two things:

  1. create apps/web/src/pages/Orders.tsx
  2. create apps/web/app/orders/page.tsx

apps/web/app/orders/page.tsx should be the same thin wrapper pattern used by the generated page.tsx files:

"use client";

import { OrdersPage } from "../../src/pages/Orders";

export default function Page() {
  return <OrdersPage />;
}

5. Test It in the Simulator

Run:

pnpm run dev

Then:

  1. send /orders in the simulator chat
  2. click the Open Orders button
  3. confirm the Mini App opens at /orders

If anything is off:

  • run teleforge doctor
  • check the route path in teleforge.app.json
  • check the file names match the manifest conventions exactly

6. Add a Baseline Test

For the bot side, copy the generated /start test pattern and add apps/bot/test/orders.test.ts.

For the web side, copy the generated page render test and add apps/web/test/orders.test.tsx.

That keeps your first feature aligned with the scaffold’s testing model instead of introducing a new test stack.

7. Optional: Send Data Back to the Bot

If your new screen should send a result back to chat:

  • use publishToBot() for a simple payload push
  • use Teleforge coordination helpers when the feature is a resumable chat -> Mini App -> chat flow

If that is your next step, read Flow Coordination.