Developer Guide

Get Started

Architecture Overview

 

WP Stories follows WordPress coding standards and MVC pattern:

┌─────────────────────────────────────────────┐
│                  WordPress Core              │
├─────────────────────────────────────────────┤
│              WP Stories Plugin               │
├──────────┬───────────┬──────────┬──────────┤
│  Admin   │   Public  │ Includes │ Widgets  │
├──────────┼───────────┼──────────┼──────────┤
│   CSS    │    JS     │   Views  │   APIs   │
└──────────┴───────────┴──────────┴──────────┘

Design Patterns

 

  • Singleton: Main plugin class
  • Factory: Story creation
  • Observer: Hook system
  • MVC: Separation of concerns

Plugin Structure

 

wp-stories/
├── admin/                      # Admin functionality
│   ├── class-wp-stories-admin.php
│   ├── css/                   # Admin styles
│   ├── js/                    # Admin scripts
│   ├── inc/                   # Settings tabs
│   └── wbcom/                 # License & settings
├── includes/                  # Core functionality
│   ├── class-wp-stories.php   # Main plugin class
│   ├── class-wp-stories-activator.php
│   ├── class-wp-stories-deactivator.php
│   ├── class-wp-stories-i18n.php
│   ├── class-wp-stories-loader.php
│   ├── wp-stories-functions.php
│   └── codestar-framework/    # Settings framework
├── public/                    # Frontend functionality
│   ├── class-wp-stories-public.php
│   ├── class-wp-stories-submit-user-stories.php
│   ├── css/                   # Frontend styles
│   ├── js/                    # Frontend scripts
│   ├── widgets/               # Widget classes
│   └── partials/              # View templates
├── languages/                 # Translations
├── edd-license/              # License management
└── wp-stories.php            # Main plugin file

Core Classes

 

Main Plugin Class

 

class Wp_Stories {
    protected $loader;        // Hook loader
    protected $wp_stories;    // Plugin slug
    protected $version;       // Plugin version
    
    public function __construct() {
        $this->load_dependencies();
        $this->set_locale();
        $this->define_admin_hooks();
        $this->define_public_hooks();
    }
    
    public function run() {
        $this->loader->run();
    }
}

Admin Class

 

class Wp_Stories_Admin {
    private $wp_stories;
    private $version;
    
    // Register custom post type
    public function wp_stories_add_custom_post_type() {
        register_post_type('wb-story', $args);
        register_post_type('wb-story-box', $args);
    }
    
    // Enqueue admin scripts
    public function enqueue_styles() {
        wp_enqueue_style($this->wp_stories, ...);
    }
}

Public Class

 

class Wp_Stories_Public {
    // Handle frontend display
    public function wp_stories_shortcode($atts) {
        // Process shortcode attributes
        // Return HTML output
    }
    
    // AJAX handlers
    public function wp_stories_load_user_stories() {
        // Load more stories via AJAX
    }
}

Database Schema

 

Custom Tables

 

WP Stories uses WordPress post meta for storage:

Post Type: wb-story

 

-- Stored in wp_posts table
post_type = 'wb-story'
post_status = 'publish'|'draft'|'private'

-- Meta data in wp_postmeta
meta_key: 'wb_story_items'      -- Serialized story data
meta_key: 'wb_story_visibility'  -- Privacy settings
meta_key: 'wb_story_views'       -- View count
meta_key: 'wb_story_expires'     -- Expiration timestamp

Post Type: wb-story-box

 

-- Story collections
post_type = 'wb-story-box'

-- Meta data
meta_key: 'wb-story-box-metabox' -- Box configuration
meta_key: 'wb_story_ids'         -- Associated story IDs

User Meta

 

// Story preferences
get_user_meta($user_id, 'wp_stories_viewed', true);
get_user_meta($user_id, 'wp_stories_settings', true);

Hooks & Filters

 

Action Hooks

 

Story Creation

 

// Before story creation
do_action('wp_stories_before_create', $story_data, $user_id);

// After story creation
do_action('wp_stories_after_create', $story_id, $story_data);

// Story deletion
do_action('wp_stories_before_delete', $story_id);
do_action('wp_stories_after_delete', $story_id);

Display Hooks

 

// Before stories display
do_action('wp_stories_before_display', $stories);

// After stories display
do_action('wp_stories_after_display', $stories);

