WordPress: How To Create a Custom Admin Button with ACF

There comes a time in a WordPress theme’s life where it has room to grow. Sometimes, a plugin will do the trick. Sometimes, you need to dig in and write your own PHP. Luckily, using the flexibility of WordPress, there are several ways to approach a custom admin button. A common approach is invoking a JavaScript function on click, then using AJAX to send a request to a PHP file. Though this is a nice solution, there are alternative ways to approach it.

In this article, I go over how to utilize ACF to create button(s) on any field group that send POST data. The process is fairly straightforward.

Here are the steps

  • Decide the best place for the button.

  • Hook up a JavaScript file with your admin button.

  • On click, append your inputs with optional data.

  • In a PHP file, look at the $_POST array and perform your logic.

From there, it’s up to you to add the logic you or your client wants. Setting it up is a multi-step process, but it’s not too much overhead and shouldn’t require any plugins. You technically don’t even need ACF to do this. However, I’ll use ACF as an example since it’s my favorite plugin. Let’s dig into it!

Where To Put the Admin Button(s)?

Technically, you can add buttons inside any <form> element. You can even use it outside of a form since we’re using JavaScript to append the <input> elements. Where you end up placing the button depends on usability. Will this button update a certain part of the site? Will it change how backend admin functionality works?

Let’s use an admin button that refreshes cached API data as an example. You want to refresh the cache of all testimonials on your site.

To refresh a single testimonial:
Place the admin button under each single testimonial. This is easily done with ACF by simple assigning a field to the testimonials post type.

To refresh all testimonials:
You can place an options page under the testimonials section of your admin sidebar. When that button is clicked in the global options page, you can add functionality to loop through each testimonial and refresh the cache on all of them.

if( function_exists('acf_add_options_page') ) {
    'page_title'  => 'Testimonial Global Options',
    'menu_title'  => 'Testimonial Global Options',
    'parent_slug' => 'edit.php?post_type=testimonials',

Buttons can now easily be added to this options subpage.

Adding a button globally:
To add a button globally, make a regular options page.

function my_custom_menu_page() {
    'My Options',           // Page title
    'My Options',           // Menu title
    'manage_options',       // Capability
    'my_custom_options',    // Menu slug
    'my_custom_options_page' // Callback function
add_action('admin_menu', 'my_custom_menu_page');

These are just examples. At the end of the day, you should make it easier for whoever will actually be using it. It might be a client, and not you. Think about what would be easiest for them.

Introducing the ACF Message Field

If you haven’t heard of the message field already, it’s a great way to add instructions under any field group. While not the prettiest solution, you should be able to add markup to it.

For a simple button, this option provides a lot of flexibility for placement. A simple button will look like this:

<div id="refresh-button-container">
    id="refresh-cache">Refresh Testimonial Data
This example produces a pretty simple button. However, you should be able to enqueue a style and make it as fancy as you want.

Using the message field, you can add this markup under any defined field group. So the button is in place.. now what?

Linking JavaScript to Your Admin Page

In order for the button to do anything.. it needs to perform an action on click. This obviously requires JavaScript, so let’s set that up. Doing so isn’t difficult and is already documented here.

We can use admin_enqueue_scripts to load a JavaScript file, but we only want to load it for the relevant admin page.

add_action( 'admin_enqueue_scripts', function ($hook) {
      //only load this script for a certain URL page slug
      if($hook == 'your-url-page-slug') {
          $path = asset_path('scripts/admin.js');
          wp_enqueue_script( 'textdomain/custom-admin-js', $path, ['jquery']);
} );

Here’s what’s happening: We’re using the admin_enqueue_scripts parameter $hook to check which admin page to load the JavaScript. You can get a specific ACF page’s slug by checking the URL. It should be after the &page= query parameter.

Once the conditional passes, simply enqueue the script like any other. I placed this in a file called admin.php, but it can be loaded in your functions.php file. You can also load it from your plugin files if you’re packaging this up into a separate plugin.

From your JS file, test to make sure everything is working.

import $ from 'jquery';

$(document).ready(function($) {
    console.log('Im working!');

Appending Data To Your Form Using JavaScript/jQuery

In the last example, we loaded the JavaScript file with jQuery as a dependency. However, this solution is simple enough to use vanilla JS, if preferred.

From here, it’s a two step process.

  • Add a click event listener to the admin button

  • Attach hidden input fields to the ACF element

Here’s the code to accomplish this:

import $ from 'jquery';

$(document).ready(function($) {

let Admin = {
    init() {
    setEventListeners() {
    refreshCache() {
        //attach hidden input then submit the form
    attachData() {
        let $buttonContainer = $('#refresh-button-container');
        let inputField = '<input type="hidden" name="refresh-cache" value="1" />';

This is a basic example and only attaches a single input. Yours may need multiple pieces of data, so you could simply attach more <input> fields. Once the button is clicked, the JavaScript code will submit the form automatically. Once the form is submitted, the last step is to check the POST data that is sent through.

Processing POST Data in Your PHP Files

Where you perform PHP logic depends on your theme’s file structure. I’d recommend performing the logic in a separate file outside of functions.php, or even in its own packaged plugin. However you do it, the process is pretty straightforward.

//Here, we're only checking if the POST data has been sent through
//so we can outsource the logic to the refreshCache function
if($_POST['refresh-cache'] == 1) {

If you’re passing multiple bits of data, you should be able to access it using the $_POST array.

if($_POST['refresh-cache'] == 1) {
    $exampleArg1 = $_POST['example_data'];
    $exampleArg2 = $_POST['more_example_data'];
    refreshCache($exampleArg1, $exampleArg2);

From there, what logic you implement is up to you. That’s pretty much it! Have fun, and make something awesome.


  • Anonymous

    Posted on

    Very nicely described, thank you!

    • brandon parker

      Posted on

      You are very welcome!