• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Step-by-Step Guide to building a custom REST API rate limiter block for Gutenberg using Vue micro-frontends

Step-by-Step Guide to building a custom REST API rate limiter block for Gutenberg using Vue micro-frontends

Project Setup and Dependencies

This guide assumes a foundational understanding of WordPress plugin development and modern JavaScript tooling. We’ll be leveraging Vue.js for our Gutenberg block’s frontend and a custom PHP solution for the backend API rate limiting. The project structure will follow standard WordPress plugin conventions, with a dedicated directory for our block’s assets.

First, create a new plugin directory within your WordPress installation’s wp-content/plugins/ folder. Let’s name it custom-api-rate-limiter.

Inside this directory, we’ll need a custom-api-rate-limiter.php file to serve as the main plugin file. We’ll also create a src/ directory for our JavaScript and Vue components, and a build/ directory for compiled assets.

For managing JavaScript dependencies and building our Vue components, we’ll use npm and a build tool like Webpack. Initialize your project with npm:

cd wp-content/plugins/custom-api-rate-limiter
npm init -y

Next, install the necessary development dependencies:

npm install --save-dev @wordpress/scripts vue vue-loader vue-template-compiler

We’ll also need to configure Webpack. Create a webpack.config.js file in the root of your plugin directory.

The @wordpress/scripts package provides a sensible default Webpack configuration that’s optimized for WordPress development. We’ll extend it to include Vue loader support.

Webpack Configuration for Vue Integration

The webpack.config.js file will look like this. This configuration ensures that Vue single-file components (.vue files) are correctly processed and compiled.

Ensure you have the vue-loader and vue-template-compiler installed as dev dependencies.

const path = require('path');
const { getWebpackConfig } = require('@wordpress/scripts/build/webpack-config');

module.exports = getWebpackConfig({
	entry: {
		// Your main JS entry point for the block
		'editor': './src/index.js',
		'block': './src/frontend.js', // For frontend rendering if needed
	},
	output: {
		path: path.resolve(__dirname, 'build'),
		filename: '[name].js',
	},
	module: {
		rules: [
			{
				test: /\.vue$/,
				loader: 'vue-loader',
			},
			// Add other rules as needed, e.g., for CSS or images
		],
	},
	plugins: [
		// Vue loader plugin is automatically configured by vue-loader itself
		// when imported and used in the module rules.
	],
});

Now, update your package.json to include build scripts:

{
  "name": "custom-api-rate-limiter",
  "version": "1.0.0",
  "description": "Custom API Rate Limiter Gutenberg Block",
  "main": "custom-api-rate-limiter.php",
  "scripts": {
    "build": "wp-scripts build",
    "start": "wp-scripts start"
  },
  "keywords": ["wordpress", "gutenberg", "vue", "api", "rate limiter"],
  "author": "Your Name",
  "license": "GPL-2.0-or-later",
  "devDependencies": {
    "@wordpress/scripts": "^26.10.0",
    "vue": "^3.3.4",
    "vue-loader": "^17.2.2",
    "vue-template-compiler": "^2.7.14"
  }
}

Run npm run build to compile your assets. This will create the build/ directory with the necessary JavaScript files.

Gutenberg Block Registration (PHP)

In your custom-api-rate-limiter.php file, register the Gutenberg block. This involves defining the block’s attributes, its editor script, and its render callback.

<?php
/**
 * Plugin Name: Custom API Rate Limiter
 * Description: A Gutenberg block to configure API rate limiting.
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL-2.0-or-later
 * Text Domain: custom-api-rate-limiter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Registers the block using the metadata loaded from the `block.json` file.
 * Behind the scenes, it registers also all assets so they can be enqueued
 * through the block editor in the corresponding context.
 *
 * @see https://developer.wordpress.org/reference/functions/register_block_type/
 */
function custom_api_rate_limiter_block_init() {
	register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'custom_api_rate_limiter_block_init' );

For this to work, you’ll need a block.json file in the root of your plugin directory. This file describes your block to WordPress.

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "custom-api-rate-limiter/block",
  "version": "1.0.0",
  "title": "API Rate Limiter",
  "category": "widgets",
  "icon": "shield-alt",
  "description": "Configure API rate limiting settings.",
  "keywords": ["api", "rate limiter", "security"],
  "attributes": {
    "requestsPerMinute": {
      "type": "number",
      "default": 60
    },
    "burstLimit": {
      "type": "number",
      "default": 10
    },
    "enabled": {
      "type": "boolean",
      "default": true
    }
  },
  "editorScript": "file:./build/editor.js",
  "editorStyle": "file:./build/index.css",
  "style": "file:./build/style-index.css",
  "viewScript": "file:./build/block.js"
}

The editorScript points to our main editor JavaScript file, which will load our Vue application. The viewScript is for any frontend rendering logic if your block needs to do more than just display static content.

