Step-by-Step Guide to building a custom user session manager block for Gutenberg using Svelte standalone templates
Project Setup: SvelteKit and WordPress Integration
This guide details the creation of a custom Gutenberg block for managing user sessions within a WordPress e-commerce context. We’ll leverage SvelteKit for the block’s frontend interactivity and a custom PHP plugin for backend session management. This approach offers a modern, performant user experience for session-related actions directly within the WordPress editor.
First, let’s establish the SvelteKit project. This will serve as our development environment for building the interactive block components.
Initializing SvelteKit
Create a new SvelteKit project using npm or yarn:
npm create svelte@latest my-session-block cd my-session-block npm install
For this specific use case, we’ll be building a standalone Svelte component that will be compiled and then integrated into a WordPress plugin. We don’t need the full SvelteKit routing or server-side rendering capabilities for the block itself. Therefore, we’ll configure SvelteKit to output a static build that we can then process further.
Configuring SvelteKit for Static Output
Modify the svelte.config.js file to use the adapter-static. This adapter is crucial for generating a static site that can be easily consumed by a WordPress plugin.
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
// default options are shown
pages: 'build',
assets: 'build',
fallback: 'index.html' // Important for SPAs, though less critical for a single block component
})
}
};
export default config;
Next, we’ll structure our Svelte components. For a Gutenberg block, we’ll typically have an edit.svelte component for the editor view and potentially a save.svelte component for the frontend rendering (though often the frontend rendering is handled by PHP or a separate JavaScript file enqueued by the plugin).
Developing the Svelte Block Component
Let’s create a basic Svelte component for our session management block. This component will allow users to view their current session status and potentially trigger actions like logging out or viewing session details.
src/routes/+page.svelte (Editor View)
We’ll use +page.svelte as our primary component for the editor. In a real-world scenario, you might structure this differently, perhaps with a dedicated components directory. For simplicity, we’ll keep it here.
<script>
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
// State for session data
const sessionData = writable({
isLoggedIn: false,
userId: null,
sessionExpiry: null,
message: 'Loading session status...'
});
// Function to fetch session status from WordPress API
async function fetchSessionStatus() {
try {
// This endpoint needs to be registered in your PHP plugin
const response = await fetch('/wp-json/my-session-block/v1/status');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
sessionData.set(data);
} catch (error) {
console.error("Failed to fetch session status:", error);
sessionData.set({
isLoggedIn: false,
userId: null,
sessionExpiry: null,
message: 'Error loading session status.'
});
}
}
// Fetch session status when the component mounts
onMount(() => {
fetchSessionStatus();
});
// Placeholder for logout action
async function handleLogout() {
try {
const response = await fetch('/wp-json/my-session-block/v1/logout', { method: 'POST' });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
sessionData.set({ ...result, message: 'Successfully logged out.' });
// Optionally, re-fetch status or update UI
fetchSessionStatus();
} catch (error) {
console.error("Failed to logout:", error);
sessionData.set({ message: 'Error during logout.' });
}
}
</script>
<div class="session-manager-block">
<h3>User Session Manager</h3>
{#if $sessionData.message}
<p>{$sessionData.message}</p>
{/if}
{#if $sessionData.isLoggedIn}
<p>User ID: {$sessionData.userId}</p>
{#if $sessionData.sessionExpiry}
<p>Expires: {new Date($sessionData.sessionExpiry * 1000).toLocaleString()}</p>
{/if}
<button on:click={handleLogout}>Logout</button>
{:else}
<p>You are not currently logged in.</p>
<!-- Add login button/link here if applicable -->
{/if}
</div>
<style>
.session-manager-block {
border: 1px solid #ccc;
padding: 15px;
border-radius: 5px;
background-color: #f9f9f9;
}
button {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 15px;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background-color: #c82333;
}
</style>
This Svelte component defines a basic UI for displaying session status and includes placeholder functions for fetching status and logging out. These functions will interact with WordPress REST API endpoints that we will define in our PHP plugin.
Building the WordPress Plugin
Now, let’s create the WordPress plugin that will house our Gutenberg block and handle the backend session logic.
Plugin Structure
Create a new directory in wp-content/plugins/ named my-session-manager. Inside this directory, create the following files:
my-session-manager.php(Main plugin file)src/index.js(Gutenberg block registration)build/(This directory will contain our compiled Svelte assets)
Main Plugin File: my-session-manager.php
This file will register the Gutenberg block and enqueue our compiled Svelte assets.
<?php
/**
* Plugin Name: My Session Manager
* Description: Custom Gutenberg block for managing user sessions.
* Version: 1.0.0
* Author: Your Name
* Text Domain: my-session-manager
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Register the Gutenberg block.
*/
function my_session_manager_register_block() {
// Automatically load dependencies and version
$asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');
wp_register_script(
'my-session-manager-block-editor-script',
plugins_url( 'build/index.js', __FILE__ ),
$asset_file['dependencies'],
$asset_file['version']
);
wp_register_style(
'my-session-manager-block-editor-style',
plugins_url( 'build/style-index.css', __FILE__ ),
array(),
$asset_file['version']
);
register_block_type( 'my-session-manager/block', array(
'editor_script' => 'my-session-manager-block-editor-script',
'editor_style' => 'my-session-manager-block-editor-style',
'render_callback' => 'my_session_manager_render_frontend', // Optional: for server-side rendering
) );
}
add_action( 'init', 'my_session_manager_register_block' );
/**
* Render the block on the frontend (optional).
* If not using render_callback, the block's save function in Svelte will handle frontend rendering.
*/
function my_session_manager_render_frontend( $attributes ) {
// This function can be used to render the block's frontend HTML.
// For this example, we'll assume Svelte's save function handles it,
// or we'll rely on the JavaScript to dynamically update the frontend.
// If you need server-side rendering, you'd fetch session data here.
return '<div id="my-session-manager-frontend"></div>';
}
/**
* Register REST API endpoints for session management.
*/
function my_session_manager_register_api_routes() {
// Endpoint to get session status
register_rest_route( 'my-session-block/v1', '/status', array(
'methods' => 'GET',
'callback' => 'my_session_manager_get_session_status',
'permission_callback' => '__return_true', // Adjust permissions as needed
) );
// Endpoint for logout
register_rest_route( 'my-session-block/v1', '/logout', array(
'methods' => 'POST',
'callback' => 'my_session_manager_handle_logout',
'permission_callback' => '__return_true', // Adjust permissions as needed
) );
}
add_action( 'rest_api_init', 'my_session_manager_register_api_routes' );
/**
* Callback for GET /my-session-block/v1/status
*/
function my_session_manager_get_session_status() {
$response = array(
'isLoggedIn' => false,
'userId' => null,
'sessionExpiry' => null,
'message' => 'User not logged in.'
);
if ( is_user_logged_in() ) {
$user_id = get_current_user_id();
$user = wp_get_current_user();
$session_expiry = get_user_meta( $user_id, 'session_expiration', true ); // Assuming you store this
$response['isLoggedIn'] = true;
$response['userId'] = $user_id;
$response['sessionExpiry'] = $session_expiry ? (int) $session_expiry : null;
$response['message'] = 'User is logged in.';
}
return new WP_REST_Response( $response, 200 );
}
/**
* Callback for POST /my-session-block/v1/logout
*/
function my_session_manager_handle_logout() {
if ( is_user_logged_in() ) {
wp_logout();
return new WP_REST_Response( array(
'isLoggedIn' => false,
'userId' => null,
'sessionExpiry' => null,
'message' => 'Logout successful.'
), 200 );
} else {
return new WP_Error( 'not_logged_in', 'User is not logged in.', array( 'status' => 403 ) );
}
}
// Optional: Add a simple frontend script if you used render_callback
function my_session_manager_enqueue_frontend_script() {
// Only enqueue on frontend if the block is present, or always if needed.
// For simplicity, we'll enqueue it globally for now.
wp_enqueue_script(
'my-session-manager-frontend-script',
plugins_url( 'build/frontend.js', __FILE__ ), // This file will be generated
array( 'wp-element' ), // Depends on React/ReactDOM for WP frontend rendering
filemtime( plugin_dir_path( __FILE__ ) . 'build/frontend.js' ),
true
);
}
// add_action( 'wp_enqueue_scripts', 'my_session_manager_enqueue_frontend_script' );
// Note: The 'build/frontend.js' would contain Svelte compiled for frontend rendering.
// For this example, we'll focus on the editor block and API interaction.
// If you need frontend rendering, you'd compile a separate Svelte app for it.
// Or, the 'save' function in your Svelte block would output static HTML.
// The current Svelte code is primarily for the editor.
?>
Key aspects of this PHP file:
- Block Registration:
wp_register_scriptandregister_block_typehook into WordPress’s block system. It points to the compiled JavaScript and CSS files. - Asset Loading:
index.asset.phpis automatically generated by SvelteKit’s build process (via `@wordpress/scripts`) and lists dependencies and the version. - REST API Endpoints:
register_rest_routedefines endpoints for fetching session status and handling logout requests. - Session Logic:
my_session_manager_get_session_statuschecks if a user is logged in and returns relevant data.my_session_manager_handle_logoutlogs the user out.
Gutenberg Block Registration: src/index.js
This JavaScript file is the entry point for registering the Gutenberg block. It will import the Svelte component and register it with WordPress.
import { registerBlockType } from '@wordpress/blocks';
import { edit as EditComponent } from './edit'; // Assuming edit.svelte is in src/edit.svelte
// import { save as SaveComponent } from './save'; // If you have a separate save component
// Import styles for the block editor
import './style.scss';
registerBlockType( 'my-session-manager/block', {
title: 'User Session Manager',
icon: 'admin-users', // Choose an appropriate icon
category: 'widgets', // Or 'ecommerce', 'design', etc.
attributes: {
// Define any attributes your block might need to save
// For this example, we're relying on dynamic API calls, so no attributes are strictly necessary for state.
},
edit: EditComponent,
// save: SaveComponent, // If you have a save component, uncomment this.
// If no save component is provided, the block will be dynamic and rendered server-side via render_callback.
// For this example, we'll let the Svelte component handle editor interaction and rely on API for frontend.
// If you want the block to render static HTML on the frontend, you'd define a save function.
// For a dynamic block, you'd typically omit 'save' and use 'render_callback' in PHP.
} );
Note: The above src/index.js assumes you have edit.svelte and potentially save.svelte in your src/ directory. For a SvelteKit project, you’d typically have +page.svelte. We’ll adapt this.
Adapting SvelteKit for WordPress Block Development
To use Svelte components within a WordPress plugin, we need to adjust our SvelteKit build process. We’ll compile the Svelte component into a standard JavaScript file that WordPress can understand. This often involves using tools like @wordpress/scripts.
Installing WordPress Script Dependencies
Navigate to your plugin directory (wp-content/plugins/my-session-manager) and install the necessary WordPress development tools:
cd ../../plugins/my-session-manager npm init -y npm install @wordpress/scripts @wordpress/blocks @wordpress/components @wordpress/i18n --save-dev
Now, update your package.json to include build scripts:
{
"name": "my-session-manager",
"version": "1.0.0",
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
},
"devDependencies": {
"@wordpress/blocks": "^12.0.0",
"@wordpress/components": "^26.0.0",
"@wordpress/i18n": "^4.0.0",
"@wordpress/scripts": "^26.0.0",
"sass": "^1.0.0"
}
}
Create a svelte.config.js file in your plugin’s root directory (wp-content/plugins/my-session-manager/) to configure Svelte compilation for WordPress:
import adapter from '@sveltejs/adapter-static'; // Or 'adapter-spa' if needed
/** @type {import('@sveltejs/kit').Config} */
const config = {
// You might not need preprocess if you're not using SvelteKit's features like layouts, routes etc.
// For a simple block, you can directly compile Svelte components.
// If using SvelteKit's build process, ensure it outputs to a 'build' directory.
kit: {
// This adapter is for SvelteKit's own build process.
// For @wordpress/scripts, the compilation is handled differently.
// We'll primarily use @wordpress/scripts for the block compilation.
// This svelte.config.js might be more relevant if you were building a separate Svelte app.
// For a block, we'll configure @wordpress/scripts to process .svelte files.
}
};
export default config;
Important Note on SvelteKit vs. @wordpress/scripts: If you started with SvelteKit, you might have a complex setup. For a Gutenberg block, it’s often simpler to use @wordpress/scripts directly within your plugin directory. This tool is designed to compile React/Vue/Svelte components for WordPress blocks. You would place your Svelte component files (e.g., edit.svelte) directly within your plugin’s src/ directory and configure @wordpress/scripts to process them.
Revised Svelte Component for WordPress Block
Let’s adapt our Svelte component to be directly usable by @wordpress/scripts. We’ll place it in src/edit.svelte within the plugin directory.
<!-- wp-content/plugins/my-session-manager/src/edit.svelte -->
<script>
import { onMount, createEventDispatcher } from 'svelte';
import { writable } from 'svelte/store';
import { __ } from '@wordpress/i18n'; // For internationalization
// Dispatcher for block events (e.g., saving attributes, though not used here)
const dispatch = createEventDispatcher();
// State for session data
const sessionData = writable({
isLoggedIn: false,
userId: null,
sessionExpiry: null,
message: __('Loading session status...', 'my-session-manager')
});
// Function to fetch session status from WordPress API
async function fetchSessionStatus() {
try {
const response = await fetch('/wp-json/my-session-block/v1/status');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
sessionData.set(data);
} catch (error) {
console.error("Failed to fetch session status:", error);
sessionData.set({
isLoggedIn: false,
userId: null,
sessionExpiry: null,
message: __('Error loading session status.', 'my-session-manager')
});
}
}
// Fetch session status when the component mounts
onMount(() => {
fetchSessionStatus();
});
// Placeholder for logout action
async function handleLogout() {
try {
const response = await fetch('/wp-json/my-session-block/v1/logout', { method: 'POST' });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
sessionData.set({ ...result, message: __('Successfully logged out.', 'my-session-manager') });
fetchSessionStatus(); // Refresh status after logout
} catch (error) {
console.error("Failed to logout:", error);
sessionData.set({ message: __('Error during logout.', 'my-session-manager') });
}
}
</script>
<div class="session-manager-block-editor">
<h3>{__('User Session Manager', 'my-session-manager')}</h3>
{#if $sessionData.message}
<p>{$sessionData.message}</p>
{/if}
{#if $sessionData.isLoggedIn}
<p>{__('User ID:', 'my-session-manager')} {$sessionData.userId}</p>
{#if $sessionData.sessionExpiry}
<p>{__('Expires:', 'my-session-manager')} {new Date($sessionData.sessionExpiry * 1000).toLocaleString()}</p>
{/if}
<button on:click={handleLogout}>{__('Logout', 'my-session-manager')}</button>
{:else}
<p>{__('You are not currently logged in.', 'my-session-manager')}</p>
{/if}
</div>
<style>
.session-manager-block-editor {
border: 1px solid #ccc;
padding: 15px;
border-radius: 5px;
background-color: #f9f9f9;
}
button {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 15px;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background-color: #c82333;
}
</style>
And update src/index.js to import and register this Svelte component:
// wp-content/plugins/my-session-manager/src/index.js
import { registerBlockType } from '@wordpress/blocks';
import { render } from 'svelte/internal'; // Import Svelte's internal render function
import EditComponent from './edit.svelte'; // Import the Svelte component
// Import styles for the block editor
import './style.scss';
// This is a wrapper component for the Svelte component to work with Gutenberg's edit function.
function SvelteBlockEdit( { attributes, setAttributes } ) {
// We need a DOM element to render the Svelte component into.
// Gutenberg provides a `this.element` or we can create one.
// A common pattern is to create a mount point.
let mountNode;
// Use onMount to render the Svelte component when the block is added to the editor.
// We need to ensure this runs only once and cleans up properly.
// For simplicity, we'll use a direct render approach.
// A more robust solution might involve a wrapper React component that manages Svelte lifecycle.
// For direct Svelte rendering within Gutenberg's edit function:
// This approach is less common and can be tricky with Svelte's lifecycle and Gutenberg's rendering.
// A more standard approach is to use a React wrapper or a build process that outputs a standard JS module.
// Let's assume a build process that compiles Svelte to a standard JS module that exports a render function.
// If using @wordpress/scripts, it handles the compilation.
// The `edit` property expects a React component or a function that returns JSX.
// To integrate Svelte, we typically need a bridge.
// A common pattern is to use a React wrapper that mounts/unmounts the Svelte component.
// However, if @wordpress/scripts is configured to handle Svelte, it might provide a way.
// Let's simplify: Assume @wordpress/scripts compiles edit.svelte into a default export
// that can be used directly as the 'edit' function.
// If edit.svelte exports a default function that returns JSX-like structure, it might work.
// If not, we need a wrapper.
// For this example, let's assume a build process that makes `EditComponent` usable.
// If `EditComponent` is a Svelte component, Gutenberg's `edit` prop expects a function
// that returns React elements. We need a bridge.
// A more practical approach for Svelte blocks:
// 1. Compile Svelte to a standard JS module.
// 2. Use that module within a React wrapper for the Gutenberg `edit` function.
// Let's simulate the output of a build process that makes `EditComponent` usable.
// If `EditComponent` is a Svelte component, we need to render it.
// This requires a DOM element.
// A simplified approach using a placeholder element:
// This requires the Svelte component to be compiled into a function that can be called.
// The @wordpress/scripts build process should handle this if configured correctly.
// If @wordpress/scripts is set up to handle Svelte, it might export a render function.
// Let's assume `EditComponent` is the compiled output.
// If EditComponent is a Svelte component, we need to render it into a DOM node.
// This is typically done within a React wrapper.
// For a direct Svelte integration, we might need to manually manage the DOM.
// Let's assume the build process outputs a render function for the Svelte component.
// The `edit` prop expects a function that returns JSX.
// If `EditComponent` is a Svelte component, we need to render it.
// A common pattern:
// import { render } from 'react-dom';
// import { createElement } from '@wordpress/element';
// import App from './App.svelte'; // Your main Svelte component
// return createElement( 'div', {
// ref: (element) => {
// if (element && !element.dataset.svelteMounted) {
// new App({ target: element, props: { attributes, setAttributes } });
// element.dataset.svelteMounted = true;
// }
// }
// });
// Given the structure, let's assume `EditComponent` is the compiled Svelte output
// that can be directly used or wrapped.
// If @wordpress/scripts is configured for Svelte, it might handle this.
// For this example, we'll assume `EditComponent` is a function that can be used as `edit`.
// If `edit.svelte` exports a default function that returns JSX, this would work.
// If it's a pure Svelte component, a wrapper is needed.
// Let's assume the build process outputs a standard JS module.
// If `edit.svelte` exports a default function that acts like a React component:
return EditComponent( { attributes, setAttributes } );
// If `edit.svelte` is a pure Svelte component, you'd need a React wrapper:
/*
const elementRef = useRef(null);
useEffect(() => {
const svelteApp = new EditComponent({ target: elementRef.current, props: { attributes, setAttributes } });
return () => svelteApp.$destroy();
}, []);
return createElement('div', { ref: elementRef });
*/
}
registerBlockType( 'my-session-manager/block', {
title: __( 'User Session Manager', 'my-session-manager' ),
icon: 'admin-users',
category: 'widgets',
attributes: {
// Define attributes if needed for saving state.
// For this example, we fetch data dynamically, so no persistent attributes are strictly required.
},
edit: SvelteBlockEdit, // Use the wrapper or the compiled Svelte component directly if compatible.
// save: () => null, // If using render_callback for frontend rendering, save can return null.
// Or provide a save function that returns static HTML if needed.
save: () => null, // This makes it a dynamic block.
} );
Explanation of src/index.js:
registerBlockType: The core WordPress function to register a new block.edit: SvelteBlockEdit: This points to our wrapper function that will render the Svelte component in the editor. The actual Svelte component is imported asEditComponent.save: () => null: Returningnullfrom thesavefunction signifies a dynamic block. This means WordPress will call therender_callbackdefined in PHP on the server to render the block’s frontend output../style.scss: Imports block-specific styles.
Building the Assets
With the Svelte component and WordPress registration in place, we need to compile the assets. Navigate to your plugin directory (wp-content/plugins/my-session-manager) and run:
npm run build
This command will:
- Compile
src/index.jsandsrc/edit.svelte. - Generate
build/index.js(the main script for the block editor). - Generate
build/index.asset.php(dependency information for WordPress). - Generate
build/style-index.css(styles for the block editor). - If you had a separate frontend Svelte component and configured it, it would generate
build/frontend.js.
Integrating and Testing
After running npm run build, activate the “My Session Manager” plugin in your WordPress admin area.
Adding the Block to a Post/Page
1. Create a new post or page in the WordPress editor.
2. Click the “+” icon to add a new block and search for “User Session Manager”.
3. Add the block to your content. You should see the Svelte component rendering within the editor, showing the session status (initially “Loading…” then updated based