Display Posts Shortcode – Full Content

The Display Posts Shortcode plugin lets you display a list of posts based on any criteria. By default it displays titles only. The example below makes it display the post’s title and full content.

  • Change favicon color for dark mode

    When you upload a favicon image in the WordPress customizer, it provides a helpful preview to see how your favicon will appear in browsers using light or dark mode.

    When the favicon color doesn’t work well with dark mode, a common fix is to replace the transparent PNG with a JPG that has a white background, but then you end up with a white square in dark mode.

    Alternatively, you can use an SVG for the favicon and modify the favicon styling based on the color scheme.

    You can see this in use in the recent NerdPress redesign we just launched.

    Create SVG favicon

    Create a square SVG with your desired icon. It will look something like this:

    <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
    	<path fill="#0F145B" d="......" />

    Remove any styling from the shapes in the SVG (so the fill and stroke attributes) and add those styles with inline CSS.

    You can use @media ( prefers-color-scheme: dark ) to style the dark mode version differently. Here’s what my SVG now looks like:

    <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
    		path {
    			fill: #0F145B;
    		@media ( prefers-color-scheme: dark ) {
    			path {
    				fill: #43C1C5;
    	<path d="....." />

    Add SVG favicon to your theme

    I added my favicon.svg to my theme’s /assets/images/ directory, but you can add it anywhere in your theme.

    Add the following code to your theme’s functions.php file to include the SVG favicon.

     * SVG Favicon
    function be_svg_favicon() {
    	echo '<link rel="icon" href="' . esc_url( get_stylesheet_directory_uri() . '/assets/images/favicon.svg' ) . '" type="image/svg+xml">';
    add_action( 'wp_head', 'be_svg_favicon', 100 );

    It seems that the SVG favicon is prioritized over the WP generated one regardless of whether it appears before or after it in the page markup, but I have the priority set to 100 so it will appear after, just in case.

    Even with this approach, you should upload a JPG version of the favicon in the WordPress customizer. There are still many browsers that don’t support SVG favicons so you’ll want a fallback.

  • InnerBlocks with ACF blocks

    My favorite new feature in Advanced Custom Fields 5.9 is support for InnerBlocks. This allows you to insert any block (core or custom) inside your ACF block.

    Rather than having to create your own fields for Title, Content, and Button in your custom block, you can simply insert <InnerBlocks /> and use the block editor to build the content inside the block.

    How to use InnerBlocks

    When registering your ACF block, include 'jsx' => true in the supports array.

    acf_register_block_type( array(
    	'title'			=> __( 'About', 'client_textdomain' ),
    	'name'			=> 'about',
    	'render_template'	=> 'partials/blocks/about.php',
    	'mode'			=> 'preview',
    	'supports'		=> [
    		'align'			=> false,
    		'anchor'		=> true,
    		'customClassName'	=> true,
    		'jsx' 			=> true,

    In your template partial for the block, include <InnerBlocks /> where you would like the editable block area to appear.

    $classes = ['block-about'];
    if( !empty( $block['className'] ) )
        $classes = array_merge( $classes, explode( ' ', $block['className'] ) );
    $anchor = '';
    if( !empty( $block['anchor'] ) )
    	$anchor = ' id="' . sanitize_title( $block['anchor'] ) . '"';
    echo '<div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
    	echo '<div class="block-about__inner">';
    		echo '<div class="block-about__content">';
    			echo '<InnerBlocks />';
    		echo '</div>';
    		echo '<div class="block-about__image">';
    			echo wp_get_attachment_image( get_field( 'image' ), 'be_thumbnail_l' );
    		echo '</div>';
    	echo '</div>';
    echo '</div>';

    Default value for InnerBlocks

    It’s helpful to fill the InnerBlocks field with default content so the block looks correct when first inserted.

    We’re building a new site for Nice Kicks, and they often need to highlight the release date and details for new sneakers. We built a Release Info block that uses InnerBlocks for the content area.

    Rather than just having an empty white box when they first insert the block, we pre-populate it with default content using a block template.

    Inside the block’s template file, create a $template array detailing which blocks should be added. Update <InnerBlocks /> to include the template.

    You can find the available block attributes in a blocks.json file for each block in wp-includes/blocks.

    $template = array(
    	array('core/heading', array(
    		'level' => 2,
    		'content' => 'Title Goes Here',
        array( 'core/paragraph', array(
            'content' => '<strong>Colorway:</strong> <br /><strong>Style Code:</strong>  <br /><strong>Release Date:</strong> <br /><strong>MSRP:</strong> ',
        ) )
    echo '<div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
    	echo '<InnerBlocks template="' . esc_attr( wp_json_encode( $template ) ) . '" />';
    	$form_id = get_option( 'options_be_release_info_form' );
    	if( !empty( $form_id ) && function_exists( 'wpforms_display' ) )
    		wpforms_display( $form_id, true, true );
    echo '</div>';

    Placeholders instead of default content

    In the above example we set the starting content for the block. If you were to publish the post without changing the text, the default content would appear in the blocks.

    Alternatively, you can use the placeholder parameter to specify placeholder text. This will not be published, and when you select the field the placeholder text disappears.

    I had two issues with placeholders, which is why I used default content instead:

    1. When you insert the block, the first block inside InnerBlocks is selected so its placeholder text is not visible. You have to insert the block then click outside the block to see the placeholder text.
    2. The placeholder field does not support HTML. In my use case, we used <strong> and <br /> to format the paragraph text, but that doesn’t work with the placeholder.

    To use placeholders with the above example, change the $template to

    $template = array(
    	array('core/heading', array(
    		'level' => 2,
    		'placeholder' => 'Title Goes Here',
    	array( 'core/paragraph', array(
    		'placeholder' => '<strong>Colorway:</strong> <br /><strong>Style #:</strong>  <br /><strong>Release Date:</strong> <br /><strong>Price:</strong> ',
    	) )

    And this was the result:

    Limit the blocks available in InnerBlocks

    You can limit which blocks can be inserted into your InnerBlocks field using the allowedBlocks attribute.

    Using the example above, I can limit the Release Info block to only include the heading and paragraph blocks:

    $allowed_blocks = array( 'core/heading', 'core/paragraph' );
    $template = array(
    	array('core/heading', array(
    		'level' => 2,
    		'content' => 'Title Goes Here',
        array( 'core/paragraph', array(
            'content' => '<strong>Colorway:</strong> <br /><strong>Style Code:</strong>  <br /><strong>Release Date:</strong> <br /><strong>MSRP:</strong> ',
        ) )
    echo '<div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
    	echo '<InnerBlocks allowedBlocks="' . esc_attr( wp_json_encode( $allowed_blocks ) ) . '" template="' . esc_attr( wp_json_encode( $template ) ) . '" />';
    	$form_id = get_option( 'options_be_release_info_form' );
    	if( !empty( $form_id ) && function_exists( 'wpforms_display' ) )
    		wpforms_display( $form_id, true, true );
    echo '</div>';

    Template lock with InnerBlocks

    You can also limit the flexibility by locking the template.

    Adding templateLock="all" prevents inserting new blocks or removing/re-arranging current blocks

    Adding templateLock="insert" prevents inserting new blocks or removing current blocks, but you can re-arrange the current blocks.

    I recently built an Icon Heading block. The icon can be selected in the block settings sidebar using a dynamic dropdown field.

    I used InnerBlocks for the heading itself so it would have all the standard options for customizing the heading (change block style, change heading type to h3). I used templateLock="all" so only the heading from my block template could be used in this block.

    $classes = ['block-icon-heading'];
    if( !empty( $block['className'] ) )
        $classes = array_merge( $classes, explode( ' ', $block['className'] ) );
    if( !empty( $block['align'] ) )
        $classes[] = 'align' . $block['align'];
    $anchor = '';
    if( !empty( $block['anchor'] ) )
    	$anchor = ' id="' . sanitize_title( $block['anchor'] ) . '"';
    $template = array(
    	array('core/heading', array(
    		'level' => 2,
    		'content' => 'Heading',
    echo '<div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
    	$icon = get_field( 'dynamic_icon_category' );
    	if( !empty( $icon ) )
    		echo '<div class="icon-heading-wrap">' . be_icon( [ 'icon' => $icon, 'group' => 'category', 'size' => 38 ] ) . '</div>';
    	echo '<InnerBlocks template="' . esc_attr( wp_json_encode( $template ) ) . '" templateLock="all" />';
    echo '</div>';
  • How to remove core WordPress blocks

    While I try to support all the core blocks in the themes I build, sometimes it makes sense to remove a few.

    Typically it’s because I built a custom block that’s similar to a core block while addressing the design and functional requirements of the theme. Most of my themes include a “Content and Image” block that’s similar to the “Media & Text” block but it uses the theme’s grid layout.

    Sometimes I’ll unregister the “Search” block and create my own that uses the searchform.php file in the theme, ensuring the Search block matches the design and functionality of the search form used everywhere else in the theme.

    Enqueue block editor assets

    You can use the enqueue_block_editor_assets hook to load scripts and styles into the block editor. My themes typically have an editor.js file that I use for block styles and unregistering block types.

    I also enqueue any custom fonts used on the frontend so I can also use them in the editor styles.

     * Gutenberg scripts and styles
    function be_gutenberg_scripts() {
    	wp_enqueue_style( 'theme-fonts', be_theme_fonts_url() );
    	wp_enqueue_script( 'theme-editor', get_template_directory_uri() . '/assets/js/editor.js', array( 'wp-blocks', 'wp-dom' ), filemtime( get_template_directory() . '/assets/js/editor.js' ), true );
    add_action( 'enqueue_block_editor_assets', 'be_gutenberg_scripts' );
     * Theme Fonts URL
    function be_theme_fonts_url() {
    	return 'https://fonts.googleapis.com/css2?family=Roboto+Slab&display=swap';

    Unregister block type

    Now that you’ve created an editor.js file and enqueued it into the block editor, you can use wp.blocks.unregisterBlockType to unregister block types.

    wp.domReady( () => {
    	wp.blocks.unregisterBlockType( 'core/media-text' );
    	wp.blocks.unregisterBlockType( 'core/search' );
    } );

    Here’s a list of all the core block types.

  • Debug code with “pretty printing”

    How often do you write print_r( $something ) to see what’s in the variable? This works great, except it appears inline right where your code is executing.

    My ea_pp() function works the same way, but outputs it in a console-like box attached to the right side of the screen. This pretty printing function was originally built by Chris Bratlien.


    I was looking to see which attributes are included in the core/gallery block, so I added the following code to functions.php:

    add_action( 'wp_footer', function() {
    	global $post;
    	$blocks = parse_blocks( $post->post_content );
    	foreach( $blocks as $block ) {
    		if( 'core/gallery' === $block['blockName'] ) {
    			ea_pp( $block );

    This displayed all the gallery block information on the right side of the screen:


    I include this in a mu-plugin locally so it only runs in my development environment, but you could also add it to a plugin or your theme’s functions.php file.

     * Pretty Printing
    function ea_pp( $obj, $label = '' ) {
    	$data = json_encode( print_r( $obj,true ) );
    	<style type="text/css">
    		#bsdLogger {
    		position: fixed;
    		top: 0;
    		right: 0px;
    		border-left: 4px solid #bbb;
    		padding: 6px;
    		background: white;
    		color: #444;
    		z-index: 999;
    		font-size: 1.25em;
    		width: 400px;
    		height: 100vh;
    		overflow: scroll;
    		.admin-bar #bsdLogger {
    			top: 32px;
    			height: calc( 100vh - 32px );
    	<script type="text/javascript">
    		var doStuff = function(){
    			var obj = <?php echo $data; ?>;
    			var logger = document.getElementById('bsdLogger');
    			if (!logger) {
    				logger = document.createElement('div');
    				logger.id = 'bsdLogger';
    			var pre = document.createElement('pre');
    			var h2 = document.createElement('h2');
    			pre.innerHTML = obj;
    			h2.innerHTML = '<?php echo addslashes($label); ?>';
    		window.addEventListener ("DOMContentLoaded", doStuff, false);
  • Hiring a developer to join our team

    I’m searching for a WordPress Developer to help us build custom WordPress themes and support our clients.

    This is the perfect role for a WordPress developer with limited freelance work experience. In addition to mastering our development approach, you’ll learn our sales process, participate in client calls, and receive personal coaching from me.

    I’ll consider both part-time and full-time applicants, and we’d start working together in August or September.

    About Us

    We are a collective of independent designers and developers working together to help publishers grow and thrive. We’re currently a team of four: two designers (Duane Smith and Andrew Pautler) and two developers (myself and Richard Buff).

    We build custom WordPress themes focused on performance, user experience, accessibility, scalability, and SEO. We do this to help publishers express their brand, grow their audience, and thrive in their marketplace.

    Minimum qualifications

    • Proficiency with WordPress theme development (PHP, HTML, CSS, SASS).
    • Basic familiarity with version control through Git and GitHub.
    • Excellent communication skills, fluent in both verbal and written English. As a fully distributed team, we spend most of our time communicating via Slack, email, and video calls.
    • You have the curiosity and desire to learn and grow your skills.

    How to apply?

    If this opportunity sounds interesting to you, then please submit an application by July 11th.

    Please clearly include the following in your cover letter:

    • Your experience with WordPress theme development.
    • What is your favorite WordPress hook/function and why.
    • Tell us a bit about yourself and why you should be considered. Details about your experience, qualifications, personality, etc are very helpful.
    • Profile links with code samples (GitHub, WordPress.org, etc).
    • Other profile links if available (Your website, Twitter, LinkedIn, etc).

    Note: The application period is now over.

  • Eliminate spam with a custom honeypot

    A custom honeypot is a simple and effective way to eliminate spam. If a hidden field in your form is filled in, you can be fairly confident the submission is spam.

    WPForms does include a built-in honeypot, but now that the plugin is used on millions of sites, most spam bots have been updated to identify and skip the WPForms field with a name of hp.

    Your custom honeypot is different. It’s unique to your form and looks like any other field to a bot. I have pretty much eliminated spam on my contact form with a custom honeypot.

    First, come up with a unique CSS class name you’ll use to identify your honeypot field. Make it something unique to your site (ie: not honeypot). If your class is my-fancy-field, add this to your theme’s stylesheet to hide that field.

    .wpforms-container .my-fancy-field {
    	display: none;

    Create a field in your form and add your custom class to it.

    Add the following code to your theme’s functions.php file or a Core Functionality plugin. If this field is ever filled in, the submission will be marked as spam and have a honeypot message of “[Custom honeypot]”.

    Make sure you update the $honeypot_class variable at the top to use your custom class name.

    * WPForms Custom Honeypot
    * @author Bill Erickson
    * @link http://www.billerickson.net/eliminate-spam-with-a-custom-honeypot/
    * @param string $honeypot, empty if not spam, honeypot text is used in WPForms Log
    * @param array $fields
    * @param array $entry
    * @param array $form_data
    function be_wpforms_custom_honeypot( $honeypot, $fields, $entry, $form_data ) {
    	$honeypot_class = 'my-fancy-field';
    	$honey_field = false;
    	foreach( $form_data['fields'] as $form_field ) {
    		if( false !== strpos( $form_field['css'], $honeypot_class ) ) {
    			$honey_field = absint( $form_field['id'] );
    	if( !empty( $entry['fields'][$honey_field] ) ) {
    		$honeypot = 'Custom honeypot';
    	return $honeypot;
    add_filter( 'wpforms_process_honeypot', 'be_wpforms_custom_honeypot', 10, 4 );


    You can also enable logging so you can see if the honeypot is working. Every time a spam entry is submitted, this will create a post in the wpforms_log post type with the honeypot message and the full submission.

    I recommend only logging this data temporarily because you don’t want to fill up your database with a bunch of unimportant spam messages.

    First, update the wpforms_logging option to log spam:

     * Enable logging of spam
    add_action( 'init', function() {
    	$debug = get_option( 'wpforms_logging' );
    	if( empty( $debug ) || ! in_array( 'spam', $debug ) )
    		update_option( 'wpforms_logging', [ 'spam' ] );

    Use this code to make the WPForms Log post type visible. You can then access it in WPForms > Logs.

     * Make log visible
    add_filter( 'wpforms_log_cpt', function( $args ) {
    	$args['show_ui'] = true;
    	unset( $args['capability_type'] );
    	return $args;
  • Using SpinupWP as a development server

    SpinupWP is a modern cloud-based server control panel. It gives you the features of a managed WordPress host on your own low-cost and scalable Digital Ocean servers.

    It’s a great choice for production sites, and I host many of my personal sites there. It also makes an excellent development environment.

    My development server requirements are:

    1. Blazing fast. Most of my clients hire me to make their site faster, so my dev server should be optimized for speed.
    2. Scalable. I can easily scale up and down the size of the server based on my current needs.
    3. Reasonably priced. I want to keep my hosting costs minimal.

    I’m currently using a $15/month droplet with 3GB of memory and 60GB of storage. When you add in the $9/month for SpinupWP, my total hosting bill of $24/month is less than most managed WordPress hosts for a single site.

    $50 credit for SpinupWP

    If you sign up for SpinupWP using the link below, you’ll get a $50 credit added to your account after 30 days.

    Sign up now

    My preferred development approach

    I use git to version control the custom themes I develop. When I’m ready to push my local changes to the server, I type git push staging master. I also use WP Migrate DB Pro to push/pull the database between environments.

    For my clients hosted with WPEngine or BigScoots, I use their built-in staging environments and git-based deployment. For more information, see my articles on git for WPEngine and git for BigScoots.

    If a client is hosted elsewhere, I use SpinupWP to replicate the same git push development workflow and features.

    Setting up a development environment

    Once you have your SpinupWP server setup, it’s easy to add new sites. I have a domain I use for development sites, with each site being setup as a subdomain.

    Log into the SpinupWP Dashboard and click the “New Site” button. Create a fresh WordPress install (here’s a guide).

    Install WP Migrate DB Pro along with the Media and Theme & Plugin addons on the production site and the new development site. Pull a copy of the database, media, themes, and plugins to your dev server. Alternatively, you can copy the site files over via SSH.

    Set up git on the server

    1. When you ssh into the server, you should already be in the site directory. Typing ls should list /files and /logs
    2. Create a bare repository. git init --bare project.git
    3. Create the post-receive hook. touch project.git/hooks/post-receive
    4. Add execute permissions to the post-receive hook. chmod +x project.git/hooks/post-receive
    5. Edit the post-receive hook (vi project.git/hooks/post-receive) and add the following to it. Make sure you update the two paths at the top to match your environment
    while read oldrev newrev ref
            # only checking out the master (or whatever branch you would like to deploy)
            if [ "$ref" = "refs/heads/$BRANCH" ];
                    echo "Ref $ref received. Deploying ${BRANCH} branch to production..."
                    git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f $BRANCH
                    echo "Ref $ref received. Doing nothing: only the ${BRANCH} branch may be deployed on this server."

    In your local environment, add the remote:

    git remote add staging ssh://[email protected]/~/project.git

    Local environment, no media

    A lot of the sites I build have massive uploads directories. To save local hard drive space and simplify database syncing, I don’t pull any of the media down locally.

    I’ll use Migrate DB Pro to pull down a local copy of the database, theme and plugins. I’ll then install BE Media from Production and use it to source media from the development server. You can install and set it up with wp cli:

    wp plugin install be-media-from-production --activate
    wp config set BE_MEDIA_FROM_PRODUCTION_URL https://clientname.cultivatewp.com --type=constant

    One of the first things I do when building a new theme is determine all the required image sizes. After adding the relevant add_image_size()‘s to the theme, I’ll push the theme and database to staging and regenerate thumbnails. I can then pull a fresh copy of the database locally and have access to all the newly generated thumbnails.

    Likewise, if I need to upload new images, I push the database to staging, upload the image on staging, then pull a fresh copy of the database locally.

  • Gutenberg Theme Development – Tips for Success

    The new NexRep website is built completely with the Gutenberg block editor.

    We’ve built dozens of WordPress sites just like NexRep that rely on the block editor for rich, engaging landing pages that are easily maintained by non-technical editors with a consistent style guide.

    These are my tips for success with Gutenberg theme development.

    Start with Atomic Design

    If you are working with a designer, make sure they are familiar with the block editor and use an atomic design approach.

    We start with the atoms of our style guide – headings, paragraph, link, and button. These are then built into molecules, like a Post Summary (post title, category, excerpt). These are then built into organisms, like a Post Listing (section header, 3 column grid of posts).

    The launch of Gutenberg was the final push I needed to no longer work with client-provided designs. Every website I build is now designed by my two design partners, Duane Smith and Andrew Pautler, who have a deep understanding of the Gutenberg block editor.

    An atomic design approach empowers content creators with the full power of the Gutenberg block editor, rather than fighting against it and trying to maintain an old template-based design approach.

    Editor Styles

    To ensure the best content editing experience, the backend editor should match the frontend as closely as possible. Here’s what the block editor looks like for the page shown in the first screenshot.

    Use an editor stylesheet to load a separate CSS file in the editor with your unique block styles. Create an editor-style.css file in your theme, and add the following code to your theme’s functions.php file to load it:

    // Editor Styles
    add_theme_support( 'editor-styles' );
    add_editor_style( 'editor-style.css' );

    You can use the editor font sizes feature to define different font size options for paragraph text. The defaults are Small, Normal, and Large, but you can load any number you’d like.


    SASS comes in handy here. Rather than trying to maintain two separate stylesheets, you can break your main stylesheet into SASS partials and build both stylesheets with the relevant partials.

    When you update a style that applies to the block editor, it automatically updates both style.css and editor-style.css.

    Take a look at my starter themes for examples of how I structure my stylesheets.

    Color Palette

    You should also update the Gutenberg color palette to use your theme colors. We use the color options for changing button colors and for adding background color to full width sections.

    We’ll typically have two versions for each color: the normal (darker) version which will have white text, and a lighter version that will use the standard text color.

    Additional editor scripts and styles

    If you use the add_editor_style() feature described above, WordPress will automatically prefix your styles so they only affect the content area. h2 {} becomes .editor-styles-wrapper h2 { }.

    In some instances, you may want to load scripts or styles that are not modified by WordPress. You can use the enqueue_block_editor_assetshook.

    On the NexRep site, we’re loading a stylesheet to change the content area width based on the selected page layout (ex: Full Width Content vs Content Sidebar).

    Block Styles

    You can add style variations to core blocks using block styles. On the NexRep site, we have two button styles (Default and Feature), and have removed unnecessary block styles from the separator and quote blocks.

    wp.domReady( () => {
    		[ 'default', 'outline', 'squared', 'fill' ]
    				name: 'default',
    				label: 'Default',
    				isDefault: true,
    				name: 'feature',
    				label: 'Feature',
    		[ 'default', 'wide', 'dots' ],
    		[ 'default', 'large' ]
    } );

    Remove core blocks

    You can also remove core blocks using wp.blocks.unregisterBlockType. We typically remove core blocks when we’ve built a custom block that’s very similar, to minimize confusion for the client.

    On NexRep, we have custom blocks that are similar to the core Media & Text, Cover, and Latest Posts blocks.

    wp.domReady( () => {
    	wp.blocks.unregisterBlockType( 'core/verse' );
    	wp.blocks.unregisterBlockType( 'core/cover' );
    	wp.blocks.unregisterBlockType( 'core/pullquote' );
    	wp.blocks.unregisterBlockType( 'core/media-text' );
    	wp.blocks.unregisterBlockType( 'core/latest-posts' );
    } );

    Custom blocks

    I’m a huge fan of Advanced Custom Fields. We use it on every website for custom blocks, site options, term meta, and more.

    One of the most common questions I’m asked by WordPress developers exploring Gutenberg is whether they need to build custom blocks natively with React or use a plugin like ACF.

    I personally think of it along the same lines as custom metaboxes. For single use blocks built for a specific website, it makes sense to use a tool like ACF for rapid development. If you plan to distribute your block publicly in a plugin, you’ll want to build it with React so there isn’t a dependency on another plugin.

    Most of the websites we build include 10-20 custom blocks. The NexRep site included these custom blocks:

    • Hero Header
    • Call to Action
    • Icon Bullets
    • Content & Image
    • Content & Video
    • Content & Icon
    • Content Image Overlay
    • Percent Bullets
    • Featured Logos
    • Nexrep Medallion
    • Quick Links
    • Post Listing
    • Staff

    Try to leverage core blocks as much as possible, and keep your custom blocks simple. In the quick video below, you’ll see how we use the Group block for the full width section with Heading and Icon Links block inside.

    Client Training

    The final step to a successful project is training the client so they are comfortable with the new theme. This is especially important when it comes to the block editor as this is likely the first time the client has interacted with it.

    We provide WP101 video tutorials to all of our clients. On websites like NexRep that include a fairly complex design with custom blocks, we record custom videos to include alongside the standard WordPress videos.

  • Category specific email optin with WPForms

    Almost all of our food blogger and publisher clients use email optins to encourage readers to join their email newsletter.

    We often customize the title and description of the form based on the current post’s category. For instance, the newsletter signup form in the Beauty category might look like:

    While we could build a separate form for every category, a simpler approach is to create a single form and use category metadata to override the default title and description for certain categories.

    Create default form

    Go to WPForms > Add New and create your newsletter signup form. Include the standard title and description that’s applicable site-wide.

    If the category does not have a specific title or description set, it will use whatever you have set here.

    Term Metabox for category override

    Using Advanced Custom Fields, create a metabox that appears on the Edit Category screen allowing editors to change the Optin Title and Optin Description. I’m using be_optin_title and be_optin_description as the respective meta keys.

    Use term meta to customize form

    Finally, we can add the following code to change the form’s title and description. This code can go in your theme’s functions.php file or a core functionality plugin.

    Make sure you change 123 at the top to the form ID you’d like to target. I’m using the ea_first_term() helper function to select the primary category if the post appears in more than one category.

     * Customize optin title/description by category
     * @link https://www.billerickson.net/category-specific-email-optin-with-wpforms/‎
     * @param array $form_data
     * @return array
    function be_wpforms_category_optin( $form_data ) {
    	if( 123 != $form_data['id'] )
    		return $form_data;
    	$term_id = false;
    	if( is_category() )
    		$term_id = get_queried_object_id();
    	elseif( is_single() )
    		$term_id = ea_first_term( [ 'field' => 'term_id' ] );
    	if( ! $term_id )
    		return $form_data;
    	$title = get_term_meta( $term_id, 'be_optin_title', true );
    	if( !empty( $title ) )
    		$form_data['settings']['form_title'] = $title;
    	$description = get_term_meta( $term_id, 'be_optin_description', true );
    	if( !empty( $description ) )
    		$form_data['settings']['form_desc'] = $description;
    	return $form_data;
    add_filter( 'wpforms_frontend_form_data', 'be_wpforms_category_optin' );
  • Git based code deployment on Big Scoots

    Many of our food blogger clients are hosted with Big Scoots, a popular managed WordPress host.

    Their portal includes one-click push between your production and staging environments, which is great for testing code changes before deploying on your production site.

    With a bit of work you can create a git-based deployment workflow similar to WPEngine. I’ve also written a similar article for using git with SpinupWP.

    If everything below looks too technical, I highly recommend using the WP Pusher plugin. It’s incredibly easy to use, free for public repos, and reasonably priced for private repos. We use WP Pusher for any clients not hosted with Big Scoots or WPEngine.

    Setting up git for production

    Open a support ticket requesting ssh access to your server and include your public key. They will respond back confirming it has been set up and provide your ssh details (server, port, user).

    Open Terminal and use ssh to connect to your server (ssh -p2222 [email protected]). We’ll follow this guide to setting up git on the server.

    We’re going to set up a bare git repository in the site directory. We want to create it in clientname.com , not clientname.com/public.

    By keeping it outside the public directory we won’t have any issues with the “Push Live to Staging” or “Push Staging to Live” features – they move the contents of the public directory.

    1. Navigate to the site directory. If the website is clientname.com, type cd domains/clientname.com
    2. Create a bare repository. git init --bare project.git
    3. Create the post-receive hook. touch project.git/hooks/post-receive
    4. Add execute permissions to the post-receive hook. chmod +x project.git/hooks/post-receive
    5. Edit the post-receive hook (vi project.git/hooks/post-receive) and add the following to it. Make sure you update the two paths at the top to match your environment
    while read oldrev newrev ref
            # only checking out the master (or whatever branch you would like to deploy)
            if [ "$ref" = "refs/heads/$BRANCH" ];
                    echo "Ref $ref received. Deploying ${BRANCH} branch to production..."
                    git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f $BRANCH
                    echo "Ref $ref received. Doing nothing: only the ${BRANCH} branch may be deployed on this server."
    1. Add the remote repository to your local environment
    cd ~/path/to/working-copy
    git remote add production ssh://[email protected]:2222/~/domains/clientname.com/project.git
    1. Push code to the production server git push production master

    Setting up git for staging

    You can use the exact same approach outlined above to setup git on staging.

    If you haven’t created a staging environment yet, log into the BigScoots Portal, go to your site, and click “Create Staging”.

    Once the staging site has been created, ssh into the server and navigate to the staging site’s directory. Using the clientname.com example above, the staging environment would be named clientnamecom.bigscoots-staging.com

    Once you have everything setup on the server, go back to your local environment and add the staging remote

    git remote add staging ssh://[email protected]:2222/~/domains/clientnamecom.bigscoots-staging.com/project.git

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk