Example Applets

Status: Draft

Overview

This document provides reference implementations based on existing IOTA SDK modules that would be good candidates for applets.

mindmap
  root((Example Applets))
    AI Website Chat
      OpenAI integration
      Chat widget
      CRM integration
    Business Analytics AI
      Report generation
      Agent framework
      Knowledge base
    Webhook Forwarder
      Minimal example
      HTTP forwarding

Example 1: AI Website Chat (Based on Website Module)

The Website module’s AI chat functionality is an ideal applet candidate.

graph TB
    subgraph "AI Website Chat Applet"
        subgraph "Backend"
            CONFIG[handlers/config.ts]
            WIDGET[handlers/widget.ts]
            MESSAGE[handlers/on-message.ts]
            AI[services/ai-service.ts]
        end

        subgraph "Frontend"
            CONFIG_PAGE[pages/ConfigPage.tsx]
            CHAT[components/ChatWidget.tsx]
        end

        subgraph "External"
            OPENAI[api.openai.com]
            DIFY[*.dify.ai]
        end
    end

    CONFIG --> AI
    WIDGET --> AI
    AI --> OPENAI
    AI --> DIFY

    style CONFIG fill:#3b82f6,stroke:#1e40af,color:#fff
    style CHAT fill:#10b981,stroke:#047857,color:#fff
    style AI fill:#f59e0b,stroke:#d97706,color:#fff

Capabilities

Feature Description
Embeddable chat widget For external websites
AI-powered responses Using OpenAI/Dify
CRM integration Client tracking
Message routing Thread management

Manifest

# manifest.yaml
manifestVersion: "1.0"
id: "ai-website-chat"
version: "1.0.0"
name:
  en: "AI Website Chat"
  ru: "AI Чат для сайта"
  uz: "Veb-sayt uchun AI Chat"
description:
  en: "Embeddable AI chatbot for your website with CRM integration"
  ru: "Встраиваемый AI чат-бот для вашего сайта с интеграцией CRM"
author:
  name: "IOTA Team"
  email: "team@iota.uz"
license: "MIT"
icon: "assets/icon.svg"
category: "communication"
minSdkVersion: "2.0.0"

runtime:
  engine: "bun"
  entrypoint: "dist/backend/server.js"
  resources:
    maxMemoryMB: 256
    maxCpuPercent: 50

permissions:
  database:
    read:
      - clients
      - chats
      - chat_messages
      - users
    write:
      - clients
      - chats
      - chat_messages
    createTables: true
  http:
    external:
      - "api.openai.com"
      - "*.dify.ai"
  events:
    subscribe:
      - "chat.message.created"
    publish:
      - "ai.response.generated"
  ui:
    navigation: true
    pages: true
    widgets: true
  secrets:
    - name: "OPENAI_API_KEY"
      description: "OpenAI API key for chat completions"
      required: true
    - name: "DIFY_API_KEY"
      description: "Dify API key for RAG (optional)"
      required: false

tables:
  - name: "configs"
    description: "AI chat configuration per tenant"
    columns:
      - name: id
        type: bigserial
        primary: true
      - name: tenant_id
        type: uuid
        required: true
        index: true
      - name: model_name
        type: varchar(100)
        default: "gpt-4"
      - name: system_prompt
        type: text
        nullable: true
      - name: temperature
        type: decimal(3,2)
        default: 0.7
      - name: max_tokens
        type: integer
        default: 2000
      - name: welcome_message
        type: text
        nullable: true
      - name: widget_color
        type: varchar(20)
        default: "#3b82f6"
    indexes:
      - columns: [tenant_id]
        unique: true

  - name: "threads"
    description: "Chat thread tracking for AI context"
    columns:
      - name: id
        type: uuid
        primary: true
        default: gen_random_uuid()
      - name: tenant_id
        type: uuid
        required: true
      - name: chat_id
        type: bigint
        required: true
        foreignKey:
          table: chats
          column: id
          onDelete: CASCADE
      - name: openai_thread_id
        type: varchar(100)
        nullable: true
      - name: context_summary
        type: text
        nullable: true