Vue Component for the Block Editor

Create the main editor entry point at src/index.js. This file will import Vue and register your Gutenberg block component.

// src/index.js
import { registerBlockType } from '@wordpress/blocks';
import App from './App.vue'; // Your main Vue component

registerBlockType( 'custom-api-rate-limiter/block', {
	edit: App,
	save: () => null, // We'll handle saving via REST API or post meta
} );

Now, create the main Vue component at src/App.vue. This component will house the UI for configuring rate limiting settings.

<!-- src/App.vue -->
<template>
	<div class="api-rate-limiter-block">
		<h3>API Rate Limiter Settings</h3>
		<div class="components-base-control">
			<label class="components-base-control__label">Requests Per Minute</label>
			<input
				type="number"
				:value="requestsPerMinute"
				@input="updateAttribute('requestsPerMinute', parseInt($event.target.value))"
				min="1"
				class="components-text-control__input"
			/>
		</div>
		<div class="components-base-control">
			<label class="components-base-control__label">Burst Limit</label>
			<input
				type="number"
				:value="burstLimit"
				@input="updateAttribute('burstLimit', parseInt($event.target.value))"
				min="1"
				class="components-text-control__input"
			/>
		</div>
		<div class="components-base-control">
			<label class="components-base-control__label">Enable Rate Limiter</label>
			<input
				type="checkbox"
				:checked="enabled"
				@change="updateAttribute('enabled', $event.target.checked)"
				class="components-checkbox-control__input"
			/>
		</div>
	</div>
</template>

<script>
import { useBlockProps } from '@wordpress/block-editor';

export default {
	props: {
		attributes: {
			type: Object,
			required: true,
		},
		setAttributes: {
			type: Function,
			required: true,
		},
	},
	setup(props) {
		const blockProps = useBlockProps();

		const updateAttribute = (key, value) => {
			props.setAttributes({ [key]: value });
		};

		return {
			blockProps,
			requestsPerMinute: props.attributes.requestsPerMinute,
			burstLimit: props.attributes.burstLimit,
			enabled: props.attributes.enabled,
			updateAttribute,
		};
	},
};
</script>

<style>
/* Add some basic styling for the block editor */
.api-rate-limiter-block {
	padding: 15px;
	border: 1px solid #ccc;
	background-color: #f9f9f9;
}
.components-base-control {
	margin-bottom: 10px;
}
.components-base-control__label {
	display: block;
	margin-bottom: 5px;
	font-weight: bold;
}
.components-text-control__input,
.components-checkbox-control__input {
	width: 100%;
	padding: 8px;
	border: 1px solid #ccc;
	box-sizing: border-box;
}
.components-checkbox-control__input {
	width: auto;
	margin-top: 5px;
}
</style>

In this Vue component:

  • We use useBlockProps from @wordpress/block-editor for standard block wrapper props.
  • The attributes prop provides the current state of the block’s attributes.
  • The setAttributes prop is a function to update these attributes.
  • We bind input fields to the attributes and use the @input or @change events to call setAttributes.
  • The save: () => null in index.js signifies that the block’s content will not be directly saved to the post content. Instead, we’ll manage these settings via post meta or a custom REST API endpoint.

Backend API Rate Limiting Logic (PHP)

The actual rate limiting logic will reside in your WordPress REST API. We’ll hook into the REST API request process to enforce limits. For simplicity, this example uses in-memory storage (transients API) for tracking requests. For production, consider a more robust solution like Redis or a dedicated rate-limiting service.

First, let’s define a function to get the rate limiting settings. These settings could be stored in post meta associated with the current post, or in plugin options. For this example, we’ll assume they are stored in post meta.

<?php
// Add this to custom-api-rate-limiter.php

/**
 * Retrieves the rate limiting settings for the current post.
 *
 * @return array Rate limiting settings.
 */
function get_api_rate_limiter_settings() {
	$settings = array(
		'enabled'           => get_post_meta( get_the_ID(), '_api_rate_limiter_enabled', true ),
		'requests_per_minute' => get_post_meta( get_the_ID(), '_api_rate_limiter_requests_per_minute', true ),
		'burst_limit'       => get_post_meta( get_the_ID(), '_api_rate_limiter_burst_limit', true ),
	);

	// Set defaults if not found
	if ( empty( $settings['enabled'] ) ) {
		$settings['enabled'] = true; // Default to enabled
	}
	if ( empty( $settings['requests_per_minute'] ) || ! is_numeric( $settings['requests_per_minute'] ) ) {
		$settings['requests_per_minute'] = 60; // Default 60 requests/min
	}
	if ( empty( $settings['burst_limit'] ) || ! is_numeric( $settings['burst_limit'] ) ) {
		$settings['burst_limit'] = 10; // Default burst limit
	}

	// Ensure values are correct types
	$settings['enabled'] = filter_var( $settings['enabled'], FILTER_VALIDATE_BOOLEAN );
	$settings['requests_per_minute'] = intval( $settings['requests_per_minute'] );
	$settings['burst_limit'] = intval( $settings['burst_limit'] );

	return $settings;
}

