, ,

Conditionally Executing Code in Different WordPress Block Editor Contexts

person with letters on his palms

When developing for the WordPress Block Editor, you often need to control when and where your JavaScript code executes. For example, you might want to:

  • Register SlotFills that only appear on the Post Editor or on the Site Editor.
  • Add editor commands that are only available in certain contexts.
  • Display custom UI elements based on post status or user capabilities.
  • Prevent functionality from running in the Site Editor vs Post Editor.

Rather than running code everywhere, you can conditionally execute functionality based on the editing context—such as post type, post status, editor mode, or other conditions.

All the examples in this article are taken from a fully functional WordPress plugin with comprehensive E2E tests. You can explore the complete implementation, run the tests, and see these patterns in action at juanma-wp/detect-site-post-editor

Table of Contents

Client-Side Context Detection

The fundamental approach on the Client-side uses WordPress’s @wordpress/data package to access editor information and determine whether to execute your code.

Example (ViewablePostTypeComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';

const ViewablePostTypeComponent = () => {
    const { isViewable, postTypeName } = useSelect( ( select ) => {
        const postType = select( editorStore ).getCurrentPostType();
        const postTypeObject = select( coreStore ).getPostType( postType );
        return {
            isViewable: postTypeObject?.viewable,
            postTypeName: postType,
        };
    }, [] );

    // Return null to prevent execution
    if ( ! isViewable ) {
        return null;
    }

    // Your code only executes when conditions are met
    return (
        <div className="example-section" data-testid="viewable-post-type">
            <h3>✓ Viewable Post Type</h3>
            <p>This component only renders for viewable post types (excludes Site Editor).</p>
            <p><strong>Current post type:</strong> {postTypeName}</p>
            <p><strong>Is viewable:</strong> {isViewable ? 'Yes' : 'No'}</p>
        </div>
    );
};

The useSelect hook retrieves data from WordPress data stores, and we can get info such as the current post type, post type details (like whether it is viewable), post status, and other relevant editor state.

A key property for distinguishing contexts is isViewable, which tells you whether a post type can be viewed on the frontend—not which editor you’re currently using.

Post types with isViewable: true (posts, pages) have a public-facing URL and are edited in the Post Editor, while those with isViewable: false (templates, template parts) are edited in the Site Editor

Anyway, this is indirect detection — we’re inferring the editor context based on the post type’s characteristics, not detecting the editor itself. The property describes the post type’s nature, and the editor context is a side effect of that.

Detecting Specific Post Types

Restrict code execution to specific post types using an allow list. Use the getCurrentPostType() selector from the editor store.

Example (SpecificPostTypeComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';

const SpecificPostTypeComponent = () => {
    const { postTypeName } = useSelect( ( select ) => {
        return {
            postTypeName: select( editorStore ).getCurrentPostType(),
        };
    }, [] );

    const allowedPostTypes = [ 'page', 'product' ];
    if ( ! allowedPostTypes.includes( postTypeName ) ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="specific-post-type">
            <h3>✓ Specific Post Type</h3>
            <p>This component only renders for 'page' and 'product' post types.</p>
            <p><strong>Current post type:</strong> {postTypeName}</p>
        </div>
    );
};

Excluding Certain Post Types

Prevent code from running on specific post types using a blocklist.

Example (ExcludePostTypesComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';

const ExcludePostTypesComponent = () => {
    const { postTypeName } = useSelect( ( select ) => {
        return {
            postTypeName: select( editorStore ).getCurrentPostType(),
        };
    }, [] );

    const excludedPostTypes = [ 'attachment', 'wp_block' ];
    if ( excludedPostTypes.includes( postTypeName ) ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="exclude-post-types">
            <h3>✓ Exclude Post Types</h3>
            <p>This component excludes 'attachment' and 'wp_block' post types.</p>
            <p><strong>Current post type:</strong> {postTypeName}</p>
        </div>
    );
};

WordPress core uses this pattern in the Query block (packages/block-library/src/query/utils.js) to exclude the attachment post type from the Query Loop:

const excludedPostTypes = [ 'attachment' ];
const filteredPostTypes = getPostTypes( { per_page: -1 } )?.filter(
    ( { viewable, slug } ) =>
        viewable && ! excludedPostTypes.includes( slug )
);

Checking Post Type Visibility

Execute code only for public/viewable post types:

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';

const { isViewable } = useSelect( ( select ) => {
    const postType = select( editorStore ).getCurrentPostType();
    const postTypeObject = select( coreStore ).getPostType( postType );
    return {
        isViewable: postTypeObject?.viewable,
    };
}, [] );

if ( ! isViewable ) {
    return null;
}

// Code here only runs for viewable post types

The property isViewable indicates whether the post type has a publicly accessible frontend URL. Post types like post and page are viewable (users can visit them on the frontend), while administrative post types like wp_navigation, wp_block (reusable blocks), or revision are not viewable—they exist only in the editor. Note that any custom post type registered with public = true is also considered viewable.

When filtering code this is a key property because you typically want editor customizations to run only for content that end users will actually see, preventing your code from executing on internal WordPress data structures or administrative screens where it wouldn’t make sense.

Gutenberg core uses this approach in several places:

  1. Post URL Check (packages/editor/src/components/post-url/check.js): Prevents post URL controls from appearing for non-viewable post types:
   import { store as coreStore } from '@wordpress/core-data';

   const postType = select( coreStore ).getPostType( postTypeSlug );
   if ( ! postType?.viewable ) {
       return false;
   }
  1. Post Preview Button (packages/editor/src/components/post-preview-button/index.js): Only renders the preview button for viewable post types:
   const canView = postType?.viewable ?? false;
   if ( ! canView ) {
       return { isViewable: canView };
   }
  1. Post Template Panel (packages/editor/src/components/post-template/panel.js): Restricts template selection to viewable post types only.

Detecting Post Status

Run code based on the current post’s publication status using getCurrentPostAttribute().

Example (PostStatusComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';

const PostStatusComponent = () => {
    const { postStatus } = useSelect( ( select ) => {
        return {
            postStatus: select( editorStore ).getCurrentPostAttribute( 'status' ),
        };
    }, [] );

    // Only show for draft posts (including auto-draft for new unsaved posts)
    if ( postStatus !== 'draft' && postStatus !== 'auto-draft' ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="post-status-draft">
            <h3>✓ Post Status (Draft)</h3>
            <p>This component only renders for draft posts.</p>
            <p><strong>Current status:</strong> {postStatus}</p>
        </div>
    );
};

Common statuses: 'draft', 'publish', 'pending', 'private', 'future', 'auto-draft' (see Post Status reference)

Detecting Page Template

Execute code only when a specific page template is being used with getEditedPostAttribute().

Example (PageTemplateComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';

const PageTemplateComponent = () => {
    const { template, postType } = useSelect( ( select ) => {
        return {
            template: select( editorStore ).getEditedPostAttribute( 'template' ),
            postType: select( editorStore ).getCurrentPostType(),
        };
    }, [] );

    // Only show for pages
    if ( postType !== 'page' ) {
        return null;
    }

    if ( template !== 'full-width' ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="page-template">
            <h3>✓ Page Template</h3>
            <p>This component only renders when the 'full-width' template is active.</p>
            <p><strong>Current template:</strong> {template || 'default'}</p>
        </div>
    );
};

Checking User Capabilities

Restrict code execution based on current user permissions using canUser().

Example (UserCapabilityComponent.js):

import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';

const UserCapabilityComponent = () => {
    const { canPublish } = useSelect( ( select ) => {
        return {
            canPublish: select( coreStore ).canUser( 'publish', 'posts' ),
        };
    }, [] );

    if ( ! canPublish ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="user-capability">
            <h3>✓ User Capability</h3>
            <p>This component only renders for users who can publish posts.</p>
            <p><strong>Can publish:</strong> {canPublish ? 'Yes' : 'No'}</p>
        </div>
    );
};

Detecting Editor Mode

Run code only in specific editor modes (visual vs. code) using getEditorMode() from the edit-post store.

Example (EditorModeComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editPostStore } from '@wordpress/edit-post';

const EditorModeComponent = () => {
    const { editorMode } = useSelect( ( select ) => {
        return {
            editorMode: select( editPostStore ).getEditorMode(),
        };
    }, [] );

    // Only show in visual editor mode
    if ( editorMode !== 'visual' ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="editor-mode">
            <h3>✓ Editor Mode (Visual)</h3>
            <p>This component only renders in visual editor mode.</p>
            <p><strong>Current mode:</strong> {editorMode}</p>
        </div>
    );
};

The editor mode can be either 'visual' or 'text' (code editor). This is useful when you want certain UI elements to only appear in the visual editor, such as block controls that don’t make sense in code editing mode.

Checking for Site Editor Context

Exclude the Site Editor and other non-viewable post types by checking the viewable property.

Example (SiteEditorContextComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';

const SiteEditorContextComponent = () => {
    const { isViewable, postType, postTypeObject } = useSelect( ( select ) => {
        const currentPostType = select( editorStore ).getCurrentPostType();
        const typeObject = select( coreStore ).getPostType( currentPostType );
        return {
            isViewable: typeObject?.viewable,
            postType: currentPostType,
            postTypeObject: typeObject,
        };
    }, [] );

    // Wait for data to load - both postType and postTypeObject should be available
    if ( ! postType || ! postTypeObject ) {
        return null;
    }

    // Only show for viewable post types (excludes Site Editor)
    if ( ! isViewable ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="site-editor-context">
            <h3>✓ Post Editor Context</h3>
            <p>This component only renders in the Post Editor, not in the Site Editor.</p>
            <p><strong>Current post type:</strong> {postType}</p>
            <p><strong>Is viewable:</strong> Yes (excludes Site Editor templates)</p>
        </div>
    );
};

This approach excludes all non-viewable post types like wp_navigation, wp_block (reusable blocks), and revision. This is the recommended pattern from WordPress core.

Excluding Design Post Types

WordPress defines a set of “design post types” that are used in the Site Editor. A common pattern in Gutenberg core is to exclude these post types to prevent UI from appearing in contexts where it doesn’t belong.

Example (ExcludeDesignPostTypesComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';

const DESIGN_POST_TYPES = [
    'wp_template',
    'wp_template_part',
    'wp_block',
    'wp_navigation',
];

const ExcludeDesignPostTypesComponent = () => {
    const { postType } = useSelect( ( select ) => {
        return {
            postType: select( editorStore ).getCurrentPostType(),
        };
    }, [] );

    if ( DESIGN_POST_TYPES.includes( postType ) ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="exclude-design-post-types">
            <h3>✓ Exclude Design Post Types</h3>
            <p>This component excludes Site Editor design post types.</p>
            <p><strong>Current post type:</strong> {postType}</p>
            <p><strong>Excluded types:</strong> {DESIGN_POST_TYPES.join( ', ' )}</p>
        </div>
    );
};

Real-world examples from Gutenberg core:

  1. Visual Editor (packages/editor/src/components/visual-editor/index.js): Uses DESIGN_POST_TYPES to determine whether to show the post title and apply layout styles.
  2. Post Status Component (packages/editor/src/components/post-status/index.js): Excludes the status panel for design post types:
   const DESIGN_POST_TYPES = [
       TEMPLATE_POST_TYPE,
       TEMPLATE_PART_POST_TYPE,
       PATTERN_POST_TYPE,
       NAVIGATION_POST_TYPE,
   ];

   if ( DESIGN_POST_TYPES.includes( postType ) ) {
       return null;
   }

Detecting Selected Block Type

Execute code only when a specific block type is selected using getSelectedBlock() from the block-editor store.

Example (SelectedBlockTypeComponent.js):

import { useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

const SelectedBlockTypeComponent = () => {
    const selectedBlockName = useSelect( ( select ) => {
        const selectedBlock = select( blockEditorStore ).getSelectedBlock();
        return selectedBlock?.name;
    }, [] );

    // Only show when a paragraph block is selected
    // Note: selectedBlockName can be undefined (no selection) or null, both are falsy
    if ( selectedBlockName !== 'core/paragraph' ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="selected-block-type">
            <h3>✓ Selected Block Type</h3>
            <p>This component only renders when a paragraph block is selected.</p>
            <p><strong>Selected block:</strong> {selectedBlockName}</p>
        </div>
    );
};

This pattern is particularly useful for contextual UI elements that should only appear when working with specific block types, such as custom formatting options or block-specific settings panels.

Checking Sidebar State

Run code based on sidebar visibility using isEditorSidebarOpened() or the interface store’s getActiveComplementaryArea().

Example (SidebarStateComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editPostStore } from '@wordpress/edit-post';

const SidebarStateComponent = () => {
    const isSidebarOpened = useSelect( ( select ) => {
        // Try edit-post store first (legacy post editor)
        const editPostSelect = select( editPostStore );
        if ( editPostSelect && editPostSelect.isEditorSidebarOpened ) {
            return editPostSelect.isEditorSidebarOpened();
        }

        // Try core/interface store (unified editor) - check if edit-post/document sidebar is active
        // Access the store by string name since we don't have the package
        const interfaceSelect = select( 'core/interface' );
        if ( interfaceSelect && interfaceSelect.getActiveComplementaryArea ) {
            // Check multiple possible scopes for the complementary area
            const editPostArea = interfaceSelect.getActiveComplementaryArea( 'core/edit-post' );
            const editSiteArea = interfaceSelect.getActiveComplementaryArea( 'core/edit-site' );

            // The document settings sidebar is active when the complementary area ends with '/document'
            return editPostArea === 'edit-post/document' || editSiteArea === 'edit-site/document';
        }

        return false;
    }, [] );

    // Only show when sidebar is open
    if ( ! isSidebarOpened ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="sidebar-state">
            <h3>✓ Sidebar State</h3>
            <p>This component only renders when the sidebar is open.</p>
            <p><strong>Sidebar opened:</strong> {isSidebarOpened ? 'Yes' : 'No'}</p>
        </div>
    );
};

This pattern handles both the legacy post editor (edit-post store) and the unified editor interface (core/interface store), making it compatible across different WordPress versions and editor contexts.

Check the Detecting and Controlling Sidebars in the Block Editor article for more examples on how to check the Sidebar State

Combining Multiple Conditions

Stack multiple conditions for precise control.

Example (CombinedConditionsComponent.js):

import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';

const CombinedConditionsComponent = () => {
    const { postTypeName, postStatus, canEdit } = useSelect( ( select ) => {
        const { getCurrentPostType, getCurrentPostAttribute } = select( editorStore );
        const { canUser } = select( coreStore );

        const postType = getCurrentPostType();
        const status = getCurrentPostAttribute( 'status' );
        const isNewPost = ! status || status === 'auto-draft';

        const permission = isNewPost
            ? canUser( 'create', { kind: 'postType', name: postType } )
            : canUser( 'update', { kind: 'postType', name: postType } );

        return {
            postTypeName: postType,
            postStatus: status,
            canEdit: permission,
        };
    }, [] );

    // Accept both 'draft' and 'auto-draft' statuses
    const isDraftPage = ( postStatus === 'draft' || postStatus === 'auto-draft' );

    // Only render when ALL conditions are explicitly met
    const shouldRender = postTypeName === 'page' && isDraftPage && canEdit === true;

    if ( ! shouldRender ) {
        return null;
    }

    return (
        <div className="example-section" data-testid="combined-conditions">
            <h3>✓ Combined Conditions</h3>
            <p>This component requires ALL conditions to be met:</p>
            <ul>
                <li><strong>Post type:</strong> page ✓</li>
                <li><strong>Status:</strong> draft ✓</li>
                <li><strong>Can edit:</strong> Yes ✓</li>
            </ul>
        </div>
    );
};

Performance Considerations

  • Empty Dependency Array: Use [] as the second argument to useSelect to run the selector only once on mount
  • Early Returns: Return null immediately when conditions aren’t met to prevent unnecessary execution
  • Selective Data: Only retrieve the specific data you need to minimize re-renders (see performance best practices)

Server-Side Context Detection

While the examples above use client-side detection with useSelect, you can also perform context detection on the server side before your JavaScript is even loaded using WordPress conditional tags and the admin_enqueue_scripts hook. This approach is more performant since it prevents unnecessary code from being enqueued at all.

Detecting the Block Editor

Check if the current screen is using the block editor.

Example (server-side-detection.php#L32-L35):

function cre_is_block_editor() {
    $screen = cre_get_current_screen();
    return $screen && $screen->is_block_editor();
}

This function checks if the current admin screen is using the block editor by calling the is_block_editor() method on the WP_Screen object.

Detecting Specific Post Types on the Server

Restrict code to specific post types before enqueueing.

Example (server-side-detection.php#L63-L74):

function cre_is_post_type( $post_type ) {
    $screen = cre_get_current_screen();
    if ( ! $screen ) {
        return false;
    }

    if ( is_array( $post_type ) ) {
        return in_array( $screen->post_type, $post_type, true );
    }

    return $screen->post_type === $post_type;
}

This function supports both single post types and arrays of post types, making it flexible for different use cases. See a practical example in page-only.js where it’s used to enqueue scripts only for pages.

Detecting Post Edit Screens

Check if you’re on a post edit screen.

Example (server-side-detection.php#L42-L45):

function cre_is_post_edit_screen() {
    $screen = cre_get_current_screen();
    return $screen && $screen->base === 'post';
}

This checks the base property of the screen object to confirm we’re on a post edit screen. See post-edit-only.js for a working example.

Detecting Site Editor

Check if you’re in the Site Editor (Full Site Editing).

Example (server-side-detection.php#L52-L55):

function cre_is_site_editor() {
    global $pagenow;
    return 'site-editor.php' === $pagenow;
}

This uses the global $pagenow variable to detect the Site Editor. See no-site-editor.js for an example that prevents scripts from loading in the Site Editor.

Checking User Capabilities on the Server

Restrict based on user permissions.

Example (server-side-detection.php#L95-L116):

function cre_user_can_publish_posts() {
    return current_user_can( 'publish_posts' );
}

function cre_user_can_edit_posts() {
    return current_user_can( 'edit_posts' );
}

function cre_user_can_edit_pages() {
    return current_user_can( 'edit_pages' );
}

These wrapper functions make capability checks more semantic and reusable. See publisher-only.js for an example that restricts functionality to users who can publish posts. Learn more about WordPress capabilities and roles.

Detecting Specific Templates on the Server

Check the page template being used.

Example (server-side-detection.php#L123-L133):

function cre_is_template( $template ) {
    global $post;

    if ( ! $post ) {
        return false;
    }

    $current_template = get_page_template_slug( $post->ID );
    return $current_template === $template;
}

This function checks if the current post uses a specific page template by comparing the template slug using get_page_template_slug().

Checking Post Status on the Server

Only load for specific post statuses.

Example (server-side-detection.php#L140-L155):

function cre_is_post_status( $status ) {
    global $post;

    if ( ! $post ) {
        return false;
    }

    $current_status = get_post_status( $post->ID );

    if ( is_array( $status ) ) {
        return in_array( $current_status, $status, true );
    }

    return $current_status === $status;
}

This function supports checking for a single status or an array of statuses using get_post_status(), making it flexible for different scenarios like checking for both ‘draft’ and ‘auto-draft’.

Combining Multiple Server-Side Conditions

Stack multiple server-side checks for precise control.

Example (server-side-detection.php#L198-L206):

function cre_check_conditions( $conditions ) {
    foreach ( $conditions as $condition ) {
        if ( is_callable( $condition ) && ! $condition() ) {
            return false;
        }
    }
    return true;
}

This helper function allows you to pass an array of condition callbacks and returns true only if all conditions pass. Here’s a practical example:

function my_plugin_enqueue_draft_pages() {
    $conditions = array(
        fn() => cre_is_post_type( 'page' ),
        fn() => cre_is_post_status( array( 'draft', 'auto-draft' ) ),
        fn() => cre_user_can_edit_pages(),
    );

    if ( cre_check_conditions( $conditions ) ) {
        wp_enqueue_script(
            'my-draft-page-script',
            plugin_dir_url( __FILE__ ) . 'draft-page.js',
            array( 'wp-editor' ),
            '1.0.0'
        );
    }
}
add_action( 'admin_enqueue_scripts', 'my_plugin_enqueue_draft_pages' );

Learn more about enqueueing scripts in WordPress.

Server-Side vs Client-Side Detection

Server-side advantages:

  • Better performance—prevents unnecessary JavaScript from loading
  • Reduces client-side bundle size
  • Simpler for basic conditions

Client-side advantages:

  • More dynamic—can react to editor state changes
  • Access to real-time editor data (selected blocks, sidebar state, etc.)
  • Better for UI that needs to update based on user interactions

Best practice: Use server-side detection for initial script loading decisions, and client-side detection for dynamic UI behavior within the loaded scripts.

Key Takeaways

Conditional execution in the WordPress Block Editor is about selecting the right context data and returning null when conditions aren’t met. This pattern keeps your code performant and contextually aware—ensuring functionality executes exactly where and when it’s needed. Combine server-side detection for optimal performance with client-side detection for dynamic behavior.

Leave a Reply

Navigation

About

Writing on the Wall is a newsletter for freelance writers seeking inspiration, advice, and support on their creative journey.

Discover more from JuanMa Codes

Subscribe now to keep reading and get access to the full archive.

Continue reading