backend:
  handlers:
    - type: http
      path: "/api/applets/ai-chat/config"
      methods: [GET, POST, PUT]
      handler: "handlers/config.ts"
      auth: required
      permissions:
        - "ai-chat.config.read"
        - "ai-chat.config.write"

    - type: http
      path: "/api/applets/ai-chat/widget"
      methods: [POST]
      handler: "handlers/widget.ts"
      auth: optional
      rateLimit:
        requests: 60
        window: 60

    - type: http
      path: "/api/applets/ai-chat/embed.js"
      methods: [GET]
      handler: "handlers/embed-script.ts"
      auth: none

    - type: event
      events:
        - "chat.message.created"
      handler: "handlers/on-message.ts"
      async: true

frontend:
  framework: react
  navigation:
    - label:
        en: "AI Chat"
        ru: "AI Чат"
      icon: "chat"
      path: "/website/ai-chat"
      permissions:
        - "ai-chat.config.read"
      parent: "website"

  pages:
    - path: "/website/ai-chat"
      title:
        en: "AI Chat Configuration"
        ru: "Настройка AI Чата"
      component: "pages/ConfigPage"
      permissions:
        - "ai-chat.config.read"

    - path: "/website/ai-chat/embed"
      title:
        en: "Embed Widget"
      component: "pages/EmbedPage"
      public: true

  widgets:
    - target: "crm.chats.detail"
      position: "sidebar-right"
      component: "widgets/AiAssistButton"
      permissions:
        - "ai-chat.assist"

  embeddables:
    - name: "chat-widget"
      component: "components/ChatWidget"
      config:
        - name: "theme"
          type: "string"
          options: ["light", "dark", "auto"]
          default: "auto"

appletPermissions:
  - key: "ai-chat.config.read"
    name:
      en: "View AI Chat Configuration"
  - key: "ai-chat.config.write"
    name:
      en: "Edit AI Chat Configuration"
  - key: "ai-chat.assist"
    name:
      en: "Use AI Assistant"

dependencies:
  modules:
    - "crm"

Backend Implementation

Server Entry Point

// src/backend/server.ts
import { serve } from "bun";
import { AppletServer } from "@iota/applet-sdk/server";
import { configHandler } from "./handlers/config";
import { widgetHandler } from "./handlers/widget";
import { onMessageHandler } from "./handlers/on-message";

const server = new AppletServer({
  handlers: {
    "handlers/config.ts": configHandler,
    "handlers/widget.ts": widgetHandler,
    "handlers/on-message.ts": onMessageHandler,
  },
});

serve({
  unix: process.env.SOCKET_PATH,
  fetch: server.handleRequest,
});

Configuration Handler

// src/backend/handlers/config.ts
import { Handler, Context } from "@iota/applet-sdk";

interface AIConfig {
  modelName: string;
  systemPrompt: string;
  temperature: number;
  maxTokens: number;
  welcomeMessage: string;
  widgetColor: string;
}

export const configHandler: Handler = async (ctx: Context) => {
  const { method, body } = ctx.request;
  const { db, permissions } = ctx.sdk;

  if (method === "GET") {
    if (!permissions.check("ai-chat.config.read")) {
      return ctx.forbidden();
    }

    const config = await db.table("applet_ai_chat_configs")
      .where("tenant_id", ctx.tenantId)
      .first();

    return ctx.json(config || getDefaultConfig());
  }

  if (method === "POST" || method === "PUT") {
    if (!permissions.check("ai-chat.config.write")) {
      return ctx.forbidden();
    }

    const data = body as AIConfig;

    // Validate
    if (data.temperature < 0 || data.temperature > 2) {
      return ctx.badRequest("Temperature must be between 0 and 2");
    }

    // Upsert config
    const existing = await db.table("applet_ai_chat_configs")
      .where("tenant_id", ctx.tenantId)
      .first();

    if (existing) {
      await db.table("applet_ai_chat_configs")
        .where("id", existing.id)
        .update(data);
    } else {
      await db.table("applet_ai_chat_configs").insert({
        ...data,
        tenant_id: ctx.tenantId,
      });
    }

    return ctx.json({ success: true });
  }
};

function getDefaultConfig(): AIConfig {
  return {
    modelName: "gpt-4",
    systemPrompt: "You are a helpful assistant for our company.",
    temperature: 0.7,
    maxTokens: 2000,
    welcomeMessage: "Hello! How can I help you today?",
    widgetColor: "#3b82f6",
  };
}

Widget Handler

