WP Manifestindependent plugin directory

RudraACF

wordpress acf

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/hardiksolanki90/acf/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Production-grade WordPress custom fields framework — ACF Pro–compatible alternative built on PHP 8.1+, PSR-4, repository pattern, and object-cache-first design.


Requirements

Dependency Minimum
PHP 8.1
WordPress 6.3
Composer 2.x

Installation

# 1. Copy plugin folder into wp-content/plugins/
cp -r myacf /path/to/wp-content/plugins/

# 2. Install PHP dependencies
cd /path/to/wp-content/plugins/myacf
composer install --no-dev --optimize-autoloader

# 3. Activate via WP Admin → Plugins, or WP-CLI:
wp plugin activate myacf

Activation runs database migrations automatically (creates wp_myacf_field_groups, wp_myacf_fields, wp_myacf_field_values).


Architecture Overview

myacf/
├── src/
│   ├── Admin/          # Admin pages, meta boxes, AJAX handlers
│   ├── Api/            # FieldApi service + global helper functions
│   ├── Compatibility/  # PHP/WP version + conflict detection
│   ├── Database/       # Models, Repositories, Migrations
│   ├── Fields/         # FieldInterface, BaseField, 16 field types
│   ├── ImportExport/   # JSON Exporter + Importer
│   ├── Licensing/      # LicenseManager + WP UpdateChecker
│   ├── OptionsPages/   # Options page registry + manager
│   ├── Performance/    # BulkLoader (cache pre-warming)
│   ├── Pro/            # ConditionalLogic
│   ├── Rest/           # REST API (myacf/v1)
│   ├── Rules/          # Rules Engine + matchers
│   ├── Sdk/            # Third-party extension SDK
│   ├── Services/       # Container, Capability, Sanitizer, Escaper, Nonce
│   └── Plugin.php      # Bootstrap + DI wiring
├── tests/Unit/         # 265 PHPUnit tests
└── myacf.php           # Plugin entry point

Key patterns:

  • Repository pattern — all DB access via FieldGroupRepository, FieldRepository, FieldValueRepository; never raw $wpdb calls outside repos
  • Object cache — every repo read is cache-first (wp_cache_get/set); CACHE_GROUP constants per repo; TTL 3600 s
  • DI Container — custom Container::singleton()/make() with no Reflection; all services resolved lazily
  • FieldInterface contract — getType / getLabel / render / renderInput / validate / save / load

Field Types

Type Class Notes
text TextField Single-line
textarea TextareaField Multi-line
number NumberField min/max/step settings
email EmailField Validates format
url UrlField
password PasswordField Never echoed in output
select SelectField choices array; multiple support
checkbox CheckboxField choices array; returns string[]
radio RadioField choices array
toggle ToggleField Stores '1'/''
image ImageField Attachment ID; return setting: id\|url\|array
file FileField Attachment ID; array includes filename/mime_type
post_object PostObjectField WP_Query select; multiple support
user UserField get_users with role__in filter
taxonomy TaxonomyField get_terms; returns term ID(s)
repeater RepeaterField Rows stored as JSON; sub-fields via parent_id
flexible_content FlexibleContentField Layouts + sub-fields; stored as JSON array
clone CloneField Inlines fields from source group(s); prefix_label/prefix_name settings

Developer API

Get / Display Values

// Get a field value
$value = myacf_get_field('field_name', $post_id);
$value = myacf_get_field('field_name', $post_id, 'post', $default);

// Echo value (HTML-escaped)
myacf_the_field('field_name', $post_id);

// Check if value exists
if (myacf_has_field('field_name', $post_id)) { ... }

// Get all fields for an object
$all = myacf_get_fields($post_id);

// User fields
$bio = myacf_get_field('bio', $user_id, 'user');

// Term fields
$desc = myacf_get_field('extra_desc', $term_id, 'term');

// Options page fields
$val = myacf_get_field('site_logo', 0, 'option');

Save / Delete

myacf_update_field('field_name', $value, $post_id);
myacf_update_field('field_name', $value, $user_id, 'user');
myacf_delete_field('field_name', $post_id);

Performance: Bulk Cache Pre-warm

