.claude/skills/klytos-options-storage/SKILL.md
Guide for saving and retrieving plugin options and site configuration in Klytos CMS. Use when a plugin needs to store or read persistent settings, manage site configuration, retrieve options, save persistent key-value data, work with the Options API, Site Config, or Storage API, manage plugin settings, query options by domain, or clean up options during plugin uninstall.
npx skillsauth add joseconti/klytos klytos-options-storageInstall 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.
Use this reference when your plugin needs to store and retrieve settings or configuration. Klytos provides three storage mechanisms:
The primary way for plugins to store settings. Key-value pairs stored in the 'options' collection with built-in request-level caching.
klytos_get_option(string $key, mixed $default = null): mixed
Get an option. Returns $default if not found.
klytos_set_option(string $key, mixed $value): void
Create or update an option. Value must be JSON-serializable.
klytos_delete_option(string $key): bool
Delete an option. Returns true if it existed.
klytos_option_exists(string $key): bool
Check if an option exists.
Format: plugin_id.setting_name
// Good — namespaced with plugin ID
klytos_set_option('my-gallery.columns', 3);
klytos_set_option('my-gallery.theme', 'dark');
klytos_set_option('seo-pro.sitemap_enabled', true);
// Bad — no namespace, risk of collision
klytos_set_option('columns', 3);
klytos_set_option('enabled', true);
Allowed characters: alphanumeric, dots, hyphens, underscores.
Rejected: empty keys, keys starting with _ (reserved).
// Save settings
klytos_set_option('my-plugin.api_key', 'sk-xxxxx');
klytos_set_option('my-plugin.max_items', 50);
klytos_set_option('my-plugin.features', [
'export' => true,
'import' => false,
'notifications' => true,
]);
// Read settings
$apiKey = klytos_get_option('my-plugin.api_key', '');
$maxItems = klytos_get_option('my-plugin.max_items', 25);
$features = klytos_get_option('my-plugin.features', []);
// Check existence
if (klytos_option_exists('my-plugin.api_key')) {
// API key is configured
}
// Delete
klytos_delete_option('my-plugin.deprecated_setting');
$options = klytos_app()->getOptionsManager();
// Get ALL options for a plugin
$allSettings = $options->getForPlugin('my-plugin');
// ['my-plugin.api_key' => 'sk-xxx', 'my-plugin.max_items' => 50, ...]
// Delete ALL options for a plugin (use in uninstall.php)
$count = $options->deleteForPlugin('my-plugin');
// Returns number of options deleted
Every option is tagged with a text_domain field identifying its owner. This happens automatically when a plugin calls klytos_set_option() — the PluginLoader injects the active text domain.
// Automatic: text_domain is inferred from the active plugin context
klytos_set_option('my-gallery.columns', 3);
// Record: { key: "my-gallery.columns", value: 3, text_domain: "my-gallery", ... }
// Explicit: set with a specific text domain
klytos_set_option_for('_core', 'site.maintenance', false);
// Query by text domain
$allGalleryOptions = klytos_get_options_by_domain('my-gallery');
// Delete all options for a text domain
$deleted = klytos_delete_options_by_domain('old-plugin');
$options = klytos_app()->getOptionsManager();
// Get/delete by text domain
$records = $options->getByTextDomain('my-plugin');
$count = $options->deleteByTextDomain('my-plugin');
$count = $options->countByTextDomain('my-plugin');
// List grouped by text domain
$grouped = $options->listGroupedByTextDomain();
// ['_core' => [...], 'my-plugin' => [...], '_unknown' => [...]]
// Classify by plugin status
$domains = $app->getPluginLoader()->getTextDomainsByStatus();
$classified = $options->classifyOptions($domains['active'], $domains['inactive']);
// ['core' => [...], 'active' => [...], 'inactive' => [...], 'orphan' => [...], 'unknown' => [...]]
// Migrate legacy options without text_domain
$migrated = $options->migrateTextDomains();
Plugins can declare whether an option contains sensitive data. This tells Klytos whether to encrypt the option at rest based on the site's encryption level (basic, medium, professional).
klytos_register_option(string $key, bool|string $sensitive = false, array $meta = []): void
Sensitivity levels:
| Value | Behavior | Use for |
|---|---|---|
| true | Always encrypted, regardless of site encryption level | API keys, tokens, passwords, secrets |
| 'user_data' | Encrypted from medium level onwards | Emails, IPs, personal data (GDPR) |
| false | Only encrypted at professional level (default) | Colors, counts, toggles, non-sensitive config |
Example:
// In your plugin's main file or install.php:
// API key — always encrypt, even on basic level
klytos_register_option('my-plugin.stripe_secret_key', true);
klytos_register_option('my-plugin.webhook_secret', true);
// User emails — encrypt from medium level (GDPR)
klytos_register_option('my-plugin.notification_email', 'user_data');
klytos_register_option('my-plugin.customer_data', 'user_data');
// Normal settings — no special encryption needed
klytos_register_option('my-plugin.theme_color', false);
klytos_register_option('my-plugin.items_per_page', false);
// false is the default, so you can omit it:
klytos_register_option('my-plugin.items_per_page');
Reading sensitivity:
$sensitivity = klytos_get_option_sensitivity('my-plugin.stripe_secret_key');
// Returns: true, 'user_data', false, or null (not registered)
Important: Registration must happen before the option is read/written. Register in your plugin's main file, not lazily. Options that are NOT registered default to false (only encrypted at professional level).
| Hook | Type | Arguments |
|---|---|---|
| option.before_set | action | string $key, mixed $value, mixed $oldValue |
| option.after_set | action | string $key, mixed $value, mixed $oldValue |
| option.before_delete | action | string $key |
| option.after_delete | action | string $key |
| option.get | filter | mixed $value, string $key |
| option.registered | action | string $key, bool\|string $sensitive, array $meta |
// Example: Log when options change
klytos_add_action('option.after_set', function (string $key, mixed $value): void {
klytos_log('info', "Option updated: {$key}");
});
// Example: Transform option on retrieval
klytos_add_filter('option.get', function (mixed $value, string $key): mixed {
if ($key === 'my-plugin.mode' && $value === null) {
return 'default'; // Override null with default
}
return $value;
});
For global site-level settings. Do NOT use this for plugin settings — use the Options API instead.
klytos_config(string $key, mixed $default = null): mixed
Supports dot-notation for nested values:
$siteName = klytos_config('site_name'); // 'My Site'
$language = klytos_config('default_language', 'en'); // 'es'
$twitter = klytos_config('social.twitter'); // '@handle'
$gaId = klytos_config('analytics.google_analytics_id'); // 'G-XXXXXXX'
$ogImage = klytos_config('seo.default_og_image'); // '/assets/images/og.jpg'
klytos_set_config(string $key, mixed $value): void
Note: Top-level keys only (no dot-notation for writing). Use sparingly.
[
'site_name' => 'My Klytos Site',
'tagline' => 'Built with AI',
'default_language' => 'es',
'description' => 'Site description',
'favicon_url' => '/assets/images/favicon.ico',
'logo_url' => '/assets/images/logo.svg',
'indexing_enabled' => true,
'editor' => 'gutenberg',
'admin_theme' => 'dark', // 'light' or 'dark'
'social' => [
'twitter' => '',
'github' => '',
'linkedin' => '',
'instagram' => '',
'youtube' => '',
'mastodon' => '',
],
'analytics' => [
'google_analytics_id' => '',
'custom_head_scripts' => '',
'custom_body_scripts' => '',
],
'seo' => [
'default_og_image' => '',
'robots_txt_extra' => '',
],
'email' => [
'transport' => 'mail', // 'mail' or 'smtp'
'from_name' => '', // Falls back to site_name
'from_email' => '', // Falls back to noreply@domain
'reply_to' => '',
'smtp_host' => '',
'smtp_port' => 587,
'smtp_user' => '',
'smtp_pass' => '',
'smtp_security' => 'tls', // 'tls', 'ssl', ''
],
'languages' => ['es', 'en'],
'last_build' => '2026-03-31T10:00:00+02:00',
]
klytos_get_site_config — Returns full configklytos_set_site_config — Partial update (only provided fields change)klytos_options_list_by_domain — List options for a text domainklytos_options_classify — Classify options by plugin statusklytos_options_delete_domain — Delete all options for a text domainklytos_options_migrate — Migrate legacy options without text_domainFor complex plugin data that doesn't fit key-value. Uses the abstracted storage layer (FileStorage or DatabaseStorage).
$storage = klytos_storage();
// Write a data record
$storage->write('my-plugin-data', 'record-1', [
'name' => 'Campaign A',
'status' => 'active',
'clicks' => 1543,
'created' => date('c'),
]);
// Read it back
$record = $storage->read('my-plugin-data', 'record-1');
// List all records (with optional filters)
$all = $storage->list('my-plugin-data', ['status' => 'active']);
// Count records
$count = $storage->count('my-plugin-data', ['status' => 'active']);
// Search
$results = $storage->search('my-plugin-data', 'Campaign', ['name']);
// Delete
$storage->delete('my-plugin-data', 'record-1');
// Check existence
$exists = $storage->exists('my-plugin-data', 'record-1');
// Transaction (atomic operations)
$storage->transaction(function ($storage) {
$storage->write('my-data', 'a', ['count' => 1]);
$storage->write('my-data', 'b', ['count' => 2]);
});
Use your plugin ID as the collection prefix:
my-plugin-data — Main datamy-plugin-cache — Cached datamy-plugin-logs — Plugin-specific logs| Scenario | System | Sensitivity | Why |
|---|---|---|---|
| Plugin toggle (on/off) | Options API | false | Simple key-value |
| Plugin API key | Options API | true | Sensitive secret — always encrypt |
| Stripe webhook secret | Options API | true | Sensitive secret — always encrypt |
| Notification email | Options API | 'user_data' | Personal data (GDPR) |
| List of 5 color presets | Options API | false | Small JSON array, non-sensitive |
| 1000+ product records | Storage API | — | Complex, queryable data |
| Site name, language | Site Config | — | Global, not plugin-specific |
| Analytics settings | Site Config | — | Site-wide configuration |
| Cache of external API data | Storage API | — | Large, structured data |
| User preferences per user | Meta API | — | Per-entity metadata |
When your plugin is uninstalled, clean up all stored data:
// plugins/my-plugin/uninstall.php
<?php
declare(strict_types=1);
$app = \Klytos\Core\App::getInstance();
// Delete all options
$app->getOptionsManager()->deleteForPlugin('my-plugin');
// Delete storage collections
$storage = $app->getStorage();
$records = $storage->list('my-plugin-data');
foreach ($records as $record) {
$storage->delete('my-plugin-data', $record['id'] ?? $record['slug'] ?? '');
}
// Delete meta on all pages
// (Only if your plugin stored meta on pages)
core/options-manager.phpcore/site-config.phpcore/storage-interface.phpcore/file-storage.phpcore/database-storage.phpcore/helpers-global.phpcore/mcp/tools/option-tools.phpadmin/system-options.phpadmin/api/options-management.phpdevelopment
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.