SDK Reference
Widgets API

Widgets API

Widgets are reusable UI components that can be placed in sidebars and widget areas.

Registering Widgets

sdk.widgets.registerWidget(id, config)

Register a new widget type.

sdk.widgets.registerWidget('about-box', {
  name: 'About Box',
  description: 'Display an about section',
  icon: '📝',
  
  settings: [
    { key: 'title', type: 'text', label: 'Title', default: 'About Us' },
    { key: 'content', type: 'textarea', label: 'Content', default: '' },
    { key: 'image', type: 'image', label: 'Image' }
  ],
  
  render: async (settings, context) => {
    return `
      <div class="widget-about">
        <h3>${settings.title}</h3>
        ${settings.image ? `<img src="${settings.image}" alt="${settings.title}">` : ''}
        <p>${settings.content}</p>
      </div>
    `;
  }
});

Widget Configuration

Settings Types

TypeDescriptionExtra Options
textSingle line text inputplaceholder, maxLength
textareaMulti-line textrows, maxLength
numberNumeric inputmin, max, step
booleanCheckbox toggle-
selectDropdown selectoptions (array)
multiselectMultiple selectionoptions (array)
imageImage picker-
colorColor picker-
categoryCategory selectormultiple
pagePage selectormultiple

Settings Examples

settings: [
  // Text input
  { 
    key: 'title', 
    type: 'text', 
    label: 'Title',
    placeholder: 'Enter title...',
    default: 'My Widget' 
  },
  
  // Number with range
  { 
    key: 'count', 
    type: 'number', 
    label: 'Number of items',
    min: 1,
    max: 20,
    default: 5 
  },
  
  // Boolean toggle
  { 
    key: 'showImages', 
    type: 'boolean', 
    label: 'Show images',
    default: true 
  },
  
  // Select dropdown
  { 
    key: 'layout', 
    type: 'select', 
    label: 'Layout',
    options: [
      { value: 'list', label: 'List View' },
      { value: 'grid', label: 'Grid View' },
      { value: 'compact', label: 'Compact' }
    ],
    default: 'list' 
  },
  
  // Image picker
  { 
    key: 'backgroundImage', 
    type: 'image', 
    label: 'Background Image' 
  },
  
  // Color picker
  { 
    key: 'accentColor', 
    type: 'color', 
    label: 'Accent Color',
    default: '#007bff' 
  },
  
  // Category selector
  { 
    key: 'category', 
    type: 'category', 
    label: 'Category',
    multiple: false 
  }
]

Render Function

The render function receives settings and context:

render: async (settings, context) => {
  // settings - the widget's configured settings
  // context - information about current page
  //   context.post - current post (on post pages)
  //   context.category - current category (on category pages)
  //   context.request - request object
  //   context.user - current user (if logged in)
  
  // Return HTML string
  return '<div>Widget HTML</div>';
}

Common Widget Examples

Social Links Widget

sdk.widgets.registerWidget('social-links', {
  name: 'Social Links',
  description: 'Display social media links',
  icon: '🔗',
  
  settings: [
    { key: 'title', type: 'text', label: 'Title', default: 'Follow Us' },
    { key: 'facebook', type: 'text', label: 'Facebook URL' },
    { key: 'twitter', type: 'text', label: 'Twitter URL' },
    { key: 'instagram', type: 'text', label: 'Instagram URL' },
    { key: 'youtube', type: 'text', label: 'YouTube URL' },
    { key: 'linkedin', type: 'text', label: 'LinkedIn URL' },
    { key: 'style', type: 'select', label: 'Style', options: [
      { value: 'icons', label: 'Icons Only' },
      { value: 'text', label: 'Text Only' },
      { value: 'both', label: 'Icons & Text' }
    ], default: 'icons' }
  ],
  
  render: async (settings) => {
    const networks = [
      { key: 'facebook', icon: '📘', name: 'Facebook' },
      { key: 'twitter', icon: '🐦', name: 'Twitter' },
      { key: 'instagram', icon: '📷', name: 'Instagram' },
      { key: 'youtube', icon: '📺', name: 'YouTube' },
      { key: 'linkedin', icon: '💼', name: 'LinkedIn' }
    ];
    
    let html = `
      <div class="widget-social">
        <h3>${settings.title}</h3>
        <ul class="social-links social-links--${settings.style}">
    `;
    
    networks.forEach(network => {
      if (settings[network.key]) {
        html += `
          <li>
            <a href="${settings[network.key]}" target="_blank" rel="noopener">
              ${settings.style !== 'text' ? `<span class="social-icon">${network.icon}</span>` : ''}
              ${settings.style !== 'icons' ? `<span class="social-name">${network.name}</span>` : ''}
            </a>
          </li>
        `;
      }
    });
    
    html += '</ul></div>';
    return html;
  }
});

Newsletter Widget

