eddev
ACF

Admin Panel Widgets

Mount React widgets inside custom WordPress admin pages.

Admin panel widgets are not ACF fields. They are React components mounted into WordPress admin screens, useful for project tools such as sync panels, importers, reports, or asset managers.

The SFF site uses this pattern for festival sync tooling and film asset management. The reusable shape is simpler than those production tools: create a widget TSX file, print a matching data-widget element from PHP, and enable the eddev admin assets on that screen.

Create The Widget

Widget files live in backend/widgets/*.tsx. The file slug is what PHP uses in data-widget.

import { defineWidget } from "eddev/admin"
import { useState } from "react"

export default defineWidget("content-sync", () => {
  const [message, setMessage] = useState<string | null>(null)
  const [busy, setBusy] = useState(false)

  async function runSync() {
    setBusy(true)
    setMessage(null)

    const response = await fetch("/wp-json/site/v1/sync-content", {
      method: "POST",
    })

    setBusy(false)
    setMessage(response.ok ? "Sync complete." : "Sync failed.")
  }

  return (
    <div className="postbox p-4">
      <p>Run the latest content sync.</p>
      <button className="button button-primary" disabled={busy} onClick={runSync}>
        {busy ? "Syncing..." : "Run sync"}
      </button>
      {message && <p>{message}</p>}
    </div>
  )
})

Keep the defineWidget name and the filename slug the same. eddev discovers the file from backend/widgets/content-sync.tsx, and the admin page mounts it with data-widget="content-sync".

Add A WordPress Admin Page

The PHP side can live wherever your backend include file loads project systems. Large sites often keep this near the system it controls, for example under backend/systems/*/*.php.

<?php

use ED\AdminAssets;

add_action("admin_menu", function () {
  add_menu_page(
    "Content Sync",
    "Content Sync",
    "edit_theme_options",
    "content-sync",
    "showContentSyncAdminPage",
    "dashicons-update",
    31
  );

  add_filter("ed_should_enqueue_admin_scripts", function ($enabled, $screen) {
    if ($screen->id === "toplevel_page_content-sync") {
      return true;
    }

    return $enabled;
  }, 10, 2);
});

function showContentSyncAdminPage() {
  AdminAssets::$enabled = true;
  ?>
  <div class="wrap">
    <h1 class="wp-heading-inline">Content Sync</h1>
    <div data-widget="content-sync"></div>
  </div>
  <?php
}

AdminAssets loads the eddev admin bundle. The widget runner scans the page for [data-widget] elements and mounts the matching React widget.

Data Access

Widgets can call normal WordPress REST endpoints with fetch. They can also use generated query hooks when the data is exposed through committed queries/**/*.graphql files.

Use a widget when the screen is an operational tool rather than page content. For example:

  • a sync panel that starts a background import and reports progress
  • an asset manager that lets editors inspect and update media for a post type
  • a small admin dashboard that combines project-specific status checks

If the UI is meant to edit a single ACF value inside a field group, build a custom field instead.

On this page