sequenceDiagram
    participant Widget as Chat Widget
    participant Handler as Widget Handler
    participant DB as Database
    participant AI as OpenAI API

    Widget->>Handler: POST /widget {message, threadId}
    Handler->>DB: Get config
    Handler->>DB: Get/create thread
    Handler->>DB: Store user message
    Handler->>DB: Get conversation history
    Handler->>AI: chat.completions.create()
    AI-->>Handler: AI response
    Handler->>DB: Store AI message
    Handler->>Handler: Publish event
    Handler-->>Widget: {threadId, response}
// src/backend/handlers/widget.ts
import { Handler, Context } from "@iota/applet-sdk";
import OpenAI from "openai";

export const widgetHandler: Handler = async (ctx: Context) => {
  const { message, threadId } = ctx.request.body;
  const { db, secrets, http } = ctx.sdk;

  // Get config
  const config = await db.table("applet_ai_chat_configs")
    .where("tenant_id", ctx.tenantId)
    .first();

  if (!config) {
    return ctx.badRequest("AI Chat not configured");
  }

  // Get or create thread
  let thread = threadId
    ? await db.table("applet_ai_chat_threads")
        .where("id", threadId)
        .first()
    : null;

  if (!thread) {
    // Create new chat in CRM
    const chat = await db.table("chats").insert({
      source: "website_widget",
      status: "open",
    });

    thread = await db.table("applet_ai_chat_threads").insert({
      chat_id: chat.id,
    });
  }

  // Store user message
  await db.table("chat_messages").insert({
    chat_id: thread.chat_id,
    content: message,
    sender_type: "client",
  });

  // Get conversation history
  const history = await db.table("chat_messages")
    .where("chat_id", thread.chat_id)
    .orderBy("created_at", "asc")
    .limit(20)
    .get();

  // Call OpenAI
  const openai = new OpenAI({
    apiKey: secrets.get("OPENAI_API_KEY"),
  });

  const completion = await openai.chat.completions.create({
    model: config.model_name,
    temperature: config.temperature,
    max_tokens: config.max_tokens,
    messages: [
      { role: "system", content: config.system_prompt },
      ...history.map((msg) => ({
        role: msg.sender_type === "client" ? "user" : "assistant",
        content: msg.content,
      })),
    ],
  });

  const aiResponse = completion.choices[0].message.content;

  // Store AI response
  await db.table("chat_messages").insert({
    chat_id: thread.chat_id,
    content: aiResponse,
    sender_type: "ai",
  });

  // Publish event
  await ctx.sdk.events.publish("ai.response.generated", {
    threadId: thread.id,
    chatId: thread.chat_id,
    response: aiResponse,
  });

  return ctx.json({
    threadId: thread.id,
    response: aiResponse,
  });
};

Frontend Implementation

Configuration Page

// src/frontend/pages/ConfigPage.tsx
import { useState, useEffect } from "react";
import {
  Card,
  Input,
  Select,
  Textarea,
  Slider,
  Button,
  ColorPicker,
} from "@iota/components";
import { useAppletContext } from "@iota/applet-sdk/react";

interface AIConfig {
  modelName: string;
  systemPrompt: string;
  temperature: number;
  maxTokens: number;
  welcomeMessage: string;
  widgetColor: string;
}

