Plugin System
OkiDoki supports a plugin system that allows you to create custom Handlebars helpers without modifying the core codebase. This enables you to extend your documentation with custom functionality while keeping your setup clean and maintainable.
Quick Start
-
Enable plugins in your
okidoki.yaml
:plugins: enabled: true directory: "plugins" # Optional, defaults to "plugins" load: [] # Optional, loads all .js files if empty
-
Create a plugins directory in your project root:
mkdir plugins
-
Create a plugin file (e.g.,
plugins/my-helpers.js
):For ES modules projects (recommended - add
"type": "module"
to your package.json):export default function(handlebarsInstance) { handlebarsInstance.registerHelper('uppercase', function(str) { return str.toUpperCase(); }); }
For CommonJS projects (use
.cjs
extension):// plugins/my-helpers.cjs module.exports = function(handlebarsInstance) { handlebarsInstance.registerHelper('uppercase', function(str) { return str.toUpperCase(); }); };
-
Use your helper in markdown:
# My Page Hello {{uppercase "world"}}!
Plugin Patterns
Pattern 1: Function Export (Recommended)
The most flexible pattern - export a function that receives the handlebars instance:
// plugins/my-helpers.js
export default function(handlebarsInstance) {
// Register as many helpers as you want
handlebarsInstance.registerHelper('myHelper', function(value) {
return `Processed: ${value}`;
});
handlebarsInstance.registerHelper('anotherHelper', function(a, b) {
return a + b;
});
console.log('✅ My custom helpers loaded');
}
Pattern 2: Object Export
Export an object with a helpers
property:
// plugins/utilities.js
export default {
helpers: {
capitalize: function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
truncate: function(str, length) {
return str.length > length ? str.substring(0, length) + '...' : str;
}
}
};
Configuration Options
Basic Configuration
# okidoki.yaml
plugins:
enabled: true # Enable/disable plugin system
directory: "plugins" # Directory containing plugin files
Advanced Configuration
plugins:
enabled: true
directory: "my-custom-plugins" # Custom plugins directory
load: # Load specific plugins only
- "my-helpers" # Loads my-helpers.js
- "date-utils" # Loads date-utils.js
# If 'load' is empty or omitted, all .js files in directory are loaded
Example Helpers
Here are some practical examples of custom helpers you might want to create:
Date Formatting
export default function(handlebarsInstance) {
handlebarsInstance.registerHelper('formatDate', function(date, format) {
const d = new Date(date);
switch(format) {
case 'short': return d.toLocaleDateString();
case 'long': return d.toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
});
case 'iso': return d.toISOString().split('T')[0];
default: return d.toLocaleDateString();
}
});
}
Usage in markdown:
Last updated: {{formatDate "2024-01-15" "long"}}
Conditional Logic
export default function(handlebarsInstance) {
handlebarsInstance.registerHelper('ifAny', function() {
const args = Array.prototype.slice.call(arguments);
const options = args.pop();
const hasValue = args.some(arg => !!arg);
return hasValue ? options.fn(this) : options.inverse(this);
});
}
Usage in markdown:
{{#ifAny author.name author.email}}
Author information available
{{else}}
No author information
{{/ifAny}}
HTML Generation
export default function(handlebarsInstance) {
handlebarsInstance.registerHelper('icon', function(iconName, options) {
const size = options.hash.size || '16';
const color = options.hash.color || 'currentColor';
const html = `<svg width="${size}" height="${size}" fill="${color}" viewBox="0 0 24 24">
<use href="#icon-${iconName}"></use>
</svg>`;
return new handlebarsInstance.SafeString(html);
});
}
Usage in markdown:
{{{icon "check" size="24" color="green"}}}
Text Processing
export default function(handlebarsInstance) {
handlebarsInstance.registerHelper('slugify', function(str) {
return str.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
});
handlebarsInstance.registerHelper('excerpt', function(text, length) {
length = length || 150;
if (text.length <= length) return text;
return text.substring(0, length).trim() + '...';
});
}
Block Helpers
You can also create block helpers that process content:
export default function(handlebarsInstance) {
handlebarsInstance.registerHelper('callout', function(type, options) {
const content = options.fn(this);
const alertType = type || 'info';
const html = `<div class="callout callout-${alertType}">
<div class="callout-content">${content}</div>
</div>`;
return new handlebarsInstance.SafeString(html);
});
}
Usage in markdown:
{{#callout "warning"}}
This is important information that users should pay attention to.
{{/callout}}
Advanced Features
Accessing Context Data
Your helpers can access the current template context:
export default function(handlebarsInstance) {
handlebarsInstance.registerHelper('siteInfo', function() {
// Access site configuration
const siteTitle = this.settings?.site?.title || 'Unknown';
const baseUrl = this.baseUrl || '/';
return `Site: ${siteTitle} (${baseUrl})`;
});
}
Using External Dependencies
If your plugin needs external libraries, make sure they’re installed in your project:
// First: npm install moment
import moment from 'moment';
export default function(handlebarsInstance) {
handlebarsInstance.registerHelper('momentDate', function(date, format) {
return moment(date).format(format || 'MMMM Do YYYY');
});
}
Best Practices
- Error Handling: Always handle edge cases and invalid inputs
- Security: Use
handlebarsInstance.Utils.escapeExpression()
for user content - Safe HTML: Use
new handlebarsInstance.SafeString()
for trusted HTML output - Naming: Use descriptive helper names that won’t conflict with built-ins
- Documentation: Comment your helpers for future maintenance
- Performance: Keep helpers lightweight for better build performance
Troubleshooting
Plugin Not Loading
- Check that
plugins.enabled: true
in yourokidoki.yaml
- Verify the plugins directory exists and contains
.js
files - Check the console output for plugin loading messages
- Ensure your plugin exports a function or helpers object
Helper Not Working
- Verify the helper is registered (check console logs)
- Test the helper with simple values first
- Check for JavaScript syntax errors in your plugin file
- Ensure you’re using the correct handlebars syntax in your templates
Common Issues
- Module type warnings: If you see Node.js warnings about module types:
- ES modules: Add
"type": "module"
to your project’spackage.json
and use.js
files - CommonJS: Use
.cjs
file extension for your plugins (e.g.,plugins/my-helpers.cjs
)
- ES modules: Add
- Path issues: Plugin directory is relative to your project root (where
okidoki.yaml
is) - Caching: Plugins are reloaded on each build, but you may need to restart dev servers
Built-in Helpers Reference
OkiDoki comes with several built-in helpers you can reference or extend:
Utility Helpers
{{eq a b}}
- Equality comparison{{isArray value}}
- Check if value is array{{isObject value}}
- Check if value is object{{joinUrl baseUrl path}}
- URL joining with slash handling
Component Helpers
{{alert "text" "type"}}
- Generate alert components (inline){{#alert "type"}}content{{/alert}}
- Generate alert components (block){{badge "text" "type"}}
- Generate badge components{{include "filename.html"}}
- Include HTML files from assets{{searchComponent variant="desktop"}}
- Generate search components
Media Helpers
{{youtube "videoId"}}
- Embed responsive YouTube video dimensions
Interactive Components
{{#tabs}}...{{/tabs}}
- Create tabbed content container{{#tab title="Tab Name"}}content{{/tab}}
- Individual tab within tabs container
Your custom helpers work alongside these built-in ones, giving you complete flexibility to create the documentation experience you need.