What are CSS Cascade Layers?
CSS Cascade Layers (@layer) are a modern CSS feature that gives you explicit control over style priority. Instead of fighting specificity wars with increasingly complex selectors or resorting to !important, layers let you organize your CSS into named groups and declare which groups take precedence.
The Traditional CSS Problem
Normally, CSS cascade priority is determined by:
- Specificity - More specific selectors win
- Source order - Later styles override earlier ones
- !important - Nuclear option that breaks maintainability
This creates problems in WordPress when plugin styles have high specificity or load late, making them difficult to override without:
- Writing overly specific selectors (
.my-class.my-class.my-class) - Using
!importanteverywhere - Fighting with load order
How Layers Solve This
With cascade layers, you declare priority upfront:
css
@layer reset, plugins, theme, utilities;
Now it doesn't matter how specific a selector is - layers declared first have lower priority:
reset- Lowest priority (CSS resets, normalizers)plugins- Plugin styles (easy to override)theme- Your theme styles (overrides plugins)utilities- Highest priority (utility classes like Tailwind)
A simple class in the theme layer will override a complex selector in the plugins layer. This gives you clean, maintainable control over your WordPress styling hierarchy.
Why Use Layers for WordPress Plugins?
WordPress plugins typically enqueue their stylesheets as regular <link> tags, which are "unlayered" and sit at the highest cascade priority. This makes them notoriously difficult to override.
Benefits of layering plugin CSS:
- Override plugin styles with simple, clean selectors
- No more specificity hacks or
!important - Organize styles by source (plugins, theme, utilities)
- Maintain clean, readable CSS
- Future-proof your styling architecture
Step 1: Disable the Plugin's Default Stylesheet
Before you can wrap plugin styles in a layer, you need to prevent WordPress from loading the original stylesheet.
Method 1: Using wp_dequeue_style (Most Plugins)
Add this to your theme's functions.php:
php
function disable_plugin_default_styles() {
wp_dequeue_style('plugin-style-handle');
wp_deregister_style('plugin-style-handle');
}
add_action('wp_enqueue_scripts', 'disable_plugin_default_styles', 100);
Important: Replace 'plugin-style-handle' with the actual handle your plugin uses.
Finding the Plugin's Style Handle
Option A: View Page Source
- View your page source (Right-click → View Page Source)
- Search for the plugin's CSS file name
- Look at the
idattribute of the<link>tag:
html
<link rel="stylesheet" id="fluent-form-styles-css" href="..." />
The handle is everything before -css, so: fluent-form-styles
Option B: Use Browser DevTools
- Open DevTools (F12)
- Go to Elements/Inspector tab
- Search for
<link>tags in the<head> - Find your plugin's stylesheet
- Note the
idattribute
Option C: Check Plugin Source Code
- Navigate to
/wp-content/plugins/your-plugin/ - Search for
wp_enqueue_stylein the plugin files - The first parameter is the handle:
php
wp_enqueue_style('my-plugin-styles', $url);
^^^^^^^^^^^^^^^^
This is the handle
Method 2: Using Plugin-Specific Filters (Some Plugins)
Some plugins provide their own filters to disable styles:
php
// Example: Fluent Forms
function disable_fluent_forms_styles() {
return false;
}
add_filter('fluentform/load_default_public', 'disable_fluent_forms_styles', 10, 2);
Check the plugin's documentation for available filters. Common filter patterns:
pluginname/load_stylespluginname/enqueue_stylespluginname/disable_css
Complete Disable Example
For maximum reliability, use both methods:
// Use plugin filter if available
function disable_plugin_filter() {
return false;
}
add_filter('pluginname/load_styles', 'disable_plugin_filter', 10, 2);
// Also dequeue/deregister
function disable_plugin_styles() {
wp_dequeue_style('plugin-style-handle');
wp_deregister_style('plugin-style-handle');
}
add_action('wp_enqueue_scripts', 'disable_plugin_styles', 100);
Step 2: Find the Plugin's CSS File Location
You need to locate the actual CSS file on your server to read and wrap it in a layer.
Standard WordPress Plugin Structure
Most plugins follow this structure:
/wp-content/plugins/
└── plugin-name/
└── assets/
└── css/
└── style.css
Common Plugin CSS Locations
Contact Form 7:
/wp-content/plugins/contact-form-7/includes/css/styles.css
WooCommerce:
/wp-content/plugins/woocommerce/assets/css/woocommerce.css
Fluent Forms:
/wp-content/plugins/fluentform/assets/css/fluent-forms-public.css
Elementor:
/wp-content/plugins/elementor/assets/css/frontend.min.css
How to Find Any Plugin's CSS File
Method 1: Check the Enqueued URL
- View page source
- Find the plugin's
<link>tag - Copy the
hrefURL:
html
<link href="http://yoursite.com/wp-content/plugins/plugin-name/assets/css/style.css" />
- The server path is:
/wp-content/plugins/plugin-name/assets/css/style.css
Method 2: Browse Plugin Directory
- Access your server via FTP/SSH/File Manager
- Navigate to
/wp-content/plugins/plugin-name/ - Look in common directories:
/assets/css//css//styles//public/css//dist/css/
Method 3: Search for CSS Files
Using SSH:
bash
find /wp-content/plugins/plugin-name -name "*.css"
This will list all CSS files in the plugin directory.
Verify the File Path
Once you think you've found it, verify with PHP in your functions.php:
php
function check_plugin_css_path() {
$css_path = WP_CONTENT_DIR . '/plugins/plugin-name/assets/css/style.css';
if (file_exists($css_path)) {
error_log('✓ CSS file found at: ' . $css_path);
} else {
error_log('✗ CSS file NOT found at: ' . $css_path);
}
}
add_action('init', 'check_plugin_css_path');
Check your debug log to confirm.
Step 3: Add PHP Function to Copy and Layer Plugin Styles
Now we'll create a function that reads the plugin's CSS, wraps it in an @layer declaration, and saves it as a new file.
The Complete Function
Add this to your theme's functions.php:
php
function generate_layered_plugin_css() {
// Define paths
$css_output_path = get_stylesheet_directory() . '/css/plugin-layered.css';
$plugin_css_path = WP_CONTENT_DIR . '/plugins/your-plugin/assets/css/style.css';
// Check if plugin CSS exists
if (!file_exists($plugin_css_path)) {
return;
}
// Regenerate if file doesn't exist or plugin CSS was updated
if (!file_exists($css_output_path) || filemtime($plugin_css_path) > filemtime($css_output_path)) {
// Read the plugin's CSS
$css_content = file_get_contents($plugin_css_path);
if ($css_content === false) {
return;
}
// Wrap in @layer
$layered_css = "@layer plugin-styles {\n" . $css_content . "\n}";
// Save to your theme
file_put_contents($css_output_path, $layered_css);
}
}
add_action('wp_enqueue_scripts', 'generate_layered_plugin_css', 1);
Customize the Paths
Output path - Where the layered CSS will be saved (in your theme):
php
get_stylesheet_directory() . '/css/plugin-layered.css'
Plugin CSS path - Location of the plugin's original CSS:
php
WP_CONTENT_DIR . '/plugins/your-plugin/assets/css/style.css'
Layer name - Choose a descriptive name for your layer:
php
"@layer plugin-styles {\n" . $css_content . "\n}"
^^^^^^^^^^^^
Your layer name
How This Works
- Runs on every page load (but only regenerates when needed)
- Checks if plugin CSS exists - exits if not found
- Compares file modification times - only regenerates if:
- Output file doesn't exist yet, OR
- Plugin CSS was updated (plugin update)
- Reads plugin CSS - gets the entire stylesheet content
- Wraps in @layer - adds the layer declaration around the CSS
- Writes to theme - saves the layered version in your theme's CSS folder
Real-World Example: Fluent Forms
function generate_layered_fluent_forms_css() {
$css_output_path = get_stylesheet_directory() . '/css/ff-styles-layered.css';
$plugin_css_path = WP_CONTENT_DIR . '/plugins/fluentform/assets/css/fluent-forms-public.css';
if (!file_exists($plugin_css_path)) {
return;
}
if (!file_exists($css_output_path) || filemtime($plugin_css_path) > filemtime($css_output_path)) {
$css_content = file_get_contents($plugin_css_path);
if ($css_content === false) {
return;
}
$layered_css = "@layer fluent-forms {\n" . $css_content . "\n}";
file_put_contents($css_output_path, $layered_css);
}
}
add_action('wp_enqueue_scripts', 'generate_layered_fluent_forms_css', 1);
Step 4: Enqueue the Layered Stylesheet
Now that we're generating the layered CSS file, we need to tell WordPress to load it.
Basic Enqueue
Add this to your existing stylesheet enqueue function or create a new one:
php
function enqueue_theme_styles() {
$stylesheet_dir = get_stylesheet_directory();
// Enqueue the layered plugin stylesheet
wp_enqueue_style(
'plugin-layered', // Handle
get_stylesheet_directory_uri() . '/css/plugin-layered.css', // URL
array(), // Dependencies
filemtime($stylesheet_dir . '/css/plugin-layered.css') // Version (cache busting)
);
}
add_action('wp_enqueue_scripts', 'enqueue_theme_styles');
Adding to Existing Enqueue Function
If you already have a function that enqueues styles, add it there:
php
function enqueue_theme_styles() {
$stylesheet_dir = get_stylesheet_directory();
// Your existing theme styles
wp_enqueue_style('theme-style', get_stylesheet_directory_uri() . '/css/style.css');
wp_enqueue_style('theme-utilities', get_stylesheet_directory_uri() . '/css/utilities.css');
// Add the layered plugin stylesheet
wp_enqueue_style(
'plugin-layered',
get_stylesheet_directory_uri() . '/css/plugin-layered.css',
array(),
filemtime($stylesheet_dir . '/css/plugin-layered.css')
);
}
add_action('wp_enqueue_scripts', 'enqueue_theme_styles');
Why Use filemtime() for Version?
php
filemtime($stylesheet_dir . '/css/plugin-layered.css')
This uses the file's modification timestamp as the version number. Benefits:
- Automatic cache busting - Browser gets new version when file changes
- No manual version updates - WordPress handles it automatically
- Better performance - Browsers cache until file actually changes
Complete Example with Multiple Stylesheets
php
function enqueue_all_styles() {
$stylesheet_dir = get_stylesheet_directory();
// Theme base styles
wp_enqueue_style(
'theme-style',
get_stylesheet_directory_uri() . '/css/style.css',
array(),
filemtime($stylesheet_dir . '/css/style.css')
);
// Layered plugin styles
wp_enqueue_style(
'fluent-forms-layered',
get_stylesheet_directory_uri() . '/css/ff-styles-layered.css',
array(),
filemtime($stylesheet_dir . '/css/ff-styles-layered.css')
);
// Utility classes (highest priority)
wp_enqueue_style(
'utilities',
get_stylesheet_directory_uri() . '/css/utilities.css',
array(),
filemtime($stylesheet_dir . '/css/utilities.css')
);
}
add_action('wp_enqueue_scripts', 'enqueue_all_styles');
Setting Layer Order
To control layer priority, add this to your main theme stylesheet:
css
/* Define layer order - earlier = lower priority */
@layer reset, plugins, theme, utilities;
Now you can organize your CSS:
css
/* Your theme styles in the 'theme' layer */
@layer theme {
.button {
background: blue;
}
}
/* Utility overrides in the 'utilities' layer */
@layer utilities {
.bg-red {
background: red !important; /* If needed */
}
}
The plugin styles are already in the plugins layer (or whatever you named it), so they'll have lower priority than your theme layer.
Common Issues and Solutions
Issue 1: Permission Denied Error
Error:
Warning: file_put_contents(): Failed to open stream: Permission denied
Cause: The web server doesn't have write permissions to your theme's /css/ directory.
Solution:
Fix directory permissions via SSH:
bash
# Give web server write access
sudo chmod 775 /path/to/your-theme/css/
sudo chown www-data:www-data /path/to/your-theme/css/
# Verify permissions
ls -la /path/to/your-theme/css/
For different web servers:
- Apache/Ubuntu:
www-data - Apache/CentOS:
apache - Nginx:
nginxorwww-data
Alternative: Create file manually first:
bash
# Create empty file with correct permissions
touch /path/to/your-theme/css/plugin-layered.css
chmod 664 /path/to/your-theme/css/plugin-layered.css
chown www-data:www-data /path/to/your-theme/css/plugin-layered.css
Issue 2: Plugin Updates Overwrite Styles
Problem: Plugin updates with new CSS aren't reflected in your layered version.
Solution: The function already handles this! It compares file modification times:
php
if (!file_exists($css_output_path) || filemtime($plugin_css_path) > filemtime($css_output_path))
When the plugin updates, its CSS file gets a new modification time, triggering automatic regeneration.
To force regeneration manually:
bash
# Delete the layered file
rm /path/to/your-theme/css/plugin-layered.css
# WordPress will regenerate it on next page load
Issue 3: Styles Not Applying
Check 1: File was created
bash
ls -la /path/to/your-theme/css/plugin-layered.css
Check 2: File has content
bash
head -20 /path/to/your-theme/css/plugin-layered.css
Should show:
css
@layer plugin-styles {
/* Plugin CSS content here */
}
Check 3: Stylesheet is enqueued
View page source and search for:
html
<link rel="stylesheet" id="plugin-layered-css" href="..." />
Check 4: Browser loaded the file
Open DevTools → Network tab → Filter by CSS → Refresh page. Look for your layered stylesheet with status 200.
Check 5: Layers are working
Inspect an element → Styles panel. You should see styles prefixed with:
@layer plugin-styles
Issue 4: Original Plugin Styles Still Loading
Problem: You see both the original and layered versions loading.
Solution: Your disable function isn't working. Try:
- Increase priority:
php
add_action('wp_enqueue_scripts', 'disable_plugin_styles', 999);
- Check for multiple handles:
Some plugins register multiple stylesheets. Find all handles:
php
function list_all_styles() {
global $wp_styles;
error_log('Registered styles: ' . print_r($wp_styles->registered, true));
}
add_action('wp_enqueue_scripts', 'list_all_styles', 999);
Check your debug log and dequeue all plugin-related handles.
- Use plugin filters:
Check plugin documentation for style-disabling filters and use them in addition to wp_dequeue_style.
Issue 5: CSS Directory Doesn't Exist
Error: File generation fails because /css/ directory doesn't exist in your theme.
Solution:
Create the directory:
bash
mkdir /path/to/your-theme/css
chmod 775 /path/to/your-theme/css
chown www-data:www-data /path/to/your-theme/css
Or add directory creation to your function:
php
function generate_layered_plugin_css() {
$css_dir = get_stylesheet_directory() . '/css';
$css_output_path = $css_dir . '/plugin-layered.css';
$plugin_css_path = WP_CONTENT_DIR . '/plugins/your-plugin/assets/css/style.css';
// Create CSS directory if it doesn't exist
if (!file_exists($css_dir)) {
mkdir($css_dir, 0775, true);
}
// Rest of the function...
}
Issue 6: Layers Not Supported in Old Browsers
Problem: Site visitors using older browsers don't see styled content.
Browser Support:
- Chrome 99+ (March 2022)
- Firefox 97+ (February 2022)
- Safari 15.4+ (March 2022)
- Edge 99+ (March 2022)
Solution: Styles still apply in older browsers, just without layer ordering. The CSS works normally, you just lose the layer-based priority control. No fallback needed for basic functionality.
Complete Working Example
Here's everything together for a real WordPress plugin (Fluent Forms):
// ==========================================
// STEP 1: Disable Original Plugin Styles
// ==========================================
// Use plugin filter
function disable_fluent_forms_default_styles() {
return false;
}
add_filter('fluentform/load_default_public', 'disable_fluent_forms_default_styles', 10, 2);
// Also dequeue/deregister for safety
function disable_fluent_forms_stylesheets() {
wp_dequeue_style('fluent-form-styles');
wp_deregister_style('fluent-form-styles');
}
add_action('wp_enqueue_scripts', 'disable_fluent_forms_stylesheets', 100);
// ==========================================
// STEP 2 & 3: Generate Layered CSS
// ==========================================
function generate_layered_fluent_forms_css() {
// STEP 2: Define paths (plugin CSS location found)
$css_output_path = get_stylesheet_directory() . '/css/ff-styles-layered.css';
$plugin_css_path = WP_CONTENT_DIR . '/plugins/fluentform/assets/css/fluent-forms-public.css';
// Check if plugin CSS exists
if (!file_exists($plugin_css_path)) {
return;
}
// Regenerate if needed
if (!file_exists($css_output_path) || filemtime($plugin_css_path) > filemtime($css_output_path)) {
// Read plugin CSS
$css_content = file_get_contents($plugin_css_path);
if ($css_content === false) {
return;
}
// STEP 3: Wrap in @layer
$layered_css = "@layer fluent-forms {\n" . $css_content . "\n}";
// Save to theme
file_put_contents($css_output_path, $layered_css);
}
}
add_action('wp_enqueue_scripts', 'generate_layered_fluent_forms_css', 1);
// ==========================================
// STEP 4: Enqueue Layered Stylesheet
// ==========================================
function enqueue_theme_styles() {
$stylesheet_dir = get_stylesheet_directory();
// Theme styles
wp_enqueue_style('theme-style', get_stylesheet_directory_uri() . '/css/style.css');
// Layered Fluent Forms
wp_enqueue_style(
'fluent-forms-layered',
get_stylesheet_directory_uri() . '/css/ff-styles-layered.css',
array(),
filemtime($stylesheet_dir . '/css/ff-styles-layered.css')
);
// Utilities
wp_enqueue_style('utilities', get_stylesheet_directory_uri() . '/css/utilities.css');
}
add_action('wp_enqueue_scripts', 'enqueue_theme_styles');
In your main theme CSS (style.css):
css
/* Define layer order */
@layer reset, fluent-forms, theme, utilities;
/* Now you can easily override Fluent Forms */
@layer theme {
.fluentform .ff-btn-submit {
background: #your-color;
/* Simple selector beats complex plugin selectors! */
}
}
Summary
What You've Accomplished:
- ✅ Disabled the plugin's original stylesheet
- ✅ Located the plugin's CSS file in the plugin directory
- ✅ Created a PHP function to copy and wrap CSS in
@layer - ✅ Enqueued the layered version with proper cache busting
- ✅ Set up automatic regeneration on plugin updates
- ✅ Gained full control over CSS priority without specificity hacks
Key Benefits:
- Clean, maintainable CSS architecture
- Easy plugin style overrides
- No more
!importantspam - Future-proof styling system
- Automatic updates when plugins change
Your WordPress site now has a professional CSS cascade layer system that makes styling predictable and maintainable!
This article is for developers who are looking to get a general overview over WordPress, but might also be suitable to you if you are not an advanced users.
Important files/directories
wp-config.php – located at web root
This files holds the credentials for your database & other configurations such as dbugging. Beyond that this files is often used by plugins to store sensitive information such as API keys, as it is the simplest straightforward option, but not necessarily the safest.
/wp-content/
Directory that holds all your relevant content: Theme, Uploads & Plugins. You can migrate you whole website with just the database and the wp-content directory.
functions.php – wp-content/themes/[theme_name]/
Used to add custom functionalities. More complex functionalities should be created as plugins in wp-content/plugins/
Important concepts
Theme/ Child Theme
Themes define custom styles and some functionalities. Every wp site requires a theme. You can either create a custom theme from scratch or use a child theme.
Reasons to use child theme:
- Make your modifications portable and replicable.
- Keep customizations separate from the parent theme.
- Allow parent themes to be updated without losing your modifications.
- Save on development time since you’re only writing the code you need.
- Are a great way to start your journey toward developing full themes.
Post Types
Post types are a way of categorising content in WordPress.
The best-known default post types are “post” and “page”, but you can create custom post types for your website i.e. “projects” or “services”. You can customise the capabilities of your post types.
All post types are stored in the wp_post table in the database.
WordPress default post types are:
- Post (Post Type: ‘post’)
- Page (Post Type: ‘page’)
- Attachment (Post Type: ‘attachment’)
- Revision (Post Type: ‘revision’)
- Navigation menu (Post Type: ‘nav_menu_item’)
- Block templates (Post Type: ‘wp_template’)
- Template parts (Post Type: ‘wp_template_part’)
Taxonomies
Each taxonomy consist of term in either hierarchical or non-hierarchical structure. The WordPress default taxonomies are categories (hierarchical) tags (non-hierarchical).
Plugins
Custom functionalities you can add to your website. Custom plugins can be added in wp-content/plugins and standard plugins can be added through the WordPress plugin directory in your dashboard.
Database
WordPress operates with a SQL database that stores all the data.
Important Tables:
- wp_options - Stores site-wide settings, plugin settings, and theme options (e.g., site URL, admin email, active theme, active plugins).
- wp_posts - Stores all content types: posts, pages, attachments, revisions, menus, and custom post types. Each row represents a “post” of some type.
- wp_postmeta - Stores metadata for posts (custom fields). Each meta is a key-value pair linked to a post.
- wp_users - Stores registered user information: username, email, hashed password, registration date, etc.
Other Tables
- wp_usermeta - Stores metadata about users (roles, capabilities, preferences).
- wp_termmeta - Stores metadata for terms (categories, tags, or custom taxonomy terms).
- wp_terms - Stores individual taxonomy terms (like “Category: News” or “Tag: WordPress”).
- wp_term_relationships - Links posts (or other objects) to taxonomy terms.
- wp_term_taxonomy - Defines the taxonomy type for each term (category, tag, or custom taxonomy) and counts associated objects.
- wp_comments - Stores comments submitted on posts/pages.
- wp_commentmeta - Stores metadata related to comments.
Hooks (Actions & Filters)
Filters are used to modify information, and actions are used to execute actions. Both need to be triggered by built in hooks. Common hooks are wp_footer or wp_head. There are hundreds of hooks, which are used to add custom scripts & stylesheets. Plugins can create custom hooks.
Additional Concepts:
Other concepts that might be worth looking at:
- Widgets
- Short codes
- REST API / AJAX References
WordPress Security
I would look at the security in 3 layers:
- Layer one is your DNS – use a proxy like cloudflare to block malicious traffic before it visits your server. This also keeps you IP address secured from attackers.
- Layer two is your server – make sure that your (1)file permissions are secure, perhaps use a (2)custom plugin on plesk or cpanel to secure your server and (3)keep your server languages & systems updated.
- Layer three is your site – run regular updates, use a good firewall plugin, keep plugins to a minimum,
Server Security
- Good quality webhosting
- KEEP BACKUPS in a separate location
- Keep your file permissions secure, limit the files you server can write in the case it gets compromised
- Create separate database users for each website, don’t share the database between websites
- Keep server languages up to date e.g. the php version (fully managed webhosts will do that for you)
- Prefer SFTP over FTP
- Assure that each website is isolated in case your server compromised
- Change the table prefix in your database from wp_ to something else
- Log activity – will allow you to see what happened when and which IP address was used
- Use SSL for your server
WordPress Security
- Limit the number of plugins
- Update plugins & theme(s)
- Add a firewall plugin to your site
Additional Security Advice
- Use a CDN like Cloudflare as a proxy between server and client to block threats before they each your server.
- Limit access to the site (especially for clients lol)
WordPress Rescources
Child Themes: https://developer.wordpress.org/themes/advanced-topics/child-themes/
Post Types: https://developer.wordpress.org/themes/classic-themes/basics/post-types/
Taxonomies: https://developer.wordpress.org/themes/classic-themes/basics/categories-tags-custom-taxonomies/
Hooks: https://developer.wordpress.org/plugins/hooks/
Custom Hooks: https://developer.wordpress.org/plugins/hooks/custom-hooks/
Security: https://developer.wordpress.org/advanced-administration/security/hardening/
If you want your Fluent Forms WordPress form to automatically calculate travel distance based on a user’s postcode, and pass that distance to your emails or database, this step-by-step guide will help you set it up — using Google Maps Distance Matrix API for accurate distance calculation.
Why Use Google Maps Distance Matrix API?
Calculating the driving distance between two locations is tricky. The Google Maps Distance Matrix API provides accurate driving distances and durations between origins and destinations, perfect for travel cost estimations or delivery quotes.
What You'll Build
- A Fluent Form with a postcode input and a distance field
- An AJAX-powered postcode lookup that sends the postcode to WordPress backend
- The backend calls Google Maps API to calculate distance from a fixed origin to the postcode destination
- The distance gets returned to JavaScript, populates the distance field, triggers Fluent Forms recalculation, and enables form submission
Step 1: Create Your Fluent Form
Make sure your form includes:
- Postcode field — where the user inputs their postcode
- Distance field — where the calculated distance will be shown and submitted
- Calculated fields (optional) — that use the distance for price or other formulas
- Submit button
Step 2: Add JavaScript to Handle Postcode Blur Event
Here’s a sample JS snippet to send the postcode to the backend and update the distance field:
jQuery(document).ready(function($) {
if ($('#fluentform_6').length === 0) {
return;
}
const $submitBtn = $('#fluentform_6 .ff-btn-submit');
const $distanceField = $('#ff_6_travel_distance');
$submitBtn.prop('disabled', true);
$('#ff_6_address_zip_').on('blur', function() {
const postcode = $(this).val().trim();
if (!postcode) {
$submitBtn.prop('disabled', true);
return;
}
$.ajax({
url: fluent_form_ajax.ajax_url,
method: 'POST',
data: {
action: 'calculate_distance',
postcode: postcode,
security: fluent_form_ajax.nonce
},
success: function(response) {
if(response.success) {
const distanceValue = response.data.distance;
$distanceField.val(distanceValue);
const nativeInputEvent = new Event('input', { bubbles: true });
$distanceField[0].dispatchEvent(nativeInputEvent);
const nativeChangeEvent = new Event('change', { bubbles: true });
$distanceField[0].dispatchEvent(nativeChangeEvent);
$submitBtn.prop('disabled', false);
} else {
console.error('Distance calculation error:', response.data.message);
$submitBtn.prop('disabled', true);
}
},
error: function() {
console.error('AJAX error calculating distance');
$submitBtn.prop('disabled', true);
}
});
});
});
Step 3: Add PHP AJAX Handler with Google Maps API Integration
Add this code to your theme's functions.php or a custom plugin.
Make sure to replace 'YOUR_GOOGLE_MAPS_API_KEY' with your actual API key.
phpCopyEditadd_action('wp_ajax_calculate_distance', 'calculate_distance_callback');
add_action('wp_ajax_nopriv_calculate_distance', 'calculate_distance_callback');
function calculate_distance_callback() {
check_ajax_referer('fluent_form_nonce', 'security');
$postcode = sanitize_text_field($_POST['postcode'] ?? '');
if (empty($postcode)) {
wp_send_json_error(['message' => 'Postcode is required']);
}
$origin = '123 Main Street, Your City, Your Country'; // Change to your fixed origin address
$destination = $postcode;
$api_key = 'YOUR_GOOGLE_MAPS_API_KEY';
// Prepare Google Maps Distance Matrix API request
$url = add_query_arg([
'origins' => urlencode($origin),
'destinations' => urlencode($destination),
'key' => $api_key,
'units' => 'metric',
], 'https://maps.googleapis.com/maps/api/distancematrix/json');
$response = wp_remote_get($url);
if (is_wp_error($response)) {
wp_send_json_error(['message' => 'Failed to contact Google Maps API']);
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (!$data || $data['status'] !== 'OK') {
wp_send_json_error(['message' => 'Invalid response from Google Maps API']);
}
$elements = $data['rows'][0]['elements'][0] ?? null;
if (!$elements || $elements['status'] !== 'OK') {
wp_send_json_error(['message' => 'No route found to postcode']);
}
// Distance in meters -> convert to kilometers (or miles if you prefer)
$distance_meters = $elements['distance']['value'];
$distance_km = round($distance_meters / 1000, 2);
wp_send_json_success(['distance' => $distance_km]);
}
Step 4: Enqueue Scripts and Localize AJAX URL and Nonce
Add this code to enqueue jQuery and localize the AJAX endpoint and nonce:
<?php
add_action('wp_enqueue_scripts', function() {
if (is_singular()) {
global $post;
if (has_shortcode($post->post_content, 'fluentform') &&
(strpos($post->post_content, 'id="6"') !== false || strpos($post->post_content, 'id=6') !== false)) {
wp_enqueue_script('jquery');
wp_localize_script('jquery', 'fluent_form_ajax', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('fluent_form_nonce'),
]);
}
}
});
Step 5: Test Your Form
- Go to your page with the form.
- Enter a postcode in the postcode field and move out of the input (blur event).
- The distance field should fill with the calculated distance from your fixed origin.
- The submit button becomes enabled.
- Submit the form.
- Check the received email or database entry to confirm the distance was submitted.
Complete Code Snippet from Github:
When building dynamic WordPress sites with Bricks Builder, there are times when you want to display posts related through a Meta Box relationship field, but also ensure there’s a fallback to show other posts of the same type if no relationships exist. This can be especially helpful for things like testimonials, related articles, or services.
In this post, I’ll walk you through a custom PHP query snippet that does exactly that: it fetches related posts via Meta Box’s relationship field and then fills in the rest of the results with other posts from the same post type — creating a seamless experience for the user and ensuring you never end up with an empty section.
The Goal
We want to:
- Query posts related to the current post via a Meta Box relationship field.
- Get all other posts of the same post type.
- Merge the two, showing related posts first, then fallback posts.
The Code
Here's the full query snippet:
phpCopyEdit$related = new WP_Query([
'relationship' => [
'id' => 'review-service',
'to' => get_the_ID(),
],
'nopaging' => true,
]);
$related_ids = wp_list_pluck($related->posts, 'ID');
$all = new WP_Query([
'post_type' => 'testimonial',
'posts_per_page' => -1,
'fields' => 'ids',
]);
$other_ids = array_diff($all->posts, $related_ids);
return [
'post_type' => 'testimonial',
'post__in' => array_merge($related_ids, $other_ids),
'orderby' => 'post__in',
'posts_per_page' => -1,
];
Step-by-Step Explanation
1. Query Related Posts via Meta Box Relationship
phpCopyEdit$related = new WP_Query([
'relationship' => [
'id' => 'review-service',
'to' => get_the_ID(),
],
'nopaging' => true,
]);
- This uses Meta Box’s custom
relationshipquery support. 'id' => 'review-service'targets your Meta Box relationship field.'to' => get_the_ID()fetches posts connected to the current post.
2. Extract the IDs of Related Posts
phpCopyEdit$related_ids = wp_list_pluck($related->posts, 'ID');
wp_list_pluck()helps us extract just the post IDs from the$relatedquery.- We'll use these to prioritize related posts in the final output.
3. Fetch All Testimonials
phpCopyEdit$all = new WP_Query([
'post_type' => 'testimonial',
'posts_per_page' => -1,
'fields' => 'ids',
]);
- This retrieves the IDs of all posts from the
testimonialpost type. 'fields' => 'ids'makes the query more efficient by returning only the IDs.
4. Exclude Already Related Posts
phpCopyEdit$other_ids = array_diff($all->posts, $related_ids);
- We don’t want to show the same post twice.
array_diff()removes any post already found in the related set.
5. Merge & Return the Final Query
phpCopyEditreturn [
'post_type' => 'testimonial',
'post__in' => array_merge($related_ids, $other_ids),
'orderby' => 'post__in',
'posts_per_page' => -1,
];
- We merge both arrays — showing related posts first, followed by the rest.
'post__in'ensures only these posts are queried and displayed in the order we set.'orderby' => 'post__in'respects the merged array order.
Where to Use This in Bricks Builder

