SDK Reference
REST API

REST API

Create custom REST API endpoints for your plugins.

Registering Endpoints

sdk.rest.register(method, path, handler)

Create a new API endpoint.

// GET endpoint
sdk.rest.register('GET', '/api/my-plugin/items', async (req, res) => {
  const items = await sdk.storage.get('items') || [];
  
  return res.json({
    success: true,
    data: items
  });
});
 
// POST endpoint
sdk.rest.register('POST', '/api/my-plugin/items', async (req, res) => {
  const { name, value } = req.body;
  
  const items = await sdk.storage.get('items') || [];
  const newItem = { id: Date.now(), name, value };
  items.push(newItem);
  
  await sdk.storage.set('items', items);
  
  return res.json({
    success: true,
    data: newItem
  });
});
 
// PUT endpoint
sdk.rest.register('PUT', '/api/my-plugin/items/:id', async (req, res) => {
  const { id } = req.params;
  const { name, value } = req.body;
  
  const items = await sdk.storage.get('items') || [];
  const index = items.findIndex(i => i.id === parseInt(id));
  
  if (index === -1) {
    return res.status(404).json({
      success: false,
      error: 'Item not found'
    });
  }
  
  items[index] = { ...items[index], name, value };
  await sdk.storage.set('items', items);
  
  return res.json({
    success: true,
    data: items[index]
  });
});
 
// DELETE endpoint
sdk.rest.register('DELETE', '/api/my-plugin/items/:id', async (req, res) => {
  const { id } = req.params;
  
  let items = await sdk.storage.get('items') || [];
  items = items.filter(i => i.id !== parseInt(id));
  
  await sdk.storage.set('items', items);
  
  return res.json({
    success: true
  });
});

Request Object

The request object contains:

sdk.rest.register('POST', '/api/my-endpoint', async (req, res) => {
  // URL parameters
  req.params.id  // From /api/items/:id
  
  // Query string
  req.query.page   // From ?page=2
  req.query.search // From ?search=hello
  
  // Request body (for POST/PUT)
  req.body.name
  req.body.data
  
  // Headers
  req.headers['content-type']
  req.headers.authorization
  
  // Cookies
  req.cookies.sessionId
  
  // User (if authenticated)
  req.user // { id, email, role, ... }
  
  // Files (for multipart uploads)
  req.files  // Array of uploaded files
});

Response Methods

sdk.rest.register('GET', '/api/example', async (req, res) => {
  // JSON response
  return res.json({ success: true, data: {} });
  
  // With status code
  return res.status(201).json({ created: true });
  
  // Error responses
  return res.status(400).json({ error: 'Invalid input' });
  return res.status(401).json({ error: 'Unauthorized' });
  return res.status(404).json({ error: 'Not found' });
  return res.status(500).json({ error: 'Server error' });
  
  // Plain text
  return res.send('Hello World');
  
  // HTML
  return res.send('<h1>Hello</h1>');
  
  // Redirect
  return res.redirect('/new-location');
  
  // Set headers
  res.setHeader('X-Custom-Header', 'value');
  return res.json({ data: true });
  
  // Set cookie
  res.cookie('name', 'value', { maxAge: 86400000 });
  return res.json({ success: true });
});

Authentication

Require Authentication

sdk.rest.register('GET', '/api/my-plugin/private', async (req, res) => {
  // Check if user is logged in
  if (!req.user) {
    return res.status(401).json({ error: 'Authentication required' });
  }
  
  // User is authenticated
  return res.json({ user: req.user });
}, {
  auth: true // Require authentication
});

Require Admin Role

sdk.rest.register('POST', '/api/my-plugin/admin-action', async (req, res) => {
  // Check admin role
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Admin access required' });
  }
  
  // Perform admin action
  return res.json({ success: true });
}, {
  auth: true,
  roles: ['admin']
});

Validation

sdk.rest.register('POST', '/api/contact', async (req, res) => {
  const { name, email, message } = req.body;
  
  // Manual validation
  const errors = [];
  
  if (!name || name.trim().length < 2) {
    errors.push({ field: 'name', message: 'Name is required' });
  }
  
  if (!email || !email.includes('@')) {
    errors.push({ field: 'email', message: 'Valid email is required' });
  }
  
  if (!message || message.trim().length < 10) {
    errors.push({ field: 'message', message: 'Message must be at least 10 characters' });
  }
  
  if (errors.length > 0) {
    return res.status(400).json({
      success: false,
      errors
    });
  }
  
  // Process valid data
  // ...
  
  return res.json({ success: true });
});

File Uploads

