Homepage.dev Module SDK

Complete developer guide for building extensible homepage modules with real-time collaboration support


🎯 SDK Overview

The Homepage.dev Module SDK enables developers to create rich, interactive modules that integrate seamlessly with the real-time collaborative homepage platform. Build everything from simple widgets to complex productivity tools with full TypeScript support and comprehensive APIs.

📖 Related Documentation:

What You Can Build

  • Productivity Tools: Task managers, calendars, note-taking apps
  • Data Dashboards: Stock tickers, analytics widgets, system monitors
  • Communication: Chat interfaces, notification centers, team updates
  • Content: RSS readers, social feeds, bookmark managers
  • External Integrations: Third-party service connectors, API dashboards
  • Custom Utilities: Timers, calculators, weather widgets, and more

🚀 Quick Start

Installation

# Create new module project
npx @homepage/create-module my-awesome-module
cd my-awesome-module

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Publish to marketplace
npm run publish

Basic Module Structure

// src/index.ts
import { HomepageModule, ModuleConfig } from '@homepage/sdk'

interface MyModuleConfig {
  title: string
  refreshInterval: number
  apiKey?: string
}

const myModule: HomepageModule<MyModuleConfig> = {
  // Module metadata
  metadata: {
    id: 'my-awesome-module',
    name: 'My Awesome Module',
    description: 'An amazing module that does incredible things',
    version: '1.0.0',
    author: {
      name: 'Your Name',
      email: 'you@example.com',
      website: 'https://yoursite.com'
    },
    category: 'productivity',
    tags: ['productivity', 'automation', 'tools'],
    icon: 'https://cdn.example.com/icon.svg'
  },

  // React component
  component: MyModuleComponent,

  // Configuration schema
  configSchema: {
    type: 'object',
    properties: {
      title: { type: 'string', default: 'My Module' },
      refreshInterval: { type: 'number', default: 30000, minimum: 5000 },
      apiKey: { type: 'string', description: 'Optional API key' }
    },
    required: ['title', 'refreshInterval']
  },

  // Default configuration
  defaultConfig: {
    title: 'My Awesome Module',
    refreshInterval: 30000
  },

  // Module permissions
  permissions: {
    required: ['storage:local'],
    optional: ['network:external'],
    network: {
      domains: ['api.example.com'],
      methods: ['GET', 'POST']
    }
  },

  // Lifecycle hooks
  lifecycle: {
    onMount: async (context) => {
      console.log('Module mounted', context)
    },
    onUnmount: async () => {
      console.log('Module unmounted')
    }
  }
}

export default myModule

🧩 Module Interface Specification

Core Module Interface

interface HomepageModule<TConfig = any> {
  // Identity and metadata
  metadata: ModuleMetadata
  
  // React component for rendering
  component: React.ComponentType<ModuleProps<TConfig>>
  
  // Configuration schema (JSON Schema)
  configSchema: JSONSchema7
  defaultConfig: TConfig
  
  // Permissions and security
  permissions: ModulePermissions
  
  // Lifecycle management
  lifecycle: ModuleLifecycle
  
  // Optional data handlers
  dataHandlers?: ModuleDataHandlers<TConfig>
}

interface ModuleMetadata {
  id: string
  name: string
  description: string
  longDescription?: string
  version: string
  
  author: {
    name: string
    email?: string
    website?: string
  }
  
  // Categorization
  category: ModuleCategory
  tags: string[]
  keywords: string[]
  
  // Visual assets
  icon: string
  screenshots: string[]
  bannerImage?: string
  
  // Display configuration
  defaultSize: { width: number; height: number }
  minSize: { width: number; height: number }
  maxSize: { width: number; height: number }
  resizable: boolean
  
  // Behavior settings
  refreshInterval?: number
  singleton?: boolean // Only one instance allowed
  requiresAuth?: boolean
  
  // Compatibility
  minHomepageVersion: string
  maxHomepageVersion?: string
  dependencies: string[]
}

type ModuleCategory = 
  | 'productivity' | 'communication' | 'entertainment' 
  | 'utilities' | 'data' | 'social' | 'education'
  | 'business' | 'developer' | 'custom'

Module Props and Context

interface ModuleProps<TConfig = any> {
  // Module configuration
  config: TConfig
  onConfigChange: (newConfig: Partial<TConfig>) => void
  