This code goes into a custom query in Bricks Builder:
- Select your Query Loop element (like a Repeater or Post element).
- Under the Query panel, choose "Custom PHP".
- Paste this snippet into the code area.
Make sure your Meta Box relationship field ID (review-service) and post type (testimonial) match your actual site setup.
Resources
- https://docs.metabox.io/extensions/mb-relationships/#get-all-to-objects-for-a-specified-from-object
- https://forum.bricksbuilder.io/t/add-a-meta-query-to-a-query-loop-where-posts-are-filtered-by-metabox-relation/4788/8
Website no accessible after Apache update, due to a update conflicting with Plesk. Read the detailed article here.
How I Implemented an Image Comparison Slider in Bricks Builder
Working with Bricks Builder regularly, I recently cam across the challenge of adding a before & after slider in Bricks.
I found a sleek solution using a web component from sneas.io. You can add the code with static images or dynamic images using custom fields.
Why Bricks Doesn’t Have This Natively
While Bricks Builder is incredibly flexible and powerful (I am a huge fan), it doesn’t come with a native image comparison slider. Which is honestly fine since Dimah's web component is not to hard to implement if you know you way around HTML.
The Static Implementation
The quickest way to get an image comparison slider working is with a static embed. Here’s how I did it:
- Enable Code Execution in Bricks
Ensure that the Bricks editor allows custom HTML/JS. - Insert a Code Block
In the Bricks editor, add a Code element and paste the following snippet:<!-- Sourced from: https://img-comparison-slider.sneas.io/ --> <img-comparison-slider> <img slot="first" src="https://link-to-your-file" width="600" height="600"/> <img slot="second" src="link-to-your-file" width="600" height="600"/> </img-comparison-slider> <script defer src="https://unpkg.com/img-comparison-slider@7/dist/index.js"></script>This gives you a fully functional, static image comparison component right on the page.
The Dynamic Implementation (With Custom Fields)
For projects requiring dynamic content — such as client-managed before/after images — I took it a step further:
- Enable Code Execution
As before, make sure custom code can be added safely in Bricks. - Allow Custom HTML Tags
By default, Bricks strips out unknown HTML tags. To use<img-comparison-slider>, we need to whitelist it. Add this to yourfunctions.php:add_filter( 'bricks/allowed_html_tags', function( $allowed_html_tags ) { $additional_tags = ['img-comparison-slider']; return array_merge( $allowed_html_tags, $additional_tags ); } );Official Bricks Docs
- Create the Structure in Bricks
- Add a Custom HTML element and use the tag
<img-comparison-slider>.
- Inside this tag, add two image elements with dynamic data bindings: html
<img slot="first" src="{YOUR_DYNAMIC_IMAGE_1}" /><img slot="second" src="{YOUR_DYNAMIC_IMAGE_2}" />
- Close the component with
</img-comparison-slider>.
- Add a Custom HTML element and use the tag
- Add the Script Block
Below the comparison container, insert another code block with:<script defer src="https://unpkg.com/img-comparison-slider@7/dist/index.js"></script>
This ensures the slider component is properly loaded and functional on the front end.
This method lets me seamlessly integrate a performant and responsive image comparison slider into any Bricks layout — with support for both static and dynamic content. It's elegant, dependency-light, and makes full use of modern web standards.
If you've ever tried uploading a big file to your website and got hit with an annoying "file exceeds maximum upload size" error — you're not alone. I recently had to deal with this while working on one of my sites, and after a bit of digging, I found the simplest way to sort it out directly in Plesk, no server restarts or scary terminal commands needed.
So, let me show you exactly how I did it step by step, and how you can do it too.
Step 1: Head to Your Domain Dashboard
First things first:
- Log into your Plesk control panel.
- On the left, click Domains and pick the domain you're working on.
- Once you’re inside, go to the Dashboard tab of that domain — that’s where the magic happens.
Step 2: Tweak the PHP Settings
This is where you’ll increase the limit:
- Scroll down and click on PHP Settings.
- Find
upload_max_filesize(you can search or just scroll through the list). - Increase it to whatever you need — I usually go with something like
64Mor128Mdepending on the project.
Step 3: Don’t Forget post_max_size — It Caught Me Out!
Here's the bit that tripped me up the first time:
Simply changing upload_max_filesize isn't enough. There's another sneaky setting called post_max_size, and it must be larger than the upload limit, otherwise your new setting won’t even take effect.
For example:
If you set:
upload_max_filesize = 64M
Make sure:post_max_size = 72M(or more, just to be safe)
Think of post_max_size as the size of the whole package, and upload_max_filesize as the size of just the file inside. If the package is too small, it won't fit no matter what.
Step 4: Save and Apply
Once you've updated both values:
- Scroll down and hit OK or Apply.
- Plesk might suggest restarting PHP — if it does, go ahead and do it.
Step 5: Test It
Now, head back to your site, CMS, or wherever you upload files, and try again. You should now be able to upload much larger files without any errors. It feels like a small victory every time!
Final Tip
I personally like to give myself a little buffer — if I need 50MB uploads, I’ll set upload_max_filesize to 64M and post_max_size to 80M. Just helps avoid edge cases.
And that’s it! No more upload errors. Hope this helps save you the headache I had the first time I ran into it.
1. Check selected template on single post
Go to the Oxygen Section of the post that is displaying a 404 and check that the "Render Page Using Template field" Displays the template you set up for the page:

