Skip to content

Custom Objects vs External Components ​

Understanding the difference between Custom Objects API and External Components SDK is crucial for choosing the right approach for your BoardAPI integration.

Overview ​

BoardAPI offers two ways to add custom functionality to your boards:

  1. Custom Objects API - Register custom object types with schemas (backend-defined)
  2. External Components SDK - Upload interactive HTML/JS components (frontend sandbox)

Both extend Excalidraw's base canvas, but serve different use cases.

Quick Comparison ​

FeatureCustom Objects APIExternal Components SDK
What it isJSON schema registryHTML/JS sandboxed components
RenderingYour frontend renders based on typeIframe with BoardAPI SDK
DistributionAPI schema onlyFull component package (HTML/CSS/JS)
InteractionVia your UI codeSelf-contained iframe
SecurityTrusted (your code)Sandboxed (iframe isolation)
UpdatesRequires your deploymentUpload new version via API
Best forStructured data objectsInteractive widgets

Custom Objects API ​

What Are Custom Objects? ​

Custom Objects are typed data structures that you define via API. BoardAPI stores the schema and validates data, but your frontend is responsible for rendering.

Think of it as a type system for board objects - you define what fields an object should have, and BoardAPI enforces that schema.

When to Use Custom Objects ​

βœ… Use Custom Objects when:

  • You need structured data with validation (e.g., task cards, vocabulary cards)
  • Your frontend already has Vue/React components to render this data
  • You want tight integration with your UI framework
  • Schema validation is critical
  • You're building a white-label platform where clients will render objects differently

❌ Don't use Custom Objects when:

  • You want to distribute ready-to-use interactive components
  • You need isolated execution environment (security)
  • You want to update components without frontend redeployment

Example: Vocabulary Card ​

Step 1: Register the object type

http
POST /api/v1/object-types
X-API-Key: your-api-key
Content-Type: application/json

{
  "typeName": "vocabulary-card",
  "displayName": "Vocabulary Card",
  "category": "educational",
  "schema": {
    "type": "object",
    "properties": {
      "word": { "type": "string", "minLength": 1 },
      "translation": { "type": "string", "minLength": 1 },
      "pronunciation": { "type": "string" },
      "image_url": { "type": "string", "format": "uri" }
    },
    "required": ["word", "translation"]
  }
}

Step 2: Create objects on a board

http
POST /api/v1/boards/{boardId}/objects
X-API-Key: your-api-key

{
  "type": "vocabulary-card",
  "position": { "x": 100, "y": 100 },
  "data": {
    "word": "hello",
    "translation": "ΠΏΡ€ΠΈΠ²Π΅Ρ‚",
    "pronunciation": "hΙ™ΛˆlΙ™ΚŠ"
  }
}

Step 3: Render in your frontend

Your Vue/React app receives objects via WebSocket and renders them:

javascript
// Your frontend (Vue example)
<template>
  <div v-for="obj in vocabularyCards" :key="obj.id">
    <VocabularyCard
      :word="obj.data.word"
      :translation="obj.data.translation"
      :position="obj.position"
    />
  </div>
</template>

<script setup>
const vocabularyCards = computed(() =>
  board.objects.filter(o => o.type === 'vocabulary-card')
)
</script>

Key Points ​

  • Schema validation happens on BoardAPI backend
  • Rendering logic lives in your frontend code
  • No sandboxing - objects are part of your trusted codebase
  • Updates require redeploying your frontend

API Reference ​

See Object Types API for full endpoint documentation.


External Components SDK ​

What Are External Components? ​

External Components are self-contained HTML/CSS/JS packages that you upload to BoardAPI. They run in isolated iframes and communicate with the board via the BoardAPI SDK.

Think of them as mini web apps that live inside your board - each component has its own code, styles, and behavior.

When to Use External Components ​

βœ… Use External Components when:

  • You want to distribute ready-to-use interactive widgets
  • You need sandboxed execution (third-party code, marketplace)
  • You want to update component logic without redeploying your main app
  • You're building a component library for multiple clients
  • You need portability across different BoardAPI integrations

❌ Don't use External Components when:

  • You only need simple data display (use Custom Objects instead)
  • Performance is critical (iframe overhead may be noticeable)
  • You need deep integration with your app's state management

Example: Clicker Button ​

Step 1: Create component package

