DEV Community

victorstackAI
victorstackAI

Posted on • Originally published at victorstack-ai.github.io

Building SyncForge Config Manager: WordPress Config as YAML, Not Guesswork

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

SyncForge Config Manager is a WordPress plugin that treats configuration like code: export it to YAML, review diffs, import safely, and roll back when needed. It wires the same core engine into admin UI, REST, and WP-CLI, so workflows stay consistent across local, staging, and production.

The Problem

A DB dump is config management. It is mostly a panic button.

What breaks teams is configuration drift: options changed in wp-admin, theme mods changed in one environment, widget trees drifting silently, rewrite config different after one plugin update. Normal release pipelines do not track any of that cleanly.

"Export, import, and sync WordPress site configuration as YAML files across environments."

  • SyncForge Config Manager README, GitHub

How It Works

The plugin boots a container, registers providers, then uses ConfigManager as the orchestration layer for export/import/diff/rollback. Providers define how each config domain maps between DB state and YAML files.

flowchart LR
  A[WordPress Bootstrap] --> B[Plugin::init]
  B --> C[Register 7 Providers]
  B --> D[Init Services]
  D --> E[ConfigManager]
  E --> F[FileHandler YAML I/O]
  E --> G[SchemaValidator + YamlSanitizer]
  E --> H[EnvironmentOverride]
  E --> I[DiffEngine]
  E --> J[AuditLogger + Snapshots]
  K[WP-CLI syncforge *] --> E
  L[REST /syncforge/v1/*] --> E
  M[Admin UI Tools > SyncForge] --> L
Enter fullscreen mode Exit fullscreen mode

```bash title="real commands from src/CLI/*.php"
wp syncforge export
wp syncforge diff --format=json
wp syncforge import --dry-run
wp syncforge import --yes
wp syncforge status
wp syncforge discover --track-all




</TabItem>

<TabItem value="rest" label="REST/Admin Path">



```bash title="real routes from src/Rest/*.php"
/wp-json/syncforge/v1/export
/wp-json/syncforge/v1/import
/wp-json/syncforge/v1/diff
/wp-json/syncforge/v1/snapshots
/wp-json/syncforge/v1/rollback/{id}
/wp-json/syncforge/v1/discover
Enter fullscreen mode Exit fullscreen mode

Implementation

ConfigManager does the heavy lifting: lock acquisition, provider ordering (topological sort), YAML read, env override merge, schema validation, sanitization, then dry-run or import.

```php title="syncforge-config-manager/src/ConfigManager.php" showLineNumbers
private function do_import_provider( Provider\ProviderInterface $provider, bool $dry_run ): array {
$config = $this->read_provider_config( $provider );

// highlight-next-line
$config = $this->container->get_environment_override()->apply_overrides( $provider->get_id(), $config );

$validation = $this->container->get_schema_validator()->validate( $provider->get_id(), $config );

if ( is_wp_error( $validation ) ) {
    throw new \RuntimeException(
        sprintf(
            esc_html__( 'Validation failed for provider %1$s: %2$s', 'syncforge-config-manager' ),
            esc_html( $provider->get_id() ),
            esc_html( $validation->get_error_message() )
        )
    );
}

// highlight-next-line
$config = $this->container->get_yaml_sanitizer()->sanitize( $config, $provider->get_id() );

if ( $dry_run ) {
    return $provider->dry_run( $config );
}

return $provider->import( $config );
Enter fullscreen mode Exit fullscreen mode

}




Option discovery avoids hardcoded plugin lists. It classifies by patterns and groups discovered keys by plugin slug (or `misc` when ownership is ambiguous).



```php title="syncforge-config-manager/src/Admin/OptionDiscovery.php" showLineNumbers
private const RUNTIME_KEYWORDS = array(
    '_version',
    '_db_version',
    '_migration',
    '_nonce',
    '_session',
    '_token_',
    '_count',
    '_dismissed',
    '_telemetry',
    '_last_run',
    '_children',
    '_site_health',
);

private const RUNTIME_SUFFIXES = array(
    '_state',
    '_rat',
    '_pubkey',
    '_auth',
    '_install',
);
Enter fullscreen mode Exit fullscreen mode
Provider ID Dependency File strategy
options none fixed files (options/general.yml, etc.) + dynamic extra groups
roles none dynamic directory (roles/{role}.yml)
menus options directory (menus/)
widgets options directory (widgets/)
theme-mods options single file (theme-mods.yml)
rewrite options single file (rewrite.yml)
block-patterns none directory (block-patterns/)

A real project change that matters: the rename from generic config-sync branding to syncforge-config-manager, reflected in plugin header and docs.

-=== Config Sync ===
+=== SyncForge Config Manager ===
-Contributors: victorstack-ai
+Contributors: victorjimenezdev
-Text Domain: config-sync
+Text Domain: syncforge-config-manager
-Plugin Name: Config Sync
+Plugin Name: SyncForge Config Manager
Enter fullscreen mode Exit fullscreen mode

Supplementary: security controls implemented in code

  • FileHandler::resolve_safe_path() rejects traversal before file access.
  • ZIP import rejects entries containing .. and only extracts .yml.
  • YamlSanitizer rejects serialized object payload patterns.
  • AuditLogger redacts secret-looking keys before storing snapshots/diffs.
  • Config directory gets .htaccess, index.php, and web.config deny files.

What I Learned

  • Provider contracts beat one giant "sync everything" routine. Dependencies are explicit and sortable.
  • The lock model (config_sync_lock in wp_options) is simple and good enough for admin/CLI concurrency.
  • The same orchestration layer serving REST, CLI, and admin reduces edge-case drift in behavior.
  • Default path choices matter more than people admit; the code defaults to wp-content/syncforge-config-manager, while docs still mention wp-content/config-sync/.

💡 Tip: Use Dry-Run as a Release Gate

Make wp syncforge diff and wp syncforge import --dry-run mandatory in staging before any production import. The plugin already has the primitives; the missing part is process discipline.

⚠️ Caution: Capability Model Is Split

REST checks manage_config_sync, while parts of admin/ZIP handling check manage_options. If this runs in a delegated ops model, unify capability checks to avoid "works in one interface, blocked in another" support drama.

References


Looking for an Architect who does not just write code, but builds the AI systems that multiply your team's output? View my enterprise CMS case studies at victorjimenezdev.github.io or connect with me on LinkedIn.


Looking for an Architect who doesn't just write code, but builds the AI systems that multiply your team's output? View my enterprise CMS case studies at victorjimenezdev.github.io or connect with me on LinkedIn.

Originally published at VictorStack AI — Drupal & WordPress Reference

Top comments (0)