sdk.rest.register('POST', '/api/my-plugin/upload', async (req, res) => {
  if (!req.files || req.files.length === 0) {
    return res.status(400).json({ error: 'No file uploaded' });
  }
  
  const file = req.files[0];
  
  // file.originalname - original filename
  // file.mimetype - MIME type
  // file.size - file size in bytes
  // file.buffer - file content
  
  // Validate file type
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
  if (!allowedTypes.includes(file.mimetype)) {
    return res.status(400).json({ error: 'Invalid file type' });
  }
  
  // Validate file size (5MB limit)
  if (file.size > 5 * 1024 * 1024) {
    return res.status(400).json({ error: 'File too large' });
  }
  
  // Save using Media API
  const result = await sdk.media.upload(file, {
    folder: 'my-plugin/uploads'
  });
  
  return res.json({
    success: true,
    file: result
  });
});

Rate Limiting

sdk.rest.register('POST', '/api/my-plugin/send', async (req, res) => {
  // ... handler
}, {
  rateLimit: {
    max: 10,       // Maximum requests
    window: 60000  // Per minute (ms)
  }
});

Example: Complete CRUD API

module.exports = async function(sdk) {
  const COLLECTION = 'bookmarks';
  
  // List all bookmarks
  sdk.rest.register('GET', '/api/bookmarks', async (req, res) => {
    const { page = 1, limit = 20, search = '' } = req.query;
    
    let bookmarks = await sdk.storage.get(COLLECTION) || [];
    
    // Search filter
    if (search) {
      const searchLower = search.toLowerCase();
      bookmarks = bookmarks.filter(b => 
        b.title.toLowerCase().includes(searchLower) ||
        b.url.toLowerCase().includes(searchLower)
      );
    }
    
    // Pagination
    const total = bookmarks.length;
    const start = (page - 1) * limit;
    const items = bookmarks.slice(start, start + limit);
    
    return res.json({
      success: true,
      data: {
        items,
        total,
        page: parseInt(page),
        pages: Math.ceil(total / limit)
      }
    });
  });
  
  // Get single bookmark
  sdk.rest.register('GET', '/api/bookmarks/:id', async (req, res) => {
    const bookmarks = await sdk.storage.get(COLLECTION) || [];
    const bookmark = bookmarks.find(b => b.id === req.params.id);
    
    if (!bookmark) {
      return res.status(404).json({
        success: false,
        error: 'Bookmark not found'
      });
    }
    
    return res.json({
      success: true,
      data: bookmark
    });
  });
  
  // Create bookmark
  sdk.rest.register('POST', '/api/bookmarks', async (req, res) => {
    const { title, url, tags = [] } = req.body;
    
    // Validation
    if (!title || !url) {
      return res.status(400).json({
        success: false,
        error: 'Title and URL are required'
      });
    }
    
    const bookmarks = await sdk.storage.get(COLLECTION) || [];
    
    const newBookmark = {
      id: sdk.utils.generateId(),
      title,
      url,
      tags,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };
    
    bookmarks.push(newBookmark);
    await sdk.storage.set(COLLECTION, bookmarks);
    
    return res.status(201).json({
      success: true,
      data: newBookmark
    });
  }, { auth: true });
  
  // Update bookmark
  sdk.rest.register('PUT', '/api/bookmarks/:id', async (req, res) => {
    const { title, url, tags } = req.body;
    
    const bookmarks = await sdk.storage.get(COLLECTION) || [];
    const index = bookmarks.findIndex(b => b.id === req.params.id);
    
    if (index === -1) {
      return res.status(404).json({
        success: false,
        error: 'Bookmark not found'
      });
    }
    
    bookmarks[index] = {
      ...bookmarks[index],
      ...(title && { title }),
      ...(url && { url }),
      ...(tags && { tags }),
      updatedAt: new Date().toISOString()
    };
    
    await sdk.storage.set(COLLECTION, bookmarks);
    
    return res.json({
      success: true,
      data: bookmarks[index]
    });
  }, { auth: true });
  
  // Delete bookmark
  sdk.rest.register('DELETE', '/api/bookmarks/:id', async (req, res) => {
    let bookmarks = await sdk.storage.get(COLLECTION) || [];
    const initialLength = bookmarks.length;
    
    bookmarks = bookmarks.filter(b => b.id !== req.params.id);
    
    if (bookmarks.length === initialLength) {
      return res.status(404).json({
        success: false,
        error: 'Bookmark not found'
      });
    }
    
    await sdk.storage.set(COLLECTION, bookmarks);
    
    return res.json({
      success: true
    });
  }, { auth: true, roles: ['admin'] });
  
  return {};
};

Using from Frontend

// Fetch bookmarks
const response = await fetch('/api/bookmarks?page=1&limit=10');
const data = await response.json();
 
// Create bookmark
await fetch('/api/bookmarks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'My Bookmark',
    url: 'https://example.com'
  })
});
 
// Delete bookmark
await fetch('/api/bookmarks/abc123', {
  method: 'DELETE'
});