| name | klytos-options-storage |
| description | 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. |
Klytos Options & Storage
When to Use This Skill
Use this reference when your plugin needs to store and retrieve settings or configuration. Klytos provides three storage mechanisms:
- Options API — Simple key-value pairs for plugin settings (most common)
- Site Config — Global site configuration (name, language, social, SEO)
- Storage API — Complex data collections (for advanced use cases)
1. Options API (Plugin Settings)
The primary way for plugins to store settings. Key-value pairs stored in the 'options' collection with built-in request-level caching.
Functions
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.
Key Naming Convention
Format: plugin_id.setting_name
klytos_set_option('my-gallery.columns', 3);
klytos_set_option('my-gallery.theme', 'dark');
klytos_set_option('seo-pro.sitemap_enabled', true);
klytos_set_option('columns', 3);
klytos_set_option('enabled', true);
Allowed characters: alphanumeric, dots, hyphens, underscores.
Rejected: empty keys, keys starting with _ (reserved).
Examples
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,
]);
$apiKey = klytos_get_option('my-plugin.api_key', '');
$maxItems = klytos_get_option('my-plugin.max_items', 25);
$features = klytos_get_option('my-plugin.features', []);
if (klytos_option_exists('my-plugin.api_key')) {
}
klytos_delete_option('my-plugin.deprecated_setting');
Bulk Operations (OptionsManager Methods)
$options = klytos_app()->getOptionsManager();
$allSettings = $options->getForPlugin('my-plugin');
$count = $options->deleteForPlugin('my-plugin');
Text Domain Tracking
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.
klytos_set_option('my-gallery.columns', 3);
klytos_set_option_for('_core', 'site.maintenance', false);
$allGalleryOptions = klytos_get_options_by_domain('my-gallery');
$deleted = klytos_delete_options_by_domain('old-plugin');
Text Domain Methods (OptionsManager)
$options = klytos_app()->getOptionsManager();
$records = $options->getByTextDomain('my-plugin');
$count = $options->deleteByTextDomain('my-plugin');
$count = $options->countByTextDomain('my-plugin');
$grouped = $options->listGroupedByTextDomain();
$domains = $app->getPluginLoader()->getTextDomainsByStatus();
$classified = $options->classifyOptions($domains['active'], $domains['inactive']);
$migrated = $options->migrateTextDomains();
Data Sensitivity Classification
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:
klytos_register_option('my-plugin.stripe_secret_key', true);
klytos_register_option('my-plugin.webhook_secret', true);
klytos_register_option('my-plugin.notification_email', 'user_data');
klytos_register_option('my-plugin.customer_data', 'user_data');
klytos_register_option('my-plugin.theme_color', false);
klytos_register_option('my-plugin.items_per_page', false);
klytos_register_option('my-plugin.items_per_page');
Reading sensitivity:
$sensitivity = klytos_get_option_sensitivity('my-plugin.stripe_secret_key');
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).
Hooks
| 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 |
klytos_add_action('option.after_set', function (string $key, mixed $value): void {
klytos_log('info', "Option updated: {$key}");
});
klytos_add_filter('option.get', function (mixed $value, string $key): mixed {
if ($key === 'my-plugin.mode' && $value === null) {
return 'default';
}
return $value;
});
2. Site Configuration (Global Settings)
For global site-level settings. Do NOT use this for plugin settings — use the Options API instead.
Reading Config
klytos_config(string $key, mixed $default = null): mixed
Supports dot-notation for nested values:
$siteName = klytos_config('site_name');
$language = klytos_config('default_language', 'en');
$twitter = klytos_config('social.twitter');
$gaId = klytos_config('analytics.google_analytics_id');
$ogImage = klytos_config('seo.default_og_image');
Writing Config
klytos_set_config(string $key, mixed $value): void
Note: Top-level keys only (no dot-notation for writing). Use sparingly.
Full Config Structure
[
'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',
'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',
'from_name' => '',
'from_email' => '',
'reply_to' => '',
'smtp_host' => '',
'smtp_port' => 587,
'smtp_user' => '',
'smtp_pass' => '',
'smtp_security' => 'tls',
],
'languages' => ['es', 'en'],
'last_build' => '2026-03-31T10:00:00+02:00',
]
Via MCP
klytos_get_site_config — Returns full config
klytos_set_site_config — Partial update (only provided fields change)
klytos_options_list_by_domain — List options for a text domain
klytos_options_classify — Classify options by plugin status
klytos_options_delete_domain — Delete all options for a text domain
klytos_options_migrate — Migrate legacy options without text_domain
3. Storage API (Complex Data)
For complex plugin data that doesn't fit key-value. Uses the abstracted storage layer (FileStorage or DatabaseStorage).
$storage = klytos_storage();
$storage->write('my-plugin-data', 'record-1', [
'name' => 'Campaign A',
'status' => 'active',
'clicks' => 1543,
'created' => date('c'),
]);
$record = $storage->read('my-plugin-data', 'record-1');
$all = $storage->list('my-plugin-data', ['status' => 'active']);
$count = $storage->count('my-plugin-data', ['status' => 'active']);
$results = $storage->search('my-plugin-data', 'Campaign', ['name']);
$storage->delete('my-plugin-data', 'record-1');
$exists = $storage->exists('my-plugin-data', 'record-1');
$storage->transaction(function ($storage) {
$storage->write('my-data', 'a', ['count' => 1]);
$storage->write('my-data', 'b', ['count' => 2]);
});
Collection Naming Convention
Use your plugin ID as the collection prefix:
my-plugin-data — Main data
my-plugin-cache — Cached data
my-plugin-logs — Plugin-specific logs
When to Use Each System
| 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 |
Rule of Thumb
- Options API: Simple settings (strings, numbers, booleans, small arrays)
- Site Config: Global site settings (read mostly, write rarely)
- Storage API: Complex data with CRUD operations, lists, searches
- Meta API: Data attached to existing entities (pages, users)
Cleanup on Uninstall
When your plugin is uninstalled, clean up all stored data:
<?php
declare(strict_types=1);
$app = \Klytos\Core\App::getInstance();
$app->getOptionsManager()->deleteForPlugin('my-plugin');
$storage = $app->getStorage();
$records = $storage->list('my-plugin-data');
foreach ($records as $record) {
$storage->delete('my-plugin-data', $record['id'] ?? $record['slug'] ?? '');
}
Source Files
- Options manager:
core/options-manager.php
- Site config:
core/site-config.php
- Storage interface:
core/storage-interface.php
- File storage:
core/file-storage.php
- Database storage:
core/database-storage.php
- Global option functions:
core/helpers-global.php
- MCP option tools:
core/mcp/tools/option-tools.php
- Admin panel:
admin/system-options.php
- Admin API:
admin/api/options-management.php