const MODELS = [
  { value: "gpt-4", label: "GPT-4" },
  { value: "gpt-4-turbo", label: "GPT-4 Turbo" },
  { value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" },
  { value: "claude-3-opus", label: "Claude 3 Opus" },
  { value: "claude-3-sonnet", label: "Claude 3 Sonnet" },
];

export function ConfigPage() {
  const { t, api, toast, permissions } = useAppletContext();
  const [config, setConfig] = useState<AIConfig | null>(null);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);

  const canEdit = permissions.check("ai-chat.config.write");

  useEffect(() => {
    loadConfig();
  }, []);

  async function loadConfig() {
    try {
      const data = await api.get<AIConfig>("/config");
      setConfig(data);
    } catch (error) {
      toast.error(t("Config.LoadError"));
    } finally {
      setLoading(false);
    }
  }

  async function handleSave() {
    if (!config) return;

    setSaving(true);
    try {
      await api.put("/config", config);
      toast.success(t("Config.Saved"));
    } catch (error) {
      toast.error(t("Config.SaveError"));
    } finally {
      setSaving(false);
    }
  }

  if (loading) {
    return <Card><Skeleton lines={6} /></Card>;
  }

  return (
    <div className="space-y-6">
      <Card title={t("Config.AISettings")}>
        <div className="space-y-4">
          <Select
            label={t("Config.Model")}
            value={config?.modelName}
            onChange={(v) => setConfig({ ...config!, modelName: v })}
            options={MODELS}
            disabled={!canEdit}
          />

          <Textarea
            label={t("Config.SystemPrompt")}
            value={config?.systemPrompt}
            onChange={(v) => setConfig({ ...config!, systemPrompt: v })}
            rows={6}
            disabled={!canEdit}
            help={t("Config.SystemPromptHelp")}
          />

          <Slider
            label={t("Config.Temperature")}
            value={config?.temperature}
            onChange={(v) => setConfig({ ...config!, temperature: v })}
            min={0}
            max={2}
            step={0.1}
            disabled={!canEdit}
          />

          <Input
            label={t("Config.MaxTokens")}
            type="number"
            value={config?.maxTokens}
            onChange={(v) => setConfig({ ...config!, maxTokens: parseInt(v) })}
            disabled={!canEdit}
          />
        </div>
      </Card>

      <Card title={t("Config.WidgetSettings")}>
        <div className="space-y-4">
          <Textarea
            label={t("Config.WelcomeMessage")}
            value={config?.welcomeMessage}
            onChange={(v) => setConfig({ ...config!, welcomeMessage: v })}
            rows={3}
            disabled={!canEdit}
          />

          <ColorPicker
            label={t("Config.WidgetColor")}
            value={config?.widgetColor}
            onChange={(v) => setConfig({ ...config!, widgetColor: v })}
            disabled={!canEdit}
          />
        </div>
      </Card>

      {canEdit && (
        <div className="flex justify-end">
          <Button
            variant="primary"
            onClick={handleSave}
            loading={saving}
          >
            {t("Common.Save")}
          </Button>
        </div>
      )}
    </div>
  );
}

Chat Widget Component

// src/frontend/components/ChatWidget.tsx
import { useState, useRef, useEffect } from "react";

interface ChatWidgetProps {
  tenantId: string;
  theme?: "light" | "dark" | "auto";
  position?: "bottom-right" | "bottom-left";
}

interface Message {
  id: string;
  content: string;
  sender: "user" | "ai";
  timestamp: Date;
}

export function ChatWidget({ tenantId, theme = "auto", position = "bottom-right" }: ChatWidgetProps) {
  const [isOpen, setIsOpen] = useState(false);
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [threadId, setThreadId] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const messagesEndRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  async function sendMessage() {
    if (!input.trim() || loading) return;

    const userMessage: Message = {
      id: crypto.randomUUID(),
      content: input,
      sender: "user",
      timestamp: new Date(),
    };

    setMessages((prev) => [...prev, userMessage]);
    setInput("");
    setLoading(true);

    try {
      const response = await fetch(`/api/applets/ai-chat/widget`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Tenant-ID": tenantId,
        },
        body: JSON.stringify({
          message: input,
          threadId,
        }),
      });

      const data = await response.json();

      setThreadId(data.threadId);

      const aiMessage: Message = {
        id: crypto.randomUUID(),
        content: data.response,
        sender: "ai",
        timestamp: new Date(),
      };

      setMessages((prev) => [...prev, aiMessage]);
    } catch (error) {
      console.error("Failed to send message:", error);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className={`chat-widget chat-widget--${position} chat-widget--${theme}`}>
      {isOpen ? (
        <div className="chat-widget__container">
          <div className="chat-widget__header">
            <span>Chat with us</span>
            <button onClick={() => setIsOpen(false)}>×</button>
          </div>

          <div className="chat-widget__messages">
            {messages.map((msg) => (
              <div key={msg.id} className={`chat-message chat-message--${msg.sender}`}>
                {msg.content}
              </div>
            ))}
            {loading && (
              <div className="chat-message chat-message--ai">
                <span className="typing-indicator">...</span>
              </div>
            )}
            <div ref={messagesEndRef} />
          </div>

          <div className="chat-widget__input">
            <input
              type="text"
              value={input}
              onChange={(e) => setInput(e.target.value)}
              onKeyPress={(e) => e.key === "Enter" && sendMessage()}
              placeholder="Type a message..."
              disabled={loading}
            />
            <button onClick={sendMessage} disabled={loading}>
              Send
            </button>
          </div>
        </div>
      ) : (
        <button className="chat-widget__toggle" onClick={() => setIsOpen(true)}>
          💬
        </button>
      )}
    </div>
  );
}