// Call in pre_get_posts or the_post to batch-load before template loop
add_action('pre_get_posts', function($query) {
    if (!$query->is_main_query()) return;
    $ids = get_posts(['fields' => 'ids', 'posts_per_page' => 10]);
    myacf_prime_cache($ids, 'post');
});

Field Group Registration (programmatic)

Field groups can be created via WP Admin → RudraACF → Add New, or programmatically via the FieldGroupRepository:

add_action('plugins_loaded', function() {
    $repo = RudraACF\Plugin::getInstance()
        ->getContainer()
        ->make(RudraACF\Database\Repositories\FieldGroupRepository::class);

    $groupId = $repo->insert(
        title: 'Article Meta',
        key:   'group_article_meta',
        settings: [
            'rules' => [[
                ['param' => 'post_type', 'operator' => '==', 'value' => 'post'],
            ]],
            'position' => 'normal',
        ],
    );
});

Location Rules

Field groups are shown based on location rules evaluated by the Rules Engine.

param Evaluates Operators
post_type Current post type == !=
taxonomy Current taxonomy (term edit screen) == !=
user_role Current user's roles == !=
page_template Active page template filename == !=
post_status Post status == !=

Rules use OR across groups, AND within each group (same as ACF):

// Show on 'post' OR 'page' post types
'rules' => [
    [['param' => 'post_type', 'operator' => '==', 'value' => 'post']],
    [['param' => 'post_type', 'operator' => '==', 'value' => 'page']],
]

Register a custom rule matcher

add_action('myacf/register_rule_matchers', function($engine) {
    $engine->registerMatcher(new MyCustomRule());
});

// MyCustomRule implements RudraACF\Rules\RuleInterface:
//   getParam(): string
//   evaluate(string $operator, mixed $value, array $context): bool

Conditional Logic (Pro)

Show/hide individual fields based on other field values. Set in field settings:

'settings' => [
    'conditional_logic' => [
        // OR across outer array, AND within each group
        [
            ['field' => 'enable_hero', 'operator' => '==',    'value' => '1'],
            ['field' => 'layout',      'operator' => '!=',    'value' => 'minimal'],
        ],
    ],
],

Available operators: == != < <= > >= contains not_contains empty not_empty

Apply in code:

$logic  = new RudraACF\Pro\ConditionalLogic();
$values = myacf_get_fields($post_id);
$fields = $logic->filterFields($fieldDefinitions, $values);

Options Pages

// Register at plugins_loaded (before admin_menu)
add_action('plugins_loaded', function() {
    myacf_register_options_page([
        'slug'       => 'theme-settings',
        'title'      => 'Theme Settings',
        'menu_title' => 'Theme Options',
        'capability' => 'manage_options',
        'icon'       => 'dashicons-art',
        'position'   => 60,
    ]);

    // Sub-page
    myacf_register_options_page([
        'slug'   => 'theme-colors',
        'title'  => 'Color Settings',
        'parent' => 'theme-settings',
    ]);
});

// Read options page fields (object_id = 0, type = 'option')
$logo = myacf_get_field('logo', 0, 'option');

REST API

Base namespace: myacf/v1

Method Route Auth Description
GET /field-groups manage_options List all field groups
GET /field-groups/{id} manage_options Single group + fields
GET /objects/{type}/{id} per-object cap All field values
GET /objects/{type}/{id}/{field} per-object cap Single field value
POST /objects/{type}/{id}/{field} per-object cap Update field value

{type} must be one of: post user term option

Per-object capability check (IDOR protection):

type read cap write cap
post read_post edit_post
user edit_user edit_user
term read edit_term
option manage_options manage_options
# Example
curl -X GET https://site.com/wp-json/myacf/v1/objects/post/42 \
  -H "Authorization: Bearer TOKEN"

curl -X POST https://site.com/wp-json/myacf/v1/objects/post/42/hero_title \
  -H "Authorization: Bearer TOKEN" \
  -d '{"value": "New Title"}'

Import / Export

Via Admin UI

WP Admin → RudraACF → Import / Export

  • Export: downloads all (or selected) field groups as JSON
  • Import: upload JSON; checkbox "Update Existing" overwrites groups with matching key

Programmatic

$container = RudraACF\Plugin::getInstance()->getContainer();

