Skip to main content

Overview

The Web Stories plugin provides a WordPress widget for displaying stories in any widget area, including sidebars, footers, and other widgetized regions.

Widget Registration

The Stories widget is registered through the WordPress widgets API:
Register_Widget.php
public function register_widgets(): void {
    register_widget( $this->stories );
}
The widget is automatically registered on the widgets_init hook.

Widget Configuration

The Stories widget extends WP_Widget with the ID web_stories_widget:
Stories.php
class Stories extends WP_Widget {
    public const SCRIPT_HANDLE = 'web-stories-widget';
    
    public function __construct( Assets $assets, Story_Post_Type $story_post_type, Stories_Script_Data $stories_script_data ) {
        $id_base        = 'web_stories_widget';
        $name           = __( 'Web Stories', 'web-stories' );
        $widget_options = [
            'description'           => __( 'Display Web Stories in sidebar section.', 'web-stories' ),
            'classname'             => 'web-stories-widget',
            'show_instance_in_rest' => true,
        ];
        
        parent::__construct( $id_base, $name, $widget_options );
    }
}
The widget includes show_instance_in_rest => true for block editor widget support.

Widget Settings

The Stories widget provides comprehensive display options:

Basic Configuration

  • Widget Title - Custom title for the widget area
  • View Type - Display layout (circles, carousel, grid, list)
  • Number of Stories - How many stories to display (1-20)
  • Order By - Sort by title or date
  • Order - Ascending or descending

Default Values

The widget uses these defaults when first added:
Stories.php
private function default_values(): array {
    return [
        'title'              => esc_html__( 'Web Stories', 'web-stories' ),
        'view_type'          => 'circles',
        'show_title'         => '',
        'show_excerpt'       => '',
        'show_author'        => '',
        'show_date'          => '',
        'show_archive_link'  => '',
        'image_alignment'    => 'left',
        'number_of_columns'  => 1,
        'number_of_stories'  => 5,
        'circle_size'        => 100,
        'archive_link_label' => __( 'View all stories', 'web-stories' ),
        'sharp_corners'      => '',
        'orderby'            => 'post_date',
        'order'              => 'DESC',
    ];
}

Widget Output

The widget renders stories using the Story_Query class:
Stories.php
public function widget( $args, $instance ): void {
    echo $args['before_widget'];
    
    $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
    
    if ( ! empty( $title ) ) {
        echo $args['before_title'] . $title . $args['after_title'];
    }
    
    $story_attrs = [
        'view_type'          => $instance['view_type'],
        'show_title'         => (bool) $instance['show_title'],
        'show_excerpt'       => (bool) $instance['show_excerpt'],
        'show_author'        => (bool) $instance['show_author'],
        'show_date'          => (bool) $instance['show_date'],
        'show_archive_link'  => (bool) $instance['show_archive_link'],
        'archive_link_label' => (string) $instance['archive_link_label'],
        'circle_size'        => min( absint( $instance['circle_size'] ), 150 ),
        'sharp_corners'      => (bool) $instance['sharp_corners'],
        'image_alignment'    => (string) $instance['image_alignment'],
        'number_of_columns'  => min( absint( $instance['number_of_columns'] ), 4 ),
        'class'              => 'web-stories-list--widget',
    ];
    
    $story_args = [
        'posts_per_page' => min( absint( $instance['number_of_stories'] ), 20 ),
        'orderby'        => $instance['orderby'],
        'order'          => $instance['order'],
    ];
    
    $story_query = new Story_Query( $story_attrs, $story_args );
    echo $story_query->render();
    
    echo $args['after_widget'];
}

Form Fields

The widget form provides interactive controls:
1

Text Input Fields

$this->input([
    'id'           => 'title',
    'name'         => 'title',
    'label'        => __( 'Widget Title', 'web-stories' ),
    'type'         => 'text',
    'value'        => $title,
    'label_before' => true,
]);
2

Dropdown Selects

$this->dropdown([
    'options'   => $view_types,
    'selected'  => $current_view_type,
    'name'      => 'view_type',
    'id'        => 'view_type',
    'label'     => __( 'Select Layout', 'web-stories' ),
    'classname' => 'widefat view_type stories-widget-field',
]);
3

Number Inputs

$this->input([
    'id'         => 'number_of_stories',
    'name'       => 'number_of_stories',
    'label'      => __( 'Number of Stories', 'web-stories' ),
    'type'       => 'number',
    'value'      => $number_of_stories,
    'attributes' => [
        'min' => 1,
        'max' => 20,
    ],
]);
4

Checkboxes

$this->input([
    'id'    => 'show_title',
    'name'  => 'show_title',
    'label' => __( 'Display Title', 'web-stories' ),
    'type'  => 'checkbox',
    'value' => $show_title,
]);
5

