Custom endpoints for charts appearing inside a post


I have the following scenario, I have created some custom tables in my database that I would like to make available as secure endpoints that return JSON (simple GET endpoints like you would easily make in Laravel) so I can consume them using Charts.js for Interactive Charts to appear inside my posts. I was thinking of just having specific JS written for each post.

I’ve come across this article ( but was curious if there was a recommended approach for doing such a thing better utilising Sage’s functionality?

Many thanks

That article is pretty much what I would have done. The permission checks are done in the register_rest_route. For WP request to the REST API, you’d have to pass a nonce to the header or you can use a plugin to use passwords.

Thank you for your response, are you able to perhaps point me to a reference on the WP request to the REST API via passing a nonce to the header?

Sure, there’s several files you gotta modify and I will be using demyx as my namespace.

Localizing a global array variable that can be accessible anywhere on the frontend. For this example, I’ve included the REST url endpoint and nonce; accessible via and demyx.nonce in your .js files.

 * Theme assets
add_action('wp_enqueue_scripts', function () {
    wp_enqueue_style('sage/main.css', asset_path('styles/main.css'), false, null);
    wp_enqueue_script('sage/main.js', asset_path('scripts/main.js'), ['jquery'], null, true);
    wp_localize_script('sage/main.js', 'demyx', [
        'rest'  => home_url() . '/wp-json/demyx/v1',
        'nonce' => wp_create_nonce('wp_rest'),

    if (is_single() && comments_open() && get_option('thread_comments')) {
}, 100);

Must add your global variable, otherwise webpack will say demyx is not defined.

module.exports = {
  'root': true,
  'extends': 'eslint:recommended',
  'globals': {
    'wp': true,
    'demyx': true,

I like to put specific functionalities of WordPress into their own files.


namespace App;
use WP_Query;
use WP_Error;
 *      All of WP REST access is only available to logged in users (aka you)
add_filter('rest_authentication_errors', function($result) {
    if (!empty( $result )) {
        return $result;
    if (! is_user_logged_in()) {
        return new WP_Error('rest_not_logged_in', 'You are not currently logged in.', array( 'status' => 401));
    return $result;
 *      REST endpoints
add_action('rest_api_init', function() {
    *      Custom endpoint
    *      https://domain.tld/wp-json/demyx/v1/custom
    register_rest_route('demyx/v1', '/custom', array(
        'methods' => ['GET'],
        'callback' => function() {
            // Gets data from the ajax request
            $get_custom_data    = (empty($_GET['custom_data']) ) ? null : sanitize_text_field($_GET['custom_data']);
            $data               = [];
            // Do your wp_query() or custom PHP query here

            return $data;
        'permission_callback' => function () {
            // Permission check, returns a boolean
            return current_user_can('edit_others_posts');

Include rest.php into the fold.

 * Sage required files
 * The mapped array determines the code library included in your theme.
 * Add or remove files to the array as needed. Supports child theme overrides.
array_map(function ($file) use ($sage_error) {
    $file = "../app/{$file}.php";
    if (!locate_template($file, true, true)) {
        $sage_error(sprintf(__('Error locating <code>%s</code> for inclusion.', 'sage'), $file), 'File not found');
    }, [

I’m placing this in common.js as an example. Feel free to put this any file of your choosing. Probably have to make blog.js to fire in only blog posts?

export default {
  init() {
    // JavaScript to be fired on all pages
      url: + '/custom',
      method: 'GET',
      data: {
        custom_data: $('input').val(),
      beforeSend: function (xhr) {
        xhr.setRequestHeader('X-WP-Nonce', demyx.nonce);
      success: function(data) {
  finalize() {
    // JavaScript to be fired on all pages, after page specific JS is fired

Let me know if I’ve missed anything.

This topic was automatically closed after 42 days. New replies are no longer allowed.