Submitting form via admin-post.php and handling errors

Am I supposed to just do normal redirects back to my form page?
Something like wp_redirect( '/my-page/', 302 );?

Yes.

How do I display my errors on the page that has the form?

Well, there are various ways, including:

  1. Passing an error code as a query string in the redirect URL: example.com/my-page/?my_form_error=tos_agree_empty. Then in your form/template, you could do something like echo $messages[ $_GET['my_form_error'] ]; where $messages is an array of error messages. (Note: In actual implementation, you should sanitize the query value.)

  2. Saving the error in a transient — a temporary database option.

On success, how do I replace the form with a success message?

Pretty much the same as with the above question. So you can:

  1. Include a “success” status in the redirect URL: example.com/my-page/?my_form_success=1. Then in your form/template, you can do something like if ( ! empty( $_GET['my_form_success'] ) ) { echo 'Success.'; } else { /* Display the form. */ }.

  2. Or once again, use the transient API to save (and also retrieve) the “success” status.

And you might find it useful by reading the article here which explains in-depth about handling form submissions using the admin-post.php, plus it also talks about AJAX (admin-ajax.php) which is really good in that no page reloads would be necessary in submitting the form. However, you should instead use the REST API to create a custom endpoint for your form submissions. 🙂

So I hope this (new) answer helps you and you can see here an example of using the transient API for persisting the form validation errors and submitted data to be used after being redirected from admin-post.php. I also hope the linked answer helps, which gives an example of handling the form submission using template_redirect which is fired on the same page — the admin_post_ hooks are fired on a different page in the admin (wp-admin) side of the site.