Radio Buttons

$this->radio([
    'options'  => [
        'left'  => __( 'Left', 'web-stories' ),
        'right' => __( 'Right', 'web-stories' ),
    ],
    'selected' => $image_alignment,
    'name'     => 'image_alignment',
    'label'    => __( 'Image Alignment', 'web-stories' ),
]);

Widget Update

When the widget is saved, values are validated and sanitized:
Stories.php
public function update( $new_instance, $old_instance ): array {
    $instance = [];
    
    $new_instance = wp_parse_args( $new_instance, $this->default_values() );
    
    $instance['title']              = wp_strip_all_tags( $new_instance['title'] );
    $instance['view_type']          = $new_instance['view_type'];
    $instance['show_title']         = $new_instance['show_title'];
    $instance['show_excerpt']       = $new_instance['show_excerpt'];
    $instance['show_author']        = $new_instance['show_author'];
    $instance['show_date']          = $new_instance['show_date'];
    $instance['show_archive_link']  = $new_instance['show_archive_link'];
    $instance['image_alignment']    = $new_instance['image_alignment'];
    $instance['number_of_columns']  = min( absint( $new_instance['number_of_columns'] ), 4 );
    $instance['number_of_stories']  = min( absint( $new_instance['number_of_stories'] ), 20 );
    $instance['circle_size']        = min( absint( $new_instance['circle_size'] ), 150 );
    $instance['archive_link_label'] = $new_instance['archive_link_label'];
    $instance['sharp_corners']      = $new_instance['sharp_corners'];
    $instance['orderby']            = $new_instance['orderby'];
    $instance['order']              = $new_instance['order'];
    
    return $instance;
}
Values are clamped to safe maximums:
  • Stories: max 20
  • Columns: max 4
  • Circle size: max 150px
The archive link only displays if archives are enabled:
Stories.php
$has_archive = $this->story_post_type->get_has_archive();

if ( $has_archive ) {
    $this->input([
        'id'    => 'show_archive_link',
        'name'  => 'show_archive_link',
        'label' => __( 'Display Archive Link', 'web-stories' ),
        'type'  => 'checkbox',
        'value' => $show_archive_link,
    ]);
}

Widget Assets

The widget enqueues its own JavaScript and CSS:
Stories.php
public function enqueue_scripts(): void {
    if ( wp_script_is( self::SCRIPT_HANDLE ) ) {
        return;
    }
    
    $this->assets->enqueue_style_asset( self::SCRIPT_HANDLE );
    $this->assets->enqueue_script_asset( self::SCRIPT_HANDLE, [ 'jquery' ] );
    
    wp_localize_script(
        self::SCRIPT_HANDLE,
        'webStoriesData',
        $this->stories_script_data->get_script_data()
    );
}

Legacy Widget Block

The widget is hidden from the legacy widget block list:
Register_Widget.php
public function hide_widget( $widget_types ) {
    if ( ! \is_array( $widget_types ) ) {
        return $widget_types;
    }
    
    $widget_types[] = $this->stories->id_base;
    return $widget_types;
}
This prevents duplicate widgets in the block editor since the Web Stories block provides better functionality.

Widget Preview

For widget preview iframes, a special body class is added:
Register_Widget.php
public function body_class( $classes ) {
    if ( is_admin() && \defined( 'IFRAME_REQUEST' ) && IFRAME_REQUEST ) {
        $classes[] = 'ws-legacy-widget-preview';
    }
    return $classes;
}

Using in Templates

Add the widget programmatically in templates:
the_widget(
    'Google\\Web_Stories\\Widgets\\Stories',
    [
        'title'             => 'Latest Stories',
        'number_of_stories' => 5,
        'view_type'         => 'circles',
        'show_title'        => true,
    ],
    [
        'before_widget' => '<div class="widget web-stories-widget">',
        'after_widget'  => '</div>',
        'before_title'  => '<h3 class="widget-title">',
        'after_title'   => '</h3>',
    ]
);

Customization

Customize widget output using CSS:
Custom Styling
.web-stories-widget {
    padding: 20px;
    background: #f5f5f5;
}

.web-stories-widget-title {
    font-size: 24px;
    margin-bottom: 15px;
}

.web-stories-list--widget {
    /* Target stories rendered by widget */
}

Best Practices

  • Place in sidebars for secondary content
  • Use in footers for site-wide story promotion
  • Add to specific page templates for context
  • Use single column layouts for mobile
  • Adjust circle size for touch targets (min 96px)
  • Test swipe interactions on carousel view

Next Steps

Gutenberg Blocks

Use blocks for richer story displays

Archive Pages

Configure story archive pages

Build docs developers (and LLMs) love