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.
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"}}!
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');
}
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;
}
}
};
# okidoki.yaml
plugins:
enabled: true # Enable/disable plugin system
directory: "plugins" # Directory containing plugin files
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
Here are some practical examples of custom helpers you might want to create:
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: {{raw-helper}}{{formatDate "2024-01-15" "long"}}{{/raw-helper}}
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}}
export default function(handlebarsInstance) {
handlebarsInstance.registerHelper('youtube', function(videoId, options) {
const width = options.hash.width || '560';
const height = options.hash.height || '315';
const html = `<iframe width="${width}" height="${height}"
src="https://www.youtube.com/embed/${videoId}"
frameborder="0" allowfullscreen></iframe>`;
return new handlebarsInstance.SafeString(html);
});
}
Usage in markdown:
{{{youtube "dQw4w9WgXcQ" width="800" height="450"}}}
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() + '...';
});
}
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}}
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})`;
});
}
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');
});
}
handlebarsInstance.Utils.escapeExpression()
for user contentnew handlebarsInstance.SafeString()
for trusted HTML outputplugins.enabled: true
in your okidoki.yaml
.js
files"type": "module"
to your project’s package.json
and use .js
files.cjs
file extension for your plugins (e.g., plugins/my-helpers.cjs
)okidoki.yaml
is)OkiDoki comes with several built-in helpers you can reference or extend:
{{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{{alert "text" "type"}}
- Generate alert components{{badge "text" "type"}}
- Generate badge components{{include "filename.html"}}
- Include HTML files from assets{{searchComponent variant="desktop"}}
- Generate search componentsYour custom helpers work alongside these built-in ones, giving you complete flexibility to create the documentation experience you need.