.claude/skills/klytos-plugin-development/SKILL.md
Complete guide for developing Klytos CMS plugins including structure, entry points, MCP tools, admin pages, hooks, routes, and best practices. Use when creating, modifying, extending Klytos functionality, adding MCP tools, admin pages, hooks, filters, or debugging plugins.
npx skillsauth add joseconti/klytos Klytos Plugin DevelopmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Klytos is an AI-First CMS controlled via MCP (Model Context Protocol). Plugins extend core functionality through a WordPress-inspired hook system (actions + filters) WITHOUT modifying core files.
Key principle: Every feature should be exposed as an MCP tool FIRST, admin UI second.
A Klytos plugin is identified by a directory plugins/{plugin-id}/ containing a PHP file named {plugin-id}.php with a Plugin Name: header in its docblock. This contract can NEVER change.
<?php
// plugins/hello-world/hello-world.php
/**
* Plugin Name: Hello World
*/
That's it. Klytos discovers it, lists it in admin, and allows activation.
plugins/{plugin-id}/
├── {plugin-id}.php ← REQUIRED: identification + entry point (PHP header)
├── klytos-plugin.json ← OPTIONAL: extended metadata
├── install.php ← Optional: runs on first activation
├── deactivate.php ← Optional: runs on deactivation
├── uninstall.php ← Optional: removes plugin data permanently
├── admin/ ← Optional: admin page views
├── assets/ ← Optional: CSS, JS, images (publicly accessible)
├── lang/ ← Optional: translation files
├── src/ ← Optional: PHP source classes
└── migrations/ ← Optional: data migrations
The main PHP file MUST contain a docblock with at least Plugin Name:. All other fields are optional.
<?php
/**
* Plugin Name: My Plugin
* Plugin URI: https://example.com/my-plugin
* Description: What this plugin does.
* Version: 1.0.0
* Author: Author Name
* Requires Klytos: 0.15.0
* Requires PHP: 8.1
* License: GPL-3.0-or-later
* Text Domain: my-plugin
* Premium: false
* Logs: true
*/
For complex structured data that doesn't fit in a PHP header comment. The id field is NOT needed — it's derived from the directory name.
{
"permissions": ["pages.edit"],
"admin_pages": [
{
"id": "settings",
"title": "My Plugin Settings",
"icon": "P",
"position": 86
}
],
"mcp_tools": ["my_plugin_do_something"]
}
The {plugin-id}.php file is both the identification AND the entry point. All hooks are registered here.
<?php
/**
* Plugin Name: My Plugin
* Version: 1.0.0
*/
// 1. Register admin sidebar menu item
klytos_add_filter('admin.sidebar_items', function (array $items): array {
$items[] = [
'id' => 'my-plugin',
'title' => 'My Plugin',
'url' => klytos_admin_url('plugins/my-plugin/admin/settings.php'),
'icon' => 'P',
'position' => 86,
];
return $items;
});
// 2. Register MCP tools
klytos_add_filter('mcp.tools_list', function (array $tools): array {
$tools[] = [
'name' => 'my_plugin_do_something',
'description' => 'Does something useful.',
'inputSchema' => [
'type' => 'object',
'properties' => [
'param1' => ['type' => 'string', 'description' => 'First parameter.'],
],
],
];
return $tools;
});
// 3. Handle MCP tool calls
klytos_add_filter('mcp.handle_tool', function (mixed $result, string $toolName, array $params): mixed {
if ($toolName !== 'my_plugin_do_something') {
return $result;
}
return [
'content' => [['type' => 'text', 'text' => 'Done!']],
'isError' => false,
];
}, 10);
// 4. Register translations
klytos_register_translations('my-plugin', klytos_plugin_path('my-plugin', 'lang'));
// 5. Hook into page lifecycle
klytos_add_action('page.after_save', function (array $page, string $action): void {
klytos_log('info', 'My plugin: page saved', ['slug' => $page['slug']]);
});
Use klytos_register_admin_page() to add sidebar items:
klytos_register_admin_page( 'my-plugin', [
'id' => 'settings',
'title' => 'My Plugin Settings',
'icon' => 'P',
'position' => 86,
'capability' => 'plugins.manage',
] );
The PHP file at plugins/my-plugin/admin/settings.php renders inside the admin layout automatically. It receives $app, $auth, $pluginId, $pageName, $manifest.
klytos_storage() → StorageInterface (read/write encrypted data)
klytos_app() → App instance
klytos_auth() → Auth instance
klytos_config($key, $default) → Read config value (dot notation)
klytos_version() → Current Klytos version
klytos_is_admin() → True if in admin context
klytos_is_mcp() → True if in MCP context
klytos_current_user() → Current user array or null
klytos_has_permission($perm) → Permission check
klytos_log($level, $msg, $ctx) → Write to log file
page.before_save, page.after_save, page.before_delete, page.after_deletepage.content (filter) — modify page HTML contentbuild.before, build.after, build.page.before, build.page.after (actions)build.head_html, build.body_end_html (filters) — inject CSS/JSadmin.sidebar_items (filter) — add menu itemsadmin.head, admin.footer (actions) — inject into admin HTMLadmin.{page}.before, admin.{page}.after (actions) — per-page hooksblock.before_save, block.after_save, block.rendered_htmlpage_template.before_save, page_template.after_saveplugin.activated, plugin.deactivated, plugin.loadedPlace JSON translation files in plugins/{plugin-id}/lang/:
plugins/my-plugin/lang/
├── en.json
└── es.json
Register them:
klytos_register_translations('my-plugin', klytos_plugin_path('my-plugin', 'lang'));
Translation file format (flat recommended):
{
"my_plugin.settings_title": "My Plugin Settings",
"my_plugin.save": "Save Changes"
}
Use translations:
echo __('my_plugin.settings_title'); // "My Plugin Settings"
echo __('my_plugin.greeting', ['name' => 'Jose']);
Plugin static assets live in plugins/{plugin-id}/assets/ and are publicly accessible via the web.
// CORRECT — full path from plugin root:
klytos_plugin_url('my-plugin', 'assets/css/style.css')
// → /admin/plugins/my-plugin/assets/css/style.css
// Loading in admin pages:
$cssUrl = klytos_plugin_url('my-plugin', 'assets/css/style.css');
?>
<link rel="stylesheet" href="<?php echo klytos_esc_url($cssUrl); ?>" nonce="<?php echo klytos_esc_attr($cspNonce); ?>">
<script src="<?php echo klytos_esc_url($jsUrl); ?>" nonce="<?php echo klytos_esc_attr($cspNonce); ?>"></script>
All <script> and <link> tags MUST include a nonce attribute:
<!-- CORRECT: Will load successfully -->
<script src="..." nonce="<?php echo klytos_esc_attr($cspNonce); ?>"></script>
<!-- WRONG: Will be blocked by CSP -->
<script src="..."></script>
Plugins opt into logging by declaring Logs: true in the PHP header. When declared, an "Enable Logs" action appears in the plugin management page.
/**
* Plugin Name: My Plugin
* Logs: true
*/
Writing logs:
klytos_log('info', 'Order processed', ['order_id' => 42], 'my-plugin');
klytos_log_error('Payment failed', ['gateway' => 'stripe'], 'my-plugin');
klytos_log_warning('Rate limit approaching', [], 'my-plugin');
klytos_log_info('Cache refreshed', [], 'my-plugin');
// Read/write plugin-specific data
$storage = klytos_storage();
// Write plugin data to its own collection
$storage->write('my-plugin-data', 'settings', [
'api_key' => 'xxx',
'enabled' => true,
]);
// Read it back
$data = $storage->read('my-plugin-data', 'settings');
klytos_storage()htmlspecialchars() or Helpers::sanitizeHtml()auth.capabilities filterklytos_register_option()When your plugin stores options, classify them by sensitivity so Klytos encrypts them appropriately based on the site's encryption level:
// In your plugin's main file ({plugin-id}.php):
// API keys and secrets — ALWAYS encrypted
klytos_register_option('my-plugin.api_key', true);
klytos_register_option('my-plugin.webhook_secret', true);
// Personal/GDPR data — encrypted from 'medium' level
klytos_register_option('my-plugin.user_email', 'user_data');
// Non-sensitive settings — only encrypted at 'professional' level
klytos_register_option('my-plugin.theme_color'); // false is the default
| Sensitivity | Encrypted at | Use for |
|---|---|---|
| true | Always (all levels) | API keys, tokens, passwords, secrets |
| 'user_data' | Medium + Professional | Emails, IPs, personal data (GDPR) |
| false (default) | Professional only | Colors, toggles, non-sensitive config |
See the klytos-options-storage skill for full documentation.
In semver, pre-release versions are lower than the release:
0.14.0-beta.4 < 0.14.0Always set Requires Klytos to the OLDEST version you actually need, not the current one.
/**
* Plugin Name: My Plugin
* Requires Klytos: 0.13.0 ← Use the last stable, not the current beta
*/
The .htaccess in the plugins/ directory blocks access to executable files. Plugin PHP files are only executed server-side by the PluginLoader (require_once), never accessed directly via URL.
The <script> tag is missing the CSP nonce attribute. Always include nonce="<?php echo klytos_esc_attr($cspNonce); ?>"
/installer/ (configurable)/installer/core//installer/plugins//installer/admin//installer/data//installer/config/For advanced topics like premium licensing, webhooks, version requirements, and more, see the references/advanced-features.md file.
development
Guide for working with dates, times, and timezones in Klytos CMS. Use when formatting dates, converting timezones, scheduling actions with timestamps, displaying local time, working with UTC storage, building timezone selectors, or using any klytos_date/klytos_gmdate/klytos_timezone functions.
tools
Guide for developing and extending the Klytos web terminal. Use when modifying terminal commands, adding terminal commands from plugins, fixing terminal bugs, extending the pseudo-terminal, working with TerminalExecutor class, registering custom permissions, adding custom category labels, or managing terminal UI and security.
development
--- name: klytos-site-builder description: Guide for building a complete website from scratch with Klytos CMS. Use when creating a new site, configuring a site after installation, setting up design/content/SEO/navigation, or when the user pastes the post-install prompt. Covers 9 phases: discovery, design reference, global config, theme, content structure, templates, content creation, additional features, and launch. --- # Klytos Site Builder ## Overview The Site Builder is a conversational AI
development
Use when creating or editing page content in Klytos CMS. Ensures every page has proper SEO structure, HTML semantics, meta tags, structured data, accessibility for maximum search engine visibility. Apply when writing page titles, descriptions, content, headings, images, internal links, JSON-LD schema, or following the SEO checklist before publishing pages.