• 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 bulk image watermarker block for Gutenberg using SolidJS high-performance reactive components

Step-by-Step Guide to building a custom bulk image watermarker block for Gutenberg using SolidJS high-performance reactive components

Gutenberg Block Development with SolidJS: A High-Performance Watermarking Solution

This guide details the construction of a custom Gutenberg block for WordPress that enables bulk image watermarking. We will leverage SolidJS, a modern reactive JavaScript library, to build a high-performance, user-friendly interface within the WordPress editor. This approach prioritizes efficient DOM manipulation and a declarative component model, leading to a smoother user experience compared to traditional jQuery-based solutions.

Project Setup and Dependencies

Before diving into the code, ensure your WordPress development environment is set up. We’ll use Node.js and npm (or yarn) for managing our JavaScript build process. The core tools for Gutenberg block development are `@wordpress/scripts` for compilation and `@wordpress/blocks` for block registration.

Initialize a new npm package in your theme’s or plugin’s JavaScript directory:

npm init -y
npm install --save-dev @wordpress/scripts @wordpress/blocks solid-js

Next, configure your package.json to include build scripts. This will allow you to compile your SolidJS components into standard JavaScript that Gutenberg can understand.

{
  "name": "gutenberg-watermarker",
  "version": "1.0.0",
  "description": "Gutenberg block for bulk image watermarking with SolidJS.",
  "main": "build/index.js",
  "scripts": {
    "build": "wp-scripts build --experimental-jsx --jsx-import source=solid-js",
    "start": "wp-scripts start --experimental-jsx --jsx-import source=solid-js"
  },
  "keywords": ["wordpress", "gutenberg", "solidjs", "watermark"],
  "author": "Your Name",
  "license": "GPL-2.0-or-later",
  "devDependencies": {
    "@wordpress/blocks": "^12.0.0",
    "@wordpress/scripts": "^26.0.0",
    "solid-js": "^1.8.0"
  }
}

The --experimental-jsx --jsx-import source=solid-js flags are crucial for enabling JSX compilation specifically for SolidJS. The build script will compile your source files into the build/ directory, and the start script will watch for changes and recompile automatically during development.

Block Registration and Server-Side Rendering

Gutenberg blocks are registered using the registerBlockType function. For blocks that require server-side processing (like image manipulation), we define both an edit and a save function. The edit function renders the block’s interface in the editor, while the save function defines how the block’s content is stored in the database. For dynamic blocks, the save function can return null, indicating that the rendering is handled entirely by a PHP callback.

Create a PHP file (e.g., gutenberg-watermarker.php) in your plugin or theme’s root directory to register the block and its server-side rendering callback.

<?php
/**
 * Plugin Name: Gutenberg Watermarker Block
 * Description: A custom Gutenberg block for bulk image watermarking using SolidJS.
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL-2.0-or-later
 */

function gutenberg_watermarker_register_block() {
    register_block_type( 'gutenberg-watermarker/block', array(
        'editor_script' => 'gutenberg-watermarker-editor-script',
        'render_callback' => 'gutenberg_watermarker_render_callback',
        'attributes' => array(
            'images' => array(
                'type' => 'array',
                'default' => [],
            ),
            'watermarkText' => array(
                'type' => 'string',
                'default' => '© My Company',
            ),
            'watermarkPosition' => array(
                'type' => 'string',
                'default' => 'bottom-right',
            ),
            'watermarkOpacity' => array(
                'type' => 'number',
                'default' => 0.7,
            ),
        ),
    ) );
}
add_action( 'init', 'gutenberg_watermarker_register_block' );

function gutenberg_watermarker_editor_assets() {
    wp_enqueue_script(
        'gutenberg-watermarker-editor-script',
        plugins_url( 'build/index.js', __FILE__ ), // Adjust path if in theme
        array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n' ),
        filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' )
    );
}
add_action( 'enqueue_block_editor_assets', 'gutenberg_watermarker_editor_assets' );

