So I'm trying to make a widget on the dashboard where this church can paste the iframe embed code from Facebook Live and then it puts that on the correct page on the site.

The problem is that I'm using Ajax to save the widget options but when you put HTML into the textarea, it does not save. I dumped the $_POST on ajax call, and the textarea has no data if its HTML code.

function add_dashboard_widgets() {
        'stream_options',            // $widget_id 
        'Stream Settings',          // $widget_name 
        'stream_options_widget' // $callback 


add_action('wp_dashboard_setup', 'add_dashboard_widgets' );

function stream_options_widget() {

    $code = stripslashes( get_option( 'stream_code' ));
    $status = get_option( 'stream_status' );

    $live = $offline = "";

    if($status == "1") {
        $live = "checked";
    } else {
        $offline = "checked";
        echo "
        <form action='". admin_url( 'admin-post.php' ) ."' method='post'>
        <div class='options_class_wrap'>
            <textarea name='stream_code' id='stream_code' col='10' style='width: 100%' rows='5'>". $code ."</textarea>
        <div class='options_class_wrap'>
            <label for='live'><input type='radio' ". $live ." name='stream_status' id='live' value='1' /> Live</label>&nbsp;&nbsp;<label for='offline'><input type='radio' ". $offline ." name='stream_status' id='offline' value='0' /> Offline</label>
            <input type='hidden' name='ajax_action' value='stream_options_ajax_call' />
            <input type='hidden' name='_ajax_nonce' value='". wp_create_nonce('stream_options_ajax_call')."' />
        <br />
        <div class='options_buttons'>
            <input type='submit' class='button' value='Save'>

function stream_options_ajax_call() {
    if(!wp_verify_nonce( $_POST['_ajax_nonce'], 'stream_options_ajax_call' )) {

    $status = wp_kses($_POST['stream_status'],array() );

    update_option( 'stream_code', stripslashes(wp_filter_post_kses(addslashes($_POST['stream_code']))));
    update_option( 'stream_status', $status );



function stream_options_ajax_call_init() {
    if(isset($_REQUEST['ajax_action']) && $_REQUEST['ajax_action'] === 'stream_options_ajax_call') {
        do_action( 'wp_ajax_stream_options_ajax_call' );

if (is_admin()){
    add_action('wp_ajax_stream_options_ajax_call', 'stream_options_ajax_call');

add_action( 'init', 'stream_options_ajax_call_init');
  • You haven't hooked stream_options_ajax_call() to the wp_ajax_ hook in this code. Does that not exist in your full code either? And I don't see any JS for submitting with AJAX. – Jacob Peattie Oct 25 '17 at 3:31
  • Yes it was there. This is a dashboard widget. – swg1cor14 Oct 25 '17 at 3:47
  • I still don't see an AJAX request. – Jacob Peattie Oct 25 '17 at 3:53
  • From what I got off of a tutorial (which I dont remember now) didnt have javascript in a dashboard widget. I can't just enqueue scripts – swg1cor14 Oct 25 '17 at 3:58
  • 1
    If you're not using JavaScript it's not AJAX (it's the J), but as far as I'm aware scripts enqueued with admin_enqueue_scripts will load on the dashboard. But is there a particular reason you're using a Dashboard widget? A customiser field, or even a settings page, would be much easier to set up. – Jacob Peattie Oct 25 '17 at 4:01

Ok, so the main problem here is ultimately just that wp_filter_post_kses is probably filtering out iframes. But I also want to address the round about way you're handling the form. You're not doing AJAX, which is sending the request to the server via Javascript, but you're trying to hook into the AJAX hooks and then run them manually on admin-post.php? It's all a huge mess.

Here's working code that will do what you want with actual AJAX, and I'll go over some things to pay attention to at the bottom:

function wpse_211908_add_dashboard_widgets() {
        'Stream Settings',
add_action('wp_dashboard_setup', 'wpse_211908_add_dashboard_widgets' );

function wpse_211908_stream_options_widget() {
    $code = stripcslashes( get_option( 'stream_code' ) );
    $status = get_option( 'stream_status' );

    <form id="stream_options" method="post">
        <p><textarea name="stream_code" rows="5" class="widefat"><?php echo esc_textarea( $code ); ?></textarea>

        <p><label><input type="checkbox" name="stream_status" value="1" <?php checked( $status, '1' ); ?>> Live</label>

        <?php wp_nonce_field( 'stream_options_ajax_call' ); ?>
        <input type="hidden" name="action" value="wpse_211908_save_stream_options">

        <p><input type="submit" class="button button-primary" value="Save">

        ( function($) {
            var $form = $( '#stream_options' );
            var $button = $form.find( 'input[type="submit"]' );

            $form.on( 'submit', function(e) {

                $button.prop( 'disabled', true ).attr( 'value', 'Saving...' );

                $.ajax( {
                    url: window.ajaxurl,
                    method: 'post',
                    data: $form.serialize(),
                    complete: function() {
                        $button.prop( 'disabled', false );
                    success: function() {
                        $button.attr( 'value', 'Saved' );
                    error: function() {
                        $button.attr( 'value', 'Save' );
                } );
            } );

            $form.on( 'input change', function() {
                $button.attr( 'value', 'Save' );
            } );
        } )( jQuery );


function wpse_211908_save_stream_options() {
    if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'stream_options_ajax_call' ) ) {

    if ( ! current_user_can( 'manage_options' ) ) {

    update_option( 'stream_code', $_POST['stream_code'] );

    $status = isset( $_POST['stream_status'] ) ? '1' : '0';
    update_option( 'stream_status', $status );

add_action( 'wp_ajax_wpse_211908_save_stream_options', 'wpse_211908_save_stream_options', 99 );

Firstly, notice that I have prefixed all my functions and any variables that are going to be shared with WordPress to avoid conflicts. This is a good practice and I recommend it. Names like add_dashboard_widgets are just begging to cause conflicts.

Notice the script tag in the widget. This is the JavaScript that will send the AJAX request. Because I have a hidden field named action with the value wpse_211908_save_stream_options, this means that it will become $_POST['action'] and I can handle it by using the wp_ajax_wpse_211908_save_stream_options hook. Using Javascript for the submission means that you don't need to have a page load when submitting.

Also in the widget notice that I have used esc_textarea() to make sure HTML in the $code variable doesn't break the HTML of the widget. And notice my use of checked() as a simpler way of outputting the checked attribute.

The second function is what handles the AJAX request. Notice that I can handle everything in the one function rather than adding actions which check fields and trigger actions. In here I send 403 errors if the nonce doesn't verify, or if the user does not have permission to update settings.

One thing you'll notice is that I don't do anything special to $_POST['stream_code']. Filtering submitted HTML isn't necessary when we're already checking that it's a trusted user. If you want to lock this down more, you can use wp_kses, but you need to provide your own list of allowed elements since iframes will get stripped by wp_filter_post_kses().

Then notice that for the $_POST['stream_status'] I simply check that it exists (with AJAX it won't come through at all when not checked) and manually set to '0' or '1'. wp_kses() is considerable overkill for sanitizing a checkbox.

Finally, to use the HTML on the front end, just use:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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