  // Module context
  context: ModuleContext
  
  // Real-time collaboration
  collaboration: CollaborationContext
  
  // Homepage integration
  homepage: HomepageContext
  
  // Utilities and APIs
  api: ModuleAPI
  storage: StorageAPI
  messaging: MessagingAPI
}

interface ModuleContext {
  // Module instance info
  instanceId: string
  moduleId: string
  version: string
  
  // User and organization
  user: {
    id: string
    name: string
    email: string
    avatar?: string
  }
  
  organization?: {
    id: string
    name: string
    slug: string
  }
  
  // Environment
  environment: 'development' | 'production'
  isPreview: boolean
  
  // Permissions granted
  permissions: string[]
  
  // Theme and styling
  theme: {
    mode: 'light' | 'dark'
    primaryColor: string
    accentColor: string
  }
}

🔄 Real-time Collaboration APIs

Collaborative State Management

interface CollaborationContext {
  // Current collaborators
  activeUsers: CollaboratorInfo[]
  
  // Shared state management
  sharedState: SharedStateAPI
  
  // Real-time events
  events: EventAPI
  
  // Conflict resolution
  conflicts: ConflictAPI
}

interface CollaboratorInfo {
  userId: string
  userName: string
  userAvatar?: string
  cursor?: { x: number; y: number }
  isActive: boolean
  lastSeen: number
}

// Shared state between module instances
interface SharedStateAPI {
  // Get shared value
  get<T>(key: string): T | undefined
  
  // Set shared value with automatic sync
  set<T>(key: string, value: T): void
  
  // Subscribe to changes
  subscribe<T>(key: string, callback: (value: T) => void): () => void
  
  // Delete shared value
  delete(key: string): void
  
  // Get all keys
  keys(): string[]
  
  // Batch operations
  batch(operations: SharedStateOperation[]): void
}

// Real-time events between modules
interface EventAPI {
  // Emit event to other modules
  emit(eventType: string, payload: any): void
  
  // Listen for events
  on(eventType: string, callback: (payload: any) => void): () => void
  
  // One-time event listener
  once(eventType: string, callback: (payload: any) => void): void
  
  // Remove event listener
  off(eventType: string, callback?: Function): void
}

Inter-Module Communication

interface MessagingAPI {
  // Send message to specific module instance
  sendMessage(targetInstanceId: string, message: ModuleMessage): void
  
  // Broadcast message to all instances of a module type
  broadcast(moduleId: string, message: ModuleMessage): void
  
  // Listen for incoming messages
  onMessage(callback: (message: ModuleMessage) => void): () => void
  
  // Request-response pattern
  request<T>(targetInstanceId: string, request: any): Promise<T>
  
  // Handle incoming requests
  onRequest<T>(handler: (request: any) => T | Promise<T>): () => void
}

interface ModuleMessage {
  id: string
  type: string
  payload: any
  timestamp: number
  fromInstanceId: string
  toInstanceId?: string
}

💾 Data Storage and Persistence

Storage API

interface StorageAPI {
  // Local storage (per module instance)
  local: LocalStorageAPI
  
  // Shared storage (across module instances)
  shared: SharedStorageAPI
  
  // User storage (per user, across all instances)
  user: UserStorageAPI
  
  // File storage for larger data
  files: FileStorageAPI
}

interface LocalStorageAPI {
  // Simple key-value storage
  get<T>(key: string): Promise<T | null>
  set<T>(key: string, value: T): Promise<void>
  delete(key: string): Promise<void>
  clear(): Promise<void>
  keys(): Promise<string[]>
  
  // Structured data with queries
  query<T>(filter: QueryFilter): Promise<T[]>
  index<T>(indexName: string, data: T[]): Promise<void>
}

interface FileStorageAPI {
  // Upload files
  upload(file: File): Promise<FileInfo>
  uploadFromUrl(url: string): Promise<FileInfo>
  
  // Download files
  download(fileId: string): Promise<Blob>
  getUrl(fileId: string): Promise<string>
  
  // File management
  delete(fileId: string): Promise<void>
  list(): Promise<FileInfo[]>
  
  // File sharing
  share(fileId: string, permissions: SharePermissions): Promise<string>
}

🌐 External APIs and Network Access

Network API