// Single story view
do_action('wp_stories_view', $story_id, $viewer_id);

Integration Hooks

 

// BuddyPress integration
do_action('wp_stories_bp_activity_posted', $activity_id, $story_id);

// PeepSo integration
do_action('wp_stories_peepso_activity_posted', $activity_id, $story_id);

Filter Hooks

 

Content Filters

 

// Filter story data before save
$story_data = apply_filters('wp_stories_before_save', $story_data);

// Filter story output
$html = apply_filters('wp_stories_output', $html, $story_id);

// Filter allowed file types
$types = apply_filters('wp_stories_allowed_types', array('jpg', 'png', 'mp4'));

Permission Filters

 

// Can user create story
$can_create = apply_filters('wp_stories_can_create', true, $user_id);

// Can user view story
$can_view = apply_filters('wp_stories_can_view', true, $story_id, $user_id);

// Can user delete story
$can_delete = apply_filters('wp_stories_can_delete', false, $story_id, $user_id);

Display Filters

 

// Filter story circle HTML
$circle_html = apply_filters('wp_stories_circle_html', $html, $story);

// Filter story viewer HTML
$viewer_html = apply_filters('wp_stories_viewer_html', $html, $story);

// Filter shortcode attributes
$atts = apply_filters('wp_stories_shortcode_atts', $atts);

PHP API

 

Core Functions

 

Story Management

 

/**
 * Get stories
 * @param int $box_id Story box ID
 * @param bool $author Get author stories
 * @param array $query_args WP_Query arguments
 * @return array|WP_Error
 */
function get_wp_stories($box_id, $author = false, $query_args = array()) {
    // Returns array of story objects
}

/**
 * Create a new story
 * @param array $data Story data
 * @return int|WP_Error Story ID or error
 */
function wp_stories_create_story($data) {
    $story_id = wp_insert_post(array(
        'post_type' => 'wb-story',
        'post_title' => $data['title'],
        'post_status' => 'publish'
    ));
    
    if ($story_id) {
        update_post_meta($story_id, 'wb_story_items', $data['items']);
    }
    
    return $story_id;
}

/**
 * Delete a story
 * @param int $story_id
 * @return bool
 */
function wp_stories_delete_story($story_id) {
    return wp_delete_post($story_id, true);
}

User Functions

 

/**
 * Get user avatar
 * @param int $user_id
 * @param int $size Avatar size
 * @return string Avatar URL
 */
function get_wp_stories_user_avatar($user_id, $size = 100) {
    // Returns avatar URL with integration support
}

/**
 * Get user display name
 * @param int $user_id
 * @return string
 */
function get_wp_stories_user_name($user_id) {
    // Returns user display name
}

/**
 * Check if user can create stories
 * @param int $user_id
 * @return bool
 */
function wp_stories_user_can_create($user_id = null) {
    if (!$user_id) {
        $user_id = get_current_user_id();
    }
    
    $allowed_roles = get_option('wp_stories_allowed_roles', array());
    $user = get_userdata($user_id);
    
    return !empty(array_intersect($allowed_roles, $user->roles));
}

Display Functions

 

/**
 * Display stories
 * @param array $args Display arguments
 * @return string HTML output
 */
function wp_stories_display($args = array()) {
    $defaults = array(
        'style' => 'circle',
        'limit' => 10,
        'columns' => 5
    );
    
    $args = wp_parse_args($args, $defaults);
    
    // Generate and return HTML
}

/**
 * Get story viewer HTML
 * @param int $story_id
 * @return string
 */
function wp_stories_get_viewer($story_id) {
    // Returns viewer modal HTML
}

JavaScript API

 

Story Editor Bridge

 

// Global editor instance
window.WPStoriesEditorBridge = {
    /**
     * Open image editor
     * @param {string} imageUrl - Image URL or base64
     * @param {object} options - Editor options
     */
    open: function(imageUrl, options) {
        options = {
            onSave: function(dataURL) {},
            onCancel: function() {},
            theme: 'light',
            ...options
        };
        
        // Initialize Vue editor
    },
    
    /**
     * Close editor
     */
    close: function() {
        // Close modal
    }
};

Story Viewer API

 

// Story viewer instance
window.WPStoriesViewer = {
    /**
     * Initialize viewer
     * @param {string} selector - Container selector
     * @param {object} options - Viewer options
     */
    init: function(selector, options) {
        // Initialize Zuck.js
    },
    
    /**
     * Open specific story
     * @param {string} storyId
     */
    open: function(storyId) {
        // Open story in viewer
    },
    
    /**
     * Navigate stories
     */
    next: function() {},
    previous: function() {},
    pause: function() {},
    play: function() {}
};

jQuery Extensions

 

// jQuery plugin for story creation
$.fn.wpStoriesCreator = function(options) {
    return this.each(function() {
        // Initialize FilePond uploader
        // Attach editor bridge
        // Handle submissions
    });
};

// Usage
$('#story-creator').wpStoriesCreator({
    maxFiles: 10,
    maxSize: '10MB',
    onUpload: function(files) {},
    onEdit: function(file) {},
    onSubmit: function(data) {}
});

AJAX Handlers

 

// Load more stories
function wpStoriesLoadMore(page, callback) {
    $.ajax({
        url: wp_stories_ajax.ajax_url,
        type: 'POST',
        data: {
            action: 'wp_stories_load_more',
            page: page,
            nonce: wp_stories_ajax.nonce
        },
        success: callback
    });
}

// Submit story
function wpStoriesSubmit(data, callback) {
    $.ajax({
        url: wp_stories_ajax.ajax_url,
        type: 'POST',
        data: {
            action: 'wp_stories_submit',
            story_data: data,
            nonce: wp_stories_ajax.nonce
        },
        success: callback
    });
}

REST API

 

Endpoints

 

Get Stories

 

GET /wp-json/wp-stories/v1/stories

Parameters:

  • box_id – Story box ID
  • user_id – Filter by user
  • limit – Number of stories
  • page – Pagination

Response:

{
    "stories": [
        {
            "id": 123,
            "title": "My Story",
            "author": 1,
            "items": [...],
            "created": "2024-01-01T00:00:00"
        }
    ],
    "total": 50,
    "pages": 5
}

Create Story

 

POST /wp-json/wp-stories/v1/stories

Body:

{
    "title": "New Story",
    "items": [
        {
            "type": "image",
            "src": "https://...",
            "duration": 5
        }
    ],
    "visibility": "public"
}

Delete Story

 

DELETE /wp-json/wp-stories/v1/stories/{id}

Authentication

 

// Register REST routes
add_action('rest_api_init', function() {
    register_rest_route('wp-stories/v1', '/stories', array(
        'methods' => 'GET',
        'callback' => 'wp_stories_rest_get_stories',
        'permission_callback' => 'wp_stories_rest_permission'
    ));
});

// Permission callback
function wp_stories_rest_permission() {
    return current_user_can('read');
}

Custom Post Types

 

Story Post Type

 

register_post_type('wb-story', array(
    'labels' => array(
        'name' => __('Stories', 'wp-stories'),
        'singular_name' => __('Story', 'wp-stories')
    ),
    'public' => true,
    'has_archive' => true,
    'supports' => array('title', 'editor', 'thumbnail', 'author'),
    'menu_icon' => 'dashicons-format-image',
    'rewrite' => array('slug' => 'stories'),
    'capability_type' => 'post',
    'map_meta_cap' => true
));

Story Box Post Type

 

register_post_type('wb-story-box', array(
    'labels' => array(
        'name' => __('Story Boxes', 'wp-stories'),
        'singular_name' => __('Story Box', 'wp-stories')
    ),
    'public' => false,
    'show_ui' => true,
    'supports' => array('title'),
    'menu_icon' => 'dashicons-grid-view'
));

Templating System

 

Template Hierarchy

 

1. Theme: /wp-stories/single-story.php
2. Plugin: /public/partials/single-story.php
3. Default: WordPress single.php

Override Templates

 

Create in your theme:

/your-theme/
    /wp-stories/
        single-story.php      # Single story
        archive-stories.php   # Stories archive
        story-circle.php      # Circle template
        story-viewer.php      # Viewer modal

Template Tags

 

// In template files
if (function_exists('wp_stories_display')) {
    wp_stories_display(array(
        'style' => 'circle',
        'limit' => 10
    ));
}

// Get story data
$story = get_wp_story($story_id);
$items = get_wp_story_items($story_id);

// Display story viewer
wp_stories_viewer($story_id);

Custom Templates

 

