Restricting PluginDocumentSettingPanel by post type

Now that the PluginDocumentSettingPanel is in WordPress core, we can add our own panels to the Document Settings panel using the registerPlugin function.

const { registerPlugin } = wp.plugins;
const { PluginDocumentSettingPanel } = wp.editPost;

const MyCustomSideBarPanel = () => (
    <PluginDocumentSettingPanel
        name="my-custom-panel"
        title="My Custom Panel"
    >
        Hello, World!
    </PluginDocumentSettingPanel>
);
registerPlugin( 'my-custom-panel', {render: MyCustomSideBarPanel } );

The above code will insert a new Panel in the Document Sidebar with the title of My Custom Panel ” and will display “Hello, World!” as it’s content. If we want this panel to appear on every post type registered, then we’re done but what if we want to restrict this panel to just a single post type?

To do this, we’ll need to know what the current post type is and then only render this SlotFill if it’s the correct post type. So how can we do this?

Two Solutions

In order to get access to the post type, we will need to leverage wp.data.select() function. This can be done directly as seen above it’s better to use either the withSelect Higher-Order Component or the newer useSelect Hook. We’ll look at both here.

withSelect

withSelect is a Higher Order Component that will allow us to give our custom component some new functionality.

const CustomSideBarPanelwithSelect = withSelect( ( select ) => {
    return {
        postType: select( 'core/editor' ).getCurrentPostType(),
    };
} )( MyCustomSideBarPanel );

Examining the above, we can see that withSelect takes a function as a parameter that returns a props object contain items that will be merged with the Component that is being wrapped, MyCustomSideBarPanel, and returns the augmented component. Which we’re callingCustomSideBarPanelWithSelect.

So with that in mind, we need to update MyCustomSideBarPanel to accept the new postType props being passed to it:

const MyCustomSideBarPanel = ( { postType } ) => (
    <PluginDocumentSettingPanel
        name="my-custom-panel"
        title="My Custom Panel"
    >
        Hello, World!
    </PluginDocumentSettingPanel>
);

Once we have this in place, we just need to update the logic inside the Component to look for the proper post type before rendering.

const MyCustomSideBarPanel = ( { postType } ) => {

	if ( 'post-type-name' !== postType ) {
		return null;
	}

	return(
            <PluginDocumentSettingPanel name="my-custom-panel" title="My Custom Panel">
		Hello, World!
	    </PluginDocumentSettingPanel>
	);
}

Full Code:

const { registerPlugin } = wp.plugins;
const { PluginDocumentSettingPanel } = wp.editPost
const { withSelect } = wp.data;

const MyCustomSideBarPanel = ( { postType } ) => {

	if ( 'post-type-name' !== postType ) {
		return null;
	}

	return(
		<PluginDocumentSettingPanel
			name="my-custom-panel"
			title="My Custom Panel"
		>
			Hello, World!
		</PluginDocumentSettingPanel>
	);
}

const CustomSideBarPanelwithSelect = withSelect( select => {
	return {
		postType: select( 'core/editor' ).getCurrentPostType(),
	};
} )( MyCustomSideBarPanel);


registerPlugin( 'my-custom-panel', { render: CustomSideBarPanelwithSelect } );

useSelect

The useSelect hook is a newer addition to Gutenberg and greatly simplifies the code required to set up access to the post type. The only change we need to make to our existing component is to add the check inside:

const { registerPlugin } = wp.plugins;
const { PluginDocumentSettingPanel } = wp.editPost
const { useSelect } = wp.data;

const MyCustomSideBarPanel = () => {
const postType = useSelect( select => select( 'core/editor' ).getCurrentPostType() );

	if ( 'post-type-name' !== postType ) {
		return null;
	}

	return(
		<PluginDocumentSettingPanel
			name="my-custom-panel"
			title="My Custom Panel"
		>
			Hello, World!
		</PluginDocumentSettingPanel>
	);
}
registerPlugin( 'my-custom-panel', { render: MyCustomSideBarPanel } );