2. Check Template Display Condition
Got to: Oxygen > Template > {select your template}
Check in your section "WHERE DOES THIS TEMPLATE APPLY?" that the correct post type is selected. For Single Post Templates make sure you select your post type under the dropdown "Singular".

3. Resave Permalinks
Got to: Settings > Permalinks
Scroll down on the Permalinks page can click "Save Changes". This will resave permalinks for your new post type and replace old settings in your .htaccess file.

4. The usual Oxygen Builder troubleshooting steps
Isotropic has a good guide on troubleshooting Oxygen Builder:
1. Back up you website
2. Regenerate CSS (Oxygen > Settings > CSS Cache TAB )

2. Resign Shortcodes (Oxygen > Settings > Security TAB )

Still need help? Get in touch with me to get help with your oxygen builder project.
What's needed
- A domain (preferably registered through Cloudflare)
- A GitHub account (sign up at GitHub.com)
- A Netlify account (sign up at Netlify.com)
Step-by-step setup
1. Set Up Your GitHub Repository
- Create a GitHub account if you don’t have one.
- Set up a new repository on GitHub.
- Upload your website files (HTML, CSS, JavaScript) using GitHub.dev or Visual Studio Code.
2. Deploy Your Site on Netlify
- Sign up at Netlify.com using your GitHub account.
- Click on Deploy a new project.
- Select GitHub as the source and choose your newly created repository.
- Name your project and click Deploy.
3. Connect Your Domain via Cloudflare
- Go to Cloudflare.com and purchase a domain.
- Configure your domain’s DNS settings to point to Netlify.
- Log in to Netlify, navigate to your project, and add your domain name.
Your website is now live and hosted for free!
Advantages of your free website
✅ Zero Hosting Fees – Unlike Wix or Squarespace, Netlify offers free hosting with no hidden costs.
✅ Full Ownership – You control your files and can move or modify them anytime.
✅ Improved Performance – Static websites can load faster and are more secure compared to CMS-based websites.
✅ High Security – Static HTML sites are more secure that
✅ Scalability – Ideal for simple portfolios, landing pages, or business info pages.
Disadvantages & Limitations
⚠️ Limited to Static Sites – This setup is not suitable for dynamic content, blogs, or e-commerce sites.
⚠️ No Built-in CMS – The basic HTML site will not allow advanced content management.
⚠️ Technical Knowledge Required – Making edits requires basic knowledge of HTML & CSS, though tools like ChatGPT can assist. I do offer this service, if you are interested drop me a message.
Who Is This Ideal For?
This setup works best for:
- Single page websites
- Low traffic websites
- Freelancers/small business just starting out
- Individuals with low budget
- You are not looking to update content frequently
If you need a cost-effective, low-maintenance website, this is a great solution!
Are you planning to build or revamp your business website and wondering whether to hire a freelancer or an agency?
As someone who has worked as both a freelancer and alongside agencies, I often help clients navigate this decision. Each option has its strengths and challenges, and understanding how they operate can make all the difference in choosing the right fit for your business.
Unfortunately there is no easy answer to this question but lets break it down together...
Knowing Your Needs
Before deciding whether to hire a freelancer or an agency, it’s essential to clarify exactly what you need from your website. This involves understanding two key areas: functional requirements and non-functional requirements.
- Functional Requirements: These are the specific features your website must include to serve its purpose. For example, an online store needs a shopping cart and payment gateway, while a service-based business might require a booking system or contact forms.
- Non-Functional Requirements: These focus on the performance and quality of your website, such as its speed, security, scalability, or user experience. These elements ensure the site runs smoothly and keeps users engaged.
Do You Even Need a Website?
Sometimes, your goals might not even require a full website. For instance:
- If you’re promoting a personal portfolio, platforms like Behance or LinkedIn might be sufficient.
- For selling products, Ebay, Etsy, or even Instagram Shops could do the job.
- If you’re building a community, email newsletters or platforms like Substack might work better.
Choosing the Right Technology
Once you’ve defined your needs, you can research the best technologies to achieve your goals. Some popular options include:
- WordPress: Versatile and ideal for content-heavy websites, blogs, or small business sites.
- Shopify/WooCommerce: Designed for e-commerce with robust tools for inventory, payments, and shipping.
- React/Vue.js: Great for highly interactive websites or web apps.
- No-Code Platforms (e.g., Squarespace, Wix): Perfect for smaller budgets or simple sites with a quick turnaround.
Finding the Right Specialist
Having a clear understanding of your requirements will make it easier to find a specialist freelancer or agency in the right field. A freelancer with expertise in WordPress might be perfect for a small business blog, while an agency with a dedicated team could be better suited for building a complex e-commerce platform or custom web app.
The more detailed your brief, the easier it will be to match your project with the right professional.
How Freelancers Operate
Freelancers are independent professionals who work on a project-by-project basis. Their operating style tends to be more flexible and personalised, often offering a close, one-on-one working relationship.
Key Characteristics of Freelancers:
- Direct Communication: You’ll work closely with the freelancer, often directly discussing ideas, updates, and feedback.
- Specialisation: Freelancers typically focus on specific areas, such as web design, development, or SEO. This makes them ideal for targeted projects.
- Flexible Workflow: Freelancers can adapt their schedule to meet your needs, but their availability may vary, especially if they’re juggling multiple clients.
- Cost-Effective: Without the overheads of a larger team, freelancers often charge lower rates than agencies.
Drawbacks of Freelancers:
- Limited Resources: Freelancers work solo, so you might need to hire multiple specialists for different aspects of the project, like design and development.
- Dependence on One Person: If your freelancer becomes unavailable or faces delays, it could affect the entire project timeline.
How Agencies Operate
Agencies are businesses with a team of professionals offering a wide range of services under one roof. They typically have structured processes and work collaboratively to complete projects.
Key Characteristics of Agencies:
- Team Expertise: Agencies provide access to a variety of specialists, from designers and developers to marketers and project managers.
- Established Processes: Agencies often follow a well-defined workflow, including planning, development, and quality assurance.
- Scalability: With more resources, agencies can handle larger or more complex projects and often meet tighter deadlines.
- Comprehensive Services: Agencies can manage your project end-to-end, from strategy and design to post-launch support.
Drawbacks of Agencies:
- Higher Costs: Agencies usually charge more to cover team salaries, office expenses, and other overheads.
- Less Personalised Attention: With multiple clients, you might not receive the same level of direct communication as you would with a freelancer.
- Rigid Processes: Agencies may have less flexibility in adapting to changes mid-project.
Pros and Cons of Hiring a Freelancer
Pros:
- Cost-Effectiveness: Freelancers are typically more affordable than agencies, as they don’t have the overheads of a team or office space.
- Flexibility: Working with a freelancer often allows for a more personal relationship and a customised approach to your project.
- Specialisation: Many freelancers focus on specific niches or skills, offering expertise in areas like WordPress, SEO, or e-commerce.
Cons:
- Limited Capacity: Freelancers may struggle to handle large or complex projects requiring multiple disciplines.
- Availability Risks: If your freelancer is unavailable or unresponsive, your project may face delays.
- Support Challenges: Freelancers may not provide consistent ongoing support or have the capacity for long-term maintenance.
Pros and Cons of Hiring an Agency
Pros
- Team Collaboration: Agencies bring together experts in design, development, marketing, and strategy, ensuring a well-rounded approach.
- Scalability: With more resources, agencies can handle large-scale or complex projects and adapt to tight deadlines or additional requirements.
- Reliability: Agencies often have dedicated project managers and established processes, making them a dependable choice for long-term support.
Cons:
- Higher Costs: Agencies charge premium prices to cover their overheads, team salaries, and administrative expenses.
- Less Personalised Service: With multiple clients, agencies may offer less one-on-one attention compared to freelancers.
- Possible Bureaucracy: Structured workflows and internal processes can lead to longer turnaround times or less flexibility.
Hybrid Solutions: Best of Both Worlds
In some cases, combining the strengths of both a freelancer and an agency can provide the ideal solution for your website needs. A hybrid approach allows businesses to tap into the specific strengths of both options, creating a balanced and cost-effective strategy.
Scenarios for Combining Freelancers and Agencies:
- Design and Development Split: You could hire a freelancer for the creative aspects, such as designing a unique and visually appealing website, and then bring in an agency to handle the more technical side, such as custom development, backend functionality, or complex integrations. This allows you to benefit from the freelancer's personal touch while leveraging the agency's resources and expertise for more technical requirements.
- SEO and Content: A freelancer with specialised knowledge in SEO or content writing can help ensure your website ranks well on search engines, while an agency can handle the larger-scale digital marketing strategy or the ongoing optimisation of your site.
- Short-Term and Long-Term Needs: Freelancers may be ideal for shorter, specific tasks like logo design or one-off landing pages, while an agency can provide ongoing support for larger projects, marketing, and strategy implementation.
Transitioning from Freelancer-Built Websites to Agency Support:
As your business grows, your website’s needs may become more complex and demanding. Here’s how you can smoothly transition:
- Gradual Expansion: If you started with a freelancer for the initial website build, you can start involving an agency when your project requires more scalability, such as adding new features or increasing traffic.
- Ongoing Support: Initially, freelancers might handle updates or small tweaks, but as your business scales, an agency can take over full-site maintenance, ensuring consistent performance and security.
- Strategic Development: As your business evolves, you may want to expand your online presence with additional marketing or technology needs. Agencies can offer a broader range of services, including integrated marketing campaigns, advanced SEO strategies, and more robust tech infrastructure, which can be challenging for a freelancer alone to manage.
By blending the strengths of freelancers and agencies, you can create a more flexible and scalable solution that adapts to your growing needs.
Finding the Best Solution for Your Business
Choosing the right approach—whether a freelancer, agency, or hybrid—depends on the specific needs of your business and the scale of your website project. Here’s a quick guide to help you decide:
1. Start with Your Goals and Budget
- Small Business or Personal Project: If your website is relatively simple, with limited features, and you’re working on a tighter budget, a freelancer may be the best option. They provide a more personalised experience and cost-effective solutions.
- Growing Business with Complex Needs: If you’re looking for a more robust website with advanced functionality, ongoing support, or the ability to scale quickly, an agency may be the better choice. Agencies have the resources to handle larger, more complex projects.
- Hybrid for a Balanced Approach: If your project involves specific tasks like custom design or unique technical features, consider combining both. For instance, hire a freelancer for design and user experience, then work with an agency for development, marketing, and long-term support.
2. Consider Future Growth
- Scalability: If your business is growing quickly and you anticipate needing frequent updates, ongoing support, or scalability, an agency may be a good long-term partner. They can provide the infrastructure and resources you need as your website evolves.
- Flexibility: If you prefer flexibility and working closely with someone who can respond quickly to changes, a freelancer may be more suitable in the early stages. However, as your website grows, you can bring in an agency to handle larger, more complex projects.
3. Evaluate Your In-House Capabilities
If you already have a team or some technical expertise, a freelancer might be enough to cover specific areas like design or content creation. However, if you lack in-house resources, an agency can provide a more comprehensive service, from design to strategy, development, and maintenance.
4. Long-Term Relationship and Support
Consider the level of ongoing support you require. Freelancers can be ideal for short-term, specific tasks, but if your business demands continuous updates, performance optimisation, or customer service, an agency with dedicated resources may offer the reliability and consistency you need.
