Overview
The Web Stories plugin provides a WordPress widget for displaying stories in any widget area, including sidebars, footers, and other widgetized regions.
The Stories widget is registered through the WordPress widgets API:
public function register_widgets () : void {
register_widget ( $this -> stories );
}
The widget is automatically registered on the widgets_init hook.
The Stories widget extends WP_Widget with the ID web_stories_widget:
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.
The Stories widget provides comprehensive display options:
General Settings
Layout Options
Content Toggles
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
Display Customization
Number of Columns - Grid columns (1-4)
Circle Size - Size for circle view (80-200px)
Image Alignment - Left or right for list view
Sharp Corners - Use sharp corners instead of rounded
Field Visibility
Display Title - Show/hide story titles
Display Excerpt - Show/hide story excerpts
Display Author - Show/hide author names
Display Date - Show/hide publication dates
Display Archive Link - Show/hide “View all” link
Archive Link Label - Custom text for archive link
Default Values
The widget uses these defaults when first added:
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' ,
];
}
The widget renders stories using the Story_Query class:
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' ];
}
The widget form provides interactive controls:
Text Input Fields
$this -> input ([
'id' => 'title' ,
'name' => 'title' ,
'label' => __ ( 'Widget Title' , 'web-stories' ),
'type' => 'text' ,
'value' => $title ,
'label_before' => true ,
]);
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' ,
]);
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 ,
],
]);
Checkboxes
$this -> input ([
'id' => 'show_title' ,
'name' => 'show_title' ,
'label' => __ ( 'Display Title' , 'web-stories' ),
'type' => 'checkbox' ,
'value' => $show_title ,
]);
Radio Buttons
$this -> radio ([
'options' => [
'left' => __ ( 'Left' , 'web-stories' ),
'right' => __ ( 'Right' , 'web-stories' ),
],
'selected' => $image_alignment ,
'name' => 'image_alignment' ,
'label' => __ ( 'Image Alignment' , 'web-stories' ),
]);
When the widget is saved, values are validated and sanitized:
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
Archive Link
The archive link only displays if archives are enabled:
$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 ,
]);
}
The widget enqueues its own JavaScript and CSS:
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 ()
);
}
The widget is hidden from the legacy widget block list:
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.
For widget preview iframes, a special body class is added:
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()
Dynamic Sidebar
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:
.web-stories-widget {
padding : 20 px ;
background : #f5f5f5 ;
}
.web-stories-widget-title {
font-size : 24 px ;
margin-bottom : 15 px ;
}
.web-stories-list--widget {
/* Target stories rendered by widget */
}
Best Practices
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