interface NetworkAPI {
  // HTTP requests (within permitted domains)
  fetch(url: string, options?: RequestOptions): Promise<Response>
  
  // WebSocket connections
  websocket(url: string, protocols?: string[]): Promise<WebSocket>
  
  // Rate limiting info
  getRateLimit(): Promise<RateLimitInfo>
  
  // Proxy for CORS issues
  proxy(url: string): Promise<string>
}

// Controlled external access
interface ExternalAPIAccess {
  // Pre-configured popular APIs
  weather: WeatherAPI
  stocks: StocksAPI
  news: NewsAPI
  calendar: CalendarAPI
  
  // OAuth integration
  oauth: OAuthAPI
  
  // Webhook handling
  webhooks: WebhookAPI
}

interface OAuthAPI {
  // Initiate OAuth flow
  authorize(provider: OAuthProvider, scopes: string[]): Promise<OAuthToken>
  
  // Refresh tokens
  refresh(provider: OAuthProvider): Promise<OAuthToken>
  
  // Get current token
  getToken(provider: OAuthProvider): Promise<OAuthToken | null>
  
  // Revoke access
  revoke(provider: OAuthProvider): Promise<void>
}

type OAuthProvider = 'google' | 'github' | 'microsoft' | 'slack' | 'discord'

🎨 UI Components and Styling

Module UI Framework

// Pre-built UI components
import { 
  Button, Input, Card, Modal, Toast,
  Chart, DataTable, Timeline, Calendar,
  Avatar, Badge, Progress, Spinner
} from '@homepage/ui'

// Styling utilities
import { 
  useTheme, useResponsive, useAnimation,
  cn, styled, css
} from '@homepage/ui/styling'

// Example component
function MyModuleComponent({ config, context, api }: ModuleProps) {
  const theme = useTheme()
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(false)
  
  // Real-time data subscription
  useEffect(() => {
    const unsubscribe = api.storage.shared.subscribe('live-data', setData)
    return unsubscribe
  }, [])
  
  return (
    <Card className="p-4 h-full">
      <div className="flex items-center justify-between mb-4">
        <h3 className="text-lg font-semibold">{config.title}</h3>
        <Button 
          size="sm" 
          onClick={refreshData}
          disabled={loading}
        >
          {loading ? <Spinner size="sm" /> : 'Refresh'}
        </Button>
      </div>
      
      <DataTable 
        data={data}
        columns={columns}
        pagination
        className="flex-1"
      />
    </Card>
  )
}

Responsive Design

// Responsive utilities
import { useResponsive, breakpoints } from '@homepage/ui'

function ResponsiveModule({ config }: ModuleProps) {
  const { isMobile, isTablet, isDesktop } = useResponsive()
  
  return (
    <div className={cn(
      "p-4",
      isMobile && "p-2",
      isTablet && "p-3", 
      isDesktop && "p-4"
    )}>
      {isMobile ? (
        <MobileLayout data={data} />
      ) : (
        <DesktopLayout data={data} />
      )}
    </div>
  )
}

// CSS-in-JS with theme support
const StyledContainer = styled.div`
  background: ${props => props.theme.colors.background};
  border: 1px solid ${props => props.theme.colors.border};
  border-radius: ${props => props.theme.radii.md};
  
  @media (max-width: ${breakpoints.md}) {
    padding: 0.5rem;
  }
`

🔧 Development Tools and CLI

Module Development CLI

# Create new module project
npx @homepage/create-module <module-name>

# Development commands
npm run dev          # Start development server with hot reload
npm run build        # Build module for production
npm run test         # Run module tests
npm run lint         # Lint module code
npm run type-check   # TypeScript type checking

# Publishing commands
npm run validate     # Validate module before publishing
npm run publish      # Publish to module marketplace
npm run unpublish    # Remove from marketplace

# Marketplace commands
npm run analytics    # View module analytics
npm run reviews      # Manage module reviews
npm run revenue      # View revenue reports

Development Server Features

interface DevServer {
  // Hot reload with state preservation
  hotReload: {
    preserveState: boolean
    reloadOnConfigChange: boolean
    reloadOnDependencyChange: boolean
  }
  
  // Mock APIs for testing
  mocking: {
    externalAPIs: boolean
    storage: boolean
    collaboration: boolean
    authentication: boolean
  }
  
