Widgets
Create reusable UI blocks for sidebars and widget areas.
Registering Widgets
sdk.widgets.registerWidget('my-widget', {
name: 'My Widget',
description: 'Description of what this widget does',
icon: '📦',
settings: [
{ key: 'title', type: 'text', label: 'Title', default: 'My Widget' },
{ key: 'count', type: 'number', label: 'Number of items', default: 5 }
],
render: async (settings, context) => {
return `
<div class="widget-my-widget">
<h3>${settings.title}</h3>
<p>Showing ${settings.count} items</p>
</div>
`;
}
});Widget Configuration
Basic Properties
| Property | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name |
description | string | No | Brief description |
icon | string | No | Emoji or icon class |
settings | array | No | Configurable options |
render | function | Yes | Returns HTML |
Settings Types
settings: [
// Text input
{
key: 'title',
type: 'text',
label: 'Widget Title',
placeholder: 'Enter title...',
default: 'My Widget'
},
// Number with range
{
key: 'count',
type: 'number',
label: 'Number of items',
min: 1,
max: 20,
default: 5
},
// Toggle
{
key: 'showImages',
type: 'boolean',
label: 'Show thumbnails',
default: true
},
// Dropdown
{
key: 'layout',
type: 'select',
label: 'Layout',
options: [
{ value: 'list', label: 'List' },
{ value: 'grid', label: 'Grid' }
],
default: 'list'
},
// Multi-select
{
key: 'categories',
type: 'multiselect',
label: 'Categories',
options: [
{ value: 'news', label: 'News' },
{ value: 'tech', label: 'Technology' },
{ value: 'lifestyle', label: 'Lifestyle' }
]
},
// Image upload
{
key: 'backgroundImage',
type: 'image',
label: 'Background Image'
},
// Color picker
{
key: 'accentColor',
type: 'color',
label: 'Accent Color',
default: '#007bff'
},
// Multi-line text
{
key: 'customHtml',
type: 'textarea',
label: 'Custom HTML',
rows: 5
},
// Category selector
{
key: 'category',
type: 'category',
label: 'Select Category'
}
]Render Function
The render function receives settings and context:
render: async (settings, context) => {
// settings - configured values
// context.post - current post (on post pages)
// context.category - current category (on category pages)
// context.user - logged in user
// context.request - request object
return '<div>Widget HTML</div>';
}Widget Examples
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: 'showThumbnail', type: 'boolean', label: 'Show thumbnails', default: true },
{ key: 'showDate', type: 'boolean', label: 'Show date', default: true }
],
render: async (settings) => {
const posts = await sdk.content.getLatestPosts(settings.count);
let html = `
<div class="widget-recent-posts">
<h3 class="widget-title">${settings.title}</h3>
<ul class="recent-posts-list">
`;
posts.forEach(post => {
html += `
<li class="recent-post-item">
${settings.showThumbnail && post.featuredImage
? `<img src="${post.featuredImage}" alt="${post.title}" class="recent-post-thumb">`
: ''
}
<div class="recent-post-info">
<a href="${post.url}" class="recent-post-title">${post.title}</a>
${settings.showDate
? `<time class="recent-post-date">${sdk.utils.formatDate(post.createdAt)}</time>`
: ''
}
</div>
</li>
`;
});
html += '</ul></div>';
return html;
}
});Categories Widget
sdk.widgets.registerWidget('categories', {
name: 'Categories',
description: 'List all categories',
icon: '📁',
settings: [
{ key: 'title', type: 'text', label: 'Title', default: 'Categories' },
{ key: 'showCount', type: 'boolean', label: 'Show post count', default: true },
{ key: 'orderBy', type: 'select', label: 'Order by', options: [
{ value: 'name', label: 'Name' },
{ value: 'count', label: 'Post count' }
], default: 'name' }
],
render: async (settings) => {
const categories = await sdk.content.getCategories();
if (settings.orderBy === 'count') {
categories.sort((a, b) => b.postCount - a.postCount);
} else {
categories.sort((a, b) => a.name.localeCompare(b.name));
}
let html = `
<div class="widget-categories">
<h3 class="widget-title">${settings.title}</h3>
<ul class="categories-list">
`;
categories.forEach(cat => {
html += `
<li>
<a href="${cat.url}">${cat.name}</a>
${settings.showCount ? `<span class="count">(${cat.postCount})</span>` : ''}
</li>
`;
});
html += '</ul></div>';
return html;
}
});About Widget
sdk.widgets.registerWidget('about', {
name: 'About',
description: 'Display about section',
icon: '👤',
settings: [
{ key: 'title', type: 'text', label: 'Title', default: 'About' },
{ key: 'image', type: 'image', label: 'Image' },
{ key: 'content', type: 'textarea', label: 'Content', rows: 4 },
{ key: 'buttonText', type: 'text', label: 'Button Text' },
{ key: 'buttonUrl', type: 'text', label: 'Button URL' }
],
render: async (settings) => {
return `
<div class="widget-about">
<h3 class="widget-title">${settings.title}</h3>
${settings.image ? `<img src="${settings.image}" alt="${settings.title}" class="about-image">` : ''}
<p class="about-content">${settings.content || ''}</p>
${settings.buttonText && settings.buttonUrl
? `<a href="${settings.buttonUrl}" class="about-button">${settings.buttonText}</a>`
: ''
}
</div>
`;
}
});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 class="widget-title">${settings.title}</h3>
<ul class="social-links style-${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="icon">${network.icon}</span>` : ''}
${settings.style !== 'icons' ? `<span class="name">${network.name}</span>` : ''}
</a>
</li>
`;
}
});
html += '</ul></div>';
return html;
}
});Newsletter Widget
sdk.widgets.registerWidget('newsletter', {
name: 'Newsletter',
description: 'Email subscription form',
icon: '📧',
settings: [
{ key: 'title', type: 'text', label: 'Title', default: 'Newsletter' },
{ key: 'description', type: 'textarea', label: 'Description', default: 'Subscribe for updates' },
{ key: 'buttonText', type: 'text', label: 'Button Text', default: 'Subscribe' },
{ key: 'buttonColor', type: 'color', label: 'Button Color', default: '#007bff' }
],
render: async (settings) => {
return `
<div class="widget-newsletter">
<h3 class="widget-title">${settings.title}</h3>
<p class="newsletter-desc">${settings.description}</p>
<form action="/api/newsletter/subscribe" method="POST" class="newsletter-form">
<input type="email" name="email" placeholder="Your email" required>
<button type="submit" style="background-color: ${settings.buttonColor}">
${settings.buttonText}
</button>
</form>
</div>
`;
}
});Custom HTML Widget
sdk.widgets.registerWidget('custom-html', {
name: 'Custom HTML',
description: 'Add custom HTML content',
icon: '🔧',
settings: [
{ key: 'title', type: 'text', label: 'Title (optional)' },
{ key: 'content', type: 'textarea', label: 'HTML Content', rows: 10 }
],
render: async (settings) => {
return `
<div class="widget-custom-html">
${settings.title ? `<h3 class="widget-title">${settings.title}</h3>` : ''}
<div class="custom-content">${settings.content || ''}</div>
</div>
`;
}
});Using Widgets in Themes
Render widget areas in your templates:
<aside class="sidebar">
{{{ sdk.ui.widgetArea('sidebar') }}}
</aside>
<footer>
<div class="footer-widgets">
<div class="footer-col">{{{ sdk.ui.widgetArea('footer-1') }}}</div>
<div class="footer-col">{{{ sdk.ui.widgetArea('footer-2') }}}</div>
<div class="footer-col">{{{ sdk.ui.widgetArea('footer-3') }}}</div>
</div>
</footer>Unregistering Widgets
sdk.widgets.unregisterWidget('my-widget');