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 conflictPattern 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.