Skip to main content

Data Sovereignty Principle

Every Flavor product owns all of its data in its own custom database tables. This is a fundamental architectural rule that applies to three separate data domains — the plugin, the theme, and Flavor Core — each with its own tables and its own access helpers.

Why Custom Tables?

  • Performance — Optimized schema, proper indexes, no EAV overhead
  • Independence — No dependency on the WordPress options table
  • Scalability — Handles thousands of products/orders efficiently
  • Clean separation — Plugin, theme and Core data never interfere

The three data domains

DomainNamespaceOptions helperCache / storeTables
Pluginwpec_*wpec_get_option() / wpec_set_option()wpec_cache_*(){prefix}ec_*
Themeflavor_*flavor_get_option() / flavor_set_option()flavor_cache_*(){prefix}flavor_*
Flavor Coreflavor_core_*flavor_core_get_option() / flavor_core_set_option() / flavor_core_delete_option()Flavor_Core_Data_Store (encrypted){prefix}flavor_core_data, {prefix}flavor_core_options

Plugin Data Access

Configuration

// Store plugin settings
wpec_set_option('store_name', 'My Store', 'general');
wpec_get_option('store_name', 'Default Store');

// NEVER do this:
update_option('wpec_store_name', 'My Store'); // WRONG!
get_option('wpec_store_name'); // WRONG!

Cache

// Plugin cache (custom ec_cache table)
wpec_cache_set('product_count', 150, 3600); // TTL in seconds
$count = wpec_cache_get('product_count');

// NEVER do this:
set_transient('wpec_product_count', 150, 3600); // WRONG!

Theme Data Access

Configuration

// Store theme settings
flavor_set_option('header_sticky', 'true', 'header');
flavor_get_option('header_sticky', 'false');

// NEVER do this:
get_option('flavor_options'); // WRONG! The blob was deleted.
update_option('flavor_header_sticky', 'true'); // WRONG!

Cache

// Theme cache (custom flavor_cache table)
flavor_cache_set('menu_html', $html, 1800);
$html = flavor_cache_get('menu_html');

// IMPORTANT: flavor_cache_get() returns null (not false)
$val = flavor_cache_get('counter');
if ($val !== null) { /* exists */ }

// For integer casts with default:
$count = (int) flavor_cache_get('counter', 0);

// NEVER do this:
set_transient('flavor_menu_html', $html, 1800); // WRONG!

Flavor Core Data Access

Flavor Core (license client, update manager, unified debug, AI foundation) owns its data in two custom tables — never in wp_options:

  • {prefix}flavor_core_data — sensitive data (license cache, fingerprint token), AES-256-GCM encrypted, accessed through Flavor_Core_Data_Store.
  • {prefix}flavor_core_options — non-sensitive settings (mode, release channel, MCP toggle, grace opt-in, self-update diagnostics, migration markers).

Non-sensitive settings use the flavor_core_*_option() helpers:

// Read / write / delete Flavor Core settings
flavor_core_get_option('release_channel', 'stable');
flavor_core_set_option('mcp_enabled', 'true', /* autoload */ false, 'general');
flavor_core_delete_option('some_stale_flag');

// NEVER do this:
update_option('flavor_core_release_channel', 'stable'); // WRONG!
get_option('flavor_core_mcp_enabled'); // WRONG!
caution

The old "sanctioned third namespace in wp_options" exception is closed. flavor_core_* data belongs in the Core tables. The only residue permitted in wp_options is the per-store bootstrap markers (the schema/migration flags that must exist before the Core tables do) — never business data.

Module Settings

Module settings use dedicated option keys:

// Correct — dedicated key per module
flavor_set_option('wishlist_enabled', 'true', 'flavor_module_wishlist');
flavor_get_option('wishlist_enabled', 'false', 'flavor_module_wishlist');

// Wrong — shared options blob
flavor_set_option('wishlist_enabled', 'true'); // Don't pollute the default group

WordPress Data (Read-Only)

For data that genuinely belongs to WordPress, use standard functions:

// Fine — reading WordPress-owned data
get_option('admin_email');
get_option('date_format');
get_option('permalink_structure');

Type Safety

flavor_get_option() has built-in type safety:

// If the caller passes an array default but the DB stores a scalar,
// it returns the default array (prevents crashes)
$colors = flavor_get_option('brand_colors', ['#000', '#fff']);
// DB has a scalar string → returns ['#000', '#fff']
// DB has a proper JSON array → returns the parsed array

Quick rules

  • Plugin options → wpec_get_option() / wpec_set_option() — never get_option('wpec_*')
  • Theme options → flavor_get_option() / flavor_set_option() — never get_option('flavor_options')
  • Core options → flavor_core_get_option() / flavor_core_set_option() — never get_option('flavor_core_*')
  • Cache → wpec_cache_*() / flavor_cache_*() — never set_transient('wpec_*' | 'flavor_*')
  • flavor_cache_get() returns null (not false) — test with !== null
  • Never set_option(key, null)option_value is NOT NULL; use delete_option(key) to clear