Building Your First Platform with Spatio Platform SDK
Learn how to build a custom platform using the Spatio Platform SDK. Create a full-stack application that integrates seamlessly with Spatio's workspace.
Building Your First Platform with Spatio Platform SDK
The Spatio Platform SDK enables you to build custom platforms that run inside Spatio's workspace. In this tutorial, we'll build a simple task management platform from scratch, learning the core concepts and SDK features along the way.
What You'll Build
By the end of this tutorial, you'll have created a task management platform with:
- A React frontend that embeds in Spatio
- Backend API endpoints for task operations
- Integration with Spatio's workspace and user data
- Persistent storage using the Platform SDK
- Real-time collaboration features
Prerequisites
Before starting, make sure you have:
- Node.js 18+ installed
- The Spatio Platform CLI installed (
npm install -g @spatio/platform-cli) - A Spatio account
- Basic knowledge of React and TypeScript
Step 1: Create Your Platform
First, create a new platform using the CLI:
spatio create task-manager
cd task-manager
When prompted, select the Basic template. This gives us a minimal starting point.
Step 2: Understanding the Project Structure
Your platform has two main parts:
task-manager/
├── src/
│ ├── frontend/ # React application
│ │ ├── App.tsx # Main component
│ │ └── components/ # UI components
│ └── backend/ # API endpoints
│ └── index.ts # Backend logic
├── spatio.config.json # Platform configuration
└── package.json
Step 3: Install Dependencies
Install the Platform SDK and UI components:
npm install @spatio/platform-sdk @spatio/design-system
Step 4: Build the Frontend
Create the Task Interface
First, define our data types. Create src/frontend/types.ts:
export interface Task {
id: string;
title: string;
description: string;
completed: boolean;
createdAt: string;
createdBy: string;
}
Create the Task Component
Create src/frontend/components/TaskItem.tsx:
import { Button, Card } from '@spatio/design-system';
import { Task } from '../types';
interface TaskItemProps {
task: Task;
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}
export function TaskItem({ task, onToggle, onDelete }: TaskItemProps) {
return (
<Card className="p-4 mb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 flex-1">
<input
type="checkbox"
checked={task.completed}
onChange={() => onToggle(task.id)}
className="h-5 w-5"
/>
<div className={task.completed ? 'line-through opacity-50' : ''}>
<h3 className="font-medium">{task.title}</h3>
<p className="text-sm text-gray-500">{task.description}</p>
</div>
</div>
<Button
variant="ghost"
onClick={() => onDelete(task.id)}
>
Delete
</Button>
</div>
</Card>
);
}
Build the Main App
Update src/frontend/App.tsx:
import { useState, useEffect } from 'react';
import { useWorkspace, useSpatioAPI, useStorage } from '@spatio/platform-sdk';
import { Button, Input, Card } from '@spatio/design-system';
import { TaskItem } from './components/TaskItem';
import { Task } from './types';
export default function App() {
const { workspace, user } = useWorkspace();
const api = useSpatioAPI();
const { data: tasks, setData: setTasks } = useStorage<Task[]>('tasks', []);
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
// Load tasks on mount
useEffect(() => {
loadTasks();
}, []);
const loadTasks = async () => {
try {
const fetchedTasks = await api.get<Task[]>('/tasks');
setTasks(fetchedTasks);
} catch (error) {
console.error('Failed to load tasks:', error);
}
};
const createTask = async () => {
if (!title.trim()) return;
const newTask: Omit<Task, 'id' | 'createdAt'> = {
title,
description,
completed: false,
createdBy: user.id,
};
try {
const created = await api.post<Task>('/tasks', newTask);
setTasks([...tasks, created]);
setTitle('');
setDescription('');
} catch (error) {
console.error('Failed to create task:', error);
}
};
const toggleTask = async (id: string) => {
const task = tasks.find(t => t.id === id);
if (!task) return;
try {
const updated = await api.patch<Task>(`/tasks/${id}`, {
completed: !task.completed,
});
setTasks(tasks.map(t => t.id === id ? updated : t));
} catch (error) {
console.error('Failed to update task:', error);
}
};
const deleteTask = async (id: string) => {
try {
await api.delete(`/tasks/${id}`);
setTasks(tasks.filter(t => t.id !== id));
} catch (error) {
console.error('Failed to delete task:', error);
}
};
return (
<div className="max-w-4xl mx-auto p-6">
<div className="mb-6">
<h1 className="text-3xl font-bold mb-2">Task Manager</h1>
<p className="text-gray-500">
Workspace: {workspace.name} | User: {user.name}
</p>
</div>
<Card className="p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">Create New Task</h2>
<div className="space-y-3">
<Input
placeholder="Task title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Input
placeholder="Description (optional)"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<Button onClick={createTask} variant="primary">
Add Task
</Button>
</div>
</Card>
<div>
<h2 className="text-xl font-semibold mb-4">
Tasks ({tasks.length})
</h2>
{tasks.length === 0 ? (
<p className="text-gray-500 text-center py-8">
No tasks yet. Create one to get started!
</p>
) : (
tasks.map(task => (
<TaskItem
key={task.id}
task={task}
onToggle={toggleTask}
onDelete={deleteTask}
/>
))
)}
</div>
</div>
);
}
Step 5: Build the Backend
Update src/backend/index.ts:
import { v4 as uuidv4 } from 'uuid';
interface Task {
id: string;
title: string;
description: string;
completed: boolean;
createdAt: string;
createdBy: string;
}
// In-memory storage (replace with database in production)
let tasks: Task[] = [];
export const handlers = {
// Get all tasks
'GET /tasks': async (req: any) => {
return {
status: 200,
body: tasks,
};
},
// Create a task
'POST /tasks': async (req: any) => {
const { title, description, createdBy } = req.body;
const task: Task = {
id: uuidv4(),
title,
description: description || '',
completed: false,
createdAt: new Date().toISOString(),
createdBy,
};
tasks.push(task);
return {
status: 201,
body: task,
};
},
// Update a task
'PATCH /tasks/:id': async (req: any) => {
const { id } = req.params;
const updates = req.body;
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
return {
status: 404,
body: { error: 'Task not found' },
};
}
tasks[taskIndex] = { ...tasks[taskIndex], ...updates };
return {
status: 200,
body: tasks[taskIndex],
};
},
// Delete a task
'DELETE /tasks/:id': async (req: any) => {
const { id } = req.params;
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
return {
status: 404,
body: { error: 'Task not found' },
};
}
tasks.splice(taskIndex, 1);
return {
status: 204,
body: null,
};
},
};
Install the UUID package:
npm install uuid
npm install -D @types/uuid
Step 6: Configure Your Platform
Update spatio.config.json:
{
"name": "task-manager",
"displayName": "Task Manager",
"description": "Simple task management for your workspace",
"version": "1.0.0",
"icon": "✓",
"capabilities": {
"workspace": true,
"storage": true
},
"routes": {
"main": "/"
}
}
Step 7: Test Your Platform
Start the development server:
spatio dev
Open your browser to http://localhost:3000. You should see your task manager running!
Try:
- Creating new tasks
- Marking tasks as complete
- Deleting tasks
Step 8: Deploy Your Platform
When you're ready to deploy:
spatio build
spatio deploy
Your platform is now live and available to your organization!
Next Steps: Advanced Features
Add Real-Time Collaboration
Use the Events API for real-time updates:
import { useEvents } from '@spatio/platform-sdk';
function App() {
const { emit, on } = useEvents();
// Emit when a task changes
const createTask = async () => {
const task = await api.post('/tasks', newTask);
emit('task:created', task);
};
// Listen for changes from other users
useEffect(() => {
const unsubscribe = on('task:created', (task) => {
setTasks(prev => [...prev, task]);
});
return unsubscribe;
}, []);
}
Connect to External Providers
Access Gmail, Salesforce, and other connected providers:
import { useProviders } from '@spatio/platform-sdk';
function App() {
const providers = useProviders();
const gmail = providers.find(p => p.type === 'gmail');
const createTaskFromEmail = async (emailId: string) => {
if (!gmail) return;
const email = await gmail.api.get(`/emails/${emailId}`);
const task = {
title: email.subject,
description: email.body,
};
await api.post('/tasks', task);
};
}
Add Navigation Between Platforms
Navigate to other platforms from your app:
import { useNavigation } from '@spatio/platform-sdk';
function App() {
const { navigate } = useNavigation();
return (
<Button onClick={() => navigate('/calendar')}>
Open Calendar
</Button>
);
}
Key Concepts Recap
Platform SDK Hooks
- useWorkspace() - Access workspace, user, and organization data
- useSpatioAPI() - Make authenticated API calls to your backend
- useStorage() - Persistent storage with automatic sync
- useProviders() - Access connected third-party services
- useEvents() - Real-time communication between users
- useNavigation() - Navigate between platforms
Best Practices
- Type Safety: Always use TypeScript for better development experience
- Error Handling: Wrap API calls in try-catch blocks
- Loading States: Show loading indicators during async operations
- Optimistic Updates: Update UI immediately, then sync with backend
- Cleanup: Unsubscribe from events in useEffect cleanup
Resources
Conclusion
You've built a fully functional task management platform using the Spatio Platform SDK! This platform:
- Runs inside Spatio's workspace
- Has a React frontend and Node.js backend
- Uses Spatio's authentication and workspace context
- Stores data persistently
- Can be extended with real-time features and external integrations
The Platform SDK gives you powerful building blocks to create custom tools tailored to your team's needs. Experiment, iterate, and build amazing platforms!
Happy building! 🚀