Similar to withSelect, useSelect takes a function as a parameter that returns whatever you want to be stored. In our case, we just need the result of calling select('core/editor').getCurrentPostType().

Hope this helps!

This Post Has 11 Comments

  1. Leonardo Losoviz

    Hi Ryan, thanks for your work on “, I’m using it on my plugin and it works great!

    I found out that there is a 3rd option to restrict it by CPT, which is to not register the script for other CPTs already in PHP. I believe this is the best way: the unneeded script will never be loaded in first place, so it’s faster, and there’s no need to unregister in JS whatever was registered in PHP, so it’s simpler.

    I talk about this option here: https://www.designbombs.com/registering-gutenberg-blocks-for-custom-post-type/

    Cheers

    1. Ryan

      Thanks for the comment! You can definitely only enqueue the script on post types where you want the plugin to appear. However, it is a lot more code to do so and you would then need to have a separate file for each plugin you want to register to account for cases. In my example, you may want to have more Fills registered that just the one that is being restricted to certain post types.

      1. Leonardo Losoviz

        However, it is a lot more code to do so

        It’s not a lot more code. That PHP function can be defined only once:


        public function get_editing_post_type(): ?string
        {
        // When in the editor, there is no direct way to obtain the post type in hook "init",
        // since $typenow has not been initialized yet
        // Hence, recreate the logic to get post type from URL if we are on post-new.php, or
        // from edited post in post.php
        if (!\is_admin()) {
        return null;
        }
        global $pagenow;
        if (!in_array($pagenow, ['post-new.php', 'post.php'])) {
        return null;
        }
        if ('post-new.php' === $pagenow) {
        if (isset($_REQUEST['post_type']) && \post_type_exists($_REQUEST['post_type'])) {
        $typenow = $_REQUEST['post_type'];
        };
        } elseif ('post.php' === $pagenow) {
        if (isset($_GET['post']) && isset($_POST['post_ID']) && (int) $_GET['post'] !== (int) $_POST['post_ID']) {
        // Do nothing
        } elseif (isset($_GET['post'])) {
        $post_id = (int) $_GET['post'];
        } elseif (isset($_POST['post_ID'])) {
        $post_id = (int) $_POST['post_ID'];
        }
        if ($post_id) {
        $post = \get_post($post_id);
        $typenow = $post->post_type;
        }
        }
        return $typenow;
        }

        And then used many times:


        if (get_editing_post_type() != 'my-custom-post-type') {
        return;
        }
        // Only now register script
        wp_enqueue_script(...);

        But even if it were more code, it’s PHP code, which doesn’t affect performance. Extra JS code does have performance issues, since it must be loaded/executed.

        you would then need to have a separate file for each plugin you want to register to account for cases

        Not really. It’s the same setup as you already have, just adding the extra piece of code if (get_editing_post_type() != 'my-custom-post-type') { return; } before registering the script, wherever you are doing it already.

        I know it doesn’t look good. That PHP function looks so 2005, but it works well (I copy/pasted that code from some existing .php file under /admin), and it beats JavaScript in performance. It’s ok to use JS for the logic, but to register/unregister scripts, PHP does the job better.

        Anyway, that’s just my opinion 🙂

        1. Ryan

          I agree that PHP is the way to go as far as managing what scripts get enqueued. In the case where there is only a single Fill being registered then controlling if the script is enqueued at all makes a lot of sense and is a best practice for all JavaScript files. If you don’t need it, don’t load it 🙂

          Once you have more than a single Fill that you want to register then having this logic in JS becomes easier to manage.
          For the sake of example, let’s say that I want to add a messaging to PluginPostStatusInfo fill that is shown on all post types but only add a PluginDocumentSettingPanel fill to the Pages post type

          With the PHP approach you laid out, you would need to create a JS file for each fill that calls registerPlugin rather than a single registerPlugin call that does the checking inside of the render component. So not only are we adding more boilerplate JS code, we’ll need a new entry point in webpack for each fill, and we’ll need to update the PHP to handle the new file/post type association. The more fills we add, the more files we need and you can see how this approach may not scale.

          This is not to say that your approach is wrong, just something to think about 🙂

          Thank for the great conversation!

  2. Leonardo Losoviz

    you would need to create a JS file for each fill that calls registerPlugin rather than a single registerPlugin call that does the checking inside of the render component

    Yes, you’re right about this, that’s actually what I’m doing. But not out of my design though: I’ve adapted the @wordpress/create-block package, to register any script (not just blocks) using the official webpack configuration. @wordpress/create-block creates single-block plugins, where each plugin/block has its own package.json, maybe its own webpack.config.js, etc. I just put all scripts together within the same plugin, but otherwise the set-up is the same, each script with its own build file.

    It is more boilerplate code, but also more control. My plugin right now has some 20 node_modules/ folders for all blocks and scripts, which is far from ideal, but it lets me control if scripts are registered, independently of the others, and what particular configuration they need for webpack… So I both like and dislike this set-up; as always, depending on the use case, it might make sense or not.

    1. Ryan

      Awesome! Great work and thanks for the mention!

  3. Dale de Silva

    Thanks Ryan!
    I’m having trouble implementing the PluginDocumentSettingPanel at all unfortunately 🙁

    I’ve tried your initial code, and I’ve tried the code from the Slotfills reference page you linked to, however, they both seem to have the same issue for me.
    – Which is an error in the console which causes the rest of the code not to run.

    Uncaught TypeError: Object(…) is not a function
    at Module../node_modules/@wordpress/edit-post/node_modules/@wordpress/block-editor/build-module/components/inserter/tips.js (editor.js:116416)

    The error dissapears and everything runs again if I add this to my webpack config under externals:
    “@wordpress/edit-post”: [“wp”, “editPost”],

    But the panel still doesn’t appear.

    Have you encountered this or know where I’m going wrong?
    Or if you can refer me to any other docs that might deal with how this plays with Webpack (if that’s the issue), I’d really appreciate it.

    Thankyou!
    Dale.

    1. Ryan

      Dale – thanks for the comment! My guess is that you’re not using the @wordpress/scripts package to build your JS. That is perfectly fine, however one great tool that comes with the package is the DependencyExtractionWebpackPlugin which does something similar to what you added to your webpack file and outputs a .PHP file containing a list of dependencies. I’ve added it to my custom webpack configurations and it works really well. If you don’t want to add it to your project, you could also access the wp directly instead of reference the package name. i.e import { registerPlugin } from '@wordpress/plugins` becomes const {registerPlugin} = wp.plugins.

      1. Dale de Silva

        Oh, thankyou!
        I actually fixed the issue a couple of hours after posting – but you’ve nailed it on the head.

        Because of not using the @wordpress/scripts package (which I didn’t know about!) I have to add both of these lines to my exports in webpack:
        “@wordpress/plugins”: [“wp”, “plugins”],
        “@wordpress/edit-post”: [“wp”, “editPost”],

        For anyone else reading along, these lines mean whenever I use these lines in my js:
        import { registerPlugin } from ‘@wordpress/plugins’;
        import { PluginDocumentSettingPanel } from ‘@wordpress/edit-post’;

        They automatically get remapped to something similar to:
        {registerPlugin} = wp.plugins;
        {PluginDocumentSettingPanel } = wp.editPost;

        This is ES6 to ES5 translation, but things specified in externals also tell webpack not to export those modules when compiling because they’re already available externally (in WordPress).

        However, for me even typing the es5 in directly didn’t work.
        Turns out the main missing bit was the dependency in PHP as Ryan said.

        In the PHP file I had to add wp-edit-post as a dependency when registering the script. ie:
        $handle = ‘dales-editor-script’;
        $src = plugins_url( ‘dist/editor.js’, __FILE__ );
        $deps = array( ‘wp-blocks’, ‘wp-i18n’, ‘wp-element’, ‘wp-editor’, ‘wp-components’, ‘wp-edit-post’, );
        wp_register_script( $handle, $src, $deps );

Leave a Reply