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:
- Custom Objects API - Register custom object types with schemas (backend-defined)
- External Components SDK - Upload interactive HTML/JS components (frontend sandbox)
Both extend Excalidraw's base canvas, but serve different use cases.
Quick Comparison β
| Feature | Custom Objects API | External Components SDK |
|---|---|---|
| What it is | JSON schema registry | HTML/JS sandboxed components |
| Rendering | Your frontend renders based on type | Iframe with BoardAPI SDK |
| Distribution | API schema only | Full component package (HTML/CSS/JS) |
| Interaction | Via your UI code | Self-contained iframe |
| Security | Trusted (your code) | Sandboxed (iframe isolation) |
| Updates | Requires your deployment | Upload new version via API |
| Best for | Structured data objects | Interactive 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
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
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:
// 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
<!-- 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
# 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
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:
{
"typeName": "task-card",
"schema": {
"type": "object",
"properties": {
"title": { "type": "string" },
"status": { "enum": ["todo", "in-progress", "done"] },
"assignee": { "type": "string" }
}
}
}Your frontend renders it:
<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:
<!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 β
| Scenario | Recommended Approach | Why |
|---|---|---|
| Educational flashcards for language learning | Custom Objects API | Structured data, your app renders it |
| Third-party game widget for clients | External Components SDK | Self-contained, sandboxed |
| Kanban task cards in your PM tool | Custom Objects API | Tight integration with your app |
| Embeddable timer widget | External Components SDK | Portable, interactive |
| User profile cards | Custom Objects API | Simple data display |
| Interactive quiz component | External Components SDK | Complex 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:
{
"objects": [
{
"type": "vocabulary-card",
"data": { "word": "hello", "translation": "ΠΏΡΠΈΠ²Π΅Ρ" }
},
{
"type": "external-component",
"data": {
"componentUrl": ".../timer-widget@1.0.0",
"props": { "duration": 300 }
}
}
]
}Your frontend will:
- Render
vocabulary-cardusing your VocabularyCard component - Load
timer-widgetin 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:
- Package your Vue component's template/logic into standalone HTML
- Replace API calls with SDK methods
- Publish as External Component
- Update board objects from
type: "your-type"totype: "external-component"
From External Components β Custom Objects β
If you need tighter integration:
- Register schema via Object Types API
- Create Vue/React component in your app
- Update board objects to use new type
- Remove external component package
Related Documentation β
- Object Types API Reference - Full API documentation
- Components SDK Overview - External Components guide
- Components Publishing - How to upload components
- SDK API Reference - All SDK methods and events
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!