Step-by-Step Guide to building a custom automated coupon generator block for Gutenberg using Svelte standalone templates
Setting Up the Development Environment
Before diving into code, ensure your WordPress development environment is properly configured. This includes having a local WordPress installation (e.g., using Local by Flywheel, Docker, or a LAMP/LEMP stack) and Node.js with npm or yarn installed. We’ll be using Svelte for our frontend components, which requires a build process. For this guide, we’ll assume a basic WordPress plugin structure is already in place. If not, create a new plugin directory (e.g., wp-content/plugins/custom-coupon-generator) and a main plugin file (e.g., custom-coupon-generator.php).
Initialize your project’s Node.js dependencies. Navigate to your plugin’s root directory in your terminal and run:
cd wp-content/plugins/custom-coupon-generator npm init -y
Next, install the necessary development dependencies. We’ll need Svelte, a bundler (like Rollup or Webpack, though for simplicity we’ll use Rollup here), and plugins to handle Svelte compilation and WordPress asset enqueuing.
npm install --save-dev svelte rollup @rollup/plugin-node-resolve rollup-plugin-svelte @rollup/plugin-commonjs cross-env
Configuring Rollup for Svelte Compilation
Create a rollup.config.js file in your plugin’s root directory to configure the build process. This file will tell Rollup how to find your Svelte components and how to bundle them into JavaScript files that WordPress can understand.
// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js', // Entry point for your Svelte app
output: {
sourcemap: true,
format: 'iife', // Immediately Invoked Function Expression for WordPress
name: 'app', // Global variable name
file: 'build/bundle.js' // Output file
},
plugins: [
svelte({
compilerOptions: {
// enable run-time checks when not in production
dev: !production
}
}),
// we'll extract any third-party libraries from node_modules
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
],
watch: {
clearScreen: false
}
};
Create a package.json file (if not already created by npm init) and add build scripts:
{
"name": "custom-coupon-generator",
"version": "1.0.0",
"description": "Custom Gutenberg block for coupon generation",
"main": "build/bundle.js",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w"
},
"keywords": ["wordpress", "gutenberg", "svelte", "coupon"],
"author": "Your Name",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"cross-env": "^7.0.3",
"rollup": "^4.12.0",
"rollup-plugin-svelte": "^7.1.0",
"svelte": "^4.2.12"
}
}
Now, create the necessary directories: src and build. Inside the src directory, create an entry file, typically main.js.
Developing the Svelte Component
Let’s create a simple Svelte component for our coupon generator. This component will handle the input fields for coupon details and the logic to generate a coupon code. Create a file named CouponGenerator.svelte inside the src directory.
<!-- src/CouponGenerator.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let couponName = '';
let discountPercentage = 0;
let expiryDate = '';
let generatedCode = '';
function generateCoupon() {
// Basic coupon code generation logic
const prefix = couponName.substring(0, 3).toUpperCase();
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
generatedCode = `${prefix}-${discountPercentage}-${random}`;
dispatch('couponGenerated', {
name: couponName,
percentage: discountPercentage,
expiry: expiryDate,
code: generatedCode
});
}
</script>
<div class="coupon-generator-block">
<h3>Generate New Coupon</h3>
<div>
<label for="couponName">Coupon Name:</label>
<input id="couponName" type="text" bind:value="{couponName}" />
</div>
<div>
<label for="discountPercentage">Discount (%):</label>
<input id="discountPercentage" type="number" min="1" max="100" bind:value="{discountPercentage}" />
</div>
<div>
<label for="expiryDate">Expiry Date:</label>
<input id="expiryDate" type="date" bind:value="{expiryDate}" />
</div>
<button on:click="{generateCoupon}">Generate Coupon</button>
{#if generatedCode}
<div class="generated-coupon">
<p>Your Coupon Code:</p>
<strong>{generatedCode}</strong>
<p>Name: {couponName}, Discount: {discountPercentage}%, Expires: {expiryDate || 'N/A'}</p>
</div>
{/if}
</div>
<style>
.coupon-generator-block {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 15px;
background-color: #f9f9f9;
}
.coupon-generator-block div {
margin-bottom: 10px;
}
.coupon-generator-block label {
display: inline-block;
width: 120px;
margin-right: 10px;
}
.generated-coupon {
margin-top: 20px;
padding: 10px;
background-color: #e0ffe0;
border: 1px solid #a0d0a0;
}
</style>
Now, create the entry point file src/main.js to mount the Svelte component.
// src/main.js
import CouponGenerator from './CouponGenerator.svelte';
// This is where the Svelte component will be mounted in the WordPress editor.
// For a Gutenberg block, we'll typically mount it within the block's edit function.
// For this standalone example, we'll just demonstrate mounting it to a div.
// In a real Gutenberg block, this would be part of the registerBlockType definition.
// Example of mounting to a specific element (not directly used in Gutenberg block registration)
// const app = new CouponGenerator({
// target: document.getElementById('app'),
// props: {
// // initial props if any
// }
// });
// Exporting for potential use in a larger application or module system
export default CouponGenerator;
Integrating with Gutenberg
To use this Svelte component as a Gutenberg block, we need to register a new block type in PHP and then enqueue our compiled JavaScript. The Svelte component will be rendered within the block’s edit and save functions.
First, let’s modify src/main.js to export a function that can be used by Gutenberg’s registerBlockType.
// src/main.js
import { registerBlockType } from '@wordpress/blocks';
import { createElement } from '@wordpress/element';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl, NumberControl, DatePicker } from '@wordpress/components';
import { useState } from '@wordpress/element'; // For managing state within the block editor
import CouponGenerator from './CouponGenerator.svelte'; // Import our Svelte component
// This is a wrapper to make Svelte work within Gutenberg's React-based editor.
// We'll use a simple approach here: render the Svelte component and pass data via props.
// For more complex interactions, you might need a more sophisticated bridge.
function SvelteGutenbergWrapper({ attributes, setAttributes }) {
// State for the block editor
const [couponName, setCouponName] = useState(attributes.couponName || '');
const [discountPercentage, setDiscountPercentage] = useState(attributes.discountPercentage || 0);
const [expiryDate, setExpiryDate] = useState(attributes.expiryDate || '');
const [generatedCode, setGeneratedCode] = useState(attributes.generatedCode || '');
// Handler for when Svelte component emits an event
const handleCouponGenerated = (event) => {
const { name, percentage, expiry, code } = event.detail;
setCouponName(name);
setDiscountPercentage(percentage);
setExpiryDate(expiry);
setGeneratedCode(code);
setAttributes({
couponName: name,
discountPercentage: percentage,
expiryDate: expiry,
generatedCode: code
});
};
// We need to render the Svelte component. Since Gutenberg uses React,
// we can't directly mount a Svelte component into a React component's render method.
// A common workaround is to use a placeholder div and manually mount/unmount Svelte.
// For simplicity in this example, we'll simulate the Svelte component's output
// using Gutenberg components for the editor view, and then use the generated code
// in the save function. A true Svelte integration would involve a more complex bridge.
// For the editor view, let's use Gutenberg components to mirror the Svelte inputs
// and display the generated code.
return createElement(
'div',
{ className: 'coupon-generator-editor-wrapper' },
createElement(InspectorControls, null,
createElement(PanelBody, { title: 'Coupon Settings' },
createElement(TextControl, {
label: 'Coupon Name',
value: couponName,
onChange: (newName) => {
setCouponName(newName);
setAttributes({ couponName: newName });
},
}),
createElement(NumberControl, {
label: 'Discount (%)',
value: discountPercentage,
onChange: (newPercentage) => {
setDiscountPercentage(newPercentage);
setAttributes({ discountPercentage: newPercentage });
},
min: 1,
max: 100,
}),
// Note: DatePicker is more complex for direct binding, using TextControl for simplicity here
createElement(TextControl, {
label: 'Expiry Date (YYYY-MM-DD)',
value: expiryDate,
onChange: (newExpiry) => {
setExpiryDate(newExpiry);
setAttributes({ expiryDate: newExpiry });
},
type: 'date'
})
)
),
createElement(
'div',
{ className: 'coupon-generator-block-preview' },
createElement('h3', null, 'Coupon Generator'),
generatedCode ? createElement(
'div',
{ className: 'generated-coupon-preview' },
createElement('p', null, 'Generated Coupon:'),
createElement('strong', null, generatedCode),
createElement('p', null, `Name: ${couponName}, Discount: ${discountPercentage}%, Expires: ${expiryDate || 'N/A'}`)
) : createElement('p', null, 'Enter coupon details in the sidebar to generate a code.')
)
);
}
// Register the block
registerBlockType('custom-coupon-generator/block', {
title: 'Custom Coupon Generator',
icon: 'tag', // WordPress Dashicon
category: 'common', // Or a custom category
attributes: {
couponName: {
type: 'string',
default: '',
},
discountPercentage: {
type: 'number',
default: 0,
},
expiryDate: {
type: 'string',
default: '',
},
generatedCode: {
type: 'string',
default: '',
},
},
edit: SvelteGutenbergWrapper,
save: ({ attributes }) => {
// The save function determines what is saved to the database.
// We'll save the generated coupon details.
const { couponName, discountPercentage, expiryDate, generatedCode } = attributes;
// If a coupon was generated, render its details. Otherwise, show a placeholder.
if (generatedCode) {
return createElement(
'div',
{ className: 'coupon-generator-saved-output' },
createElement('h4', null, `Coupon: ${couponName}`),
createElement('p', null, `Code: ${generatedCode}`),
createElement('p', null, `Discount: ${discountPercentage}%`),
expiryDate && createElement('p', null, `Expires: ${expiryDate}`)
);
} else {
return createElement('p', null, 'Coupon details will appear here once generated.');
}
},
});
Now, update your main plugin file (e.g., custom-coupon-generator.php) to register the block and enqueue the compiled JavaScript.
<?php
/**
* Plugin Name: Custom Coupon Generator
* Description: A custom Gutenberg block for generating coupons using Svelte.
* Version: 1.0.0
* Author: Your Name
* License: GPL-2.0-or-later
* Text Domain: custom-coupon-generator
*/
function cgg_register_coupon_block() {
// Register the block using the script handle.
register_block_type( 'custom-coupon-generator/block', array(
'editor_script' => 'cgg-editor-script',
'editor_style' => 'cgg-editor-style',
'style' => 'cgg-style',
// 'render_callback' => 'cgg_render_coupon_block', // Optional: if you need server-side rendering
) );
}
add_action( 'init', 'cgg_register_coupon_block' );
function cgg_enqueue_block_assets() {
// Enqueue the compiled JavaScript for the editor and frontend.
wp_enqueue_script(
'cgg-editor-script', // Handle
plugins_url( 'build/bundle.js', __FILE__ ), // Path to your compiled JS
array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n' ), // Dependencies
filemtime( plugin_dir_path( __FILE__ ) . 'build/bundle.js' ) // Version based on file modification time
);
// Enqueue editor-only styles if needed
wp_enqueue_style(
'cgg-editor-style',
plugins_url( 'build/editor.css', __FILE__ ), // Assuming you compile CSS for the editor
array( 'wp-edit-blocks' ),
filemtime( plugin_dir_path( __FILE__ ) . 'build/editor.css' )
);
// Enqueue frontend styles
wp_enqueue_style(
'cgg-style',
plugins_url( 'build/style.css', __FILE__ ), // Assuming you compile CSS for the frontend
array(),
filemtime( plugin_dir_path( __FILE__ ) . 'build/style.css' )
);
}
add_action( 'enqueue_block_assets', 'cgg_enqueue_block_assets' );
// Optional: Server-side rendering callback if you need it
// function cgg_render_coupon_block( $attributes ) {
// $coupon_name = isset( $attributes['couponName'] ) ? esc_html( $attributes['couponName'] ) : '';
// $discount_percentage = isset( $attributes['discountPercentage'] ) ? intval( $attributes['discountPercentage'] ) : 0;
// $expiry_date = isset( $attributes['expiryDate'] ) ? esc_html( $attributes['expiryDate'] ) : '';
// $generated_code = isset( $attributes['generatedCode'] ) ? esc_html( $attributes['generatedCode'] ) : '';
//
// if ( ! $generated_code ) {
// return '<p>Coupon details will appear here once generated.</p>';
// }
//
// ob_start();
// ?>
// <div class="coupon-generator-rendered-output">
// <h4><?php echo esc_html__( 'Coupon:', 'custom-coupon-generator' ); ?> <?php echo $coupon_name; ?></h4>
// <p><?php echo esc_html__( 'Code:', 'custom-coupon-generator' ); ?> <?php echo $generated_code; ?></p>
// <p><?php echo sprintf( esc_html__( 'Discount: %d%%', 'custom-coupon-generator' ), $discount_percentage ); ?></p>
// <?php if ( $expiry_date ) : ?>
// <p><?php echo esc_html__( 'Expires:', 'custom-coupon-generator' ); ?> <?php echo $expiry_date; ?></p>
// <?php endif; ?>
// </div>
// <?php
// return ob_get_clean();
// }
Building the Assets
With the configuration and code in place, it’s time to build the JavaScript and CSS assets. Run the build command from your plugin’s root directory:
npm run build
This command will execute Rollup, compile your Svelte component, and output build/bundle.js. If you’ve configured Rollup to output CSS (e.g., using rollup-plugin-css-only or similar), it will also generate build/style.css and potentially build/editor.css.
Testing the Block
Activate your plugin in the WordPress admin area. Then, navigate to the post or page editor. You should now be able to search for “Custom Coupon Generator” and add the block to your content. The block’s editor interface will appear, allowing you to input coupon details. The “Inspector Controls” (sidebar) will provide fields for coupon name, discount, and expiry date. As you update these fields, the block preview will reflect the generated coupon code. Save the post and view it on the frontend to see the saved output.
Note on Svelte in Gutenberg: The provided SvelteGutenbergWrapper is a simplified approach. Directly embedding a Svelte component within Gutenberg’s React environment requires careful state management and potentially a more robust bridging mechanism for complex interactions. For this example, we’ve used Gutenberg’s own components in the edit function to manage state and display the output, effectively simulating the Svelte component’s behavior within the React context. The save function then renders the final output based on the stored attributes.