// Register custom template
add_filter('wp_stories_templates', function($templates) {
    $templates['custom'] = array(
        'label' => 'Custom Style',
        'template' => 'path/to/template.php'
    );
    return $templates;
});

Image Editor Integration

 

Vue Editor API

 

// Initialize editor
const editor = new WPStoriesVueEditor({
    container: '#editor-container',
    image: 'path/to/image.jpg',
    options: {
        theme: 'light',
        locale: 'en',
        cssMaxWidth: 700,
        cssMaxHeight: 500
    }
});

// Event handlers
editor.on('save', (dataURL) => {
    // Handle saved image
});

editor.on('cancel', () => {
    // Handle cancellation
});

// Methods
editor.loadImage(url);
editor.applyFilter('grayscale');
editor.addText('Hello World');
editor.destroy();

FilePond Integration

 

// Configure FilePond with editor
FilePond.registerPlugin(
    FilePondPluginImageEdit,
    FilePondPluginImagePreview,
    FilePondPluginFileValidateType
);

const pond = FilePond.create(inputElement, {
    imageEditEditor: {
        open: (file, instructions) => {
            WPStoriesEditorBridge.open(file, {
                onSave: (output) => instructions.confirm(output),
                onCancel: () => instructions.cancel()
            });
        }
    }
});

Creating Extensions

 

Basic Extension Structure

 

/**
 * Plugin Name: WP Stories Extension
 * Description: Extends WP Stories functionality
 */

class WP_Stories_Extension {
    
    public function __construct() {
        add_action('wp_stories_init', array($this, 'init'));
    }
    
    public function init() {
        // Add custom functionality
        add_filter('wp_stories_allowed_types', array($this, 'add_types'));
        add_action('wp_stories_after_create', array($this, 'after_create'), 10, 2);
    }
    
    public function add_types($types) {
        $types[] = 'webp';
        return $types;
    }
    
    public function after_create($story_id, $data) {
        // Custom processing
    }
}

new WP_Stories_Extension();

Adding Custom Filters

 

// Add Instagram-style filter
add_filter('wp_stories_image_filters', function($filters) {
    $filters['instagram'] = array(
        'label' => 'Instagram',
        'css' => 'filter: contrast(1.1) brightness(1.1)',
        'canvas' => array(
            'brightness' => 10,
            'contrast' => 10
        )
    );
    return $filters;
});

Custom Story Types

 

// Register custom story type
add_filter('wp_stories_types', function($types) {
    $types['product'] = array(
        'label' => 'Product Story',
        'icon' => 'dashicons-cart',
        'fields' => array(
            'product_id' => array(
                'type' => 'select',
                'label' => 'Select Product',
                'options' => 'callback:get_products'
            )
        )
    );
    return $types;
});

Performance Optimization

 

Caching

 

// Object caching
$cache_key = 'wp_stories_' . $user_id;
$stories = wp_cache_get($cache_key);

if (false === $stories) {
    $stories = get_wp_stories($box_id);
    wp_cache_set($cache_key, $stories, '', 3600);
}

// Transients
$transient_key = 'wp_stories_popular';
$popular = get_transient($transient_key);

if (false === $popular) {
    $popular = calculate_popular_stories();
    set_transient($transient_key, $popular, DAY_IN_SECONDS);
}

Asset Optimization

 

// Conditional loading
add_action('wp_enqueue_scripts', function() {
    if (is_page() && has_shortcode(get_the_content(), 'wp-stories')) {
        wp_enqueue_script('wp-stories');
        wp_enqueue_style('wp-stories');
    }
});

// Async/defer loading
add_filter('script_loader_tag', function($tag, $handle) {
    if ('wp-stories' === $handle) {
        return str_replace(' src', ' defer src', $tag);
    }
    return $tag;
}, 10, 2);

Database Optimization

 

// Batch operations
function wp_stories_bulk_delete($story_ids) {
    global $wpdb;
    
    $placeholders = array_fill(0, count($story_ids), '%d');
    $format = implode(', ', $placeholders);
    
    $wpdb->query(
        $wpdb->prepare(
            "DELETE FROM {$wpdb->posts} 
             WHERE ID IN ($format) 
             AND post_type = 'wb-story'",
            $story_ids
        )
    );
}

// Indexed queries
add_action('init', function() {
    // Add index for better performance
    add_post_meta_index('wb_story_expires');
});

Testing

 

Unit Testing

 

