• 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 CSV bulk exporter block for Gutenberg using Vanilla JS Web Components

Step-by-Step Guide to building a custom CSV bulk exporter block for Gutenberg using Vanilla JS Web Components

Setting Up the Plugin Environment

We’ll begin by establishing a basic WordPress plugin structure. This involves creating a main plugin file and a directory for our block assets. For this example, we’ll name our plugin custom-csv-exporter.

Create a new folder named custom-csv-exporter in your WordPress installation’s wp-content/plugins/ directory. Inside this folder, create a PHP file named custom-csv-exporter.php.

Add the standard WordPress plugin header to custom-csv-exporter.php:

<?php
/**
 * Plugin Name: Custom CSV Exporter
 * Description: A custom Gutenberg block for exporting data to CSV.
 * Version: 1.0.0
 * Author: Your Name
 * Author URI: https://yourwebsite.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: custom-csv-exporter
 */

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

/**
 * Register the block.
 */
function custom_csv_exporter_register_block() {
	// Register the script for the editor.
	wp_register_script(
		'custom-csv-exporter-editor-script',
		plugins_url( 'build/index.js', __FILE__ ),
		array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n' ),
		filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' )
	);

	// Register the script for the front-end.
	wp_register_script(
		'custom-csv-exporter-frontend-script',
		plugins_url( 'build/frontend.js', __FILE__ ),
		array(), // No dependencies for the frontend script itself
		filemtime( plugin_dir_path( __FILE__ ) . 'build/frontend.js' )
	);

	// Register the block.
	register_block_type( 'custom-csv-exporter/csv-exporter', array(
		'editor_script' => 'custom-csv-exporter-editor-script',
		'script'        => 'custom-csv-exporter-frontend-script',
		'editor_style'  => 'custom-csv-exporter-editor-style', // Will be defined later
		'style'         => 'custom-csv-exporter-style',       // Will be defined later
		'attributes'    => array(
			'data' => array(
				'type'    => 'string',
				'default' => '[]', // Store data as JSON string
			),
			'fileName' => array(
				'type'    => 'string',
				'default' => 'export.csv',
			),
		),
	) );

	// Register editor styles.
	wp_register_style(
		'custom-csv-exporter-editor-style',
		plugins_url( 'build/index.css', __FILE__ ),
		array( 'wp-edit-blocks' ),
		filemtime( plugin_dir_path( __FILE__ ) . 'build/index.css' )
	);

	// Register front-end styles.
	wp_register_style(
		'custom-csv-exporter-style',
		plugins_url( 'build/style.css', __FILE__ ),
		array(),
		filemtime( plugin_dir_path( __FILE__ ) . 'build/style.css' )
	);
}
add_action( 'init', 'custom_csv_exporter_register_block' );

Next, create a build directory within your plugin folder. This directory will house the compiled JavaScript and CSS files. We’ll also need a src directory for our source files.

Your plugin structure should now look like this:

custom-csv-exporter/
├── build/
├── src/
└── custom-csv-exporter.php

Frontend JavaScript with Vanilla JS Web Components

For the frontend rendering of our block, we’ll leverage Vanilla JavaScript and Web Components. This approach provides encapsulation and reusability. We’ll define a custom element that handles the CSV export logic.

Create a file named csv-exporter-element.js inside the src/ directory.

// src/csv-exporter-element.js

class CsvExporterElement extends HTMLElement {
	constructor() {
		super();
		this.attachShadow({ mode: 'open' });
		this.data = [];
		this.fileName = 'export.csv';
	}

	connectedCallback() {
		this.render();
		this.setupEventListeners();
	}

	static get observedAttributes() {
		return ['data', 'fileName'];
	}

	attributeChangedCallback(name, oldValue, newValue) {
		if (name === 'data') {
			try {
				this.data = JSON.parse(newValue);
			} catch (e) {
				console.error('Failed to parse data attribute:', e);
				this.data = [];
			}
		} else if (name === 'fileName') {
			this.fileName = newValue || 'export.csv';
		}
		this.render(); // Re-render when attributes change
	}