function gutenberg_watermarker_render_callback( $attributes ) {
    // This function would typically handle the actual image processing
    // and return the HTML for the watermarked images.
    // For simplicity, we'll just return a placeholder.
    $images = isset( $attributes['images'] ) ? $attributes['images'] : [];
    $watermark_text = isset( $attributes['watermarkText'] ) ? esc_html( $attributes['watermarkText'] ) : '© My Company';

    ob_start();
    ?>
    <div class="wp-block-gutenberg-watermarker">
        <p>Watermarked Images (Preview not available in frontend):</p>
        <ul>
            <?php foreach ( $images as $image_id ) : ?>
                <li>Image ID: <?php echo $image_id; ?> (Watermarked with: <?php echo $watermark_text; ?>)</li>
            </?php endforeach; ?>
        </ul>
    </div>
    <?php
    return ob_get_clean();
}

In this PHP file:

  • register_block_type registers our block with a unique namespace gutenberg-watermarker/block.
  • We define initial attributes for images, watermark text, position, and opacity.
  • editor_script points to our compiled JavaScript file.
  • render_callback is set to gutenberg_watermarker_render_callback, which will be executed on the frontend to display the watermarked images.
  • enqueue_block_editor_assets is hooked to load our editor script only when the editor is active.

SolidJS Component for the Editor Interface

Now, let’s build the SolidJS component for the block’s editor interface. This component will handle image selection, watermark configuration, and a preview of the watermarking effect. Create a new file, e.g., src/editor.jsx.

