get_post_meta is not working inside shortcode

I believe the issue is that in the shortcode you don’t actually have access to the global $post object.

  1. You could switch out your WP_Query with a get_posts() that returns an array.
  2. You could use get_the_ID();.
  3. You could call global $post at the top of your shortcode function.

The problem is that global $post is technically out of scope when used in this function. The Loop and Custom WP_Queries will work fine in templates because wp() puts $post in the global namespace for the templates to use whenever template is loaded all behind the scenes. As soon as you add any generic function, shortcode callback function, or add_shortcode() passing in an anonymous function as a 2nd parameter, the global variables will be out of scope until you bring them into scope by calling global $post.

To add onto this. The reason the get_*() functions work ( such as
get_the_title() or get_the_ID() ) is because they all call get_post() which uses the global $post and bubbles the value upward.

To read more about this you can check out PHP Variable Scope or Variable Scope and WordPress by David Hayes