class WP_Stories_Test extends WP_UnitTestCase {
    
    public function test_story_creation() {
        $story_id = wp_stories_create_story(array(
            'title' => 'Test Story',
            'items' => array()
        ));
        
        $this->assertIsInt($story_id);
        $this->assertGreaterThan(0, $story_id);
    }
    
    public function test_story_deletion() {
        $story_id = $this->factory->post->create(array(
            'post_type' => 'wb-story'
        ));
        
        $result = wp_stories_delete_story($story_id);
        $this->assertTrue($result);
    }
}

Integration Testing

 

// Jest tests
describe('Story Editor', () => {
    test('opens with image', () => {
        const editor = new WPStoriesEditor();
        editor.open('test.jpg');
        expect(editor.isOpen).toBe(true);
    });
    
    test('saves edited image', (done) => {
        const editor = new WPStoriesEditor();
        editor.open('test.jpg', {
            onSave: (dataURL) => {
                expect(dataURL).toContain('data:image');
                done();
            }
        });
        editor.save();
    });
});

E2E Testing

 

// Cypress tests
describe('Story Creation Flow', () => {
    it('creates a new story', () => {
        cy.visit('/wp-admin');
        cy.get('#menu-posts-wb-story').click();
        cy.get('.page-title-action').click();
        cy.get('#title').type('Test Story');
        cy.get('#publish').click();
        cy.contains('Story published');
    });
});

Contributing

 

Development Setup

 

# Clone repository
git clone https://github.com/wbcomdesigns/wp-stories.git

# Install dependencies
npm install
composer install

# Build assets
npm run build

# Watch for changes
npm run watch

# Run tests
npm test
phpunit

Coding Standards

 

Follow WordPress Coding Standards:

// PHP
function wp_stories_example_function( $param1, $param2 ) {
    if ( ! empty( $param1 ) ) {
        return sanitize_text_field( $param1 );
    }
    
    return $param2;
}
// JavaScript
function wpStoriesExampleFunction( param1, param2 ) {
    if ( param1 ) {
        return param1.trim();
    }
    
    return param2;
}

Code Review Checklist

  • Follows WordPress coding standards
  • Includes inline documentation
  • Has unit tests
  • Passes all existing tests
  • Updates documentation
  • Maintains backward compatibility
  • Includes security considerations
  • Performance optimized

API Reference

 

Global Functions

 

// Core functions
get_wp_stories($box_id, $author, $query_args, $return_ids, $story_args)
get_wp_story_items($items, $box_id, $author, $story_id)
get_wp_stories_user_avatar($user_id, $size)
get_wp_stories_user_name($user_id)

// Display functions
wp_stories_display($args)
wp_stories_shortcode($atts)
wp_stories_viewer($story_id)

// CRUD operations
wp_stories_create_story($data)
wp_stories_update_story($story_id, $data)
wp_stories_delete_story($story_id)
wp_stories_get_story($story_id)

// User functions
wp_stories_user_can_create($user_id)
wp_stories_user_can_view($story_id, $user_id)
wp_stories_user_can_delete($story_id, $user_id)
wp_stories_get_user_stories($user_id)

Classes

 

// Main classes
Wp_Stories                          // Core plugin class
Wp_Stories_Admin                    // Admin functionality
Wp_Stories_Public                   // Frontend functionality
Wp_Stories_Activator                // Activation hooks
Wp_Stories_Deactivator              // Deactivation hooks
Wp_Stories_i18n                     // Internationalization
Wp_Stories_Loader                   // Hook loader

// Widget classes
WP_Stories_Activity_Feed_Widget     // Activity feed widget
WP_Stories_User_Public_Stories_Widget // User stories widget
WP_Stories_User_Single_Stories_Widget // Single story widget

// Submit handler
Wp_Stories_Submit_User_Stories      // User submission handler

JavaScript Objects

 

// Global objects
WPStoriesEditorBridge               // Image editor interface
WPStoriesViewer                     // Story viewer
WPStoriesUploader                   // File uploader
WPStoriesNotifications              // Notification system

// jQuery plugins
$.fn.wpStoriesCreator               // Story creator
$.fn.wpStoriesVueEditor             // Vue editor wrapper
$.fn.wpStoriesViewer                // Viewer initializer

Resources

 

Documentation

 

Tools

 

Last updated: August 27, 2025