// Export
$exporter = $container->make(RudraACF\ImportExport\Exporter::class);
$json     = $exporter->export();           // all groups
$json     = $exporter->export([1, 3]);     // specific group IDs

// Import
$importer = $container->make(RudraACF\ImportExport\Importer::class);
$stats    = $importer->import($json, updateExisting: true);
// $stats = ['imported' => 2, 'skipped' => 0, 'errors' => []]

Third-Party SDK

Hook into myacf/sdk/ready to register extensions after all services boot:

add_action('myacf/sdk/ready', function(RudraACF\Sdk\Sdk $sdk) {

    // Register a custom field type
    $sdk->registerFieldType(new MyPlugin\Fields\ColorPickerField());

    // Register a custom rule matcher
    $sdk->registerRuleMatcher(new MyPlugin\Rules\MembershipRule());

    // Register an options page
    $sdk->registerOptionsPage([
        'slug'  => 'my-plugin-settings',
        'title' => 'My Plugin Settings',
    ]);

    // Raw container access (escape hatch)
    $container = $sdk->getContainer();
});

Custom Field Type

use RudraACF\Fields\BaseField;

final class ColorPickerField extends BaseField
{
    public function getType(): string  { return 'color_picker'; }
    public function getLabel(): string { return 'Color Picker'; }

    public function renderInput(array $field, mixed $value): void
    {
        $name = $this->inputName($field);
        $id   = $this->inputId($field);
        echo '<input type="color" name="' . esc_attr($name) . '" '
           . 'id="' . esc_attr($id) . '" '
           . 'value="' . esc_attr((string) $value) . '">';
    }

    public function validate(mixed $value, array $field): true|string
    {
        if (!empty($field['settings']['required']) && empty($value)) {
            return 'This field is required.';
        }
        if ($value !== '' && !preg_match('/^#[0-9a-fA-F]{3,6}$/', (string) $value)) {
            return 'Must be a valid hex color (e.g. #ff0000).';
        }
        return true;
    }

    public function save(mixed $value, array $field): mixed { return sanitize_hex_color($value); }
    public function load(mixed $value, array $field): mixed { return $value; }
}

Licensing (Pro)

WP Admin → RudraACF → License

Enter license key to activate. Key is validated against MYACF_LICENSE_SERVER_URL (define in wp-config.php for Pro builds):

// wp-config.php
define('MYACF_LICENSE_SERVER_URL', 'https://licenses.yoursite.com/api');

Programmatic:

$license = $container->make(RudraACF\Licensing\LicenseManager::class);
$result  = $license->activate('YOUR-KEY-HERE');
// ['success' => true, 'message' => 'License activated successfully.']

$license->isActive();     // bool
$license->getLicenseKey(); // string
$license->check();         // bool — re-validates against server (24h cache)
$license->deactivate();    // frees the activation slot

The UpdateChecker automatically injects plugin update info into WP's update system when an active license is present.


Running Tests

cd myacf
composer install
./vendor/bin/phpunit --testsuite Unit

265 tests, 409 assertions. No real WordPress or database needed — full stub environment in tests/bootstrap.php.


Compatibility

On plugin activation, CompatibilityChecker blocks activation and shows an error if:

  • PHP < 8.1
  • WordPress < 6.3
  • Advanced Custom Fields (free or Pro) is active — field name conflicts

Storage Schema

-- Field groups
wp_myacf_field_groups (id, title, key, settings JSON, created_at, updated_at)

-- Field definitions
wp_myacf_fields (id, group_id, parent_id, type, name, label, instructions, settings JSON, sort_order)

-- Field values
wp_myacf_field_values (id, object_type, object_id, field_id, value LONGTEXT)

parent_id links sub-fields to their Repeater/FlexContent/Clone parent. object_type is one of post user term option. For options pages object_id = 0.


Hooks Reference

Hook Type Description
myacf/sdk/ready action Fires after all services boot; receives Sdk instance
myacf/register_field_types action Fires inside FieldTypeRegistry build; receives FieldTypeRegistry
myacf/register_rule_matchers action Fires inside RulesEngine build; receives RulesEngine
myacf/rules/context filter Filter the rules context array before evaluation