sdk.widgets.registerWidget('newsletter', {
  name: 'Newsletter Signup',
  description: 'Email subscription form',
  icon: '📧',
  
  settings: [
    { key: 'title', type: 'text', label: 'Title', default: 'Subscribe' },
    { key: 'description', type: 'textarea', label: 'Description', default: 'Get updates in your inbox' },
    { key: 'buttonText', type: 'text', label: 'Button Text', default: 'Subscribe' },
    { key: 'buttonColor', type: 'color', label: 'Button Color', default: '#007bff' },
    { key: 'successMessage', type: 'text', label: 'Success Message', default: 'Thanks for subscribing!' }
  ],
  
  render: async (settings) => {
    return `
      <div class="widget-newsletter">
        <h3>${settings.title}</h3>
        <p>${settings.description}</p>
        <form class="newsletter-form" action="/api/newsletter/subscribe" method="POST">
          <input type="email" name="email" placeholder="Your email" required>
          <button type="submit" style="background-color: ${settings.buttonColor}">
            ${settings.buttonText}
          </button>
        </form>
      </div>
      <style>
        .newsletter-form {
          display: flex;
          gap: 10px;
        }
        .newsletter-form input {
          flex: 1;
          padding: 10px;
          border: 1px solid #ddd;
          border-radius: 4px;
        }
        .newsletter-form button {
          color: white;
          border: none;
          padding: 10px 20px;
          border-radius: 4px;
          cursor: pointer;
        }
      </style>
    `;
  }
});

Recent Posts Widget

sdk.widgets.registerWidget('recent-posts', {
  name: 'Recent Posts',
  description: 'Display latest posts',
  icon: '📰',
  
  settings: [
    { key: 'title', type: 'text', label: 'Title', default: 'Recent Posts' },
    { key: 'count', type: 'number', label: 'Number of posts', min: 1, max: 20, default: 5 },
    { key: 'showImage', type: 'boolean', label: 'Show thumbnails', default: true },
    { key: 'showDate', type: 'boolean', label: 'Show date', default: true },
    { key: 'showExcerpt', type: 'boolean', label: 'Show excerpt', default: false },
    { key: 'category', type: 'category', label: 'Filter by category', multiple: false }
  ],
  
  render: async (settings, context) => {
    const filter = {};
    if (settings.category) {
      filter.category = settings.category;
    }
    
    const posts = await sdk.content.getPosts({
      filter,
      limit: settings.count,
      sort: { createdAt: -1 }
    });
    
    let html = `
      <div class="widget-recent-posts">
        <h3>${settings.title}</h3>
        <ul class="post-list">
    `;
    
    posts.items.forEach(post => {
      html += '<li class="post-item">';
      
      if (settings.showImage && post.featuredImage) {
        html += `<img src="${post.featuredImage}" alt="${post.title}" class="post-thumb">`;
      }
      
      html += '<div class="post-info">';
      html += `<a href="${post.url}" class="post-title">${post.title}</a>`;
      
      if (settings.showDate) {
        html += `<time class="post-date">${sdk.utils.formatDate(post.createdAt)}</time>`;
      }
      
      if (settings.showExcerpt && post.excerpt) {
        html += `<p class="post-excerpt">${post.excerpt.substring(0, 80)}...</p>`;
      }
      
      html += '</div></li>';
    });
    
    html += '</ul></div>';
    return html;
  }
});

Popular Posts Widget

sdk.widgets.registerWidget('popular-posts', {
  name: 'Popular Posts',
  description: 'Display most viewed posts',
  icon: '🔥',
  
  settings: [
    { key: 'title', type: 'text', label: 'Title', default: 'Popular Posts' },
    { key: 'count', type: 'number', label: 'Number of posts', min: 1, max: 20, default: 5 },
    { key: 'timeframe', type: 'select', label: 'Timeframe', options: [
      { value: 'all', label: 'All Time' },
      { value: 'month', label: 'This Month' },
      { value: 'week', label: 'This Week' }
    ], default: 'all' },
    { key: 'showViews', type: 'boolean', label: 'Show view count', default: true }
  ],
  
  render: async (settings) => {
    const posts = await sdk.content.getPosts({
      limit: settings.count,
      sort: { views: -1 }
    });
    
    let html = `
      <div class="widget-popular">
        <h3>${settings.title}</h3>
        <ol class="popular-list">
    `;
    
    posts.items.forEach((post, index) => {
      html += `
        <li class="popular-item">
          <span class="rank">${index + 1}</span>
          <div class="popular-content">
            <a href="${post.url}">${post.title}</a>
            ${settings.showViews ? `<span class="views">${post.views || 0} views</span>` : ''}
          </div>
        </li>
      `;
    });
    
    html += '</ol></div>';
    return html;
  }
});

Rendering Widgets in Templates

In your theme templates, render widget areas:

<aside class="sidebar">
  {{{ sdk.ui.widgetArea('sidebar') }}}
</aside>
<footer>
  <div class="footer-widgets">
    {{{ sdk.ui.widgetArea('footer-1') }}}
    {{{ sdk.ui.widgetArea('footer-2') }}}
    {{{ sdk.ui.widgetArea('footer-3') }}}
  </div>
</footer>

Unregistering Widgets

sdk.widgets.unregisterWidget('about-box');