I have a post type the uses post_save to take the address from the post-meta and retrieve the lat/lng coordinates from the Google API. I need a way of notifying the user if there was an issue with retrieving the coordintes. I tried using admin_notices, but nothing displayed:

public static function update_notice() {
  echo "<div class='error'><p>Failed to retrieve coordinates. Please check key and address.<p></div>";
  remove_action('admin_notices', 'update_notice');

add_action('admin_notices', array('GeoPost', 'update_notice'));

I'm not sure if I'm using it incorrectly or in the wrong context. To be clear, in the actual code the add_action is in another function in the same class. That's working fine.


5 Answers 5


The reason this doesn't work is because there is a redirection happening after the save_post action. One way you can achieve what you want is by implementing a quick work around using query vars.

Here is a sample class to demonstrate:

class My_Awesome_Plugin {
  public function __construct(){
   add_action( 'save_post', array( $this, 'save_post' ) );
   add_action( 'admin_notices', array( $this, 'admin_notices' ) );

  public function save_post( $post_id, $post, $update ) {
   // Do you stuff here
   // ...

   // Add your query var if the coordinates are not retrieve correctly.
   add_filter( 'redirect_post_location', array( $this, 'add_notice_query_var' ), 99 );

  public function add_notice_query_var( $location ) {
   remove_filter( 'redirect_post_location', array( $this, 'add_notice_query_var' ), 99 );
   return add_query_arg( array( 'YOUR_QUERY_VAR' => 'ID' ), $location );

  public function admin_notices() {
   if ( ! isset( $_GET['YOUR_QUERY_VAR'] ) ) {
   <div class="updated">
      <p><?php esc_html_e( 'YOUR MESSAGE', 'text-domain' ); ?></p>

Hope this helps you a little bit. Cheers

  • Works great, thanks! But there's a missing closing bracket in the first line in the public function admin_notices() (an extra closing bracket in the if ( ! isset(.. line)
    – Rhys Wynne
    Jan 28, 2015 at 16:04
  • I've added remove_query_arg('YOUR_QUERY_VAR'); as i found it can be set from the last update. Jul 6, 2018 at 8:12
  • +1 Good answer.
    – Mark
    Aug 7, 2018 at 7:35

Made a wrapper class for this kind of scenario. Actually the class can be used in any scenario involving displaying notices. I use the PSR standards, so the naming is atypical of Wordpress code.

class AdminNotice
    const NOTICE_FIELD = 'my_admin_notice_message';

    public function displayAdminNotice()
        $option      = get_option(self::NOTICE_FIELD);
        $message     = isset($option['message']) ? $option['message'] : false;
        $noticeLevel = ! empty($option['notice-level']) ? $option['notice-level'] : 'notice-error';

        if ($message) {
            echo "<div class='notice {$noticeLevel} is-dismissible'><p>{$message}</p></div>";

    public static function displayError($message)
        self::updateOption($message, 'notice-error');

    public static function displayWarning($message)
        self::updateOption($message, 'notice-warning');

    public static function displayInfo($message)
        self::updateOption($message, 'notice-info');

    public static function displaySuccess($message)
        self::updateOption($message, 'notice-success');

    protected static function updateOption($message, $noticeLevel) {
        update_option(self::NOTICE_FIELD, [
            'message' => $message,
            'notice-level' => $noticeLevel


add_action('admin_notices', [new AdminNotice(), 'displayAdminNotice']);
AdminNotice::displayError(__('An error occurred, check logs.'));

The notice is displayed once.

  • Thanks, works like a charm!
    – JorensM
    Mar 10, 2023 at 9:54

In addition to @jonathanbardo's answer which is great and functions well, if you want to remove the query argument after the new page is loaded, you can use the removable_query_args filter. You get an array of argument names to which you can append your own argument. Then WP will take care of removing all of the arguments in the list from the URL.

public function __construct() {
    add_filter('removable_query_args', array($this, 'add_removable_arg'));

public function add_removable_arg($args) {
    array_push($args, 'my-query-arg');
    return $args;

Something like:


Will become:


Simple, elegant, based on get_settings_errors().

function wpse152033_set_admin_notice($id, $message, $status = 'success') {
    set_transient('wpse152033' . '_' . $id, [
        'message' => $message,
        'status' => $status
    ], 30);

function wpse152033_get_admin_notice($id) {
    $transient = get_transient( 'wpse152033' . '_' . $id );
    if ( isset( $_GET['settings-updated'] ) && $_GET['settings-updated'] && $transient ) {
        delete_transient( 'wpse152033' . '_' . $id );
    return $transient;


In your post request handler:

wpse152033_set_admin_notice(get_current_user_id(), 'Hello world', 'error');
wp_redirect(add_query_arg('settings-updated', 'true',  wp_get_referer()));

Where you want to use the admin notice, usually in the admin_notices hook.

$notice = $this->get_admin_notice(get_current_user_id());
if (!empty($notice) && is_array($notice)) {
    $status = array_key_exists('status', $notice) ? $notice['status'] : 'success';
    $message = array_key_exists('message', $notice) ? $notice['message'] : '';
    print '<div class="notice notice-'.$status.' is-dismissible">'.$message.'</div>';
  • This is a very nice implementation, kudos to you, kind sir. Would it make sense to drop the line wp_redirect... and simply add a 3rd array element to the transient using a noonce to add extra security? The transient will return nothing if it's not set, that would be enough to break out of the if statement in get_admin_notice, wouldn't it?
    – csaborio
    May 23, 2021 at 3:58
  • 1
    I made this gist just in case it helps someone: gist.github.com/csaborio001/d5a8fa84fba3ff4ff22d43f64179feba To use, simply AdminNotice::display_admin_message( 'message', 'status'), and you would need to hook the process_messages with the 'admin_notices' hook.
    – csaborio
    May 23, 2021 at 4:20

You can achive this by doing a redirect and passing along query arguments with the filter redirect_post_location. There is also redirect_term_location which will work for taxonomies/terms.

First add the admin_notices action which will always be active, but will only show the notice under certain conditions.

add_action( 'admin_notices', 'general_admin_notice' );

function general_admin_notice(){
  global $pagenow;

  if ( 'post.php' === $pagenow && isset($_GET['post']) && 'custom_post_type' === get_post_type( $_GET['post'] ) ){

    if ( isset($_GET['empty'])) {
      // Turn string into array, so we can loop trough it.
      $terms_id = explode( ',', $_GET['empty'] );

      echo '<div class="notice notice-error is-dismissible">
                foreach ( $terms_id as $term_id ) {
                  $term = get_term( $term_id, 'custom_taxonomy' );
                  echo '<a href="'.get_term_link( $term ).'">'.$term->name.'</a>, ';
              echo 'nutrients are empty.</p>

Then you need to redirect the page after save and pass a query argument with add_query_arg. The way I have done it here you can have a dynamic input which is shown in the admin notice.

        if ( !empty($empty_error) ) {
            add_filter('redirect_post_location', function($loc) use ($empty_error) {
                trigger_error( $empty_error);
                return add_query_arg( 'empty', implode(',', $empty_error), $loc );

In my case I do an array_push on the variable $empty_error with a term id. The admin notice will then show all the terms which has an error with an link to the respective term.

You can also use removable_query_args to remove the added query args, so the url looks cleaner. The admin notice will then go away if you reload the page.

add_filter('removable_query_args', 'add_removable_arg');

function add_removable_arg($args) {
    array_push($args, 'empty');
    return $args;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.