Hooks and Filters
WP Stories provides action hooks and filter hooks that developers can use to customize plugin behavior. All hooks listed below are verified in the plugin source code.
Filter Hooks
Section titled “Filter Hooks”wp_stories_enable_image_optimization
Section titled “wp_stories_enable_image_optimization”Controls whether uploaded story images are automatically optimized (resized and compressed).
Default: true
File: public/class-wp-stories-submit-user-stories.php
add_filter( 'wp_stories_enable_image_optimization', function( $enabled, $attachment_id ) { // Disable optimization for a specific attachment return $enabled;}, 10, 2 );wp_stories_image_quality
Section titled “wp_stories_image_quality”Sets the JPEG compression quality for optimized story images.
Default: 85
File: public/class-wp-stories-submit-user-stories.php
add_filter( 'wp_stories_image_quality', function( $quality ) { return 75; // Lower quality for smaller file sizes} );wp_stories_max_image_dimension
Section titled “wp_stories_max_image_dimension”Sets the maximum width or height (in pixels) for optimized story images. Images larger than this are scaled down proportionally.
Default: 2048
File: public/class-wp-stories-submit-user-stories.php
add_filter( 'wp_stories_max_image_dimension', function( $dimension ) { return 1920;} );wp_stories_min_image_size_for_optimization
Section titled “wp_stories_min_image_size_for_optimization”Sets the minimum file size (in bytes) before optimization kicks in. Images smaller than this threshold are left untouched.
Default: 500000 (500 KB)
File: public/class-wp-stories-submit-user-stories.php
add_filter( 'wp_stories_min_image_size_for_optimization', function( $size ) { return 200000; // Optimize images over 200 KB} );wp_stories_allowed_image_types
Section titled “wp_stories_allowed_image_types”The default image extensions offered and accepted for story uploads, before the owner’s Allowed File Types setting is saved.
Default: the image types WordPress accepts, limited to browser-playable ones: jpg, jpeg, jpe, png, gif, webp, avif. SVG/SVGZ are always removed after this filter.
File: includes/wp-stories-functions.php (get_wp_stories_default_allowed_image_types())
add_filter( 'wp_stories_allowed_image_types', function( $types ) { return array_values( array_diff( $types, array( 'gif' ) ) ); // No GIFs by default.} );wp_stories_allowed_video_types
Section titled “wp_stories_allowed_video_types”The default video extensions offered and accepted for story uploads, before the owner’s Allowed File Types setting is saved.
Default: the video types WordPress accepts, limited to browser-playable ones: mp4, m4v, webm, ogv, mov, qt. Formats such as WMV, MPEG or 3GP are left out because most browsers cannot play them; use wp_stories_restrict_to_web_playable or the settings checkboxes to allow them.
File: includes/wp-stories-functions.php (get_wp_stories_default_allowed_video_types())
add_filter( 'wp_stories_allowed_video_types', function( $types ) { $types[] = 'webm'; return $types;} );wp_stories_allowed_cvr_img_types
Section titled “wp_stories_allowed_cvr_img_types”Defines the allowed image file extensions for story collection cover images.
Default: ['png', 'gif', 'jpg', 'jpeg']
File: includes/wp-stories-functions.php
add_filter( 'wp_stories_allowed_cvr_img_types', function( $types ) { return array( 'jpg', 'jpeg', 'png' );} );wp_stories_allowed_cvr_img_size
Section titled “wp_stories_allowed_cvr_img_size”Sets the maximum file size (in bytes) for story collection cover images.
File: includes/wp-stories-functions.php
add_filter( 'wp_stories_allowed_cvr_img_size', function( $bytes ) { return 2 * 1024 * 1024; // 2 MB} );wp_stories_avatar_url
Section titled “wp_stories_avatar_url”Filters the avatar URL used for story circles.
File: includes/wp-stories-functions.php
add_filter( 'wp_stories_avatar_url', function( $avatar_url, $user_id, $size ) { // Return a custom avatar URL return $avatar_url;}, 10, 3 );wp_stories_author_name
Section titled “wp_stories_author_name”Filters the author name shown on story circles and in the viewer header.
Since 2.5.0 the default is the name members see elsewhere on the site: the PeepSo full name, else the BuddyPress display name, else the WordPress display name. Earlier versions returned the login name. Messaging links use the member’s URL slug, not this value, so changing the name never breaks them.
File: includes/wp-stories-functions.php
add_filter( 'wp_stories_author_name', function( $name, $user_id ) { // Customize the displayed author name return $name;}, 10, 2 );wp_stories_login_url
Section titled “wp_stories_login_url”Filters the login URL displayed to logged-out users in the story activity feed widget.
Default: wp_login_url()
File: public/widgets/class-wp-stories-activity-feed-widget.php
add_filter( 'wp_stories_login_url', function( $url ) { return home_url( '/custom-login/' );} );wp_stories_template_settings
Section titled “wp_stories_template_settings”Filters the BuddyPress template used for the stories profile tab.
Default: 'members/single/plugins'
File: public/class-wp-stories-public.php
add_filter( 'wp_stories_template_settings', function( $template ) { return 'members/single/custom-template';} );wp_stories_PeepSoUser_per_page
Section titled “wp_stories_PeepSoUser_per_page”Rows per page in the PeepSo “Seen by” list (the viewers of one story item); Load more fetches the next page. The BuddyPress list uses wp_stories_viewers_per_page.
Default: 10
File: includes/wp-stories-functions.php (wp_stories_viewer_list_html())
add_filter( 'wp_stories_PeepSoUser_per_page', function( $per_page ) { return 20;} );wp_story_activity_count
Section titled “wp_story_activity_count”Sets the number of stories fetched for activity feeds.
Default: 50
File: includes/wp-stories-functions.php
add_filter( 'wp_story_activity_count', function( $count ) { return 100;} );wp_stories_delete_stories_attachement
Section titled “wp_stories_delete_stories_attachement”How many days an expired story is kept before scheduled deletion removes it (and, if Delete Story Attachments is on, its uploaded media).
Default: the Delete After Expiry setting on the Displaying Options tab, or 7 when it is not set.
File: includes/wp-stories-functions.php
add_filter( 'wp_stories_delete_stories_attachement', function( $days ) { return 14; // Keep attachments for 14 days} );wp_stories_story_ids_select_ajax
Section titled “wp_stories_story_ids_select_ajax”Controls whether the story IDs selector in the Story Collection metabox uses AJAX loading.
Default: false
File: admin/wp-stories-metabox.php
add_filter( 'wp_stories_story_ids_select_ajax', '__return_true' );wb_stories_loaders
Section titled “wb_stories_loaders”Controls whether skeleton loading placeholders are shown while stories load.
Default: true
File: public/class-wp-stories-public.php
add_filter( 'wb_stories_loaders', '__return_false' );wb_stories_loaders_count
Section titled “wb_stories_loaders_count”Sets the number of skeleton loader placeholders displayed while stories are loading.
Default: 10
File: public/class-wp-stories-public.php
add_filter( 'wb_stories_loaders_count', function( $count ) { return 5;} );Action Hooks
Section titled “Action Hooks”wp_stories_before_story_submit
Section titled “wp_stories_before_story_submit”Fires before a new story is saved to the database. Use this to run custom validation or pre-processing.
File: public/class-wp-stories-submit-user-stories.php
add_action( 'wp_stories_before_story_submit', function() { // Custom logic before story is saved} );wp_stories_before_item_delete
Section titled “wp_stories_before_item_delete”Fires before a single story item is deleted via AJAX.
File: public/class-wp-stories-submit-user-stories.php
add_action( 'wp_stories_before_item_delete', function() { // Log item deletion or run cleanup} );wp_stories_before_story_delete
Section titled “wp_stories_before_story_delete”Fires before an entire story post is deleted via AJAX.
File: public/class-wp-stories-submit-user-stories.php
add_action( 'wp_stories_before_story_delete', function() { // Run cleanup tasks before story removal} );wp_stories_tab_contents
Section titled “wp_stories_tab_contents”Removed in 2.4.0. The legacy admin wrapper this fired from was replaced by the card-panel admin, and the hook went with it. It is listed here only so anyone who used it knows where it went.
Replace it with either:
wp_stories_admin_setting_tabs- a filter over the whole tab registry, to add a tab of your own (see below), or- the per-tab actions
wp_stories_general_settings_options,wp_stories_display_options_options,wp_stories_user_publishing_options,wp_stories_style_options_options,wp_stories_buddypress_integration,wp_stories_peepso_integration- to append fields to a tab that already exists.
wp_stories_general_settings_options
Section titled “wp_stories_general_settings_options”Fires after the default general settings options are rendered. Use this to add custom options to the General tab.
File: includes/admin/views/settings-general.php
add_action( 'wp_stories_general_settings_options', function( $settings ) { // Add custom fields to General Settings}, 10, 1 );wp_stories_display_options_options
Section titled “wp_stories_display_options_options”Fires after the default display options are rendered. Use this to add custom options to the Display Options tab.
File: includes/admin/views/settings-display.php
add_action( 'wp_stories_display_options_options', function( $options ) { // Add custom fields to Display Options}, 10, 1 );wp_stories_style_options_options
Section titled “wp_stories_style_options_options”Fires after the default styling options are rendered. Use this to add custom options to the Styling Options tab.
File: includes/admin/views/settings-style.php
add_action( 'wp_stories_style_options_options', function( $options ) { // Add custom fields to Styling Options}, 10, 1 );wp_stories_user_publishing_options
Section titled “wp_stories_user_publishing_options”Fires after the default user publishing options are rendered. Use this to add custom options to the User Publishing tab.
File: includes/admin/views/settings-user-publishing.php
add_action( 'wp_stories_user_publishing_options', function( $options ) { // Add custom fields to the User Publishing tab}, 10, 1 );wp_stories_buddypress_integration
Section titled “wp_stories_buddypress_integration”Fires after the BuddyPress integration settings are rendered. Use this to add custom BuddyPress-related options.
File: includes/admin/views/settings-integrations.php
add_action( 'wp_stories_buddypress_integration', function( $integrations ) { // Add custom BuddyPress integration settings}, 10, 1 );wp_stories_peepso_integration
Section titled “wp_stories_peepso_integration”Fires after the PeepSo integration settings are rendered. Use this to add custom PeepSo-related options.
File: includes/admin/views/settings-integrations.php
add_action( 'wp_stories_peepso_integration', function( $integrations ) { // Add custom PeepSo integration settings}, 10, 1 );Access control (2.4.2+)
Section titled “Access control (2.4.2+)”wp_stories_user_can_view_story_item
Section titled “wp_stories_user_can_view_story_item”Filter. includes/wp-stories-functions.php
The access check used by the story AJAX endpoints: the story-media endpoint, the
view recorder and the like toggle. The stories row itself is built from
wp_stories_visibility_allows(), which this filter does not change, so a rule
added here controls what those endpoints serve, not which circles appear.
It already enforces: post type, post_status = publish (a deleted story is moved
to draft), the Story Expiry window, that the item exists and is not disabled,
and the item’s per-item visibility.
// Let editors read every member story, including private ones.add_filter( 'wp_stories_user_can_view_story_item', function ( $allowed, $post_id, $index, $user_id ) { if ( ! $allowed && user_can( $user_id, 'edit_others_posts' ) ) { return true; } return $allowed;}, 10, 4 );| Param | Type | Notes |
|---|---|---|
$allowed |
bool |
The decision so far. |
$post_id |
int |
Story post ID. |
$index |
int|null |
Item index in wb_story_items; null when checking the story itself. |
$user_id |
int |
User being checked. 0 for logged-out visitors. |
Returning
trueunconditionally re-opens the access bugs fixed in 2.4.2. Widen deliberately, and always keep a capability or ownership test in the condition.
Settings (2.4.2+)
Section titled “Settings (2.4.2+)”wp_stories_settings_schema
Section titled “wp_stories_settings_schema”Filter. includes/wp-stories-settings.php
The schema every option group is sanitized against. Register your own fields here so they get typed validation instead of the generic text fallback.
Types: flag, enum_flag, int (with min/max/default), enum, color,
list_enum, list_role, list_ext.
add_filter( 'wp_stories_settings_schema', function ( $schema ) { $schema['wp_stories_display_options']['my_addon_limit'] = array( 'type' => 'int', 'min' => 1, 'default' => 20, ); return $schema;} );Fields you do not declare are still preserved on save - they are passed through a
recursive text sanitizer rather than dropped - so an add-on that appends fields
via the wp_stories_*_options actions keeps working without registering here.
wp_stories_sanitized_settings
Section titled “wp_stories_sanitized_settings”Filter. includes/wp-stories-settings.php
The sanitized array, immediately before it is written. Args: $output, $input,
$group.
Story cleanup (2.4.2+)
Section titled “Story cleanup (2.4.2+)”wp_stories_delete_stories_batch_size
Section titled “wp_stories_delete_stories_batch_size”Filter. includes/wp-stories-functions.php. Default 200.
How many stories per post type the daily cleanup handles in one run. Whatever is left over is picked up by the next run.
wp_stories_story_attachment_ids
Section titled “wp_stories_story_attachment_ids”Filter. includes/wp-stories-functions.php. Args: $attachment_ids, $post_id.
The attachments deleted alongside a story. The default list contains only media
whose post_parent is the story, so images picked from the Media Library are
never removed.
Adding IDs here permanently deletes those attachments. Do not add media that might be used elsewhere on the site.
wp_stories_delete_stories_attachement
Section titled “wp_stories_delete_stories_attachement”Filter. Historic name, kept for back-compat. It controls the retention period in days, despite reading like an attachment toggle. Since 2.4.2 the period is a normal setting (Displaying Options > Delete After Expiry), and this filter applies on top of it.
Uploads
Section titled “Uploads”wp_stories_web_playable_extensions
Section titled “wp_stories_web_playable_extensions”Filter. includes/wp-stories-functions.php. Args: $list, $type.
The extensions considered browser-playable for image or video. Since 2.5.0
this is also the default upload allowlist.
wp_stories_restrict_to_web_playable
Section titled “wp_stories_restrict_to_web_playable”Filter. includes/wp-stories-functions.php. Args: true, $type.
Since 2.5.0 the default is true: until the owner saves Allowed File Types,
members can only upload formats browsers can play (JPG, PNG, GIF, WebP, AVIF,
MP4, M4V, MOV, WebM, OGV). Formats like AVI, WMV, MKV or HEIC uploaded fine
before but never played in the viewer. Return false to default to every
type WordPress accepts. Owners can also widen the list on the User Publishing
tab, which always offers every WordPress-accepted type.
wp_stories_admin_setting_tabs
Section titled “wp_stories_admin_setting_tabs”Filter. includes/admin/class-wpst-admin.php. Args: $tabs.
The admin tab registry. Each entry is keyed by tab slug with label, icon
(dashicon class) and group (main, settings or account).
add_filter( 'wp_stories_admin_setting_tabs', function ( $tabs ) { $tabs['my-addon'] = array( 'label' => __( 'My Add-on', 'my-addon' ), 'icon' => 'dashicons-admin-generic', 'group' => 'settings', ); return $tabs;} );wpst_install_catalog
Section titled “wpst_install_catalog”Filter. includes/admin/class-wpst-plugin-installer.php. Args: $catalog.
The Discover tab’s install catalog, keyed by plugin slug.
The catalog is an allow-list, and it is the reason the install endpoint cannot be pointed at arbitrary code. Anything you add here becomes installable by an administrator in one click, and its package must resolve on the store host. Do not add entries from untrusted input.
wpst_license_api_sslverify
Section titled “wpst_license_api_sslverify”Filter. edd-license/edd-plugin-license.php. Default true.
sslverify on the EDD licence API call.
Seen by lists
Section titled “Seen by lists”wp_stories_PeepSoUser_per_page
Section titled “wp_stories_PeepSoUser_per_page”Filter. includes/wp-stories-functions.php (wp_stories_viewer_list_html()). Default 10.
Rows per page in the PeepSo “Seen by” list before “Load more”.
wp_stories_viewers_per_page
Section titled “wp_stories_viewers_per_page”Filter. includes/wp-stories-functions.php (wp_stories_viewer_list_html()). Args: $per_page. Default 20.
How many people the BuddyPress “Seen by” list shows before “Load more”. Both lists show the most recent viewers first.
Privacy (2.5.0+)
Section titled “Privacy (2.5.0+)”wp_stories_show_in_rest
Section titled “wp_stories_show_in_rest”Filter. includes/wp-stories-functions.php. Args: $show, $post_type. Default false.
Whether wb-story, wb-story-box or wb-user-story is exposed in the WordPress
REST API. Off by default: the plugin itself uses AJAX only, and the core REST
collections do not apply story visibility, so private and friends-only stories
would be listed. Return true only if you add your own permission checks.
add_filter( 'wp_stories_show_in_rest', function ( $show, $post_type ) { return 'wb-story-box' === $post_type; // Admin story boxes only.}, 10, 2 );Posting and loading (2.5.0+)
Section titled “Posting and loading (2.5.0+)”wp_stories_current_user_can_post
Section titled “wp_stories_current_user_can_post”Filter. includes/wp-stories-functions.php. Args: $allowed.
Whether the current member may post stories. Default: logged in and in one of the roles ticked under User Publishing > Authorized User Roles (all roles when none are ticked). The same rule decides the “Add story” buttons, whether the submit form is printed, and whether the upload request is accepted (403 otherwise).
wp_stories_lazy_editor
Section titled “wp_stories_lazy_editor”Filter. public/class-wp-stories-public.php. Args: $lazy. Default true.
Pages that show stories load only the small viewer script. For members who can
post, the posting editor (FilePond, image editor, form script) is printed into
a <template id="wpst-editor-assets"> and loaded on their first “Add story”
tap. Return false to load it with the page instead, for example if a script
optimizer moves or combines the template’s contents.
add_filter( 'wp_stories_lazy_editor', '__return_false' );wp_stories_submit_form_printed
Section titled “wp_stories_submit_form_printed”Action. public/class-wp-stories-submit-user-stories.php.
Fires when a story submit form is printed. WP Stories uses it to queue the posting editor for that page.
wp_stories_query_limit
Section titled “wp_stories_query_limit”Filter. includes/wp-stories-functions.php. Args: $limit. Default 50.
How many stories a site-wide tray fetches when no member is given (the public
stories widget, the user-stories shortcodes without an id), most recent first.
Matches the activity tray’s wp_story_activity_count. One member’s stories
are not capped here; the per-member story limit already bounds them.
CSS custom properties (2.5.0+)
Section titled “CSS custom properties (2.5.0+)”Set these in your theme or in Appearance > Customize > Additional CSS. Each one falls back to the plugin default when it is not set.
–wpst-circle-size
Section titled “–wpst-circle-size”Size of the Instagram-style story circles, the “Add story” circle and their loading placeholders. Default min(18vw, 90px). The Circle Size setting sets this for you. Set it yourself for an exact size:
.stories.snapgram,.wp-stories-user-stories:has(> .stories.snapgram) { --wpst-circle-size: min(20vw, 100px);}–wpst-tray-label-size
Section titled “–wpst-tray-label-size”Font size of the names under the story circles. Default 12px.
–wpst-like-burst-color
Section titled “–wpst-like-burst-color”Color of the heart shown when a member double-taps a story to like it. Default #fff.

