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
| Type | Description | Extra Options |
|---|---|---|
text | Single line text input | placeholder, maxLength |
textarea | Multi-line text | rows, maxLength |
number | Numeric input | min, max, step |
boolean | Checkbox toggle | - |
select | Dropdown select | options (array) |
multiselect | Multiple selection | options (array) |
image | Image picker | - |
color | Color picker | - |
category | Category selector | multiple |
page | Page selector | multiple |
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');