Admin API
Extend the admin dashboard with custom pages, settings panels, and UI elements.
Admin Pages
sdk.admin.registerPage(slug, config)
Add a custom page to the admin dashboard.
sdk.admin.registerPage('analytics', {
title: 'Analytics Dashboard',
icon: '📊',
render: async (context) => {
// context.request - the request object
// context.user - current admin user
return `
<div class="analytics-page">
<h1>Analytics Dashboard</h1>
<div class="stats-grid">
<div class="stat-card">
<h3>Total Views</h3>
<span class="stat-value">12,345</span>
</div>
<div class="stat-card">
<h3>Posts</h3>
<span class="stat-value">89</span>
</div>
</div>
</div>
`;
}
});The page will be accessible at /admin/analytics.
Page with Actions
sdk.admin.registerPage('bulk-actions', {
title: 'Bulk Actions',
icon: '⚡',
render: async () => {
return `
<div class="bulk-actions">
<h1>Bulk Actions</h1>
<div class="action-card">
<h3>Regenerate Thumbnails</h3>
<p>Regenerate all image thumbnails</p>
<button onclick="runAction('regenerate-thumbnails')">Run</button>
</div>
<div class="action-card">
<h3>Clear Cache</h3>
<p>Clear all cached content</p>
<button onclick="runAction('clear-cache')">Run</button>
</div>
<div id="result"></div>
</div>
<script>
async function runAction(action) {
const result = document.getElementById('result');
result.textContent = 'Running...';
const response = await fetch('/api/admin/actions/' + action, {
method: 'POST'
});
const data = await response.json();
result.textContent = data.message;
}
</script>
`;
}
});Settings Panels
sdk.admin.registerSettings(id, config)
Add a settings panel in the admin settings page.
sdk.admin.registerSettings('seo-settings', {
title: 'SEO Settings',
icon: '🔍',
fields: [
{
key: 'metaTitle',
type: 'text',
label: 'Default Meta Title',
placeholder: '{{ siteName }} - {{ pageTitle }}'
},
{
key: 'metaDescription',
type: 'textarea',
label: 'Default Meta Description',
maxLength: 160
},
{
key: 'enableOgTags',
type: 'boolean',
label: 'Enable Open Graph Tags',
default: true
},
{
key: 'twitterHandle',
type: 'text',
label: 'Twitter Handle',
placeholder: '@yoursite'
},
{
key: 'robotsTxt',
type: 'textarea',
label: 'Custom robots.txt',
rows: 10
}
],
// Called when settings are saved
onSave: async (values) => {
// Validate or transform values
return values;
}
});Field Types
| Type | Description | Options |
|---|---|---|
text | Single line input | placeholder, maxLength |
textarea | Multi-line text | rows, maxLength |
number | Numeric input | min, max, step |
boolean | Toggle switch | - |
select | Dropdown | options |
multiselect | Multiple select | options |
color | Color picker | - |
image | Image upload | - |
code | Code editor | language |
json | JSON editor | - |
Dashboard Widgets
sdk.admin.registerDashboardWidget(id, config)
Add a widget to the admin dashboard home.
sdk.admin.registerDashboardWidget('quick-stats', {
title: 'Quick Stats',
size: 'medium', // 'small', 'medium', 'large', 'full'
render: async () => {
const posts = await sdk.content.countPosts();
const categories = await sdk.content.countCategories();
return `
<div class="quick-stats">
<div class="stat">
<span class="stat-label">Posts</span>
<span class="stat-value">${posts}</span>
</div>
<div class="stat">
<span class="stat-label">Categories</span>
<span class="stat-value">${categories}</span>
</div>
</div>
`;
},
// Optional: refresh interval in seconds
refreshInterval: 60
});Activity Feed Widget
sdk.admin.registerDashboardWidget('activity-feed', {
title: 'Recent Activity',
size: 'large',
render: async () => {
const activities = await sdk.storage.get('recent_activities') || [];
let html = '<ul class="activity-feed">';
activities.slice(0, 10).forEach(activity => {
html += `
<li class="activity-item">
<span class="activity-icon">${activity.icon}</span>
<span class="activity-text">${activity.text}</span>
<time class="activity-time">${sdk.utils.timeAgo(activity.timestamp)}</time>
</li>
`;
});
html += '</ul>';
return html;
}
});Admin Menu
sdk.admin.addMenuItem(config)
Add an item to the admin sidebar menu.
sdk.admin.addMenuItem({
id: 'my-plugin-menu',
label: 'My Plugin',
icon: '🔧',
href: '/admin/my-plugin',
position: 50, // Lower = higher in menu
// Optional: sub-menu items
children: [
{ id: 'settings', label: 'Settings', href: '/admin/my-plugin/settings' },
{ id: 'reports', label: 'Reports', href: '/admin/my-plugin/reports' }
]
});Admin Notices
sdk.admin.addNotice(config)
Display a notice in the admin area.
// Info notice
sdk.admin.addNotice({
type: 'info',
message: 'New features available! Check the changelog.',
dismissible: true
});
// Warning notice
sdk.admin.addNotice({
type: 'warning',
message: 'Your license expires in 7 days.',
dismissible: false
});
// Error notice
sdk.admin.addNotice({
type: 'error',
message: 'Failed to connect to external service.',
dismissible: true
});
// Success notice
sdk.admin.addNotice({
type: 'success',
message: 'All changes saved successfully!',
dismissible: true,
timeout: 5000 // Auto-dismiss after 5 seconds
});Content Type Customization
sdk.admin.extendPostEditor(config)
Add fields to the post editor.
sdk.admin.extendPostEditor({
section: 'sidebar', // 'main', 'sidebar', 'bottom'
render: (post) => {
return `
<div class="custom-fields-panel">
<h4>Custom Fields</h4>
<div class="field">
<label>Reading Time (minutes)</label>
<input type="number" name="customFields.readTime" value="${post?.customFields?.readTime || ''}">
</div>
<div class="field">
<label>Sponsored Content</label>
<input type="checkbox" name="customFields.sponsored" ${post?.customFields?.sponsored ? 'checked' : ''}>
</div>
</div>
`;
},
// Validate/transform before save
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;
}
});Admin Actions
sdk.admin.registerAction(name, handler)
Register a custom admin action.
sdk.admin.registerAction('export-posts', async (params, context) => {
const posts = await sdk.content.getPosts({ limit: 1000 });
// Generate export
const csv = generateCSV(posts.items);
return {
success: true,
download: {
filename: 'posts-export.csv',
content: csv,
contentType: 'text/csv'
}
};
});
// Can be triggered from admin UI or via API
// POST /api/admin/actions/export-postsExample: Complete Plugin Admin
module.exports = async function(sdk) {
// Add menu item
sdk.admin.addMenuItem({
id: 'newsletter',
label: 'Newsletter',
icon: '📧',
href: '/admin/newsletter',
children: [
{ id: 'subscribers', label: 'Subscribers', href: '/admin/newsletter/subscribers' },
{ id: 'campaigns', label: 'Campaigns', href: '/admin/newsletter/campaigns' },
{ id: 'settings', label: 'Settings', href: '/admin/newsletter/settings' }
]
});
// Main page
sdk.admin.registerPage('newsletter', {
title: 'Newsletter Dashboard',
icon: '📧',
render: async () => {
const subscribers = await sdk.storage.get('newsletter_subscribers') || [];
const campaigns = await sdk.storage.get('newsletter_campaigns') || [];
return `
<div class="newsletter-dashboard">
<h1>Newsletter</h1>
<div class="stats-row">
<div class="stat-card">
<h3>Subscribers</h3>
<span>${subscribers.length}</span>
</div>
<div class="stat-card">
<h3>Campaigns</h3>
<span>${campaigns.length}</span>
</div>
</div>
</div>
`;
}
});
// Subscribers page
sdk.admin.registerPage('newsletter/subscribers', {
title: 'Subscribers',
render: async () => {
const subscribers = await sdk.storage.get('newsletter_subscribers') || [];
let html = `
<h1>Subscribers</h1>
<table class="admin-table">
<thead>
<tr>
<th>Email</th>
<th>Subscribed</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
`;
subscribers.forEach(sub => {
html += `
<tr>
<td>${sub.email}</td>
<td>${sdk.utils.formatDate(sub.subscribedAt)}</td>
<td>
<button onclick="removeSubscriber('${sub.email}')">Remove</button>
</td>
</tr>
`;
});
html += '</tbody></table>';
return html;
}
});
// Settings
sdk.admin.registerSettings('newsletter-settings', {
title: 'Newsletter Settings',
fields: [
{ key: 'fromName', type: 'text', label: 'From Name' },
{ key: 'fromEmail', type: 'text', label: 'From Email' },
{ key: 'confirmationSubject', type: 'text', label: 'Confirmation Email Subject' },
{ key: 'confirmationBody', type: 'textarea', label: 'Confirmation Email Body' }
]
});
// Dashboard widget
sdk.admin.registerDashboardWidget('newsletter-stats', {
title: 'Newsletter',
size: 'small',
render: async () => {
const subscribers = await sdk.storage.get('newsletter_subscribers') || [];
return `<p><strong>${subscribers.length}</strong> subscribers</p>`;
}
});
return {};
};