Admin Extensions
Add custom admin pages, settings panels, and dashboard widgets.
Admin Pages
Create custom pages in the admin area:
sdk.admin.registerPage('my-page', {
title: 'My Custom Page',
icon: '⚙️',
render: async (context) => {
return `
<div class="admin-page">
<h1>My Custom Page</h1>
<p>Custom content here</p>
</div>
`;
}
});Accessible at /admin/my-page.
Page with Data
sdk.admin.registerPage('statistics', {
title: 'Statistics',
icon: '📊',
render: async () => {
const posts = await sdk.content.countPosts();
const categories = await sdk.content.countCategories();
const views = await sdk.storage.get('total_views') || 0;
return `
<div class="statistics-page">
<h1>Statistics</h1>
<div class="stats-grid">
<div class="stat-card">
<h3>Total Posts</h3>
<span class="stat-value">${posts}</span>
</div>
<div class="stat-card">
<h3>Categories</h3>
<span class="stat-value">${categories}</span>
</div>
<div class="stat-card">
<h3>Total Views</h3>
<span class="stat-value">${views.toLocaleString()}</span>
</div>
</div>
</div>
`;
}
});Page with Actions
sdk.admin.registerPage('tools', {
title: 'Tools',
icon: '🔧',
render: async () => {
return `
<div class="tools-page">
<h1>Admin Tools</h1>
<div class="tool-card">
<h3>Clear Cache</h3>
<p>Remove all cached data</p>
<button onclick="runTool('clear-cache')">Clear Cache</button>
</div>
<div class="tool-card">
<h3>Regenerate Thumbnails</h3>
<p>Recreate all image thumbnails</p>
<button onclick="runTool('regenerate-thumbs')">Regenerate</button>
</div>
<div id="result"></div>
</div>
<script>
async function runTool(tool) {
const result = document.getElementById('result');
result.textContent = 'Running...';
try {
const res = await fetch('/api/admin/tools/' + tool, { method: 'POST' });
const data = await res.json();
result.textContent = data.message || 'Done!';
} catch (err) {
result.textContent = 'Error: ' + err.message;
}
}
</script>
`;
}
});
// Register the API endpoint
sdk.rest.register('POST', '/api/admin/tools/:tool', async (req, res) => {
const { tool } = req.params;
switch (tool) {
case 'clear-cache':
await sdk.storage.delete('cache');
return res.json({ success: true, message: 'Cache cleared' });
case 'regenerate-thumbs':
// Regeneration logic
return res.json({ success: true, message: 'Thumbnails regenerated' });
default:
return res.status(404).json({ error: 'Tool not found' });
}
}, { auth: true, roles: ['admin'] });Settings Panels
Add settings to the admin settings page:
sdk.admin.registerSettings('my-plugin-settings', {
title: 'My Plugin Settings',
icon: '⚙️',
fields: [
{
key: 'apiKey',
type: 'text',
label: 'API Key',
placeholder: 'Enter your API key',
description: 'Get your key from example.com'
},
{
key: 'enabled',
type: 'boolean',
label: 'Enable Feature',
default: true
},
{
key: 'limit',
type: 'number',
label: 'Item Limit',
min: 1,
max: 100,
default: 10
},
{
key: 'displayMode',
type: 'select',
label: 'Display Mode',
options: [
{ value: 'grid', label: 'Grid' },
{ value: 'list', label: 'List' },
{ value: 'carousel', label: 'Carousel' }
],
default: 'grid'
}
],
onSave: async (values) => {
// Validate or process before save
if (values.apiKey && !values.apiKey.startsWith('sk_')) {
throw new Error('Invalid API key format');
}
return values;
}
});Field Types
| Type | Description | Options |
|---|---|---|
text | Single line text | placeholder, maxLength, secret |
textarea | Multi-line text | rows, maxLength |
number | Numeric input | min, max, step |
boolean | Toggle switch | - |
select | Dropdown | options |
multiselect | Multi-select | options |
color | Color picker | - |
image | Image upload | - |
code | Code editor | language |
json | JSON editor | - |
Dashboard Widgets
Add widgets to the admin dashboard:
sdk.admin.registerDashboardWidget('quick-stats', {
title: 'Quick Stats',
size: 'medium', // 'small', 'medium', 'large', 'full'
render: async () => {
const posts = await sdk.content.countPosts();
const drafts = await sdk.content.countPosts({ status: 'draft' });
return `
<div class="quick-stats">
<div class="stat">
<span class="label">Published</span>
<span class="value">${posts}</span>
</div>
<div class="stat">
<span class="label">Drafts</span>
<span class="value">${drafts}</span>
</div>
</div>
`;
},
// Auto-refresh interval in seconds
refreshInterval: 60
});Widget Sizes
| Size | Description |
|---|---|
small | 1/4 width |
medium | 1/2 width |
large | 3/4 width |
full | Full width |
Admin Menu
Add items to the admin sidebar:
sdk.admin.addMenuItem({
id: 'my-plugin',
label: 'My Plugin',
icon: '🔌',
href: '/admin/my-plugin',
position: 50, // Lower = higher position
children: [
{ id: 'dashboard', label: 'Dashboard', href: '/admin/my-plugin' },
{ id: 'settings', label: 'Settings', href: '/admin/my-plugin/settings' },
{ id: 'reports', label: 'Reports', href: '/admin/my-plugin/reports' }
]
});Admin Notices
Display notices to admin users:
// Info notice
sdk.admin.addNotice({
type: 'info',
message: 'New update available!',
dismissible: true
});
// Warning
sdk.admin.addNotice({
type: 'warning',
message: 'Your license expires in 7 days',
dismissible: false
});
// Error
sdk.admin.addNotice({
type: 'error',
message: 'Connection failed. Check your API key.',
dismissible: true
});
// Success with auto-dismiss
sdk.admin.addNotice({
type: 'success',
message: 'Settings saved!',
dismissible: true,
timeout: 3000
});Post Editor Extensions
Add custom fields to the post editor:
sdk.admin.extendPostEditor({
section: 'sidebar', // 'main', 'sidebar', 'bottom'
render: (post) => {
const readTime = post?.customFields?.readTime || '';
const sponsored = post?.customFields?.sponsored || false;
return `
<div class="custom-fields-panel">
<h4>Custom Fields</h4>
<div class="field">
<label for="readTime">Reading Time (minutes)</label>
<input type="number" id="readTime" name="customFields.readTime" value="${readTime}">
</div>
<div class="field">
<label>
<input type="checkbox" name="customFields.sponsored" ${sponsored ? 'checked' : ''}>
Sponsored Content
</label>
</div>
</div>
`;
},
onSave: async (post, formData) => {
post.customFields = post.customFields || {};
post.customFields.readTime = parseInt(formData.get('customFields.readTime')) || null;
post.customFields.sponsored = formData.get('customFields.sponsored') === 'on';
return post;
}
});Complete Example
module.exports = async function(sdk) {
// Add menu item
sdk.admin.addMenuItem({
id: 'analytics',
label: 'Analytics',
icon: '📊',
href: '/admin/analytics',
position: 30,
children: [
{ id: 'overview', label: 'Overview', href: '/admin/analytics' },
{ id: 'posts', label: 'Posts', href: '/admin/analytics/posts' },
{ id: 'settings', label: 'Settings', href: '/admin/analytics/settings' }
]
});
// Overview page
sdk.admin.registerPage('analytics', {
title: 'Analytics Overview',
icon: '📊',
render: async () => {
const views = await sdk.storage.get('page_views') || {};
const totalViews = Object.values(views).reduce((a, b) => a + b, 0);
return `
<h1>Analytics Overview</h1>
<div class="stats-grid">
<div class="stat-card">
<h3>Total Views</h3>
<span>${totalViews.toLocaleString()}</span>
</div>
</div>
`;
}
});
// Posts analytics page
sdk.admin.registerPage('analytics/posts', {
title: 'Post Analytics',
render: async () => {
const posts = await sdk.content.getPosts({ limit: 20, sort: { views: -1 } });
let html = '<h1>Top Posts</h1><table class="admin-table">';
html += '<thead><tr><th>Title</th><th>Views</th></tr></thead><tbody>';
posts.items.forEach(post => {
html += `<tr><td>${post.title}</td><td>${post.views || 0}</td></tr>`;
});
html += '</tbody></table>';
return html;
}
});
// Settings
sdk.admin.registerSettings('analytics-settings', {
title: 'Analytics Settings',
fields: [
{
key: 'trackingEnabled',
type: 'boolean',
label: 'Enable Tracking',
default: true
},
{
key: 'excludeAdmins',
type: 'boolean',
label: 'Exclude Admin Users',
default: true
},
{
key: 'retentionDays',
type: 'number',
label: 'Data Retention (days)',
min: 7,
max: 365,
default: 90
}
]
});
// Dashboard widget
sdk.admin.registerDashboardWidget('analytics-summary', {
title: 'Site Analytics',
size: 'medium',
render: async () => {
const today = new Date().toISOString().split('T')[0];
const views = await sdk.storage.get(`views:${today}`) || 0;
return `
<div class="analytics-widget">
<p>Today's views: <strong>${views}</strong></p>
</div>
`;
},
refreshInterval: 60
});
return {};
};