  // Performance profiling
  profiling: {
    renderTime: boolean
    memoryUsage: boolean
    networkRequests: boolean
    realtimeLatency: boolean
  }
  
  // Debugging tools
  debugging: {
    stateInspector: boolean
    eventLogger: boolean
    errorBoundary: boolean
    accessibilityChecker: boolean
  }
}

🧪 Testing Framework

Module Testing Utilities

import { 
  renderModule, mockModuleContext, mockCollaboration,
  fireEvent, waitFor, screen
} from '@homepage/testing'

describe('MyModule', () => {
  test('renders with default config', async () => {
    const { container } = await renderModule(MyModule, {
      config: { title: 'Test Module' },
      context: mockModuleContext()
    })
    
    expect(screen.getByText('Test Module')).toBeInTheDocument()
  })
  
  test('handles real-time updates', async () => {
    const collaboration = mockCollaboration()
    const { rerender } = await renderModule(MyModule, {
      collaboration
    })
    
    // Simulate real-time update
    collaboration.sharedState.set('data', [{ id: 1, name: 'Item 1' }])
    
    await waitFor(() => {
      expect(screen.getByText('Item 1')).toBeInTheDocument()
    })
  })
  
  test('handles external API calls', async () => {
    const mockAPI = jest.fn().mockResolvedValue({ data: [] })
    
    await renderModule(MyModule, {
      api: {
        network: { fetch: mockAPI }
      }
    })
    
    fireEvent.click(screen.getByRole('button', { name: /refresh/i }))
    
    expect(mockAPI).toHaveBeenCalledWith('https://api.example.com/data')
  })
})

Performance Testing

import { benchmarkModule, profileMemory, measureLatency } from '@homepage/testing'

test('performance benchmarks', async () => {
  const results = await benchmarkModule(MyModule, {
    iterations: 100,
    scenarios: ['mount', 'update', 'unmount']
  })
  
  expect(results.mount.average).toBeLessThan(100) // < 100ms
  expect(results.memory.peak).toBeLessThan(50 * 1024 * 1024) // < 50MB
})

📊 Analytics and Monitoring

Module Analytics API

interface ModuleAnalytics {
  // Track user interactions
  trackEvent(eventName: string, properties?: Record<string, any>): void
  
  // Track performance metrics
  trackPerformance(metric: PerformanceMetric): void
  
  // Track errors
  trackError(error: Error, context?: Record<string, any>): void
  
  // Custom metrics
  trackMetric(name: string, value: number, unit: string): void
  
  // User journey tracking
  trackPageView(page: string): void
  trackUserAction(action: string, target: string): void
}

// Usage in module
function MyModule({ api }: ModuleProps) {
  const handleButtonClick = () => {
    api.analytics.trackEvent('button_clicked', {
      button: 'refresh',
      config: config.title
    })
    
    refreshData()
  }
  
  useEffect(() => {
    api.analytics.trackPageView('module_mounted')
  }, [])
}

Revenue Tracking

interface RevenueAPI {
  // Track subscription events
  trackSubscription(event: 'started' | 'renewed' | 'cancelled'): void
  
  // Track usage for freemium models
  trackUsage(feature: string, count: number): void
  
  // In-app purchases
  trackPurchase(item: string, amount: number, currency: string): void
  
  // Conversion funnel
  trackConversion(step: string, success: boolean): void
}

🛡️ Security and Permissions

Permission System

interface ModulePermissions {
  // Required permissions (user must grant)
  required: Permission[]
  
  // Optional permissions (module works without)
  optional: Permission[]
  
  // Network access
  network?: {
    domains: string[]                    // Allowed domains
    methods: ('GET' | 'POST' | 'PUT' | 'DELETE')[]
    rateLimit?: number                   // Requests per minute
  }
  
  // Storage access
  storage?: {
    quota: number                        // Storage quota in MB
    types: ('local' | 'shared' | 'user')[]
  }
  
  // Device permissions
  device?: {
    camera?: boolean
    microphone?: boolean
    geolocation?: boolean
    notifications?: boolean
  }
  
  // API access
  apis?: {
    external: string[]                   // External API access
    internal: string[]                   // Homepage API access
  }
}

