Plugin Development
REST API

REST API

Create custom API endpoints for your plugins.

Registering Endpoints

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
  });
});

HTTP Methods

// GET - Retrieve data
sdk.rest.register('GET', '/api/items', handler);
 
// POST - Create data
sdk.rest.register('POST', '/api/items', handler);
 
// PUT - Update data
sdk.rest.register('PUT', '/api/items/:id', handler);
 
// PATCH - Partial update
sdk.rest.register('PATCH', '/api/items/:id', handler);
 
// DELETE - Remove data
sdk.rest.register('DELETE', '/api/items/:id', handler);

Request Object

sdk.rest.register('POST', '/api/items/:id', async (req, res) => {
  // URL parameters
  req.params.id           // From :id in URL
  
  // Query string (?page=1&search=hello)
  req.query.page          // '1'
  req.query.search        // 'hello'
  
  // Request body (JSON)
  req.body.name           // Posted data
  req.body.value
  
  // Headers
  req.headers['content-type']
  req.headers.authorization
  
  // Current user (if authenticated)
  req.user                // { id, email, role, ... }
  
  // Files (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: 'Bad request' });
  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('OK');
  
  // HTML
  return res.send('<h1>Hello</h1>');
  
  // Redirect
  return res.redirect('/other-url');
  
  // Set headers
  res.setHeader('X-Custom', 'value');
  return res.json({ data: true });
  
  // Set cookie
  res.cookie('session', 'abc123', { maxAge: 86400000 });
  return res.json({ success: true });
});

Authentication

Require Login

sdk.rest.register('GET', '/api/user/profile', async (req, res) => {
  if (!req.user) {
    return res.status(401).json({ error: 'Authentication required' });
  }
  
  return res.json({ user: req.user });
}, {
  auth: true
});

Require Admin Role

sdk.rest.register('POST', '/api/admin/action', async (req, res) => {
  // Handler only runs if user is admin
  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;
  const errors = [];
  
  if (!name || name.length < 2) {
    errors.push({ field: 'name', message: 'Name required' });
  }
  
  if (!email || !email.includes('@')) {
    errors.push({ field: 'email', message: 'Valid email required' });
  }
  
  if (!message || message.length < 10) {
    errors.push({ field: 'message', message: 'Message too short' });
  }
  
  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }
  
  // Process valid data...
  return res.json({ success: true });
});

File Uploads

sdk.rest.register('POST', '/api/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];
  
  // Validate type
  if (!file.mimetype.startsWith('image/')) {
    return res.status(400).json({ error: 'Only images allowed' });
  }
  
  // Validate size (5MB)
  if (file.size > 5 * 1024 * 1024) {
    return res.status(400).json({ error: 'File too large' });
  }
  
  // Save file
  const result = await sdk.media.upload(file, {
    folder: 'my-plugin/uploads'
  });
  
  return res.json({
    success: true,
    url: result.url
  });
});

Rate Limiting

sdk.rest.register('POST', '/api/send', async (req, res) => {
  // Handler
}, {
  rateLimit: {
    max: 10,        // Max requests
    window: 60000   // Per minute
  }
});

Complete CRUD Example

module.exports = async function(sdk) {
  const COLLECTION = 'notes';
  
  // List all notes
  sdk.rest.register('GET', '/api/notes', async (req, res) => {
    const { page = 1, limit = 20 } = req.query;
    
    let notes = await sdk.storage.get(COLLECTION) || [];
    
    // Sort by date
    notes.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
    
    // Paginate
    const start = (page - 1) * limit;
    const items = notes.slice(start, start + parseInt(limit));
    
    return res.json({
      items,
      total: notes.length,
      page: parseInt(page),
      pages: Math.ceil(notes.length / limit)
    });
  });
  
  // Get single note
  sdk.rest.register('GET', '/api/notes/:id', async (req, res) => {
    const notes = await sdk.storage.get(COLLECTION) || [];
    const note = notes.find(n => n.id === req.params.id);
    
    if (!note) {
      return res.status(404).json({ error: 'Note not found' });
    }
    
    return res.json(note);
  });
  
  // Create note
  sdk.rest.register('POST', '/api/notes', async (req, res) => {
    const { title, content } = req.body;
    
    if (!title) {
      return res.status(400).json({ error: 'Title required' });
    }
    
    const notes = await sdk.storage.get(COLLECTION) || [];
    
    const note = {
      id: sdk.utils.generateId(),
      title,
      content: content || '',
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };
    
    notes.push(note);
    await sdk.storage.set(COLLECTION, notes);
    
    return res.status(201).json(note);
  }, { auth: true });
  
  // Update note
  sdk.rest.register('PUT', '/api/notes/:id', async (req, res) => {
    const { title, content } = req.body;
    
    const notes = await sdk.storage.get(COLLECTION) || [];
    const index = notes.findIndex(n => n.id === req.params.id);
    
    if (index === -1) {
      return res.status(404).json({ error: 'Note not found' });
    }
    
    notes[index] = {
      ...notes[index],
      ...(title !== undefined && { title }),
      ...(content !== undefined && { content }),
      updatedAt: new Date().toISOString()
    };
    
    await sdk.storage.set(COLLECTION, notes);
    
    return res.json(notes[index]);
  }, { auth: true });
  
  // Delete note
  sdk.rest.register('DELETE', '/api/notes/:id', async (req, res) => {
    let notes = await sdk.storage.get(COLLECTION) || [];
    const initialLength = notes.length;
    
    notes = notes.filter(n => n.id !== req.params.id);
    
    if (notes.length === initialLength) {
      return res.status(404).json({ error: 'Note not found' });
    }
    
    await sdk.storage.set(COLLECTION, notes);
    
    return res.json({ success: true });
  }, { auth: true });
  
  return {};
};

Using from Frontend

// List
const res = await fetch('/api/notes');
const data = await res.json();
 
// Create
await fetch('/api/notes', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'New Note', content: 'Content here' })
});
 
// Update
await fetch('/api/notes/abc123', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Updated Title' })
});
 
// Delete
await fetch('/api/notes/abc123', {
  method: 'DELETE'
});