/**
 * Saves the rate limiting settings to post meta.
 * This function would be called from a REST API endpoint or a settings page.
 *
 * @param int $post_id The post ID.
 * @param array $settings The settings to save.
 */
function save_api_rate_limiter_settings( $post_id, $settings ) {
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return false;
	}

	update_post_meta( $post_id, '_api_rate_limiter_enabled', $settings['enabled'] ? 'true' : 'false' );
	update_post_meta( $post_id, '_api_rate_limiter_requests_per_minute', intval( $settings['requests_per_minute'] ) );
	update_post_meta( $post_id, '_api_rate_limiter_burst_limit', intval( $settings['burst_limit'] ) );

	return true;
}

Now, let’s implement the rate limiting check. We’ll hook into the rest_pre_dispatch filter, which runs before a REST API request is dispatched to its handler.

<?php
// Add this to custom-api-rate-limiter.php

/**
 * Checks if the current REST API request exceeds the rate limit.
 *
 * @param mixed $result The result of the dispatch.
 * @param WP_REST_Server $server The REST server instance.
 * @param WP_REST_Request $request The current request object.
 * @return WP_Error|mixed The result or a WP_Error if rate limit is exceeded.
 */
function check_api_rate_limit( $result, $server, $request ) {
	// Only apply rate limiting to specific endpoints or globally if desired.
	// For this example, we'll apply it to all requests for demonstration.
	// In a real-world scenario, you'd likely want to target specific API endpoints.

	$settings = get_api_rate_limiter_settings();

	if ( ! $settings['enabled'] ) {
		return $result; // Rate limiting is disabled.
	}

	$user_id = get_current_user_id();
	// Use IP address for anonymous users or a more robust identifier.
	$identifier = ! empty( $user_id ) ? 'user_' . $user_id : 'ip_' . $_SERVER['REMOTE_ADDR'];
	$cache_key_base = 'api_rate_limit_' . $identifier;

	$current_time = time();
	$window_start = $current_time - 60; // 60 seconds for a minute window

	// Get request timestamps from cache
	$request_timestamps = get_transient( $cache_key_base );
	if ( ! is_array( $request_timestamps ) ) {
		$request_timestamps = array();
	}

	// Filter out timestamps older than the window
	$request_timestamps = array_filter( $request_timestamps, function( $timestamp ) use ( $window_start ) {
		return $timestamp >= $window_start;
	} );

	// Check burst limit
	if ( count( $request_timestamps ) >= $settings['burst_limit'] ) {
		return new WP_Error(
			'rest_api_rate_limit_exceeded',
			__( 'Too many requests in a short period. Please try again later.', 'custom-api-rate-limiter' ),
			array( 'status' => 429 ) // HTTP 429 Too Many Requests
		);
	}

	// Add current request timestamp
	$request_timestamps[] = $current_time;

	// Save updated timestamps back to transient, with a slightly longer expiration
	// to cover the full minute window plus a buffer.
	set_transient( $cache_key_base, $request_timestamps, MINUTE_IN_SECONDS + 10 );

	// Check requests per minute limit (this is a simpler check, a sliding window is more accurate)
	if ( count( $request_timestamps ) > $settings['requests_per_minute'] ) {
		// This check is a bit redundant with the burst limit if burst is smaller than RPM.
		// A more sophisticated approach would use a sliding window algorithm.
		// For simplicity here, we'll just log a warning or potentially block.
		// In a true sliding window, you'd calculate the number of requests within the *exact* last 60 seconds.
		// This implementation is a fixed window with a burst check.
	}

	return $result;
}
add_filter( 'rest_pre_dispatch', 'check_api_rate_limit', 10, 3 );

Important Considerations for the Backend Logic:

  • Identifier: The example uses user_id or REMOTE_ADDR. For better accuracy and to handle proxies, consider using headers like X-Forwarded-For, but be aware of spoofing risks.
  • Storage: Transients are suitable for development or low-traffic sites. For production, use Redis (via a plugin like Redis Object Cache) or a dedicated rate-limiting service.
  • Sliding Window vs. Fixed Window: The provided logic uses a simplified fixed window. A true sliding window algorithm offers more accurate rate limiting by considering requests within the exact last 60 seconds, not just within a fixed minute block. Implementing a sliding window typically involves storing timestamps and calculating the count within the dynamic window.
  • Endpoint Specificity: The example applies the check globally. You’ll likely want to modify the check_api_rate_limit function to only apply to specific REST API routes using $request->get_route().
  • Error Handling: The WP_Error with status 429 is the standard way to signal rate limiting.