type Permission = 
  | 'storage:local' | 'storage:shared' | 'storage:user'
  | 'network:external' | 'network:internal'
  | 'device:camera' | 'device:microphone' | 'device:geolocation'
  | 'notifications:show' | 'notifications:push'
  | 'collaboration:read' | 'collaboration:write'
  | 'analytics:basic' | 'analytics:detailed'

Security Best Practices

// Input validation
import { validateConfig, sanitizeInput, escapeHTML } from '@homepage/security'

function MyModule({ config }: ModuleProps) {
  // Always validate configuration
  const validatedConfig = validateConfig(config, configSchema)
  
  // Sanitize user inputs
  const handleUserInput = (input: string) => {
    const sanitized = sanitizeInput(input)
    const safe = escapeHTML(sanitized)
    return safe
  }
  
  // Secure API calls
  const fetchData = async () => {
    try {
      const response = await api.network.fetch('/api/data', {
        headers: {
          'Authorization': `Bearer ${await api.oauth.getToken('provider')}`
        }
      })
      return response.json()
    } catch (error) {
      api.analytics.trackError(error)
      throw error
    }
  }
}

💰 Monetization and Marketplace

Publishing to Marketplace

// module.config.ts
export const moduleConfig = {
  // Basic info
  metadata: {
    name: 'My Awesome Module',
    description: 'Brief description',
    category: 'productivity',
    tags: ['productivity', 'tools']
  },
  
  // Pricing model
  pricing: {
    model: 'freemium',              // 'free' | 'paid' | 'freemium'
    price: 499,                     // Price in cents ($4.99)
    currency: 'USD',
    trialDays: 14,
    subscriptionType: 'monthly'     // 'monthly' | 'yearly'
  },
  
  // Revenue sharing (70% developer, 30% platform)
  revenueShare: {
    developer: 70,
    platform: 30
  }
}

Freemium Features

function MyModule({ context, api }: ModuleProps) {
  const { subscription } = context.user
  const isPro = subscription?.tier === 'pro'
  
  // Feature gating
  const handleAdvancedFeature = () => {
    if (!isPro) {
      api.ui.showUpgradeModal({
        feature: 'Advanced Analytics',
        price: '$4.99/month'
      })
      return
    }
    
    // Pro feature implementation
    showAdvancedAnalytics()
  }
  
  return (
    <div>
      <BasicFeatures />
      {isPro ? (
        <AdvancedFeatures />
      ) : (
        <UpgradePrompt feature="Advanced Features" />
      )}
    </div>
  )
}

📚 Example Modules

Stock Ticker Module

import { HomepageModule } from '@homepage/sdk'
import { LineChart, Card } from '@homepage/ui'

interface StockConfig {
  symbols: string[]
  refreshInterval: number
  showChart: boolean
}

const StockTicker: HomepageModule<StockConfig> = {
  metadata: {
    id: 'stock-ticker',
    name: 'Stock Ticker',
    description: 'Real-time stock price tracking',
    category: 'data',
    defaultSize: { width: 4, height: 2 }
  },
  
  component: function StockTickerComponent({ config, api }) {
    const [stocks, setStocks] = useState([])
    const [loading, setLoading] = useState(false)
    
    // Real-time stock updates
    useEffect(() => {
      const fetchStocks = async () => {
        setLoading(true)
        try {
          const data = await api.network.fetch(
            `https://api.stockprice.com/quotes?symbols=${config.symbols.join(',')}`
          )
          const quotes = await data.json()
          setStocks(quotes)
          
          // Share with other instances
          api.collaboration.sharedState.set('stocks', quotes)
        } catch (error) {
          api.analytics.trackError(error)
        } finally {
          setLoading(false)
        }
      }
      
      fetchStocks()
      const interval = setInterval(fetchStocks, config.refreshInterval)
      return () => clearInterval(interval)
    }, [config.symbols, config.refreshInterval])
    
    return (
      <Card className="p-4 h-full">
        <h3 className="text-lg font-semibold mb-4">Stock Prices</h3>
        
        <div className="space-y-2">
          {stocks.map(stock => (
            <div key={stock.symbol} className="flex justify-between">
              <span className="font-medium">{stock.symbol}</span>
              <span className={`font-mono ${
                stock.change >= 0 ? 'text-green-600' : 'text-red-600'
              }`}>
                ${stock.price} ({stock.change > 0 ? '+' : ''}{stock.change}%)
              </span>
            </div>
          ))}
        </div>
        
        {config.showChart && (
          <LineChart 
            data={stocks}
            xKey="timestamp"
            yKey="price"
            className="mt-4 h-32"
          />
        )}
        
        {loading && <div className="absolute inset-0 bg-white/80 flex items-center justify-center">Loading...</div>}
      </Card>
    )
  },
  
  configSchema: {
    type: 'object',
    properties: {
      symbols: {
        type: 'array',
        items: { type: 'string' },
        default: ['AAPL', 'GOOGL', 'MSFT']
      },
      refreshInterval: {
        type: 'number',
        default: 30000,
        minimum: 5000
      },
      showChart: {
        type: 'boolean',
        default: true
      }
    }
  },
  
  permissions: {
    required: ['network:external'],
    network: {
      domains: ['api.stockprice.com'],
      methods: ['GET']
    }
  }
}

