Plugin Development
Admin Extensions

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

TypeDescriptionOptions
textSingle line textplaceholder, maxLength, secret
textareaMulti-line textrows, maxLength
numberNumeric inputmin, max, step
booleanToggle switch-
selectDropdownoptions
multiselectMulti-selectoptions
colorColor picker-
imageImage upload-
codeCode editorlanguage
jsonJSON 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

SizeDescription
small1/4 width
medium1/2 width
large3/4 width
fullFull 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 {};
};