html
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    button {
      padding: 12px 24px;
      font-size: 16px;
      border-radius: 8px;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <button id="clickBtn">Click me! (0)</button>

  <script src="https://cdn.boardapi.io/sdk/v2.4.0/board-sdk.min.js"></script>
  <script>
    const board = new BoardSDK()
    let count = 0

    board.onReady(() => {
      const props = board.getProps()
      const btn = document.getElementById('clickBtn')
      btn.style.backgroundColor = props.color || '#667eea'

      btn.addEventListener('click', () => {
        count++
        btn.textContent = `Click me! (${count})`

        // Emit event to board
        board.emitEvent('click', { count })
      })
    })
  </script>
</body>
</html>

Step 2: Package and publish

bash
# Create manifest
cat > manifest.json << EOF
{
  "name": "clicker-button",
  "version": "1.0.0",
  "description": "Interactive click counter button",
  "entry": "index.html"
}
EOF

# Create ZIP
zip -r clicker-button.zip index.html manifest.json

# Publish via API
curl -X POST https://app.boardapi.io/api/v1/components/publish \
  -H "X-API-Key: your-api-key" \
  -F "package=@clicker-button.zip"

Step 3: Add to board

http
POST /api/v1/boards/{boardId}/objects
X-API-Key: your-api-key

{
  "type": "external-component",
  "position": { "x": 200, "y": 200 },
  "width": 200,
  "height": 80,
  "data": {
    "componentUrl": "https://app.boardapi.io/components/clicker-button@1.0.0",
    "props": {
      "color": "#667eea",
      "label": "Click me!"
    }
  }
}

Key Points ​

  • Full isolation - components run in sandboxed iframes
  • Self-contained - all code, styles, and assets in one package
  • Hot-swappable - upload new versions without frontend changes
  • Event-based - communicate with board via SDK events
  • Portable - same component works across different client apps

SDK Reference ​

See Components SDK API for full SDK documentation.


Side-by-Side Example ​

Let's implement the same feature (a task card) using both approaches:

Approach 1: Custom Objects API ​

Register schema:

json
{
  "typeName": "task-card",
  "schema": {
    "type": "object",
    "properties": {
      "title": { "type": "string" },
      "status": { "enum": ["todo", "in-progress", "done"] },
      "assignee": { "type": "string" }
    }
  }
}

Your frontend renders it:

vue
<template>
  <div class="task-card">
    <h3>{{ task.data.title }}</h3>
    <select v-model="task.data.status" @change="updateTask">
      <option value="todo">To Do</option>
      <option value="in-progress">In Progress</option>
      <option value="done">Done</option>
    </select>
  </div>
</template>

Pros:

  • βœ… Fast rendering (no iframe)
  • βœ… Full access to your app's state/UI
  • βœ… Can use your design system

Cons:

  • ❌ Requires frontend code change to update
  • ❌ Not portable to other apps

Approach 2: External Components SDK ​

Package component:

html
<!DOCTYPE html>
<html>
<body>
  <div class="task-card">
    <h3 id="title"></h3>
    <select id="status">
      <option value="todo">To Do</option>
      <option value="in-progress">In Progress</option>
      <option value="done">Done</option>
    </select>
  </div>

  <script src="https://cdn.boardapi.io/sdk/board-sdk.min.js"></script>
  <script>
    const board = new BoardSDK()
    board.onReady(() => {
      const props = board.getProps()
      document.getElementById('title').textContent = props.title
      document.getElementById('status').value = props.status

      document.getElementById('status').addEventListener('change', (e) => {
        board.emitEvent('statusChanged', { status: e.target.value })
      })
    })
  </script>
</body>
</html>

Pros:

  • βœ… Self-contained - update without app redeployment
  • βœ… Sandboxed - safe for third-party code
  • βœ… Portable across different client apps

Cons:

  • ❌ Iframe overhead (slight performance cost)
  • ❌ Isolated from your app's state management

Decision Matrix ​

Use this flowchart to decide which approach to use:

Do you need to distribute ready-to-use interactive components?
β”‚
β”œβ”€ YES β†’ External Components SDK
β”‚        (Package once, use everywhere)
β”‚
└─ NO β†’ Is this data that YOUR frontend will render?
         β”‚
         β”œβ”€ YES β†’ Custom Objects API
         β”‚        (Schema validation + your UI)
         β”‚
         └─ NO β†’ Reconsider - maybe you don't need custom objects at all?
                  (Use Excalidraw's built-in shapes)

Common Scenarios ​

ScenarioRecommended ApproachWhy
Educational flashcards for language learningCustom Objects APIStructured data, your app renders it
Third-party game widget for clientsExternal Components SDKSelf-contained, sandboxed
Kanban task cards in your PM toolCustom Objects APITight integration with your app
Embeddable timer widgetExternal Components SDKPortable, interactive
User profile cardsCustom Objects APISimple data display
Interactive quiz componentExternal Components SDKComplex interaction logic

Combining Both Approaches ​

You can use both in the same board! For example:

  • Custom Objects for structured data cards (vocabulary, tasks, profiles)
  • External Components for interactive widgets (timers, games, calculators)

Example board state:

json
{
  "objects": [
    {
      "type": "vocabulary-card",
      "data": { "word": "hello", "translation": "ΠΏΡ€ΠΈΠ²Π΅Ρ‚" }
    },
    {
      "type": "external-component",
      "data": {
        "componentUrl": ".../timer-widget@1.0.0",
        "props": { "duration": 300 }
      }
    }
  ]
}

Your frontend will:

  1. Render vocabulary-card using your VocabularyCard component
  2. Load timer-widget in an iframe via ExternalComponent wrapper

Performance Considerations ​

Custom Objects ​

  • ⚑ Fast: Direct rendering, no iframe overhead
  • ⚑ Efficient: Part of your main app bundle
  • ⚠️ Scaling: Large objects can bloat your app

External Components ​

  • ⚠️ Iframe overhead: Each component = separate iframe
  • ⚠️ Loading time: HTTP request to fetch component
  • βœ… Lazy loading: Components load only when visible
  • βœ… Memory isolation: Crashed component doesn't affect board

Best practice: Use Custom Objects for frequently-used simple data, External Components for complex/rare interactive features.


Migration Path ​

From Custom Objects β†’ External Components ​

If you outgrow Custom Objects (e.g., need sandboxing), migration is straightforward:

  1. Package your Vue component's template/logic into standalone HTML
  2. Replace API calls with SDK methods
  3. Publish as External Component
  4. Update board objects from type: "your-type" to type: "external-component"

From External Components β†’ Custom Objects ​

If you need tighter integration:

  1. Register schema via Object Types API
  2. Create Vue/React component in your app
  3. Update board objects to use new type
  4. Remove external component package


Summary ​

Custom Objects API = Schema registry for structured data (you render it) External Components SDK = Self-contained interactive widgets (iframe sandboxed)

Choose based on:

  • Control vs Portability
  • Performance vs Isolation
  • Integration depth vs Updateability

Both are powerful - use the right tool for the job!