export default StockTicker

Team Chat Module

const TeamChat: HomepageModule = {
  metadata: {
    id: 'team-chat',
    name: 'Team Chat',
    description: 'Real-time team communication',
    category: 'communication',
    defaultSize: { width: 4, height: 3 }
  },
  
  component: function TeamChatComponent({ api, context, collaboration }) {
    const [messages, setMessages] = useState([])
    const [newMessage, setNewMessage] = useState('')
    
    // Listen for new messages
    useEffect(() => {
      const unsubscribe = collaboration.events.on('message', (message) => {
        setMessages(prev => [...prev, message])
      })
      return unsubscribe
    }, [])
    
    const sendMessage = () => {
      if (!newMessage.trim()) return
      
      const message = {
        id: crypto.randomUUID(),
        text: newMessage,
        userId: context.user.id,
        userName: context.user.name,
        timestamp: Date.now()
      }
      
      // Broadcast to all team chat instances
      collaboration.events.emit('message', message)
      setNewMessage('')
    }
    
    return (
      <Card className="p-4 h-full flex flex-col">
        <h3 className="text-lg font-semibold mb-4">Team Chat</h3>
        
        <div className="flex-1 overflow-y-auto space-y-2 mb-4">
          {messages.map(message => (
            <div key={message.id} className="flex space-x-2">
              <Avatar size="sm" src={message.userAvatar} />
              <div>
                <div className="text-sm font-medium">{message.userName}</div>
                <div className="text-sm text-gray-600">{message.text}</div>
              </div>
            </div>
          ))}
        </div>
        
        <div className="flex space-x-2">
          <Input
            value={newMessage}
            onChange={(e) => setNewMessage(e.target.value)}
            placeholder="Type a message..."
            onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
            className="flex-1"
          />
          <Button onClick={sendMessage}>Send</Button>
        </div>
      </Card>
    )
  },
  
  permissions: {
    required: ['collaboration:write']
  }
}

🚀 Publishing Your Module

Publishing Checklist

  • Module follows naming conventions
  • All required permissions declared
  • Configuration schema is valid
  • Module has proper error handling
  • Analytics tracking implemented
  • Security best practices followed
  • Module tested across different screen sizes
  • Documentation and screenshots provided
  • Privacy policy and terms (if applicable)

Review Process

  1. Automated Testing: Security scan, performance test, compatibility check
  2. Manual Review: Code quality, user experience, marketplace guidelines
  3. Approval Timeline: 2-5 business days for new modules
  4. Updates: Fast-track approval for minor updates

Developer Success Program

  • Technical Support: Dedicated developer support channel
  • Marketing Support: Featured placement for high-quality modules
  • Revenue Optimization: Analytics and conversion optimization guidance
  • Community: Developer forums, hackathons, and events

📞 Support and Resources

Documentation

  • API Reference: Complete TypeScript definitions
  • Tutorials: Step-by-step module building guides
  • Examples: Open-source example modules
  • Best Practices: Performance, security, and UX guidelines

Developer Support

  • Discord Community: Real-time developer chat
  • GitHub Issues: Bug reports and feature requests
  • Office Hours: Weekly developer Q&A sessions
  • 1:1 Support: Premium support for verified developers

Build amazing modules that enhance productivity and bring teams together. The Homepage.dev SDK provides everything you need to create powerful, real-time collaborative experiences.

📖 Continue Your Journey:

Happy building! 🚀

Last Updated: January 2025

On this page