Example 2: Business Analytics AI (Conceptual)

Based on the Shyona module, this represents a more complex applet.

graph TB
    subgraph "Business Analytics AI"
        subgraph "Frontend"
            DASHBOARD[Dashboard Page]
            REPORTS[Reports Page]
            ASK[Ask AI Page]
            AGENTS[Agents Page]
        end

        subgraph "Backend Services"
            ENGINE[Analytics Engine]
            FRAMEWORK[Agent Framework]
            KB[Knowledge Base]
        end

        subgraph "Data"
            ALL_TABLES[Read: All Tables]
            REPORTS_TABLE[Write: analytics_reports]
            CACHE[Write: analytics_cache]
        end
    end

    DASHBOARD --> ENGINE
    REPORTS --> ENGINE
    ASK --> ENGINE
    AGENTS --> FRAMEWORK

    ENGINE --> ALL_TABLES
    ENGINE --> REPORTS_TABLE
    FRAMEWORK --> KB

    style ENGINE fill:#3b82f6,stroke:#1e40af,color:#fff
    style FRAMEWORK fill:#10b981,stroke:#047857,color:#fff
    style KB fill:#f59e0b,stroke:#d97706,color:#fff

Key Features

Feature Description
Natural language queries Ask business questions in plain English
SQL generation AI generates and executes safe queries
Agent orchestration Multi-agent task automation
Knowledge base Vector search for business context

Manifest (Condensed)

manifestVersion: "1.0"
id: "business-analytics-ai"
version: "1.0.0"
name:
  en: "AI Business Analytics"
  ru: "AI Бизнес-Аналитика"

runtime:
  engine: "bun"
  entrypoint: "dist/backend/server.js"
  resources:
    maxMemoryMB: 512
    maxCpuPercent: 70

permissions:
  database:
    read:
      - "*"  # Read access to all tables for analytics
    write:
      - analytics_reports
      - analytics_cache
    createTables: true
  http:
    external:
      - "api.openai.com"
      - "api.anthropic.com"
  events:
    subscribe:
      - "*"  # Subscribe to all events for analytics
    publish:
      - "analytics.report.generated"
      - "analytics.insight.discovered"

Analytics Engine Service

flowchart TB
    REQUEST[Report Request] --> GATHER[Gather Data]
    GATHER --> SQL[Execute SQL Queries]
    SQL --> ANALYZE[AI Analysis]
    ANALYZE --> INSIGHTS[Generate Insights]
    INSIGHTS --> STORE[Store Report]
    STORE --> RETURN[Return Results]

    style ANALYZE fill:#3b82f6,stroke:#1e40af,color:#fff
    style INSIGHTS fill:#10b981,stroke:#047857,color:#fff
// src/backend/services/analytics-engine.ts
import OpenAI from "openai";
import { Context } from "@iota/applet-sdk";

export class AnalyticsEngine {
  private openai: OpenAI;
  private ctx: Context;

  constructor(ctx: Context) {
    this.ctx = ctx;
    this.openai = new OpenAI({
      apiKey: ctx.sdk.secrets.get("OPENAI_API_KEY"),
    });
  }

  async generateReport(type: string, params: Record<string, any>) {
    // Gather data based on report type
    const data = await this.gatherData(type, params);

    // Use AI to analyze and generate insights
    const analysis = await this.analyzeWithAI(type, data);

    // Store report
    const report = await this.ctx.sdk.db.table("applet_analytics_reports").insert({
      report_type: type,
      parameters: params,
      result: analysis,
      generated_at: new Date(),
    });

    return report;
  }