	render() {
		this.shadowRoot.innerHTML = `
			
			
`; } setupEventListeners() { const exportButton = this.shadowRoot.getElementById('export-csv-button'); if (exportButton) { exportButton.addEventListener('click', () => this.exportToCsv()); } } exportToCsv() { if (!this.data || this.data.length === 0) { alert('No data available to export.'); return; } const csvRows = []; // Assuming the first element's keys are the headers const headers = Object.keys(this.data[0]); csvRows.push(headers.join(',')); for (const row of this.data) { const values = headers.map(header => { const escaped = String(row[header] || '').replace(/"/g, '""'); // Escape quotes return `"${escaped}"`; // Enclose in quotes }); csvRows.push(values.join(',')); } const blob = new Blob([csvRows.join('\n')], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); if (link.download !== undefined) { // Feature detection const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', this.fileName); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); // Clean up } } } // Define the custom element if (!customElements.get('csv-exporter-element')) { customElements.define('csv-exporter-element', CsvExporterElement); }

This Web Component:

  • Attaches a shadow DOM for encapsulation.
  • Observes data and fileName attributes.
  • Parses the data attribute (expected to be a JSON string) into an array.
  • Renders a simple button within its shadow DOM.
  • Attaches a click listener to the button to trigger the CSV export.
  • Implements the CSV generation logic, including basic escaping for quoted fields.
  • Uses the File API to create a downloadable CSV file.

Gutenberg Block Registration and Editor Logic

Now, we’ll create the JavaScript file that registers our Gutenberg block and handles its behavior within the WordPress editor. This file will use the WordPress Block Editor API and will also be responsible for instantiating our Web Component.

Create a file named index.js inside the src/ directory.

// src/index.js
const { registerBlockType } = wp.blocks;
const { InspectorControls, useBlockProps } = wp.blockEditor;
const { PanelBody, TextControl, Button } = wp.components;
const { useState, useEffect } = wp.element;
const { __ } = wp.i18n;

// Import the Web Component definition
import './csv-exporter-element';

registerBlockType('custom-csv-exporter/csv-exporter', {
	title: __('Custom CSV Exporter', 'custom-csv-exporter'),
	icon: 'download',
	category: 'widgets',
	attributes: {
		data: {
			type: 'string',
			default: '[]',
		},
		fileName: {
			type: 'string',
			default: 'export.csv',
		},
	},

	edit: ({ attributes, setAttributes }) => {
		const { data, fileName } = attributes;
		const [jsonData, setJsonData] = useState(JSON.parse(data));
		const [tempDataInput, setTempDataInput] = useState(data); // For the textarea input

		// Update block attributes when internal state changes
		useEffect(() => {
			setAttributes({ data: JSON.stringify(jsonData) });
		}, [jsonData]);

		useEffect(() => {
			setAttributes({ fileName: fileName });
		}, [fileName]);

		const handleDataChange = (event) => {
			setTempDataInput(event.target.value);
			try {
				const parsedData = JSON.parse(event.target.value);
				setJsonData(parsedData);
			} catch (e) {
				console.error('Invalid JSON input:', e);
				// Optionally show an error to the user
			}
		};

		const handleFileNameChange = (value) => {
			setAttributes({ fileName: value });
		};

		const blockProps = useBlockProps();

		// A simple way to display the data in the editor for demonstration
		// In a real-world scenario, you'd likely fetch this data dynamically
		const displayData = jsonData.length > 0 ? jsonData : [{ message: 'No data loaded. Paste JSON into the input below.' }];

		return (
			<>
				<InspectorControls>
					<PanelBody title={__('Export Settings', 'custom-csv-exporter')} initialOpen={true}>
						<TextControl
							label={__('File Name', 'custom-csv-exporter')}
							value={fileName}
							onChange={handleFileNameChange}
							help={__('Enter the desired name for the CSV file (e.g., my-data.csv).', 'custom-csv-exporter')}
						/>
						<TextareaControl
							label={__('JSON Data', 'custom-csv-exporter')}
							value={tempDataInput}
							onChange={handleDataChange}
							help={__('Paste your data here as a JSON array of objects.', 'custom-csv-exporter')}
							rows={10}
						/>
					</PanelBody>
				</InspectorControls>
				<div {...blockProps}>
					<h4>{__('CSV Exporter Block', 'custom-csv-exporter')}</h4>
					<p>{__('Configure data and file name in the block settings sidebar.', 'custom-csv-exporter')}</p>
					{/* We don't render the Web Component directly in the editor for simplicity,
					    but we can simulate its presence or use it if needed for live preview.
					    For this example, we'll just show a placeholder and rely on the inspector. */}
					<div style={{ marginTop: '20px', textAlign: 'center' }}>
						<em>{__('Export button will appear on the frontend.', 'custom-csv-exporter')}</em>
					</div>
				</div>
			</>
		);
	},

	save: ({ attributes }) => {
		const { data, fileName } = attributes;
		// We render the custom element here, passing attributes as properties/data attributes
		// The Web Component will pick these up.
		// Using data-* attributes is a common way to pass data to Web Components from HTML.
		return (
			<csv-exporter-element data={data} fileName={fileName}></csv-exporter-element>
		);
	},
});

Key aspects of the index.js file:

  • registerBlockType: Registers our custom block with WordPress.
  • title, icon, category: Standard block registration properties.
  • attributes: Defines the data that the block will store. We use data (as a JSON string) and fileName.
  • edit function: This is the React component that renders the block in the Gutenberg editor.
    • It uses InspectorControls to add a sidebar panel for configuring the file name and pasting JSON data.
    • useState and useEffect hooks manage the internal state of the editor view and synchronize it with block attributes.
    • A TextareaControl is used for inputting JSON data, with basic parsing to validate it.
    • Crucially, the edit function does not render the <csv-exporter-element> directly. Instead, it provides controls in the sidebar. The actual export button is handled by the Web Component on the frontend.
  • save function: This function defines how the block’s output is saved to the database.
    • It renders our custom Web Component, <csv-exporter-element>.
    • The block’s attributes (data and fileName) are passed as attributes to the custom element. The Web Component’s attributeChangedCallback will pick these up.

Frontend JavaScript for Block Rendering

The save function in our block registration renders the <csv-exporter-element>. However, for this element to be correctly instantiated and function on the frontend when the page loads, we need a separate JavaScript file that ensures the Web Component is registered and ready. This file will be enqueued as the block’s script.

Create a file named frontend.js inside the src/ directory.

// src/frontend.js

// Import the Web Component definition.
// This ensures that customElements.define() is called when the script loads.
import './csv-exporter-element';

// No further action is strictly needed here if the Web Component
// definition itself handles its rendering and event binding.
// The 'save' function in index.js already outputs the  tag.
// When the browser parses the HTML, it will find this tag.
// If the custom element has been defined (which it has, via the import above),
// the browser will automatically instantiate it.
// The attributes passed in the save function (data, fileName) will be
// available to the Web Component instance.

console.log('Custom CSV Exporter frontend script loaded.');

This frontend.js file is minimal. Its primary purpose is to import the Web Component definition. When WordPress enqueues this script for the block, the import statement ensures that customElements.define('csv-exporter-element', CsvExporterElement); is executed. The browser then automatically upgrades any instances of <csv-exporter-element> found in the HTML output generated by the save function.

Styling the Block

We need CSS for both the editor and the frontend. Create two files in the src/ directory: index.css for the editor styles and style.css for the frontend styles.

src/index.css (Editor Styles)

/* src/index.css */

.wp-block-custom-csv-exporter-csv-exporter {
	border: 1px dashed #999;
	padding: 15px;
	background-color: #f0f0f0;
	text-align: center;
}

.wp-block-custom-csv-exporter-csv-exporter h4 {
	margin-top: 0;
	color: #333;
}

src/style.css (Frontend Styles)

/* src/style.css */

/* Styles for the Web Component itself can be defined here if needed,
   but the Web Component's shadow DOM has its own internal styles.
   This file is for any global styles that might affect the block container
   on the frontend, or if you want to style the host element from outside. */

.wp-block-custom-csv-exporter-csv-exporter {
	/* Example: Add some margin on the frontend */
	margin-bottom: 20px;
}

Build Process with @wordpress/scripts

To compile our JavaScript and CSS from the src/ directory into the build/ directory, we’ll use the @wordpress/scripts package. This package provides a convenient way to handle the build process for WordPress block development.

First, navigate to your plugin’s root directory (custom-csv-exporter/) in your terminal and initialize a Node.js project:

cd wp-content/plugins/custom-csv-exporter
npm init -y

Next, install the @wordpress/scripts package as a development dependency:

npm install @wordpress/scripts --save-dev

Now, add the following scripts to your package.json file:

{
  "name": "custom-csv-exporter",
  "version": "1.0.0",
  "description": "",
  "main": "custom-csv-exporter.php",
  "scripts": {
    "build": "wp-scripts build",
    "start": "wp-scripts start"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@wordpress/scripts": "^26.10.0"
  }
}

You can now run the build process:

npm run build

This command will compile:

  • src/index.js into build/index.js (for the editor).
  • src/index.css into build/index.css (for the editor).
  • src/frontend.js into build/frontend.js (for the frontend).
  • src/style.css into build/style.css (for the frontend).

The npm run start command will watch for changes in your src/ directory and automatically recompile the assets, which is very useful during development.

Activation and Usage

After running npm run build, activate the “Custom CSV Exporter” plugin from your WordPress admin area. Then, navigate to the post or page editor.

Add a new block and search for “Custom CSV Exporter”. Insert the block.

In the block’s settings sidebar (click the block and then the cog icon in the toolbar), you will find the “Export Settings” panel. Here you can:

  • Enter the desired File Name for your export.
  • Paste your data as a JSON array of objects into the JSON Data textarea. For example:
    [
      {"id": 1, "name": "Alice", "email": "[email protected]"},
      {"id": 2, "name": "Bob", "email": "[email protected]"},
      {"id": 3, "name": "Charlie", "email": "[email protected]"}
    ]
    

Save the post/page and view it on the frontend. You should see the “Export to CSV” button rendered by the Web Component. Clicking this button will download the CSV file with the data and filename you configured.

Advanced Considerations and Enhancements

This implementation provides a solid foundation. Here are some areas for further development:

  • Dynamic Data Fetching: Instead of a textarea, implement a mechanism to fetch data dynamically (e.g., from a custom post type, a specific database table, or an external API) using WordPress REST API or AJAX. This would involve adding more controls in the edit function to select data sources.
  • Data Validation and Error Handling: Implement more robust validation for the JSON input and provide clearer feedback to the user in the editor if the data is malformed or if export fails.
  • CSV Formatting Options: Add options for delimiter (comma, semicolon, tab), enclosure characters, and handling of special characters within data fields. The current implementation uses basic double-quote escaping.
  • Large Datasets: For very large datasets, consider server-side CSV generation to avoid browser memory limitations and timeouts. This would involve an AJAX request to a WordPress AJAX handler that generates and streams the CSV.
  • Internationalization (i18n): Ensure all user-facing strings are translatable using WordPress’s i18n functions (__, _x, etc.). We’ve included basic internationalization in the block registration.
  • Accessibility: Ensure the button and any associated controls are accessible (e.g., proper ARIA attributes, keyboard navigation).
  • Web Component Styling: For more complex styling, consider passing CSS variables from the block editor to the Web Component’s shadow DOM or using CSS custom properties.

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 Classic Core PHP extensions utilizing modern Rewrite API custom endpoints schemas
  • Troubleshooting hook execution order overrides in production when using modern Understrap styling structures wrappers
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency ActiveCampaign automation API handlers
  • How to refactor legacy knowledge base document categories queries using modern WP_Query and custom Transient caching
  • Building secure B2B pricing grids with custom WordPress Options API endpoints and role overrides

Categories

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

Recent Posts

  • How to build custom Classic Core PHP extensions utilizing modern Rewrite API custom endpoints schemas
  • Troubleshooting hook execution order overrides in production when using modern Understrap styling structures wrappers
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency ActiveCampaign automation API handlers

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • 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