import { createSignal, For, Show } from 'solid-js';
import { registerBlockType } from '@wordpress/blocks';
import {
    MediaUpload,
    MediaUploadCheck,
    InspectorControls,
} from '@wordpress/block-editor';
import { PanelBody, TextControl, RangeControl, Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

// Placeholder for image watermarking logic
const applyWatermark = async (imageUrl, watermarkText, position, opacity) => {
    // In a real-world scenario, this would involve:
    // 1. Fetching the image data.
    // 2. Using Canvas API or a server-side library to apply the watermark.
    // 3. Returning a data URL or a processed image URL.
    console.log('Applying watermark:', { imageUrl, watermarkText, position, opacity });
    // Simulate a delay and return the original URL for now
    await new Promise(resolve => setTimeout(resolve, 500));
    return imageUrl; // Placeholder
};

const EditorComponent = (props) => {
    const { attributes, setAttributes } = props;

    const [isProcessing, setIsProcessing] = createSignal(false);
    const [processedImages, setProcessedImages] = createSignal([]);

    const onSelectImages = (media) => {
        const newImages = media.map(item => ({
            id: item.id,
            url: item.url,
            processedUrl: null,
        }));
        setAttributes({ images: [...(attributes.images || []), ...newImages.map(img => img.id)] });
        setProcessedImages(prev => [...prev, ...newImages]);
    };

    const handleWatermark = async () => {
        setIsProcessing(true);
        const currentImages = processedImages().length > 0 ? processedImages() : (attributes.images || []).map(id => ({ id, url: `/wp-content/uploads/some-image-${id}.jpg` })); // Mock URLs

        const results = await Promise.all(
            currentImages.map(async (img) => {
                const processedUrl = await applyWatermark(
                    img.url,
                    attributes.watermarkText,
                    attributes.watermarkPosition,
                    attributes.watermarkOpacity
                );
                return { ...img, processedUrl };
            })
        );
        setProcessedImages(results);
        setIsProcessing(false);
    };

    const removeImage = (idToRemove) => {
        setAttributes({ images: attributes.images.filter(id => id !== idToRemove) });
        setProcessedImages(prev => prev.filter(img => img.id !== idToRemove));
    };

    return (
        <>
            <InspectorControls>
                <PanelBody title={__('Watermark Settings', 'gutenberg-watermarker')} initialOpen={true}>
                    <TextControl
                        label={__('Watermark Text', 'gutenberg-watermarker')}
                        value={attributes.watermarkText}
                        onChange={(value) => setAttributes({ watermarkText: value })}
                    />
                    <RangeControl
                        label={__('Opacity', 'gutenberg-watermarker')}
                        value={attributes.watermarkOpacity}
                        onChange={(value) => setAttributes({ watermarkOpacity: value })}
                        min={0}
                        max={1}
                        step={0.1}
                    />
                    <!-- Add controls for position, font size, etc. -->
                </PanelBody>
            </InspectorControls>

            <div className="gutenberg-watermarker-editor">
                <h3>{__('Bulk Image Watermarker', 'gutenberg-watermarker')}</h3>

                <MediaUploadCheck>
                    <MediaUpload
                        onSelect={onSelectImages}
                        allowedTypes={['image']}
                        multiple
                        value={attributes.images}
                        render={({ open }) => (
                            <Button
                                variant="primary"
                                onClick={open}
                                isBusy={isProcessing()}
                                disabled={isProcessing()}
                            >
                                {__('Select Images', 'gutenberg-watermarker')}
                            </Button>
                        )}
                    />
                </MediaUploadCheck>

                <Show when={attributes.images && attributes.images.length > 0}>
                    <div className="selected-images-list">
                        <h4>{__('Selected Images', 'gutenberg-watermarker')}</h4>
                        <ul>
                            <For each={processedImages() || attributes.images}>
                                {(imageOrId) => {
                                    const image = typeof imageOrId === 'object' ? imageOrId : { id: imageOrId, url: `/wp-content/uploads/image-${imageOrId}.jpg` }; // Mock URL
                                    return (
                                        <li key={image.id}>
                                            <img src={image.processedUrl || image.url} alt="" width="50" />
                                            <span>Image ID: {image.id}</span>
                                            <Button isSmall icon="trash" onClick={() => removeImage(image.id)} />
                                        </li>
                                    );
                                }}
                            </For>
                        </ul>
                        <Button
                            variant="secondary"
                            onClick={handleWatermark}
                            isBusy={isProcessing()}
                            disabled={isProcessing() || !attributes.images || attributes.images.length === 0}
                        >
                            {isProcessing() ? __('Watermarking...', 'gutenberg-watermarker') : __('Apply Watermark', 'gutenberg-watermarker')}
                        </Button>
                    </div>
                </Show>

                <!-- Add a preview area here if client-side processing is implemented -->
            </div>
        </>
    );
};

registerBlockType('gutenberg-watermarker/block', {
    title: __('Bulk Image Watermarker', 'gutenberg-watermarker'),
    icon: 'format-image',
    category: 'media',
    attributes: {
        images: {
            type: 'array',
            default: [],
        },
        watermarkText: {
            type: 'string',
            default: '© My Company',
        },
        watermarkPosition: {
            type: 'string',
            default: 'bottom-right',
        },
        watermarkOpacity: {
            type: 'number',
            default: 0.7,
        },
    },
    edit: EditorComponent,
    save: () => null, // Dynamic block, rendering handled by PHP
});

In this editor.jsx file:

  • We import necessary components from @wordpress/blocks, @wordpress/block-editor, @wordpress/components, and @wordpress/i18n.
  • SolidJS’s createSignal is used for managing component state (isProcessing, processedImages).
  • MediaUpload and MediaUploadCheck are WordPress components for handling media library interactions.
  • InspectorControls provides a sidebar panel for settings like watermark text and opacity.
  • The applyWatermark function is a placeholder. In a production environment, this would contain the actual image processing logic, likely using the Canvas API for client-side processing or making an AJAX request to a server-side endpoint for more robust manipulation.
  • The handleWatermark function orchestrates the watermarking process for selected images.
  • setAttributes is used to update the block’s attributes, which are then saved to the WordPress database.
  • The save: () => null indicates a dynamic block.

Styling the Block

Add some basic CSS to style your block in the editor. Create a file named src/style.scss:

.gutenberg-watermarker-editor {
    padding: 15px;
    border: 1px solid #ddd;
    border-radius: 4px;

    .selected-images-list {
        margin-top: 20px;
        border-top: 1px solid #eee;
        padding-top: 15px;

        ul {
            list-style: none;
            padding: 0;
            margin: 0;

            li {
                display: flex;
                align-items: center;
                margin-bottom: 10px;
                gap: 10px;

                img {
                    border: 1px solid #ccc;
                }

                span {
                    flex-grow: 1;
                }
            }
        }
    }
}

You’ll need to tell @wordpress/scripts to compile this SCSS file. Update your package.json scripts:

{
  // ... other configurations
  "scripts": {
    "build": "wp-scripts build --experimental-jsx --jsx-import source=solid-js && wp-scripts build --style=src/style.scss",
    "start": "wp-scripts start --experimental-jsx --jsx-import source=solid-js && wp-scripts start --style=src/style.scss"
  },
  // ... other configurations
}

Building and Activating the Block

Run the build command to compile your SolidJS components and SCSS:

npm run build

This will create a build/ directory containing index.js and style-index.css. Ensure your PHP file is correctly placed within your WordPress theme’s or plugin’s directory structure. If you placed the PHP file in the root of a plugin, the paths in the PHP file (e.g., plugins_url, plugin_dir_path) should be correct. If it’s in a theme, adjust accordingly.

Activate the plugin or ensure the theme is active. Then, navigate to the WordPress editor. You should now be able to add the “Bulk Image Watermarker” block to your posts or pages.

Advanced Considerations and Future Enhancements

Client-Side vs. Server-Side Processing: The provided applyWatermark is a placeholder. For true bulk watermarking, you’ll need a robust implementation. Client-side processing using the Canvas API can be performant for smaller images but has limitations. For larger images or more complex operations (like batch processing and saving to the media library), a server-side approach via WordPress REST API endpoints or AJAX handlers is recommended. This would involve sending image IDs and watermark settings to PHP, where libraries like GD or ImageMagick can perform the heavy lifting.

Error Handling and Feedback: Implement comprehensive error handling for image loading, processing, and saving. Provide clear feedback to the user during the watermarking process, especially for long-running operations.

Performance Optimization: For very large batches of images, consider debouncing or throttling the handleWatermark function. If using client-side processing, optimize Canvas operations and consider Web Workers to avoid blocking the main thread.

Internationalization (i18n): Ensure all user-facing strings are wrapped in __() or _x() for translation, as demonstrated in the example.

Accessibility: Pay close attention to ARIA attributes and keyboard navigation for all interactive elements within the block’s interface.

By combining SolidJS’s reactive paradigm with Gutenberg’s block architecture, you can create powerful and performant custom tools for WordPress users. This example provides a solid foundation for building sophisticated image manipulation blocks.

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 securely integrate Twilio SMS Gateway endpoints into WordPress custom plugins using Filesystem API
  • Troubleshooting WooCommerce hook execution loops in production when using modern Timber Twig templating engines wrappers
  • Implementing automated compliance reporting for custom affiliate click tracking logs ledgers using custom PHP-Spreadsheet exports
  • Troubleshooting WP_DEBUG notice floods in production when using modern Understrap styling structures wrappers
  • How to build custom WooCommerce core overrides extensions utilizing modern Metadata API (add_post_meta) schemas

Categories

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

Recent Posts

  • How to securely integrate Twilio SMS Gateway endpoints into WordPress custom plugins using Filesystem API
  • Troubleshooting WooCommerce hook execution loops in production when using modern Timber Twig templating engines wrappers
  • Implementing automated compliance reporting for custom affiliate click tracking logs ledgers using custom PHP-Spreadsheet exports

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (849)
  • Debugging & Troubleshooting (644)
  • Security & Compliance (623)
  • 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