Saving data to ACF Repeater Field with JavaScript – Tutorial

In this tutorial we will create a Custom post Type named Terms, use Advanced Custom Fields PRO (with ACF Repeater Field) to make a beautiful admin interface and create a new Custom Post Type Item programatically with JavaScript via WP REST API using Nonce authentication.

Needed Plugins

First, download and install the following plugins:

Create Custom post with Post Type Generator

// Register Custom Post Type - Terms
function term_post_type()
    $labels = array(
        'name'                  => _x('Terms', 'Post Type General Name', 'text_domain'),
        'singular_name'         => _x('Term', 'Post Type Singular Name', 'text_domain'),
        'menu_name'             => __('Terms', 'text_domain'),
        'name_admin_bar'        => __('Terms', 'text_domain'),
        'archives'              => __('Term Archives', 'text_domain'),
        'attributes'            => __('Term Attributes', 'text_domain'),
        'parent_item_colon'     => __('Parent Term:', 'text_domain'),
        'all_items'             => __('All Terms', 'text_domain'),
        'add_new_item'          => __('Add New Term', 'text_domain'),
        'add_new'               => __('Add New', 'text_domain'),
        'new_item'              => __('New Term', 'text_domain'),
        'edit_item'             => __('Edit Term', 'text_domain'),
        'update_item'           => __('Update Term', 'text_domain'),
        'view_item'             => __('View Term', 'text_domain'),
        'view_items'            => __('View Terms', 'text_domain'),
        'search_items'          => __('Search Term', 'text_domain'),
        'not_found'             => __('Not found', 'text_domain'),
        'not_found_in_trash'    => __('Not found in Trash', 'text_domain'),
        'featured_image'        => __('Featured Image', 'text_domain'),
        'set_featured_image'    => __('Set featured image', 'text_domain'),
        'remove_featured_image' => __('Remove featured image', 'text_domain'),
        'use_featured_image'    => __('Use as featured image', 'text_domain'),
        'insert_into_item'      => __('Insert into term', 'text_domain'),
        'uploaded_to_this_item' => __('Uploaded to this term', 'text_domain'),
        'items_list'            => __('Terms list', 'text_domain'),
        'items_list_navigation' => __('Terms list navigation', 'text_domain'),
        'filter_items_list'     => __('Filter terms list', 'text_domain'),
    $rewrite = array(
        'slug'                  => 'term',
        'with_front'            => true,
        'pages'                 => true,
        'feeds'                 => true,
    $args = array(
        'label'                 => __('Term', 'text_domain'),
        'description'           => __('Term Post Type Description', 'text_domain'),
        'labels'                => $labels,
        'supports'              => array( 'title', 'excerpt', 'author', 'revisions', 'custom-fields', ),
        'taxonomies'            => array( 'category', 'post_tag' ),
        'hierarchical'          => false,
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 5,
        'menu_icon'             => 'dashicons-book',
        'show_in_admin_bar'     => true,
        'show_in_nav_menus'     => true,
        'can_export'            => true,
        'has_archive'           => true,
        'exclude_from_search'   => false,
        'publicly_queryable'    => true,
        'rewrite'               => $rewrite,
        'capability_type'       => 'post',
        'show_in_rest'          => true,
        'rest_base'             => 'terms',
    register_post_type('term_post_type', $args);
add_action('init', 'term_post_type', 0);

To hide default Posts menu entry add the following code into functions.php file in your theme:

/*  Remove 'Posts' entry from admin menu */
function post_remove() { 
add_action('admin_menu', 'post_remove');

Now we have a nice looking menu:
Removed Posts, added Terms in menu

Create Custom Fields with Advanced Custom Fields PRO – ACF Repeater Field

Because we would like to have a custom looking entry page with only the fields we actually use, we will install ACF plugin and then create a new Field Group of entry fields for our custom post type Terms. The structure of the fields is the following:

Label Name Type
Approved term_approved True/ False
New term_new True/False
Origin term_origin Text
Definitions definitions Repeater
– Approved definition_approved True/False
– Definition definition Text
– Likes definition_likes Number
– Author definition_author Text
– Examples examples Repeater
– – Approved example_approved True False
– – Example example Text area
– – Author example_author Text
– Related Terms related_terms Post Object

Here is also the screenshot of the whole page:

ACF fields - ACF Repeater Field

Create a new Term

Now we have enerything we need to create a new term, so let’s add a new one. In navigation select Terms > Add new:

Add a new term

and populate the fields:

Sample term

Now we can examine how the post looks like if we send a GET request. I suggest you use Postman as it is easy to install and use, but if you prefer you can also visit <YOUR_WORDPRESS_URL>/wp-json/wp/v2/terms/


Postman Terms

You can see we have all our ACF fields saved in the object acf.

Let’s now prepare a sample JavaScript file that will enter a new term.

WordPress Nonce for Authentication

For authentication we will use Nonce. You can read more about it in WordPress Codex.

Add to your functions.php the following code that will generate nonce.

function my_resources()
        get_template_directory_uri() . '/my_script.js',
        null, // dependency
        1.0, // version
        true  // loads script in footer

        'my_script',   // handle - name of script
        'magicalData', //  name - name of the object we want to output
        array('nonce' => wp_create_nonce('wp_rest')) // data - output

add_action('wp_enqueue_scripts', 'my_resources');

This code outputs the value of nonce into your page:

<script type='text/javascript'>
/* <![CDATA[ */
var magicalData = {"nonce":"397e0b60df"};
/* ]]> */

Next, to define the key for ACF to REST API we will later use to define fields we would like to send to REST AP add to functions.php:

// DEFINE the KEY for post meta_keys to send data to
add_filter('acf/rest_api/key', function ($key, $request, $type) {
    return 'terms';
}, 10, 3);

Now we can add a script my_script.js to our theme to handle a new post entry.

Notice the terms object in postContent with new entries for ACF Repeater Field, magicalData.nonce usage and of course change <YOUR_WORDPRESS_URL> to your page URL.

var postContent = {
    'title': 'Beautiful term',
    'terms': {
        'term_approved': true,
        'term_new': true,
        'term_origin': 'Ang. Fake ',
        'definitions': [ {
                'definition_approved': true,
                'definition': 'The best definition ever',
                'definition_author': 'Roxy',
                'examples': [ {
                        'example': 'Some strange usage of the word',
                        'example_author': 'Ruby',
                        'example_approved': true
                        'example': 'What is beautiful?',
                        'example_author': 'Rocky',
                        'example_approved': true
                'definition_likes': 0,
                'definition': 'What about this one',
                'definition_approved': true,
                'definition_author': 'Red',
                'examples': [ {
                    'example': 'Another example',
                    'example_author': 'Ron',
                    'example_approved': true
                } ],
                'definition_likes': 0,
     // related_terms : ''
    'status': 'publish' // or draft if you don't want to publish it

var createPost = new Promise( ( resolve, reject ) => {

    var headers = new Headers( {
        'Content-Type': 'application/json;charset=UTF-8',
        'X-WP-Nonce': magicalData.nonce
    } );
    console.log( JSON.stringify( postContent ) );

    var post = fetch( '<YOUR_WORDPRESS_URL>/wp-json/wp/v2/terms/', {
            credentials: 'same-origin',
            method: 'POST',
            body: JSON.stringify( postContent ),
            headers: headers
        } )
        .then( response => {
            if ( response.ok ) {
                return resolve( response );
            } else {
                reject( Error( response ) );
        } )
        .catch( error => {
            console.log( error );
        } );
} );

createPost.then( data => {
    // display the name of the blog
    console.log( data );

}, error => {
    console.log( error );
} );

Now if we visit the WordPress page, a new post will be generated and saved.

Of course this code is not suitable for a live page, but it shows you how to use WP REST API with JavaScript to create custom post entries in the database. You can evolve it into anything you like.