  private async gatherData(type: string, params: Record<string, any>) {
    const { db } = this.ctx.sdk;
    const { startDate, endDate } = params;

    switch (type) {
      case "financial_summary":
        return {
          payments: await db.table("payments")
            .whereBetween("created_at", [startDate, endDate])
            .get(),
          expenses: await db.table("expenses")
            .whereBetween("created_at", [startDate, endDate])
            .get(),
          revenue: await db.raw(`
            SELECT SUM(amount) as total
            FROM payments
            WHERE created_at BETWEEN $1 AND $2
          `, [startDate, endDate]),
        };

      case "sales_performance":
        return {
          orders: await db.table("orders")
            .whereBetween("created_at", [startDate, endDate])
            .get(),
          topProducts: await db.raw(`
            SELECT p.name, SUM(oi.quantity) as total_sold
            FROM order_items oi
            JOIN products p ON oi.product_id = p.id
            WHERE oi.created_at BETWEEN $1 AND $2
            GROUP BY p.id
            ORDER BY total_sold DESC
            LIMIT 10
          `, [startDate, endDate]),
        };

      default:
        throw new Error(`Unknown report type: ${type}`);
    }
  }

  private async analyzeWithAI(type: string, data: any) {
    const prompt = this.buildAnalysisPrompt(type, data);

    const response = await this.openai.chat.completions.create({
      model: "gpt-4",
      messages: [
        {
          role: "system",
          content: "You are a business analyst. Analyze the provided data and generate actionable insights.",
        },
        {
          role: "user",
          content: prompt,
        },
      ],
      response_format: { type: "json_object" },
    });

    return JSON.parse(response.choices[0].message.content!);
  }

  private buildAnalysisPrompt(type: string, data: any): string {
    return `
Analyze the following ${type} data and provide:
1. Key metrics summary
2. Trends and patterns
3. Anomalies or concerns
4. Actionable recommendations

Data:
${JSON.stringify(data, null, 2)}

Respond in JSON format with sections: summary, metrics, trends, anomalies, recommendations.
    `;
  }
}

Example 3: Webhook Forwarder (Minimal Applet)

A minimal applet demonstrating the simplest possible implementation.

graph LR
    WEBHOOK[Incoming Webhook] --> APPLET[Webhook Forwarder]
    APPLET --> FORWARD[Forward URL]

    style APPLET fill:#3b82f6,stroke:#1e40af,color:#fff

Manifest

# manifest.yaml
manifestVersion: "1.0"
id: "webhook-forwarder"
version: "1.0.0"
name:
  en: "Webhook Forwarder"

runtime:
  engine: "bun"
  entrypoint: "dist/server.js"

permissions:
  http:
    external:
      - "*"  # Forward to any URL
  secrets:
    - name: "FORWARD_URL"
      required: true

backend:
  handlers:
    - type: http
      path: "/api/applets/webhook/receive"
      methods: [POST]
      handler: "handlers/receive.ts"
      auth: none

Handler

// src/handlers/receive.ts
import { Handler, Context } from "@iota/applet-sdk";

export const receiveHandler: Handler = async (ctx: Context) => {
  const forwardUrl = ctx.sdk.secrets.get("FORWARD_URL");
  const payload = ctx.request.body;

  // Forward the webhook
  const response = await fetch(forwardUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Forwarded-From": "iota-sdk",
      "X-Tenant-ID": ctx.tenantId,
    },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    return ctx.json({
      success: false,
      error: `Forward failed: ${response.status}`,
    }, 502);
  }

  return ctx.json({ success: true, forwarded: true });
};

Development Workflow Summary

flowchart LR
    CREATE[1. Create] --> DEV[2. Develop]
    DEV --> BUILD[3. Build]
    BUILD --> PACKAGE[4. Package]
    PACKAGE --> PUBLISH[5. Publish]
    PUBLISH --> INSTALL[6. Install]

    style CREATE fill:#3b82f6,stroke:#1e40af,color:#fff
    style INSTALL fill:#10b981,stroke:#047857,color:#fff

Commands

# 1. Create new applet
npx create-iota-applet my-applet
cd my-applet

# 2. Development
bun dev          # Start dev server with hot reload
bun test         # Run tests
bun tsc --noEmit # Type check

# 3. Build & Package
bun run build    # Production build
bun run package  # Creates: my-applet-1.0.0.zip

# 4. Publish
iota-applet login
iota-applet publish

# 5. Install in SDK (via Admin UI)
# Settings > Applets > Browse > Search > Install

Next Steps


Back to top

IOTA SDK - Multi-tenant Business Management Platform