Saving Block Attributes to Post Meta

Since our block’s save function returns null, we need a mechanism to save the block’s attributes (rate limiting settings) to post meta. We can achieve this by hooking into the REST API save process for posts.

<?php
// Add this to custom-api-rate-limiter.php

/**
 * Saves block attributes to post meta when a post is saved via the REST API.
 *
 * @param WP_Post $post The post object.
 * @param WP_REST_Request $request The REST request object.
 * @return WP_Post The modified post object.
 */
function save_rate_limiter_block_meta( $post, $request ) {
	if ( ! current_user_can( 'edit_post', $post->ID ) ) {
		return $post;
	}

	$meta_data = $request->get_param( 'meta' );

	if ( isset( $meta_data['_api_rate_limiter_enabled'] ) ) {
		update_post_meta( $post->ID, '_api_rate_limiter_enabled', $meta_data['_api_rate_limiter_enabled'] ? 'true' : 'false' );
	}
	if ( isset( $meta_data['_api_rate_limiter_requests_per_minute'] ) ) {
		update_post_meta( $post->ID, '_api_rate_limiter_requests_per_minute', intval( $meta_data['_api_rate_limiter_requests_per_minute'] ) );
	}
	if ( isset( $meta_data['_api_rate_limiter_burst_limit'] ) ) {
		update_post_meta( $post->ID, '_api_rate_limiter_burst_limit', intval( $meta_data['_api_rate_limiter_burst_limit'] ) );
	}

	return $post;
}
add_filter( 'rest_after_insert_post', 'save_rate_limiter_block_meta', 10, 2 );

This hook ensures that when the post is saved (e.g., via the Gutenberg editor’s save action), any meta fields corresponding to our block’s attributes are updated. The Gutenberg editor automatically sends block attributes as part of the `meta` payload in the REST API request when saving a post.

Frontend Rendering (Optional)

If your block needs to display information on the frontend (e.g., a status indicator), you would implement a render_callback in your block.json and corresponding PHP function, or use the viewScript defined in block.json for client-side rendering.

For this specific rate limiter block, frontend rendering might not be necessary, as its primary function is backend enforcement. However, if you wanted to display a message like “API access is currently limited for this resource,” you could add a render_callback.

Example block.json update:

{
  // ... other properties
  "render": "file:./render.php"
}

And a corresponding render.php file:

<!-- render.php -->
<?php
$enabled = get_post_meta( get_the_ID(), '_api_rate_limiter_enabled', true );
$requests_per_minute = get_post_meta( get_the_ID(), '_api_rate_limiter_requests_per_minute', true );
$burst_limit = get_post_meta( get_the_ID(), '_api_rate_limiter_burst_limit', true );

if ( $enabled ) : ?>
	<div class="api-rate-limiter-frontend-notice">
		API Rate Limiting is enabled for this resource (RPM: <?php echo esc_html( $requests_per_minute ); ?>, Burst: <?php echo esc_html( $burst_limit ); ?>).
	</div>
<?php endif; ?>

Remember to run npm run build after making changes to your Vue components or JavaScript entry points to recompile the assets.

Testing and Deployment

Thoroughly test the rate limiting functionality by making repeated requests to your WordPress REST API endpoints. Use tools like curl or Postman to simulate API calls and observe the 429 responses when limits are exceeded. Verify that the settings saved via the Gutenberg block are correctly applied by the backend logic.

For deployment, ensure that your build process (npm run build) is executed, and the compiled assets in the build/ directory are included with your plugin files. The PHP code should be placed within your plugin’s main file or included appropriately.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • How to build custom Sage Roots modern environments extensions utilizing modern WordPress Database Class ($wpdb) schemas
  • How to build custom Genesis child themes extensions utilizing modern Filesystem API schemas
  • How to build custom Elementor custom widgets extensions utilizing modern Metadata API (add_post_meta) schemas
  • Troubleshooting PHP-FPM child process pool exhaustion in production when using modern Timber Twig templating engines wrappers
  • How to build custom WooCommerce core overrides extensions utilizing modern WordPress Database Class ($wpdb) schemas

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (617)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (826)
  • PHP (5)
  • PHP Development (32)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (594)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (173)
  • WordPress Theme Development (357)

Recent Posts

  • How to build custom Sage Roots modern environments extensions utilizing modern WordPress Database Class ($wpdb) schemas
  • How to build custom Genesis child themes extensions utilizing modern Filesystem API schemas
  • How to build custom Elementor custom widgets extensions utilizing modern Metadata API (add_post_meta) schemas

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (826)
  • Debugging & Troubleshooting (617)
  • Security & Compliance (594)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala