Return to Blog

Display the current user’s posts in a WordPress Gutenberg theme

Check out my plugin! Dynamically show posts created by the current user in a Gutenberg theme!

If you’re looking to create a bespoke membership platform for your WordPress website, you’ll need to show your members their posts from the front end.

IMBO, WordPress is the gold standard for helping people who don’t have traditional computer skills (i.e. familiarity with computer terms and computer operations) establish a presence on the internet. In this modern era of AI, attaining the knowledge to create a custom WordPress website is now more accessible than ever to everyone.

I used ChatGPT when I started developing this plugin to provide a high level explanation of the project because I had no idea where to begin. I entered this prompt:

What are the development tools I can use to create a custom WordPress Gutenberg block?

Here’s the response:

To create a custom Gutenberg block in WordPress, you’ll typically combine several development tools and libraries. Here’s a breakdown of the most useful ones:


🔨 Core WordPress Tools

  • @wordpress/scripts
    Provides a zero-config build setup (Webpack, Babel, etc.) for modern JS development. Handles JSX, ESNext, and SCSS out of the box.
  • @wordpress/block-editor & @wordpress/blocks
    Packages for working with the block editor API, registering blocks, block variations, and inspector controls.
  • npx create-block
    Scaffolds a block plugin with all the boilerplate (block.json, PHP loader, JS build scripts).

🧑‍💻 Development Environment

  • Node.js & npm (or yarn/pnpm)
    Required for running build tools, installing packages, and managing dependencies.
  • Webpack / Babel
    (Already included in @wordpress/scripts) – bundles modern JavaScript and converts JSX/ESNext into browser-safe code.
  • ESLint + Prettier
    Linting and code formatting for consistency with WordPress coding standards.

🎨 Styling Tools

  • PostCSS / SCSS
    Works seamlessly with @wordpress/scripts. Lets you use modern CSS features, nesting, and variables.
  • Tailwind CSS (optional)
    Can be integrated if you prefer utility-first styling inside your block.

🧪 Testing & Debugging

  • Jest (@wordpress/jest-preset-default)
    Unit testing for block logic.
  • Storybook
    Useful for developing and testing blocks in isolation, outside of WordPress.
  • React DevTools & Redux DevTools
    Helpful since Gutenberg is React + Redux under the hood.

  • wp-scripts build/watch/start
    For live reloading during block development.
  • wp-env
    Spins up a local WordPress environment with Docker for testing your block quickly.
  • PHPStan / PHPCS
    For static analysis and ensuring your PHP parts (e.g., block registration hooks, server-side rendering) follow standards.

🛠️ Advanced Tools (Optional)


Typical Workflow:

  1. Test in Storybook or a local wp-env instance.
  2. Run npx create-block my-block to scaffold.
  3. Use npm start (from @wordpress/scripts) for live reloading while coding.
  4. Edit block.json for metadata and edit.js for the React component.
  5. Style with SCSS/PostCSS or Tailwind.
  6. Register server-side PHP entry in the plugin loader.

I didn’t use all of the tools suggested by ChatGPT, but the most critical tools for this block are the @wordpress/scripts and @wordpress/create-block. This is the official tool from WordPress.org that sets up all the little details behind the scenes that’ll keep our block working when there’s changes to our WordPress setup, like updating the version. If you’re unfamiliar with using npm and creating npm packages, head to this freecodecamp tutorial to get the basics down. This article will only cover npm for use in a WordPress gutenberg block.


We’ll use a couple of other tools for our current user posts block:

Check out the links for more specific details on each tool; this article will only cover their use for this block.

Create a directory for the block

We need to make a folder on our computer to hold our block. Open your file browser or terminal and create a folder and name it current-user-posts.

Now we need to open the Command Prompt/Powershell/Terminal and navigate the shell to our new folder, enter cd <path/to/your/folder> in the terminal. For example,

cd /Users/<your-name>/blocks/current-user-posts

Bootstrap setup with NPM

In the terminal, type npm init and follow the prompts to make the block a npm package, and then type:

npm install @wordpress/scripts --save-dev

This package is the official npm package from WordPress for authoring Gutenberg blocks.

Now we need to setup the foundation for our block:

npx @wordpress/create-block@latest current-user-posts --variant=dynamic --namespace=<your-brand>

Replace <your-brand> with your name or company name. This is required for keeping our block from interfering with other WordPress blocks and plugins. This will create folders and files inside our block folder:

Directory structure after running npx @wordpress/create-block…

Your package.json file should look like this:

{
	"name": "current-user-posts",
	"version": "0.1.0",
	"description": "Example block scaffolded with Create Block tool.",
	"author": "The WordPress Contributors",
	"license": "GPL-2.0-or-later",
	"main": "build/index.js",
	"scripts": {
		"build": "wp-scripts build --webpack-copy-php --blocks-manifest",
		"format": "wp-scripts format",
		"lint:css": "wp-scripts lint-style",
		"lint:js": "wp-scripts lint-js",
		"packages-update": "wp-scripts packages-update",
		"plugin-zip": "wp-scripts plugin-zip",
		"start": "wp-scripts start --webpack-copy-php --blocks-manifest"
	},
	"devDependencies": {
		"@wordpress/scripts": "^30.23.0"
	}
}

Updating block.json

We need to give our block some UI controls, known as block supports in the WordPress docs. We can update the block.json file created by the create-block package to include controls in our block:

We’ll remove the viewscript property because the output of the block is rendered on the server. viewscript is gives us a way to change the frontend view of the block using javascript, however, the frontend view of the block is being rendered on the server instead of in the browser.

Next we’ll add attributes to block.json to hold the current user id and the namespace of the block. In edit.js we’ll populate the currentUserId with the current user’s id. Then if we want to access the current user id in another block, it’ll already be loaded into the browser and we won’t have to query the server again. The namespace attribute is used to pair our query filter with our block, more on that later.

Then add the allowedBlocks property with an array containing core/query. This tells the server that we only want to allow the Query Loop block from WordPress to be used in our block and nothing else. This prevents the block from being used in ways that we don’t intend it to; our block is only meant to display the current user’s posts in a query loop block, so let’s keep it that way.

Finally, we’ll add the support for layout, alignment, and aria labels to the block. These are the settings that the user sees on the right side of the block editor that differ for each block.

Updating edit.js

The edit.js file tells WordPress what to display in the block editor when a user inserts our block.

In it we get the current user id from the store using useSelect...Then we add it to the InnerBlocks template array. The template array is used to dynamic attributes to the InnerBlocks component and it tells the block editor which block(s) we want to use in our custom block, but only the blocks we specified in the allowedBlocks property in block.json. The namespace attribute is there so the block editor can identify this query loop block from other ones on the page that are outside of our custom block. It’s also used to tie our custom block to our block variation, more on that later. Then inside of the query args object after the namespace, we populate the author field with the current user id we got earlier.

To expose the current user id to other blocks in the block editor, we update the currentUserId attribute we set in block.json with the current user id we got the from store. Since we don’t want to set the attribute before we get the current user id, we need to wrap setAttributes(...)in useEffect, which tells the browser to wait until we have the current user id, the attributes object, and the setAttributes method before calling setAttributes.

Then we specify the markup for the block in the return statement using JSX. We use the InnerBlocks component we imported at the top of the file, and pass in the allowedBlocks and the template array we configured.

save.js and render.php – puts our block on the frontend

save.js

When working with dynamic blocks, we need the blocks to be rendered on the server and then sent to the browser. save.js tells the server to send the rendered block to the frontend in the same page/post/custom post type that we added our block to in the block editor. For example, if we add our block the dashboard page in the block editor, the save.js file maps the block from the block editor to the dashboard page on the frontend. The render.php file tells the server how to render the output of the block.

Since we specified a dynamic block when we ran npx @wordpress/create-block…, WordPress didn’t create a save.js, so we have to create one now. Add a file to src/current-user-posts and name it save.js, then add the following code:

/**
 * Client-side save function
 * 
 * React hook that is used to mark the block wrapper element. It provides all the necessary props like the class name.
 *
 * @since 0.1.0
 * @author <your-name>
 * @file Client-side save function
 * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
 * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#innerblocks
 * @module <your-brand>-current-user-posts/save
 */
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';

/**
 * The save function defines the way in which the different attributes should
 * be combined into the final markup, which is then serialized by the block
 * editor into `post_content`.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save
 *
 * @return {Element} Element to render.
 */
export default function save() {
  return (
    <div { ...useBlockProps.save() }>
      <InnerBlocks.Content />
    </div>
  );
}

If you’ve built dynamic blocks before, you’ll know that we shouldn’t need to specify a save.js file because the server can determine where to send the rendered block with just the render.php file, but there’s a caveat to this when using the InnerBlocks component. The server can’t determine where to render the output of an InnerBlocks component from render.php alone; we need to use InnerBlocks.Content inside the return statement in save.js so the server knows where to put the rendered output from InnerBlocks in edit.js.

render.php

The dynamic block will be rendered on the server, but we need to tell WordPress how we want to render the block. In render.php, we tell the server that we want our dynamic block to look the same on the front end as it does in the block editor.

First we create a wrapper element for the block and we pass in the block props as attributes that WordPress makes available to us through get_block_wrapper_attributes().

Then we ask WordPress for the current user id and pass it into the query object bound to the query loop block variation that we include with our custom block (see below for more on this block variation), but why do we need to do this if we’ve already determined the current user id in the block editor? It’s because we want WordPress to dynamically determine the posts that appear in our custom block when the block is being rendered. If we passed the current user id we determined in the block editor into the rendering logic of the block on the server, our custom block would show only the posts of the user who added the block to the page/template (probably the admin or editor of the site) when a registered user of our site sees our custom block. Basically, we’re telling WordPress, “I want you to figure out who the current user is and put their posts in the block; I don’t want you to load the posts of the user who added this block to the page in the block editor.”

After WordPress has the posts of the “true” current user, then we load them into the block, loop through the inner blocks of our custom block and call render_block on each one. The attributes and styles applied to the block in the block editor will also be passed into the rendering as well so the look and functionality of the block is the same on the front end as it is in the block editor.

Next we’ll include some javascript components provided by WordPress that are needed to make our block function correctly in the block editor. These components aren’t technically apart of our custom block, so we’ll need to segregate them from our block code.

Inc directory

Create a new folder in the root of the plugin called inc, short for includes. This is a standard naming convention in WordPress plugins that tells developers looking at our code in the future that the plugin includes additional files that supplement/necessitate the functionality of our plugin. This directory will contain two files: block-variation.js and query-filter.js.

inc/block-variation.js

Since we’re using the Query Loop block provided by WordPress Core in a custom context, we’ll need to filter the functionality of the block to meet our needs. Specifically, we need to remove the ability for the user to filter the author of the query, because our block should only display the posts from one user – the current one.

Then create a new file and name it block-variation.js and add the following code:

const VARIATION_NAME = '<your-brand>/current-user-posts-query';
wp.domReady( () => {
  wp.blocks.registerBlockVariation( 'core/query', {
    name: VARIATION_NAME,
    title: 'CUP Query',
    description: 'A query loop block that displays posts created by the    current user.',
    isActive: [ 'namespace' ],
    icon: 'list-view',
    attributes: {
      namespace: VARIATION_NAME,
      query: {
	perPage: null,
        pages: 0,
	offset: 0,
	postType: 'post',
	order: 'desc',
	orderBy: 'date',
	author: '', // Populated dynamically in filter/edit callback
	search: '',
	exclude: [],
	sticky: '',
	inherit: false,
      },
    },
    innerBlocks: [
      [
	'core/post-template',
	{},
	[
	  [ 'core/post-featured-image', {} ],
	  [ 'core/post-title', { placeholder: 'Post Title' } ],
	  [ 'core/post-excerpt', { placeholder: 'Post Excerpt' } ],
	  [ 'core/post-date', { placeholder: 'Post Date' } ]
	],
      ],
      [ 'core/query-pagination' ],
      [ 'core/query-no-results' ],
    ],
    scope: [ 'block' ],
    allowedControls: [
      'inherit',
      'postType',
      'order',
      'sticky',
      'taxQuery',
      'search',
      'format',
      'parents',
      // author controls removed
    ]
  } );
} );

This file operates as follows:

  • VARIATION_NAME is the unique namespace for this block variation, which is used by other components in WordPress to refer this specific variation
  • wp.domReady tells WordPress to execute the code within the curly braces when the page has finished loading in the user’s browser. If we didn’t wrap our code in this function, this code wouldn’t execute when the user selects our block in the editor
  • wp.blocks.registerBlockVariation is a method provided by WordPress for altering registered blocks. The first parameter is the handle of the block we want to make a variation of, core/query, and the second parameter is the configuration object we want the variation to use.
  • isActive corresponds to the currently selected block in the block editor. We need to specify a condition that tells the block editor when to load this block variation and it’s configured block supports into the editor. [ 'namespace' ] instructs the editor to load this block variation when the block with our namespace ('<your-brand>/current-user-posts') is active/currently selected. Read more on this here
  • attributes represents the block attributes of the block as discussed earlier. You can probably tell this is just a copy of the query config object we put into edit.js, except we’re passing in an empty string for the author parameter. This query config object is overwritten by query-filter.js, and is acting as a placeholder to make sure our block variation is registered properly by WordPress.
  • innerBlocks provides the default blocks that will populate our core/query variation if the user doesn’t select a block pattern to use in the block on insertion.
  • scope instructs WordPress where we want our block to be available. We set it to [ 'block' ] because we only want this block variation to load in another block. See a more detailed explanation of this here.
  • allowedControls specifies the block supports we want to make available in our block variation. We remove 'author' from the array because we don’t want the user to change the author; this query block variation displays posts from one author only – the current user

inc/query-filter.js

We’ve created a custom block, configured it’s rendering on the frontend, and created a UI for it in the block editor, but there’s a bug in it that keeps it from working properly.

When we insert our custom block in the block editor and select a block pattern for the query loop block, the custom query object gets overwritten by the default query loop configuration associated with the query loop block – it no longer only displays the posts of the current user.

To fix this, create a file in the inc directory and name it query-filter.js, then paste in the following code:

wp.domReady( () => {
  const { addFilter } = wp.hooks;
  const { createHigherOrderComponent } = wp.compose;
  const { createElement } = wp.element;
  const { useSelect } = wp.data;
  
  const currentUserPostsQuery = createHigherOrderComponent(
    function( BlockEdit ) {
      return function( props ) {
	const { name, attributes, setAttributes } = props;

	// Check for core/query block and our query variation
	if ( name === 'core/query' && attributes.namespace === '<your-brand>/current-user-posts-query') {

	  /**
	  * Retrieves the current user from the WordPress data store.
	  */
	  const currentUser = useSelect( select => {
	    return select( 'core' ).getCurrentUser();
	  } );

	  /**
	  * Check if the query needs to be reset.
	  */
	  const shouldReset = attributes.query == null || attributes.query.author !== currentUser.id;
					
	  // Exit if we don't need to reset
	  if ( ! shouldReset ) {
	    return createElement( BlockEdit, props );
	  }

	  /**
	  * Default query object scoped to the current user.
	  */
	  const DEFAULT_QUERY = {
	    'perPage': null,
	    'pages': 0,
	    'offset': 0,
	    'postType': 'post',
	    'order': 'desc',
	    'orderBy': 'date',
	    'author': currentUser.id,
	    'search': '',
	    'exclude': [],
	    'sticky': '',
	    'inherit': true,
	    'taxQuery': null,
	    'parents': [],
	    'format': []
	  };

	  setAttributes( { query: { ...DEFAULT_QUERY } } );
        }

        return createElement( BlockEdit, props );
      };
    }, 'currentUserPostsQuery'
  );

  addFilter(
    'editor.BlockEdit',
    '<your-brand>/current-user-posts-query-filter',
    currentUserPostsQuery
  );
} );

Let’s step through this piece by piece:

  • We call wp.domReady to execute the code when the page has finished loading
  • createHigherOrderComponent is a wrapper for our code that gives us access to the context of the block editor. The default query config object that is overriding the query config object we wrote in edit.js is accessible through the block editor context. Calling createHightOrderComponent and passing it to the editor.BlockEdit filter lets us access the context of the block editor, where we can replace the default query config object with the one we wrote
  • The BlockEdit component contains the props associated with our block, like the name, attributes, and setAttributes method
  • We test if this our block is present in the block editor, and simply return the block editor and passed props if our block isn’t present
  • If our block is present, we create a new query object that matches the one in edit.js and re-render our block with the updated query object
  • Finally we call the editor.BlockEdit filter hook and pass it our higher order component, which is then executed in the block editor

Putting it all together – current-user-posts.php

Finally we need to update the entry file of our custom block with the files we added to the inc directory. In the root directory, on the same level as the build, src, and inc directories, open the file named current-user-posts.php and then update the file to match the following diff:

Our custom block will enqueue block-variation.js and query-filter.js when it loads into WordPress.

Now we should have a working custom block that displays the current user’s posts on the frontend of a Gutenberg theme. However, we can’t place this block just anywhere on the frontend because it requires that the current user be a registered member of the site. If a user who doesn’t have an account on your site, such as a new visitor, the block won’t display any posts. Make sure the block is only placed on pages/templates that can only be seen by registered users of your site, such as on a dashboard page that is shown to users after they login.