SDK Reference
Storage API

Storage API

Persistent key-value storage for plugins.

Basic Operations

sdk.storage.get(key)

Retrieve a value.

const value = await sdk.storage.get('my-key');
 
// Returns null if key doesn't exist
if (value === null) {
  console.log('Key not found');
}

sdk.storage.set(key, value)

Store a value. Value can be any JSON-serializable data.

// Store string
await sdk.storage.set('name', 'John');
 
// Store number
await sdk.storage.set('count', 42);
 
// Store object
await sdk.storage.set('settings', {
  enabled: true,
  limit: 100,
  options: ['a', 'b', 'c']
});
 
// Store array
await sdk.storage.set('items', [
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' }
]);

sdk.storage.delete(key)

Remove a key.

await sdk.storage.delete('my-key');

sdk.storage.has(key)

Check if a key exists.

if (await sdk.storage.has('settings')) {
  const settings = await sdk.storage.get('settings');
}

Namespaced Storage

Keys are automatically namespaced to your plugin to prevent conflicts.

// In my-plugin
await sdk.storage.set('count', 10);
// Actually stored as: my-plugin:count
 
// In another-plugin  
await sdk.storage.set('count', 20);
// Actually stored as: another-plugin:count
 
// Both can exist without conflict

Pattern Operations

sdk.storage.getAll(pattern)

Get all keys matching a pattern.

// Store some data
await sdk.storage.set('user:1', { name: 'John' });
await sdk.storage.set('user:2', { name: 'Jane' });
await sdk.storage.set('user:3', { name: 'Bob' });
 
// Get all users
const users = await sdk.storage.getAll('user:*');
// Returns: { 'user:1': {...}, 'user:2': {...}, 'user:3': {...} }

sdk.storage.deleteAll(pattern)

Delete all keys matching a pattern.

// Delete all user data
await sdk.storage.deleteAll('user:*');

Common Patterns

Settings with Defaults

async function getSettings() {
  const defaults = {
    enabled: true,
    maxItems: 10,
    displayMode: 'grid'
  };
  
  const saved = await sdk.storage.get('settings') || {};
  
  return { ...defaults, ...saved };
}
 
async function updateSettings(updates) {
  const current = await getSettings();
  await sdk.storage.set('settings', { ...current, ...updates });
}
 
// Usage
const settings = await getSettings();
await updateSettings({ maxItems: 20 });

Counter

async function incrementCounter(name) {
  const key = `counter:${name}`;
  const current = (await sdk.storage.get(key)) || 0;
  const newValue = current + 1;
  await sdk.storage.set(key, newValue);
  return newValue;
}
 
// Usage
const views = await incrementCounter('pageviews');

Cache with Expiry

async function cacheGet(key) {
  const item = await sdk.storage.get(`cache:${key}`);
  
  if (!item) return null;
  
  if (item.expiry && Date.now() > item.expiry) {
    await sdk.storage.delete(`cache:${key}`);
    return null;
  }
  
  return item.value;
}
 
async function cacheSet(key, value, ttlSeconds = 3600) {
  await sdk.storage.set(`cache:${key}`, {
    value,
    expiry: Date.now() + (ttlSeconds * 1000)
  });
}
 
// Usage
let data = await cacheGet('api-response');
 
if (!data) {
  data = await fetchFromAPI();
  await cacheSet('api-response', data, 600); // Cache 10 minutes
}

Queue

async function enqueue(queueName, item) {
  const queue = (await sdk.storage.get(`queue:${queueName}`)) || [];
  queue.push({
    id: Date.now(),
    data: item,
    addedAt: new Date().toISOString()
  });
  await sdk.storage.set(`queue:${queueName}`, queue);
}
 
async function dequeue(queueName) {
  const queue = (await sdk.storage.get(`queue:${queueName}`)) || [];
  
  if (queue.length === 0) return null;
  
  const item = queue.shift();
  await sdk.storage.set(`queue:${queueName}`, queue);
  
  return item;
}
 
// Usage
await enqueue('emails', { to: '[email protected]', subject: 'Hello' });
const nextEmail = await dequeue('emails');

Rate Limiting

async function checkRateLimit(key, maxRequests, windowMs) {
  const now = Date.now();
  const windowKey = `ratelimit:${key}`;
  
  let record = await sdk.storage.get(windowKey);
  
  if (!record || now > record.windowEnd) {
    record = {
      count: 0,
      windowEnd: now + windowMs
    };
  }
  
  record.count++;
  await sdk.storage.set(windowKey, record);
  
  return {
    allowed: record.count <= maxRequests,
    remaining: Math.max(0, maxRequests - record.count),
    resetAt: record.windowEnd
  };
}
 
// Usage
const result = await checkRateLimit('user:123:api', 100, 60000);
 
if (!result.allowed) {
  return res.status(429).json({
    error: 'Rate limit exceeded',
    retryAfter: result.resetAt - Date.now()
  });
}

Example: Favorites Plugin

module.exports = async function(sdk) {
  
  async function getFavorites(userId) {
    return (await sdk.storage.get(`favorites:${userId}`)) || [];
  }
  
  async function addFavorite(userId, postId) {
    const favorites = await getFavorites(userId);
    
    if (!favorites.includes(postId)) {
      favorites.push(postId);
      await sdk.storage.set(`favorites:${userId}`, favorites);
    }
    
    return favorites;
  }
  
  async function removeFavorite(userId, postId) {
    let favorites = await getFavorites(userId);
    favorites = favorites.filter(id => id !== postId);
    await sdk.storage.set(`favorites:${userId}`, favorites);
    return favorites;
  }
  
  async function isFavorite(userId, postId) {
    const favorites = await getFavorites(userId);
    return favorites.includes(postId);
  }
  
  // REST API
  sdk.rest.register('GET', '/api/favorites', async (req, res) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    
    const favorites = await getFavorites(req.user.id);
    const posts = await Promise.all(
      favorites.map(id => sdk.content.getPost(id))
    );
    
    return res.json({ favorites: posts.filter(Boolean) });
  });
  
  sdk.rest.register('POST', '/api/favorites/:postId', async (req, res) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    
    const favorites = await addFavorite(req.user.id, req.params.postId);
    return res.json({ success: true, count: favorites.length });
  });
  
  sdk.rest.register('DELETE', '/api/favorites/:postId', async (req, res) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    
    const favorites = await removeFavorite(req.user.id, req.params.postId);
    return res.json({ success: true, count: favorites.length });
  });
  
  // Shortcode
  sdk.shortcodes.register('favorite-button', async (attrs, content, context) => {
    if (!context.user) {
      return '<a href="/login">Log in to save favorites</a>';
    }
    
    const postId = attrs.post || (context.post && context.post.id);
    if (!postId) return '';
    
    const isFav = await isFavorite(context.user.id, postId);
    
    return `
      <button 
        class="favorite-btn ${isFav ? 'is-favorite' : ''}"
        data-post-id="${postId}"
        onclick="toggleFavorite(this)"
      >
        ${isFav ? '★ Saved' : '☆ Save'}
      </button>
    `;
  });
  
  return {};
};

Storage Limits

  • Maximum key length: 256 characters
  • Maximum value size: 1MB per key
  • Total storage per plugin: 100MB

For larger data needs, use the Media API or external storage.