Wbcom Designs WPMediaVerse Docs
Back to product Buy Now

Getting Started

Install, configure, and start sharing media in minutes.

Why WPMediaVerse

WPMediaVerse is a purpose-built media platform for WordPress. Unlike plugins that bolt media features onto WordPress posts or attachments, WPMediaVerse uses its own high-performance database architecture designed from the ground up for media-heavy communities.

The Problem with WordPress Attachments

Most WordPress media plugins store user uploads as wp_posts with post_type = 'attachment'. This creates serious problems at scale:

  • wp_posts table bloat - A community with 50,000 photos adds 50,000 rows to the same table that holds your pages, blog posts, menu items, and revisions. Every WP_Query on your site slows down.
  • wp_postmeta overhead - Each media item needs 10-20 meta rows for stats, privacy, tags, and file paths. At 50K media, that is 500K-1M rows in wp_postmeta - the single biggest performance bottleneck in WordPress.
  • No native stats - WordPress attachments have no built-in view counting, reaction tracking, or engagement metrics. Plugins that add these use postmeta, compounding the bloat.
  • Mixed concerns - Admin queries for pages and posts compete with media queries. There is no way to independently optimize media storage.

How WPMediaVerse Is Different

WPMediaVerse stores all media in dedicated custom tables, completely separate from wp_posts:

Table Purpose
mvs_media_index Core media record - title, author, file URL, privacy, status, timestamps
mvs_media_meta Sparse key-value metadata (thumbnails, EXIF, groups)
mvs_media_stats Views, reactions, comments, favorites - one row per media
mvs_reactions Individual emoji reactions with user attribution
mvs_comments Threaded comment system separate from wp_comments
mvs_favorites User favorites / saved items
mvs_follows User follow relationships
mvs_conversations Direct message conversations
mvs_messages Individual chat messages

What This Means for You

  • Zero wp_posts bloat - 100,000 media items add zero rows to wp_posts. Your pages, menus, and blog posts are unaffected.
  • Indexed queries - Every table has purpose-built indexes. Fetching "latest 12 public photos by user X" is a single indexed query, not a WP_Query with meta joins.
  • Independent scaling - You can optimize, partition, or replicate media tables without touching the rest of WordPress.
  • No postmeta bottleneck - Stats, privacy, and metadata live in dedicated columns or a sparse meta table. No serialized arrays, no autoload bloat.
  • Real-time stats - Views, reactions, and comments are atomic counters in mvs_media_stats, not meta values that need cache invalidation.

Performance at Scale

Metric Attachment-based plugins WPMediaVerse
10K media: wp_posts rows added 10,000 0
10K media: wp_postmeta rows added 100,000-200,000 0
Query: latest 12 photos WP_Query + 3 meta joins Single indexed SELECT
Query: user's photo count COUNT on wp_posts + meta COUNT on mvs_media_index (indexed)
Stats update (view count) update_post_meta (cache bust) UPDATE mvs_media_stats SET views = views + 1

Files Are Still in wp-content

WPMediaVerse stores database records in custom tables. The actual files (images, videos, audio) are stored in the standard WordPress uploads directory:

wp-content/uploads/wpmediaverse/2026/03/photo-name.webp
wp-content/uploads/wpmediaverse/2026/03/photo-name-300x200.webp  (thumbnail)
wp-content/uploads/wpmediaverse/2026/03/photo-name-1024x768.webp (large)
wp-content/uploads/wpmediaverse/2026/03/photo-name-150x150.webp  (square)

With Pro's cloud storage, files can also be stored on Amazon S3 or BunnyCDN while database records remain local.

How It Compares

Feature rtMedia BuddyBoss Media MediaPress WPMediaVerse
Storage wp_posts + postmeta wp_posts + postmeta wp_posts + postmeta Custom tables
Requires BuddyPress Yes Yes (BuddyBoss) Yes No (optional)
Standalone mode No No No Yes
Custom privacy levels Basic Basic Basic 6 levels (Pro)
Direct messaging No Separate plugin No Built-in
Gamification No No No Challenges, battles, tournaments (Pro)
Video transcoding No No No Multi-quality + HLS (Pro)
Cloud storage No No No S3 + BunnyCDN (Pro)
AI moderation No No No OpenAI + Vision + Rekognition
Upload quotas No No No Per-user packages (Pro)
Layout modes 1 1 1 5 (grid + 4 Pro layouts)

Use Cases

Photography Community

A social network for photographers to share, discover, and compete. Users upload photos, follow each other, enter weekly challenges, and battle head-to-head. The Pinterest or Instagram layout creates a visual-first experience.

Portfolio Showcase

Designers, artists, and photographers use WPMediaVerse as their portfolio. The Dribbble layout presents work in a professional shot grid. Clients can view galleries, leave comments, and message directly.

School or University

Students submit media projects through the upload system. Teachers use collections to curate work. Privacy controls ensure only enrolled members see content. Quotas limit storage per student.

Company Intranet

Employees share photos from events, marketing assets, and training videos. Group media tabs organize content by department. AI moderation flags inappropriate uploads automatically.

BuddyPress Community

Add a rich media layer to any BuddyPress social network. Members get media tabs on profiles and groups, uploads appear in the activity stream, and followers see new content in their feed. Everything works out of the box - activate BuddyPress and the integration is automatic.

Frequently Asked Questions

Will my media appear in the WordPress Media Library?

No. WPMediaVerse media is managed through its own admin pages (Media > All Media) and frontend dashboard (My Media). This is intentional - it keeps the WordPress Media Library clean for your theme images, post attachments, and other site assets.

Can I use WPMediaVerse without BuddyPress?

Yes. WPMediaVerse is a standalone plugin. It creates its own pages (/media/, /my-media/, /upload-media/) and works on any WordPress site. BuddyPress integration activates automatically when BuddyPress is installed but is completely optional.

What happens to my data if I deactivate the plugin?

Deactivation stops all plugin functionality but your data stays intact in the database. Reactivating restores everything. Deleting the plugin (uninstall) removes all custom tables and data permanently.

Can I migrate from rtMedia / MediaPress / BuddyBoss?

Yes. WPMediaVerse Pro includes WP-CLI migration tools that import media records, preserving original upload dates, author attribution, and file URLs. See Migration Tools.

How does search work?

WPMediaVerse has its own search system that queries the mvs_media_index table directly. It searches titles and descriptions. Tags and categories use WordPress taxonomies for filtering.

Does it work with page builders?

Yes. WPMediaVerse provides Gutenberg blocks (Media Grid, Explore Feed, Album Viewer) and shortcodes for Elementor, Beaver Builder, and other builders. The blocks use the WordPress Interactivity API for dynamic behavior.

Installation

Get WPMediaVerse running on your WordPress site in under five minutes - install, activate, and your community is ready to start uploading.

Requirements

  • WordPress 6.5+
  • PHP 7.4+
  • MySQL 5.7+ or MariaDB 10.4+
  • BuddyPress 12.0+ (optional, for social integration)

Installing via WordPress Admin

  1. Go to Plugins > Add New Plugin in your WordPress dashboard.
  2. Search for WPMediaVerse.
  3. Click Install Now, then Activate.

WordPress plugin search showing WPMediaVerse install button

Installing via ZIP Upload

  1. Download the wpmediaverse.zip file from wbcomdesigns.com.
  2. Go to Plugins > Add New Plugin > Upload Plugin.
  3. Choose the ZIP file and click Install Now.
  4. Click Activate Plugin.

Upload plugin screen with WPMediaVerse ZIP selected

Installing via FTP

  1. Unzip the downloaded archive.
  2. Upload the wpmediaverse folder to /wp-content/plugins/.
  3. Go to Plugins in your dashboard and activate WPMediaVerse.

What Happens on Activation

When you activate WPMediaVerse, the plugin automatically:

  • Creates custom database tables for media index, stats, reactions, comments, favorites, follows, conversations, messages, collections, access grants, and webhooks - all separate from wp_posts for maximum performance.
  • Registers the mvs_album and mvs_collection custom post types (media itself uses the custom mvs_media_index table, not wp_posts).
  • Registers the mvs_tag and mvs_category taxonomies.
  • Adds default capabilities to the Administrator, Editor, Author, Contributor, and Subscriber roles.
  • Redirects you to the Setup Wizard for initial configuration.

Deactivation and Uninstall

Deactivation stops all plugin functionality but keeps your data intact.

Uninstall (deleting the plugin) removes all plugin data including:

  • All mvs_media, mvs_album, and mvs_collection posts
  • All custom database tables
  • All plugin options and transients

To preserve data after uninstalling, export your media library first.

Setup Wizard

After you activate WPMediaVerse, a 3-step wizard guides you through the only settings you need to make your first upload possible. The whole thing takes about two minutes.

Setup wizard welcome screen

Step 1: Permissions

Configure which user roles can upload and manage media.

Setup wizard permissions step with role checkboxes

By default, the wizard grants upload access to Subscribers, Authors, Editors, and Administrators. You can restrict or expand this per role.

Each role maps to a specific set of capabilities:

Role Upload Edit Own Delete Own Moderate Manage Settings
Administrator Yes Yes Yes Yes Yes
Editor Yes Yes Yes Yes No
Author Yes Yes Yes No No
Contributor No No No No No
Subscriber Yes Yes Yes No No

You can change role capabilities at any time in Media > Settings > Permissions.

Step 2: Display

Configure how media appears on your site.

Setup wizard display step showing grid column and items-per-page options

Option Choices Default
Grid Columns 2, 3, 4 3
Items Per Page 12, 24, 48 12
Thumbnail Style Square (cropped), Original proportions Square

Step 3: Done

The wizard marks setup as complete and redirects you to the Media Overview dashboard.

Setup wizard completion screen

From here you can:

  • Upload your first media file
  • Configure AI moderation settings
  • Set up BuddyPress integration (if BuddyPress is active)

Skipping the Wizard

If you close the wizard without completing it, you can return to it via Media > Setup. The wizard will not run automatically on subsequent visits once step 1 is saved.

Your First Upload

Upload your first photo in two minutes - drag, drop, set privacy, done. Here is exactly how it works.

Option 1: Use the Upload Block or Shortcode

Add the upload form to any page using either the Gutenberg block or shortcode.

In the Block Editor: Add the WPMediaVerse: Media Upload block to your page.

In the Classic Editor or any text area:

[mvs_upload]

Frontend media upload form with drag-and-drop area and privacy selector

Option 2: Upload via REST API

Use the REST endpoint directly (for custom integrations):

curl -X POST https://yoursite.com/wp-json/mvs/v1/media \
  -H "X-WP-Nonce: YOUR_NONCE" \
  -F "file=@/path/to/image.webp" \
  -F "title=My First Upload" \
  -F "privacy=public"

The Upload Process

When you upload a file, WPMediaVerse:

  1. Validates the MIME type against your allowed file types list.
  2. Checks the file size against your configured maximum (default: 100 MB).
  3. Scans for duplicate files using SHA-256 hash comparison.
  4. Strips EXIF GPS data from images (if enabled - on by default).
  5. Stores the file using your configured storage driver (local by default).
  6. Creates an mvs_media post with the title, privacy level, and file metadata.
  7. Runs AI analysis if auto-analyze is enabled (requires OpenAI API key).
  8. Runs AI moderation if auto-moderate is enabled.
  9. Records BuddyPress activity if BuddyPress is active.

Supported File Types

By default, WPMediaVerse accepts:

Type Formats
Images JPEG, PNG, GIF, WebP
Video MP4, WebM
Audio MP3 (MPEG), OGG

You can customize allowed file types in Media > Settings > General.

Setting Privacy on Upload

Each file can have one of 6 privacy levels:

Level Who Can See It
Public Everyone, including logged-out visitors
Members Only Any logged-in WordPress user
Friends BuddyPress friends of the uploader (requires BuddyPress)
Group Members of a specific BuddyPress group (requires BuddyPress)
Private Only the uploader and administrators
Custom A specific list of user IDs (managed via API or access rules)

The default privacy level is set in Media > Settings > General > Default Privacy Level.

After Uploading

Your uploaded media appears:

  • On the media archive page (/wp-json/mvs/v1/media in the API)
  • In the Media post type list in your admin dashboard
  • In the Media tab on your BuddyPress profile (if BuddyPress is active)
  • In your media dashboard (use [mvs_dashboard] shortcode)

Free vs Pro Comparison

WPMediaVerse Free is a full-featured media platform. WPMediaVerse Pro unlocks advanced tools for professional communities, monetization, and engagement.

What's New in 1.2.0

Both editions ship together at 1.2.0 (May 5, 2026). Highlights:

  • Free - 2 new Gutenberg blocks (Member Photos, PDF Viewer), 4 new sort options on Media Grid (popularity, views, reactions, random), Lightbox Download + Fullscreen + Share fixes, per-media Edit modal, Open Graph + Twitter Card meta on /media/{slug}/, Bulk Actions on the admin All Media screen, and a full WCAG 2.1 AA accessibility pass on every customer-facing surface.
  • Pro - every Pro feature is now a first-class Gutenberg block. 12 new Pro blocks: Tournament, Tournaments List, Challenge, Challenges List, Battle, Battles Active, Instagram Feed, Flickr Feed, Pinterest Feed, Dribbble Feed, Leaderboard, Compete Hub. Each block has a matching [mvs_pro_*] shortcode. Layouts can now be mixed per-page instead of being locked to one site-wide setting.

→ Full release notes: What's New in 1.2.0 · Pro feature overview

Quick Comparison

Feature Free Pro
Media Upload & Management
Drag & drop upload (photo, video, audio) Yes Yes
Bulk upload Yes Yes
Title, description, tags, categories Yes Yes
EXIF stripping & duplicate detection Yes Yes
Thumbnail generation (3 sizes) Yes Yes
Custom storage path Yes Yes
Amazon S3 cloud storage -- Yes
BunnyCDN cloud storage -- Yes
Feed Layouts
Default grid layout Yes Yes
Instagram layout (square grid + stories) -- Yes
Pinterest layout (masonry cards) -- Yes
Flickr layout (justified gallery) -- Yes
Dribbble layout (portfolio shots) -- Yes
Social Features
Follow / unfollow users Yes Yes
Emoji reactions on media Yes Yes
Threaded comments Yes Yes
Favorites (save for later) Yes Yes
@Mentions in comments Yes Yes
Share to social media Yes Yes
Direct messages (1-on-1 chat) Yes Yes
Voice messages in DMs Yes Yes
Media sharing in DMs Yes Yes
Message requests & privacy controls Yes Yes
Content Organization
Albums with cover photos Yes Yes
Collections (curated boards) Yes Yes
Stories (coming soon) Planned Planned
Gallery groups (multi-photo posts) Yes Yes
Privacy & Moderation
Public / Members Only / Private Yes Yes
Followers Only privacy level -- Yes
Group Members Only privacy level -- Yes
Custom privacy (specific users) -- Yes
Album-level privacy inheritance -- Yes
Privacy presets -- Yes
Bulk privacy updates -- Yes
AI content moderation (OpenAI) Yes Yes
Google Cloud Vision moderation -- Yes
AWS Rekognition moderation -- Yes
User reporting Yes Yes
User blocking Yes Yes
GDPR data export & erasure Yes Yes
Video
Video upload & playback Yes Yes
Multi-quality transcoding (720p/480p/360p) -- Yes
HLS adaptive streaming -- Yes
Video chapter markers -- Yes
Resume playback (pick up where you left off) -- Yes
Auto-captions via OpenAI Whisper -- Yes
Video analytics & heatmaps -- Yes
Image Processing
Basic text watermarking Yes Yes
Logo watermarking with positioning -- Yes
Watermark opacity & font control -- Yes
Gamification
Photo Challenges (themed competitions) -- Yes
1v1 Photo Battles -- Yes
Single-elimination Tournaments -- Yes
Media Boosts (spend points for visibility) -- Yes
Upload Streaks with milestones -- Yes
Weekly Autopilot (auto-create challenges) -- Yes
XP integration with wb-gamification -- Yes
Quotas & Monetization
Per-user upload quotas (count + storage) -- Yes
Quota packages (Free, Premium, etc.) -- Yes
MemberPress integration -- Yes
Paid Memberships Pro integration -- Yes
WooCommerce integration -- Yes
Credit transaction log -- Yes
User Profiles
Public profile page (/media/@username/) Yes Yes
Follow / Message buttons Yes Yes
Custom avatar upload Yes Yes
Inline profile editing -- Yes
Admin Tools
Media overview dashboard Yes Yes
Media list with bulk actions Yes Yes
Settings (general, display, social, AI) Yes Yes
Competitions dashboard -- Yes
Challenge manager with theme library -- Yes
Tournament bracket manager -- Yes
Battle monitor -- Yes
Video analytics dashboard -- Yes
Quota & credits management -- Yes
Media stats & insights -- Yes
BuddyPress Integration
Profile media tab Yes Yes
Group media tab Yes Yes
Activity stream media Yes Yes
Notifications (likes, comments, follows) Yes Yes
Gutenberg Blocks
Core blocks (Media Grid, Player, Album, Upload, Stats, Explore, Dashboard, etc.) Yes (12) Yes (inherited)
Member Photos block (mvs/member-photos) Yes Yes
PDF Viewer block (mvs/pdf-viewer) Yes Yes
Pro feature blocks (Tournament, Challenge, Battle, Leaderboard, Compete Hub) -- Yes (5)
Pro feed-layout blocks (Instagram, Flickr, Pinterest, Dribbble) -- Yes (4)
Pro list blocks (Tournaments List, Challenges List, Battles Active) -- Yes (3)
Mix layouts per-page -- Yes
Developer
REST API (80+ endpoints) Yes Yes
Pro REST API (37 additional endpoints) -- Yes
Lightbox download + share REST endpoints Yes Yes
33 action hooks + 16 filter hooks Yes Yes
Template override system Yes Yes
Custom storage driver API Yes Yes
WP-CLI commands (8 commands) Yes Yes
wp mvs generate-video-thumbnails (poster backfill) Yes Yes
Migration tools (rtMedia, MediaPress, BuddyBoss) -- Yes
MigrationPage admin (per-platform cards) -- Yes
Accessibility
WCAG 2.1 AA pass on customer-facing UI Yes Yes
aria-label on icon buttons + aria-pressed on toggles Yes Yes
:focus-visible outlines + keyboard navigation Yes Yes
Theme dark-mode support (BuddyX Pro / generic class-based) Yes Yes
Integrations
BuddyPress 12+ Yes Yes
wb-gamification -- Yes
MemberPress -- Yes
Paid Memberships Pro -- Yes
WooCommerce -- Yes
OpenAI (moderation + captions) Yes Yes
Google Cloud Vision -- Yes
AWS Rekognition -- Yes
Amazon S3 -- Yes
BunnyCDN -- Yes

What You Get Free

WPMediaVerse Free is not a stripped-down trial. It is a complete media platform with:

  • Full upload system with drag & drop, bulk upload, and duplicate detection
  • Albums, collections, and gallery groups
  • Complete social layer: follows, reactions, comments, favorites, mentions, sharing
  • Built-in direct messaging with voice messages, file sharing, and read receipts
  • User profiles with follow/message buttons and media grids
  • AI content moderation via OpenAI
  • BuddyPress integration (profiles, groups, activity, notifications)
  • Full REST API with 50+ endpoints
  • GDPR compliance (data export + erasure)
  • User blocking and reporting

What Pro Adds

WPMediaVerse Pro is for sites that need professional-grade features:

  • Visual identity - Choose from Instagram, Pinterest, Flickr, or Dribbble layouts to match your community's style
  • Scale - Offload media to S3 or BunnyCDN for global CDN delivery and unlimited storage
  • Video intelligence - Multi-quality transcoding, adaptive streaming, chapters, auto-captions, and engagement analytics
  • Engagement - Gamification system with challenges, battles, tournaments, boosts, and streaks that keep users coming back
  • Monetization - Quota packages with MemberPress/WooCommerce integration let you sell tiered upload plans
  • Privacy - Six privacy levels with album inheritance, presets, and bulk management
  • AI - Google Vision and AWS Rekognition for auto-tagging and advanced content moderation
  • Migration - Import from rtMedia, MediaPress, or BuddyBoss with one WP-CLI command

Upgrading

  1. Purchase a Pro license at wbcomdesigns.com
  2. Upload and activate the wpmediaverse-pro.zip plugin
  3. Enter your license key at Media > License
  4. Pro features activate immediately - no data migration needed, no settings lost

Integrations

WPMediaVerse connects with 12+ third-party services and plugins out of the box. No custom code, no middleware - configure credentials in settings and the integration activates.

Integration Map

Integration Plugin What It Does Free Pro
BuddyPress Free Profile media tabs, group media, activity stream, notifications Yes Yes
BuddyNext Free Enhanced member directory and profile blocks Yes Yes
wb-gamification Pro XP points, badges, leaderboards for all competitions -- Yes
Amazon S3 Pro Store all media files on S3 with CDN delivery -- Yes
BunnyCDN Pro Store and deliver media via BunnyCDN edge network -- Yes
OpenAI Free AI content moderation, auto-tagging, description generation Yes Yes
Google Cloud Vision Pro Advanced image labeling, object detection, safe search -- Yes
AWS Rekognition Pro Face detection, content moderation, celebrity recognition -- Yes
OpenAI Whisper Pro Automatic video/audio transcription to WebVTT captions -- Yes
MemberPress Pro Auto-assign quota packages based on membership level -- Yes
Paid Memberships Pro Pro Auto-assign quota packages based on PMPro level -- Yes
WooCommerce Pro Sell upload quota packages as WooCommerce products -- Yes
WordPress Webhooks Free Send real-time HTTP notifications on media events Yes Yes

Community & Social

BuddyPress

WPMediaVerse is the most complete media solution for BuddyPress communities. The integration activates automatically when BuddyPress is detected - no configuration needed.

What users get:

  • A Media tab on every member profile showing their uploads in a grid
  • A Media tab in every group where members upload and share within the group
  • Media uploads appear as activity items in the BuddyPress activity stream with thumbnails
  • Notifications when someone likes, comments on, or shares your media
  • One-click media sharing from the lightbox directly into BuddyPress activity

What admins get:

  • Zero configuration - activate BuddyPress and the integration works
  • Media tab visibility follows BuddyPress privacy settings
  • Activity items follow BuddyPress moderation rules
  • Compatible with BuddyPress 12.0+

See BuddyPress Integration for full details.

BuddyNext

If you use the BuddyNext theme, WPMediaVerse detects it automatically and enhances the member directory with media counts and the profile layout with media grid blocks.

wb-gamification (Pro)

WPMediaVerse Pro registers 14 gamification actions with the wb-gamification plugin:

Action When Default XP
Upload a photo User uploads media 10
Receive a like Someone reacts to your media 5
Receive a comment Someone comments on your media 5
Follow a user User follows someone 2
Enter a challenge User submits to a challenge 10
Win a challenge (1st) First place in challenge 100
Win a challenge (2nd) Second place 50
Win a challenge (3rd) Third place 25
Create a battle User starts a 1v1 battle 5
Win a battle Win a head-to-head battle 50
Register for tournament User joins a tournament 10
Win a tournament match Advance one round 25
Win a tournament Tournament champion 200
Reach streak milestone 7/30/100/365 day streak 50-5000

All XP values are configurable in Media > Settings > Gamification. The wb-gamification plugin handles points, badges, leaderboards, and leveling - WPMediaVerse triggers the actions.

Cloud Storage

Amazon S3 (Pro)

Offload every upload to an S3 bucket. Files are served from S3 directly (or via CloudFront if configured).

What you get:

  • Unlimited storage (pay-as-you-go with AWS)
  • Global CDN delivery when paired with CloudFront
  • Signed URLs for private/members-only media
  • Automatic retry (3 attempts) on upload failure
  • Connection test button in admin to verify credentials

Setup: Paste your bucket name, region, access key, and secret key into Media > Settings > Storage > Amazon S3. Click "Test Connection" to verify. New uploads go to S3 immediately.

Security: Store credentials in wp-config.php instead of the database:

define( 'MVS_PRO_AWS_ACCESS_KEY', 'AKIA...' );
define( 'MVS_PRO_AWS_SECRET_KEY', '...' );

See Cloud Storage for IAM policy and full setup guide.

BunnyCDN (Pro)

Store and deliver media through BunnyCDN's global edge network.

What you get:

  • 114 edge locations worldwide
  • Automatic image optimization
  • Per-request pricing (no minimum commitment)
  • Simpler setup than AWS (one API key)

Setup: Enter your storage zone name, API key, and CDN hostname into Media > Settings > Storage > BunnyCDN.

Custom Storage Drivers

WPMediaVerse uses a StorageDriverInterface that any developer can implement. Build drivers for Google Cloud Storage, DigitalOcean Spaces, Wasabi, Backblaze B2, or any S3-compatible service.

See Custom Storage Drivers for the interface spec.

AI & Machine Learning

OpenAI (GPT + Vision)

Built into the free plugin. Uses the OpenAI API for:

  • Content moderation - Automatically flag inappropriate uploads before they appear on the site
  • Auto-tagging - AI suggests relevant tags based on image content
  • Description generation - Generate alt text and descriptions for accessibility
  • Monthly budget cap - Set a dollar limit to prevent unexpected API costs

Setup: Paste your OpenAI API key into Media > Settings > AI & Moderation.

Google Cloud Vision (Pro)

Adds Google's image analysis capabilities:

  • Label detection - Identify objects, locations, activities in photos
  • Safe search - Detect explicit, violent, or medical content
  • Text detection - Extract text from images (OCR)
  • Circuit breaker pattern prevents API hammering on failures

Setup: Paste your Google Cloud API key into Media > Settings > AI & Moderation > Google Vision.

AWS Rekognition (Pro)

Adds Amazon's image and video analysis:

  • Object and scene detection - Identify objects with confidence scores
  • Face detection - Detect faces with attributes (smile, glasses, age range)
  • Content moderation - Flag suggestive, violent, or explicit content
  • Circuit breaker pattern with automatic recovery

Setup: Uses the same AWS credentials as S3 storage, or set separate credentials in Media > Settings > AI & Moderation > AWS Rekognition.

OpenAI Whisper (Pro)

Automatic speech-to-text transcription for video and audio uploads:

  • Generates WebVTT caption files
  • Captions are searchable and displayed as subtitles in the video player
  • Process runs asynchronously via Action Scheduler
  • Supports 50+ languages

Setup: Enable at Media > Settings > Video > Auto-Captions (uses the same OpenAI API key).

Monetization

MemberPress (Pro)

Automatically assign upload quota packages based on MemberPress membership levels.

How it works:

  1. Create quota packages in Media > Quotas (e.g., "Free: 50 photos", "Premium: unlimited")
  2. Map each MemberPress membership to a quota package
  3. When a user purchases or is assigned a membership, their quota updates automatically
  4. When a membership expires, the user reverts to the default package

Same automatic package assignment, but using PMPro membership levels instead of MemberPress.

WooCommerce (Pro)

Sell upload quota packages as WooCommerce products:

How it works:

  1. Create quota packages in Media > Quotas
  2. Create a WooCommerce product and map it to a quota package
  3. When a customer completes checkout, their quota package activates
  4. If the order is refunded or cancelled, the package reverts to default

This lets you sell storage tiers directly from your WooCommerce store.

Webhooks

WPMediaVerse can send real-time HTTP POST notifications to external services when events occur:

Event Payload
Media uploaded Media ID, file URL, author, type, privacy
Media deleted Media ID
Comment posted Comment ID, media ID, author, content
Reaction added Media ID, user ID, reaction type
Report submitted Media ID, reporter ID, reason
Moderation changed Media ID, old status, new status

Use cases:

  • Notify a Slack channel when new media is uploaded
  • Trigger a Zapier workflow on media events
  • Sync media metadata to an external CMS or DAM
  • Log moderation actions to an audit system

Setup: Add webhook URLs at Media > Settings > Webhooks. Each webhook can filter by event type.

See Webhooks for payload formats and authentication.

WordPress Core

Site Health

WPMediaVerse registers 3 custom tests in Tools > Site Health:

  • Database tables - Verifies all custom tables exist and have the expected schema
  • Upload directory - Checks that the wpmediaverse upload directory is writable
  • Required pages - Confirms Dashboard, Explore, and Upload pages are assigned

GDPR / Privacy Tools

  • Export Personal Data - Exports all user media, comments, reactions, favorites, DMs, and follow relationships
  • Erase Personal Data - Removes all of the above when processing an erasure request
  • Privacy Policy - Suggests privacy policy text via WordPress's built-in privacy policy tool

See GDPR & Privacy Compliance.

REST API

WPMediaVerse exposes 50+ REST endpoints in the free plugin and 30+ additional endpoints in Pro, all under the mvs/v1 and mvs-pro/v1 namespaces. Any external application, mobile app, or headless frontend can consume the full API.

See REST API Reference and Pro REST API Reference.

WP-CLI

8 CLI commands for automation, migration, and maintenance:

wp mvs stats              # Show media stats
wp mvs migrate            # Run database migrations
wp mvs reindex            # Rebuild media index
wp mvs cache-flush        # Flush all caches
wp mvs prune-views        # Clean old view records
wp mvs cleanup-expired    # Remove expired stories/tokens
wp mvs moderation-stats   # Show moderation queue stats
wp mvs import-rtmedia     # Import from rtMedia (Pro)

See WP-CLI Commands.

Gutenberg Blocks

5 blocks for the block editor:

Block Description
Media Grid Display a filtered grid of media items
Explore Feed Full explore page with search, tags, and pagination
Album Viewer Display an album's contents
Lock Overlay Restrict content visibility with a paywall-style overlay
Profile Edit Inline profile editing block

See Gutenberg Blocks.

Shortcodes

For classic editor and page builders:

Shortcode Description
[mvs_gallery] Media grid with filtering
[mvs_upload] Upload form
[mvs_dashboard] User's media dashboard
[mvs_profile] User profile card

See Shortcodes.

What's New in 1.5.0

WPMediaVerse 1.5.0 closes two privacy and access bugs around non-public media, heals video posters that were stored with the wrong path before this version, and unifies the upload-and-serve pipeline so the underlying bug pattern cannot recur. There are no new customer-facing features; this is a correctness and robustness release.

Included in Free. Every item on this page applies to the free version. Pro picks up these fixes automatically because Pro builds on top of free - Pro 1.5.0 itself ships no code changes, it is a lockstep version bump. Install both updates together when running Pro.

Non-public uploads render their own thumbnails

Members, Friends, Only Me, Group, and Custom-access media no longer return a 403 for their own thumbnails after upload. The owner, and any viewer who has been granted access, now sees the thumbnail correctly - on local storage and on every cloud driver (BunnyCDN, S3, R2, Spaces).

Private uploads leave zero public footprint

Private media is now fully invisible to everyone except its owner:

  • No BuddyPress activity entry is created for a private upload.
  • The profile activity tab no longer surfaces broken thumbnail cards for private items.
  • The profile media tab badge no longer counts private items when someone else is viewing.
  • The Explore grid stays clean of private media.

Other non-public levels (Members, Friends, Group, Custom) keep their normal audience-discovery behavior - this change is specifically about media marked Private.

Legacy video posters heal automatically

Videos and audio uploaded before 1.5.0 could store a poster path pointing at the wrong subdirectory, which showed up as a broken still frame on cards, in the lightbox, and in feed previews. Database migration v15 runs once on the first page load after updating and re-derives the correct poster path from the on-disk file location. It is idempotent and safe to let run.

For cloud-storage sites, the migration is cloud-aware: it respects CDN-authoritative paths so existing cloud video posters keep resolving. If a poster still looks broken after the update, run wp mvs cloud-thumbs-backfill to push any local-only poster variants to the active cloud driver.

One unified read path for media URLs

Theme overrides and shortcode users can now call the same Core\MediaUrl helper that the plugin's own templates use, so custom integrations no longer have to know anything about the signed-URL plumbing. See Template Overrides for how to use it from a theme.

One unified upload pipeline

Image variants, video posters, audio cover art, and WebP/AVIF siblings now all flow through a single writer, so the file layout is identical for every media type. WebP and AVIF generation in particular was collapsed into one shared publisher, removing a duplicate-write footgun where the two paths could disagree about the destination directory. Adding a new output format in the future is now one extension point instead of five.

Thumbnails embedded in long-lived surfaces (notification emails, RSS) are minted with a one-hour access TTL. Sites that cache those surfaces at the CDN for longer can widen the window with the new mvs_broadcast_thumbnail_ttl filter.

Under the hood

The upload and read pipeline is now backed by five focused services - MediaUrl, VariantSpec, StorageRouter, MediaVariantWriter, and PosterService. Existing methods are kept as shims for at least two releases per the deprecation policy, so custom code calling the old paths keeps working.

Upgrade notes

  • Update Free to 1.5.0 first, then Pro to 1.5.0 if you run it.
  • Migrator v15 runs automatically on the first request after the update. No manual action is needed.
  • Cloud-storage sites with pre-1.5.0 video uploads: if any poster still appears broken after the update, run wp mvs cloud-thumbs-backfill.

What's New in 1.4.0

WPMediaVerse 1.4.0 is a robustness release that hardens cloud storage, gives site owners precise control over AI spend, and finishes a full mobile and translation pass. This page also folds in the headline additions from 1.3.0 (the image optimization pipeline) so you have a single place to catch up.

Included in Free. Every item on this page applies to the free version unless marked Pro. Pro picks up these improvements automatically because Pro builds on top of free.

Storage that follows the media, not a toggle

Each media item is now served from where it is actually stored, combined with its privacy. Switching the active storage driver, or turning a cloud service on, no longer breaks media you uploaded earlier. Public media stored on cloud serves directly from the CDN. Local media serves through the plugin's own delivery route. There is nothing to toggle: the old "Serve public cloud media directly" checkbox has been retired because direct CDN serving for public media is now automatic. See Cloud Storage for the full behavior.

Private media stays on local disk (Pro)

Only public media is eligible for the cloud. Private and restricted uploads, including their thumbnails and generated variants, never leave your local disk. Each item is effectively "cloud or local" based on its own privacy, so a private photo is never copied to a CDN.

Tighter access checks on private media

The delivery route re-verifies the viewer's permission on every request for non-public media, so a shared link cannot be used to leak a private item. If a Cloudflare R2 bucket has no public domain configured, WPMediaVerse will not emit a raw bucket URL (those are never public) and falls back to serving locally.

Per-feature control over AI

AI is opt-in and off by default, and you now choose exactly which AI features run and what they cost:

  • Generate Descriptions and Generate Tags are independent toggles under Auto-Analyze, so you can run one without the other.
  • The AI Provider dropdown correctly switches between OpenAI, Google Vision, and AWS Rekognition (Pro). Earlier builds could silently fall back to OpenAI.
  • The Monthly AI Budget now caps moderation calls too, not just analysis and tagging. A fresh install ships with a conservative $10 cap so AI never runs against an unbounded bill before you set a budget.

See AI & Moderation Settings for the full matrix.

Image optimization pipeline (added in 1.3.0)

Uploads are now optimized losslessly: file sizes shrink with no visible quality loss, and an original is never made larger (if there is no gain, the original is kept). Animated GIFs are detected and left untouched. WebP copies are generated by default and AVIF copies are available as an opt-in, and browsers that support those formats receive the smaller file automatically across the explore grid, BuddyPress activity, dashboard, single media, and the lightbox. The All Media admin screen gains an Optimization column with savings badges and per-row Optimize and Details actions. See Image Optimization.

Reliable moderation queue actions

The per-row Approve and Reject buttons in the Moderation Queue work correctly again. They were previously dead because of nested forms in the markup.

100% mobile ready

Every customer-facing surface was verified at a 390px viewport. Explore tag chips, the upload privacy selector, and the lightbox close and gallery navigation controls were all raised to the 44px touch target floor, and the layouts produce zero horizontal overflow on a phone.

100% translation ready

Every user-facing string across the chat and messaging templates is wrapped for translation, the BuddyPress activity media script is bridged for JavaScript translations, and the translation template files were regenerated. The plugin is ready for full localization out of the box.

What's New in 1.2.0

WPMediaVerse 1.2.0 is a UX completeness release focused on filling gaps surfaced by real-world use. Twelve customer-visible improvements ship together, plus a full WCAG 2.1 AA accessibility pass.

Included in Free - Every item on this page applies to the free version. Pro picks up these improvements automatically because Pro builds on top of free.

Member Photos block + shortcode

A new mvs/member-photos Gutenberg block and matching [mvs_member_photos] shortcode render any member's media grid. The block auto-detects the right user - explicit userId attribute first, then the BuddyPress displayed user, then the post author, then the current user - so the same block can be dropped into a profile template, a member-specific page, or an author archive without any wiring.

PDF Viewer block + shortcode

A new mvs/pdf-viewer block and [mvs_pdf_viewer] shortcode embed PDFs using the browser-native viewer (the #view=FitH URL fragment). Configurable height, optional toolbar, five distinct empty states (no media, wrong type, missing file, access denied, generic). No third-party JS, no plugins to install, no licensing fuss.

Sort options on Media Grid

The Media Grid block and [mvs_gallery] shortcode now support four new sort orders - Most Popular, Most Viewed, Most Reactions, and Random - alongside the existing Date and Title sorts. A new direction toggle (ascending / descending) and a per-author filter (userId) make the grid usable as a "Top media by this member" surface.

Search autocomplete on Explore

The Explore page (and [mvs_explore_feed] shortcode) now shows a type-ahead dropdown as you type in the search box - top eight title matches, debounced, with full keyboard navigation (Arrow keys, Enter, Escape) and ARIA combobox semantics for screen reader users.

The lightbox toolbar gains two new buttons - Download (writes to media stats; gated by the new global Allow Downloads toggle) and Fullscreen (also bound to the F keyboard shortcut). The Share button's third-popup window.prompt() fallback is gone; it's now navigator.share → clipboard copy → toast on failure.

Per-media Edit modal

The settings cog on dashboard cards opens a prefilled edit modal - change title, description, privacy level, and per-media Allow Downloads in place. No more clicking through to a separate edit screen for a quick rename.

Open Graph + Twitter Card meta

/media/{slug}/ URLs now emit og:* and twitter:card=summary_large_image meta tags. Paste a media URL into Slack, Twitter, LinkedIn, or Discord and it unfurls with title, description, and the cover image - no extra plugin, no SEO setup.

Upload modal polish

Preview tiles in the upload modal now show the filename (truncated tastefully) and a per-tile remove (×) button. Audio files get a proper audio-fallback icon instead of a broken thumbnail. The tags input gains a row of eight popular tag pills below it - click to append, no duplicates.

Bulk Actions in admin All Media

The Media > All Media admin screen now supports multi-select. Tick the rows you want, pick Move to Trash / Restore / Delete permanently from the bulk-actions dropdown, and confirm. Context-aware - the available actions change based on whether you're viewing trashed or live media.

Chat panel visibility setting

A new Chat Panel Visibility dropdown under Media > Settings > Social lets you scope where the floating chat panel renders: Everywhere (default), WPMediaVerse pages only, BuddyPress pages only, or Disabled. For code-level overrides there's the mvs_should_render_chat_panel filter.

Global Allow Downloads toggle

A single switch under Media > Settings > Display hides the lightbox Download button site-wide and refuses the download REST endpoint. Pair with the per-media Allow Downloads checkbox in the Edit modal for fine-grained control.

WCAG 2.1 AA pass

Every interactive surface added or touched in 1.2.0 was audited against WCAG 2.1 AA - aria-label (sentence-form) on icon buttons, aria-pressed on toggles, :focus-visible outlines on every focusable element, role="tablist" + role="tab" on segmented toggles, decorative emoji marked aria-hidden. Keyboard navigation works everywhere mouse navigation works.

Settings

Configure every aspect of your media platform.

General Settings

Access these settings at Media > Settings > General.

General settings tab

General Section

Option Default Description
Max Upload Size 100 MB Maximum file size per upload. Enter value in MB. The plugin reads this setting server-side - WordPress's upload_max_filesize PHP ini value also applies.
Allowed File Types image/jpeg, image/png, image/gif, image/webp, video/mp4, video/webm, audio/mpeg, audio/ogg Comma-separated list of allowed MIME types.
Default Privacy Level Public The privacy level assigned to new uploads when the user does not choose one. Options: Public, Members Only, Private.
Duplicate Detection Warn (allow upload) What to do when a user uploads a file with a SHA-256 hash matching an existing media item. Options: Warn (allow upload), Skip (reject duplicate), Allow (no check).
Strip EXIF Data Enabled When enabled, removes GPS coordinates and device information from uploaded JPEG images before storing them.

Storage Section

Option Default Description
Storage Driver Local (WordPress uploads) Where uploaded files are stored. Local stores files in wp-content/uploads/wpmediaverse/YYYY/MM/. Amazon S3 and BunnyCDN require WPMediaVerse Pro.
Signed URL Expiry 3600 (1 hour) How long temporary signed URLs remain valid for private media files. Set in seconds.

Changing the storage driver only affects new uploads. Existing files remain in their original storage location. A notice appears after saving if the driver is changed.

Pages Section

Assign existing WordPress pages to WPMediaVerse page roles. WPMediaVerse uses these assignments to generate links in the navigation, chat panel, and notification emails.

Option Option Key Description
Dashboard Page mvs_page_dashboard The main logged-in user dashboard. Displays the follow feed, quick upload, and activity summary.
Explore Page mvs_page_explore The public media browse archive. Used as the landing page for non-logged-in visitors.
Upload Page mvs_page_upload The dedicated upload form page. Linked from the dashboard and the navigation bar.

Create a standard WordPress page for each role, then select it from the corresponding dropdown. Each page should contain only the matching WPMediaVerse shortcode ([mvs_dashboard], [mvs_explore], [mvs_upload]) with no other content.

If a page assignment is empty, WPMediaVerse falls back to the site home URL for that link. Set all three pages to avoid broken navigation.

Display Settings

Access these settings at Media > Settings > Display.

Display settings tab showing grid and thumbnail options

Media Display Section

Option Default Description
Grid Columns 3 Number of columns in the media grid. Applies to [mvs_gallery], the Media Grid block, and the explore archive. Options: 2, 3, 4 columns.
Items Per Page 12 Number of media items loaded per page. Options: 12, 24, 48.
Thumbnail Style Square (cropped) Controls the aspect ratio of grid thumbnails. Square crops images to a 1:1 ratio. Original proportions preserves the file's native aspect ratio.
Allow Downloads On Master toggle for the lightbox Download button. When off, the button is hidden site-wide and the /mvs/v1/media/{id}/download REST endpoint refuses requests. Per-media Allow Downloads (set in the per-media Edit modal) is still honoured when this master toggle is on.

The full-screen lightbox includes a toolbar with three quick-action buttons.

Button Behaviour
Download Streams the original file to the user and increments the mvs_media_stats.downloads counter. Hidden when Allow Downloads is off (either site-wide or per-media). Rate-limited to 30 requests per minute per user.
Fullscreen Expands the lightbox to fill the viewport using the browser Fullscreen API. Also bound to the F keyboard shortcut.
Share Uses navigator.share where supported, falls back to copying the URL to the clipboard, falls back to a toast error. No window.prompt() fallback.

All three buttons carry aria-label text and :focus-visible outlines for keyboard users.

Lightbox with toolbar showing download, fullscreen, share, and reaction controls

How Display Settings Interact with Shortcodes

The [mvs_gallery] shortcode deliberately uses the backend settings for Grid Columns and Items Per Page rather than letting shortcode attributes override them. This ensures consistent display across your site.

The [mvs_album] shortcode allows you to override the column count directly:

[mvs_album id="123" columns="4"]

Thumbnail Generation

WPMediaVerse generates thumbnails for uploaded images using WordPress's built-in image editor. Thumbnails are stored alongside the original file in the wpmediaverse/YYYY/MM/ upload directory.

For video files, a placeholder thumbnail is displayed.

WPMediaVerse Pro: Additional Display Options

WPMediaVerse Pro adds a Watermark section to this tab, allowing you to overlay a text or image watermark on downloaded media files. The free version includes a basic watermark option under the General tab.

Permissions

Access these settings at Media > Settings > Permissions.

Permissions tab showing role-capability matrix

Custom Capabilities

WPMediaVerse registers the following custom WordPress capabilities:

Capability Description
upload_mvs_media Upload new media files
edit_mvs_media Edit own media posts
edit_others_mvs_media Edit media posts created by other users
delete_mvs_media Delete own media posts
delete_others_mvs_media Delete media posts created by other users
moderate_mvs_media Access the moderation queue and approve/reject media
manage_mvs_settings Access the WPMediaVerse settings page
manage_mvs_access Manage custom access grants for private media
read_mvs_media View media (used for private media visibility checks)
publish_mvs_media Publish media posts immediately (without pending review)

Default Role Assignments

Role upload edit own delete own moderate manage settings
Administrator Yes Yes Yes Yes Yes
Editor Yes Yes Yes Yes No
Author Yes Yes Yes No No
Contributor No No No No No
Subscriber Yes Yes Yes No No

Changing Permissions

  1. Go to Media > Settings > Permissions.
  2. Check or uncheck capabilities for each role.
  3. Click Save Permissions.

The page shows how many roles were updated. If no changes were needed, it shows "No changes were needed."

Granting Capabilities Programmatically

You can grant or revoke capabilities in your theme's functions.php or a custom plugin:

// Grant moderation capability to a custom role.
$role = get_role( 'shop_manager' );
if ( $role ) {
    $role->add_cap( 'moderate_mvs_media' );
    $role->add_cap( 'manage_mvs_access' );
}

Important Notes

  • The moderate_mvs_media capability grants access to the moderation queue and the ability to see all media regardless of privacy level.
  • The manage_mvs_settings capability is required to access any WPMediaVerse admin page.
  • Capabilities are stored in the WordPress wp_user_roles option and persist after plugin deactivation.

AI & Moderation Settings

Access these settings at Media > Settings > AI & Moderation.

AI and Moderation settings tab

AI Features Section

AI is opt-in and off by default. Nothing calls an AI provider until you (1) supply an API key and (2) turn on at least one of the toggles below. There is no separate "enable AI" master switch because a missing key already disables every AI feature - you stay in full control of what runs and what it costs.

Option Default Description
AI Provider OpenAI (GPT-4 Vision) The AI service used for image analysis, tagging, and moderation. Free version: OpenAI only. Pro adds Google Vision and AWS Rekognition (selectable from this dropdown).
OpenAI API Key (empty) Your OpenAI API key. You can also define MVS_OPENAI_API_KEY in wp-config.php instead.
OpenAI Model GPT-4o Mini Model used for analysis calls. GPT-4o Mini is cheaper; GPT-4o provides higher quality results.
Auto-Analyze Uploads Off Master switch for the two per-feature toggles below. When off, neither descriptions nor tags are generated on upload.
Generate Descriptions On When Auto-Analyze is on, use AI to generate a description / alt text for each upload. Turn off to skip description calls and only generate tags.
Generate Tags On When Auto-Analyze is on, use AI to suggest tags for each upload. Turn off to skip tag-suggestion calls.
Auto-Apply Tags Off When enabled, AI-suggested tags are automatically assigned to the mvs_tag taxonomy. Requires Generate Tags to be on.
Auto-Moderate Uploads Off When enabled, each new upload is checked for policy violations. The action taken depends on the When AI Flags Content setting below.
Monthly AI Budget ($) $10 Hard cap on AI spend per calendar month, covering analysis, tagging and moderation calls. When the cap is reached, all AI calls stop until the next month. Set to 0 for unlimited spend - recommended only after you configure a billing alert on the provider account itself.
Estimated Cost per Call ($) $0.01 Used for budget tracking. Adjust based on your actual API pricing.

Choosing exactly which AI features run

Each toggle is independent so a site owner enables only what they want to pay for:

  • Descriptions only - Auto-Analyze on, Generate Descriptions on, Generate Tags off.
  • Tags only - Auto-Analyze on, Generate Descriptions off, Generate Tags on (add Auto-Apply Tags to write them to the taxonomy automatically).
  • Moderation only - leave Auto-Analyze off and turn on Auto-Moderate; uploads are scanned for policy violations without generating descriptions or tags.

The budget cap applies to all of the above, so moderation calls also stop once the monthly cap is hit.

Setting the API Key via wp-config.php

// wp-config.php
define( 'MVS_OPENAI_API_KEY', 'sk-your-key-here' );

When this constant is defined, the settings page field is disabled and shows a notice.

Moderation Section

Option Default Description
When AI Flags Content Flag for review What happens when AI detects a policy violation. Options: Flag for review (keeps media visible but adds it to the moderation queue), Hide (sets media to private), Reject (moves media to draft).
Auto-Hide Threshold 3 Number of user reports required to automatically hide a media item. The media is set to private and added to the moderation queue. Set to 0 to disable automatic hiding.

Moderation Queue

Administrators with the moderate_mvs_media capability can review flagged media at Media > Moderation Queue.

Moderation queue with pending media items

The queue shows:

  • Media flagged by AI
  • Media that reached the auto-hide threshold from user reports
  • Media manually flagged by moderators

Log Viewer

The AI & moderation activity log is available at Media > AI Logs. It shows each AI call, the result, estimated cost, and any action taken.

AI log viewer showing analysis results

Budget Alerts

When monthly AI spend reaches 80% of your budget, WPMediaVerse adds an admin notice. When the budget is fully consumed, all AI calls - analysis, tagging, and moderation - are suspended and a warning appears on the settings page. Because a fresh install ships with a conservative $10 default cap, AI never silently runs against an unbounded bill before you have chosen a budget.

Webhooks

Access these settings at Media > Settings > Webhooks.

Webhooks allow WPMediaVerse to send signed HTTP POST notifications to external services when media events occur.

Webhooks settings tab showing webhook URL list

Supported Events

Event Triggered When
media.uploaded A new media file is successfully uploaded
media.updated An existing media post is saved/updated
media.deleted A media post is permanently deleted
media.moderated A media item's moderation status changes (approved, flagged, rejected)
media.reaction A user adds a reaction to a media item
media.comment A user posts a comment on a media item

Adding a Webhook

  1. Go to Media > Settings > Webhooks.
  2. Click Add Webhook.
  3. Enter the URL that should receive the POST request.
  4. Select which events should trigger this webhook.
  5. Optionally enter a secret key for signature verification.
  6. Click Save Webhooks.

Add webhook form with URL field and event checkboxes

Payload Format

All webhook payloads are sent as JSON via HTTP POST with these headers:

Content-Type: application/json
X-WPMediaVerse-Event: media.uploaded
X-WPMediaVerse-Signature: sha256=HMAC_SIGNATURE

Example: media.uploaded Payload

{
  "event": "media.uploaded",
  "timestamp": "2025-03-27T12:00:00Z",
  "site_url": "https://yoursite.com",
  "data": {
    "media_id": 123,
    "title": "My Photo",
    "media_type": "image",
    "file_url": "https://yoursite.com/wp-content/uploads/wpmediaverse/2025/03/photo.webp",
    "privacy": "public",
    "author_id": 1,
    "created_at": "2025-03-27T12:00:00Z"
  }
}

Verifying Webhook Signatures

Use the secret key you configured to verify that requests are from WPMediaVerse:

$payload   = file_get_contents( 'php://input' );
$signature = $_SERVER['HTTP_X_WPMEDIAVERSE_SIGNATURE'] ?? '';
$secret    = 'your-webhook-secret';

$expected = 'sha256=' . hash_hmac( 'sha256', $payload, $secret );

if ( ! hash_equals( $expected, $signature ) ) {
    http_response_code( 401 );
    exit( 'Invalid signature' );
}

Retry Behavior

Failed webhook deliveries (non-2xx HTTP response or connection timeout) are retried up to 3 times with exponential backoff. Failed attempts are logged in the AI/webhook log visible at Media > AI Logs.

Social & Messaging Settings

Access these settings at Media > Settings > Social.

Social and Messaging settings tab

These settings control who can send direct messages and online presence visibility.


Direct Messages

Option Key Default Description
Who Can Send DMs mvs_dm_access everyone Controls who is allowed to send direct messages to other users on the site. Options: everyone, followers (people the recipient follows back), nobody.
Minimum Account Age (days) mvs_dm_min_age 0 Number of days a user account must exist before that user can initiate new conversations. Set to 0 to disable the restriction.
Online Status Visibility mvs_show_online_status everyone Who can see when a user is currently online in the messaging interface. Options: everyone, followers, nobody.
Chat Panel Visibility mvs_chat_panel_visibility everywhere Where the floating chat panel renders. Options: everywhere, mvs_pages (WPMediaVerse pages only - Explore, Dashboard, Upload, single media), bp_pages (BuddyPress pages only), disabled. For code-level overrides, hook into the mvs_should_render_chat_panel filter - return false to suppress the panel on a specific request.

Setting Who Can Send DMs here applies site-wide. Users cannot override this individually at this time.


Defining Options via wp-config.php

You can lock any Social setting as a constant to prevent admin changes:

// wp-config.php
define( 'MVS_DM_ACCESS', 'followers' );        // Lock DM access to followers only.
define( 'MVS_DM_MIN_AGE', 7 );                 // Require 7-day-old accounts to send DMs.
define( 'MVS_SHOW_ONLINE_STATUS', false );      // Disable online status site-wide.

When a constant is detected, the corresponding field on the settings page is shown as read-only.

Features

Uploads, albums, social features, AI moderation, and more.

Media Upload

Free + Pro - Core functionality is included free. Features marked with (Pro) require WPMediaVerse Pro.

Share photos, videos, and audio with your community - no admin access needed, straight from any page on your site.

What You Can Do

  • Drag and drop files directly onto the upload zone
  • Paste an image from your clipboard to upload instantly
  • Select multiple files at once for bulk upload
  • Set a title, description, and tags for each file before submitting
  • Choose who can see each file: public, members only, friends, or private
  • Upload JPEG, PNG, GIF, WebP images; MP4 and WebM videos; MP3 and OGG audio

How It Works (for Users)

  1. Go to the upload page your site administrator set up (often /upload/ or /media/upload/)
  2. Drag your files onto the upload zone, or click Browse to pick files from your device
  3. To paste a screenshot or copied image, click inside the upload zone and press Ctrl+V (Cmd+V on Mac)
  4. For each file, enter a title and optional tags. Select a privacy level from the dropdown
  5. Click Upload - a progress bar shows each file uploading
  6. When finished, your files appear in your media dashboard and your profile page
  7. Find all your uploads anytime under My Media or your profile's Media tab

Frontend upload form with drag-and-drop zone and privacy dropdown

For Site Owners

  1. Go to Media > Settings > General and confirm the allowed file types match what your community needs
  2. Add the upload form to any page: in the block editor, insert the WPMediaVerse: Media Upload block; in the classic editor, add [mvs_upload]
  3. Set the default privacy level and maximum file size for your site
  4. Enable Strip EXIF Data (on by default) to automatically remove GPS coordinates from photos before storage
  5. Users see the upload form immediately on that page when logged in

Supported File Formats

Type Formats
Images JPEG, PNG, GIF, WebP
Video MP4, WebM
Audio MP3 (MPEG), OGG

Customize allowed types in Media > Settings > General.

Bulk Album Upload (1.2.0)

When uploading multiple files at once into an album, WPMediaVerse now creates one activity entry for the whole batch ("Varun uploaded 3 photos to album Portrait Series") with a thumbnail grid, instead of one separate activity per file. Single-file uploads and ad-hoc photo posts retain their existing per-post behaviour.

Per-Media Edit Modal (1.2.0)

The settings cog on dashboard cards now opens a prefilled edit modal - change title, description, privacy level, and per-media Allow Downloads in place. Save updates the row live without a page reload. The matching PUT /mvs/v1/media/{id} REST endpoint accepts an allow_download boolean.

When you change the title, the URL slug stays stable by default. Tick the new Update URL slug checkbox if you want the slug regenerated from the new title - the page redirects to the new URL automatically so existing tabs and bookmarks don't 404.

What Happens After Upload

  • Thumbnails are generated automatically for images and videos (videos: ffmpeg poster extraction; run wp mvs generate-video-thumbnails to backfill any uploaded before 1.2.0)
  • EXIF GPS data is stripped from JPEG files (other metadata like camera model is kept)
  • A SHA-256 hash is computed to detect duplicate files
  • If AI moderation is enabled, the file is queued for automatic review
  • BuddyPress activity is recorded if BuddyPress is active on your site

Adding the Upload Form

Gutenberg Block: Add the WPMediaVerse: Media Upload block to any page or post.

Shortcode:

[mvs_upload]
[mvs_upload max_files="5" show_privacy="true"]
Attribute Default Description
max_files 10 Maximum number of files the user can upload at once
show_privacy true Whether to show the privacy level selector

Upload Validation

Every upload goes through these checks in order:

  1. MIME type check - file content is inspected (not just the extension) against your allowed types list.
  2. File size check - measured server-side against mvs_max_upload_size (default 100 MB).
  3. Extension block list - blocks PHP, shell, and other executable extensions even if the MIME passes.
  4. Double extension block - rejects filenames like photo.php.jpg.
  5. Duplicate detection - computes a SHA-256 hash and compares against existing uploads. Behavior depends on your Duplicate Detection setting (warn, skip, or allow).

EXIF Stripping

When Strip EXIF Data is enabled (default), GPS coordinates and device metadata are removed from JPEG images before storage. Non-GPS EXIF data (camera model, focal length) is retained.

Storage Path

Files are stored at:

wp-content/uploads/wpmediaverse/YYYY/MM/filename.ext

WordPress's wp_unique_filename() prevents filename collisions.

Programmatic Upload

Use the UploadService to upload files from code:

$upload_service = WPMediaVerse\Core\Plugin::container()->get( 'upload' );

$result = $upload_service->handle(
    $_FILES['my_file'],
    get_current_user_id(),
    array(
        'title'       => 'My File',
        'privacy'     => 'public',
        'description' => 'Optional description',
    )
);

if ( is_wp_error( $result ) ) {
    // Handle error.
} else {
    $media_id = $result; // int - new mvs_media post ID.
}

Actions Fired During Upload

  • mvs_before_media_insert - fires before the mvs_media post is created.
  • mvs_before_upload_form - fires before the upload form HTML is rendered (used by Pro for quota display).
  • mvs_media_uploaded - fires after the post is created and indexed. Passes $media_id (int).

Media Post Meta

Each mvs_media post stores:

Meta Key Type Description
_mvs_file_url string Public URL of the stored file
_mvs_file_path string Relative storage path
_mvs_media_type string image, video, or audio
_mvs_file_type string Full MIME type (e.g., image/jpeg)
_mvs_file_size int File size in bytes
_mvs_privacy string Privacy level (see Privacy & Access Control)
_mvs_sha256 string SHA-256 hash for duplicate detection
_mvs_group_id int BuddyPress group ID (for group privacy)

Albums

Included in Free - This feature is available in the free version of WPMediaVerse.

Group your photos into beautiful collections - tell a story, document a trip, or organize your portfolio with a single shareable album.

What You Can Do

  • Create albums to organize related photos and videos together
  • Add photos from your existing media library to any album
  • Set a cover photo that represents the album
  • Control album privacy independently from individual photo privacy
  • Share albums with friends or keep them private
  • Embed any album on a page using a block or shortcode

How It Works (for Users)

  1. Go to your media dashboard and click Create Album
  2. Give your album a title and optional description
  3. Choose a privacy level: public, members only, friends, or private
  4. Click Add Media to pick photos from your uploads - select as many as you like
  5. Drag photos in the album to reorder them. Click the star on any photo to set it as the cover
  6. Click Save Album - your album is live and appears on your profile
  7. To share your album, copy the album link from the album page and send it to anyone

Album creation form with title field and privacy selector

For Site Owners

  1. Albums are enabled by default once WPMediaVerse is activated
  2. To embed a specific album on a page, use the WPMediaVerse: Album Viewer block in the block editor - select the album from the block sidebar
  3. Or use the shortcode [mvs_album id="123"] where 123 is the album's post ID
  4. Users manage their own albums from their media dashboard
  5. Admins can view and delete any album from Media > Albums in wp-admin

Album Privacy

Albums have their own privacy level independent of the media items they contain. If a user can see the album but not a specific media item (because the item's privacy is more restrictive), that item is hidden from the album view.

Displaying an Album

Gutenberg Block: Add the WPMediaVerse: Album Viewer block, then select an album from the block settings.

Shortcode:

[mvs_album id="123"]
[mvs_album id="123" columns="4" show_title="true" show_description="true"]
Attribute Default Description
id (required) Album post ID
columns 3 Grid columns to display
show_title true Show the album title above the grid
show_description true Show the album description

REST API Endpoints for Albums

Method Endpoint Description
GET /mvs/v1/albums List albums
POST /mvs/v1/albums Create album
GET /mvs/v1/albums/{id} Get album
PUT /mvs/v1/albums/{id} Update album
DELETE /mvs/v1/albums/{id} Delete album
GET /mvs/v1/albums/{id}/items List album media
POST /mvs/v1/albums/{id}/items Add media to album
DELETE /mvs/v1/albums/{id}/items/{media_id} Remove media from album

Creating an Album via API

curl -X POST https://yoursite.com/wp-json/mvs/v1/albums \
  -H "X-WP-Nonce: YOUR_NONCE" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Summer 2025",
    "description": "Photos from our trip",
    "privacy": "public"
  }'

Adding Media to Albums via API

curl -X POST https://yoursite.com/wp-json/mvs/v1/albums/ALBUM_ID/items \
  -H "X-WP-Nonce: YOUR_NONCE" \
  -H "Content-Type: application/json" \
  -d '{"media_ids": [101, 102, 103]}'

This fires the mvs_album_items_added action, which triggers BuddyPress activity updates if BuddyPress is active.

BuddyPress Activity

When media is added to an album and BuddyPress is active, the mvs_album_items_added action updates the upload activity item to reference the album. This replaces the generic "uploaded media" activity with "uploaded media to [Album Name]".

Collections

Included in Free - This feature is available in the free version of WPMediaVerse.

Save and curate media from anyone on your site into personal boards - like Pinterest boards, but for your community's photos and videos.

What You Can Do

  • Save any public media item to a personal collection with one click
  • Create multiple collections for different themes or moods (e.g., "Travel Inspiration", "Black and White")
  • Curate manually by hand-picking individual photos, or let smart rules auto-fill a collection
  • Smart collections stay fresh automatically - tag a rule once and the collection updates itself
  • Share collections publicly or keep them private
  • Browse your saved collections from your media dashboard

How It Works (for Users)

  1. When you find a photo you love, click the Save button (bookmark icon) below it
  2. Choose an existing collection from the dropdown, or click New Collection to create one
  3. Give your new collection a name and choose a privacy level, then click Create
  4. The photo is added instantly - you'll see the bookmark icon turn solid to confirm
  5. Find all your collections under My Media > Collections in your dashboard
  6. To manage a collection, open it and click Edit to rename it, reorder items, or remove ones you no longer want
  7. To share a collection, copy the link from the collection page

Media item with bookmark/save button and collection picker

For Site Owners

  1. Collections are available to all users with upload access once WPMediaVerse is activated
  2. To embed a collection on any page, use [mvs_collection id="456"] or the WPMediaVerse: Collection Viewer block
  3. Smart collections are especially useful for curated showcase pages - create a smart collection filtered by a tag and embed it on your homepage
  4. Manage all collections from Media > Collections in wp-admin
  5. Use the Collection Settings meta box on any collection post to switch between manual and smart mode and configure smart rules

Collection Types

Type Description
Manual You add specific media items by hand. Items are stored in the wp_mvs_favorites table with the collection ID.
Smart Rules-based. The collection resolves its item list dynamically at request time based on criteria like tag, category, media type, or date range.

Displaying a Collection

Shortcode:

[mvs_collection id="456"]
[mvs_collection id="456" columns="3" per_page="20"]
Attribute Default Description
id (required) Collection post ID
columns 3 Grid columns to display
per_page 20 Maximum number of media items to show

If no id is provided, the shortcode outputs an error message. If the collection post is not published, the shortcode shows "Collection not found."

REST API Endpoints for Collections

Method Endpoint Description
GET /mvs/v1/collections List collections
POST /mvs/v1/collections Create collection
GET /mvs/v1/collections/{id} Get collection with resolved items
PUT /mvs/v1/collections/{id} Update collection
DELETE /mvs/v1/collections/{id} Delete collection

Creating a Smart Collection via API

curl -X POST https://yoursite.com/wp-json/mvs/v1/collections \
  -H "X-WP-Nonce: YOUR_NONCE" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Nature Photos",
    "type": "smart",
    "rules": {
      "tags": ["nature", "landscape"],
      "media_type": "image"
    },
    "privacy": "public"
  }'

Collection Meta Box

In the WordPress admin, each mvs_collection post has a Collection Settings meta box that lets you set the collection type and define smart rules without using the API.

Collection meta box in WordPress admin

Stories (Coming Soon)

Planned Feature - Stories infrastructure exists in the codebase but is not yet available as a user-facing feature.

Current Status

The backend StoryService is built and can mark any media item as a time-limited story with automatic expiration. However, there is currently:

  • No "Post as Story" option in the upload form
  • No dedicated REST endpoint for creating stories
  • No story viewer UI for browsing stories

What Works Today

Instagram Layout - Recent Uploaders Bar

When the Instagram layout is active (Pro), the explore page shows a horizontal bar of circular avatars above the feed. This displays users who uploaded recently - similar to the Instagram stories tray visual style, but these are links to user profiles, not ephemeral story content.

Instagram layout with story-style avatar bar

Backend Service

The StoryService class provides:

  • create( $media_id, $duration_hours ) - mark media as a story with expiration
  • is_active( $media_id ) - check if a story is still live
  • cleanup_expired() - hourly cron removes expired story flags
  • Default duration: 24 hours

Meta Keys

Key Value Description
is_story 1 Media is marked as a story
story_expires_at 2026-04-01 12:00:00 UTC expiration datetime

Hooks

Hook When Parameters
mvs_story_created Media marked as story $media_id, $expires_at
mvs_story_expired Story auto-expired by cleanup cron $media_id

Planned Features

The following are planned for a future release:

  • "Post as Story" toggle in the upload modal
  • Full-screen story viewer with tap-to-advance navigation
  • Story highlight reels on user profiles
  • Story reactions and reply-to-story DMs
  • REST API endpoints for story CRUD

Social Features

Included in Free - This feature is available in the free version of WPMediaVerse.

React, comment, follow, and share - WPMediaVerse turns your media site into a living community where every photo starts a conversation.

What You Can Do

  • React to any photo or video with emoji reactions (like, love, wow, and more)
  • Comment on media and edit your comment within 15 minutes of posting
  • Follow photographers you love - their new uploads appear in your feed
  • Save media to your favorites for quick access later
  • @mention other members in comments to notify them directly
  • Share any public media item to social networks or copy a direct link
  • Report inappropriate content directly from the media page

How It Works (for Users)

Following Someone

  1. Visit any member's profile page
  2. Click Follow - their public uploads now appear in your feed
  3. To stop following, click Unfollow on their profile
  4. See everyone you follow under My Media > Following

Reacting to Media

  1. Open any media item
  2. Click the reaction bar below the photo
  3. Choose your reaction - like, love, wow, haha, sad, or angry
  4. Your reaction is shown instantly. Click the same reaction to remove it
  5. Click a different reaction to change yours

Media item with reaction bar showing emoji counts

Commenting

  1. Scroll to the comments section below any media item
  2. Type your comment in the text box and press Enter or click Post
  3. To @mention someone, type @ followed by their username - they receive a notification
  4. To edit your comment, click the pencil icon next to it (available within 15 minutes of posting)
  5. To delete your comment, click the trash icon

Saving to Favorites

  1. Click the heart icon on any media item
  2. The item is added to your favorites list
  3. Find all your favorites under My Media > Favorites in your dashboard
  4. To save to a named collection instead, click the bookmark icon and choose or create a collection

Sharing Media

  1. Open any public media item
  2. Click Share to see options: copy the direct link, or share to social networks
  3. The share link respects the media's privacy - private media returns an error for unauthorized viewers

For Site Owners

  1. All social features are enabled by default
  2. Configure reaction types and moderation in Media > Settings > Social
  3. Set the comment edit window (default: 15 minutes) in Media > Settings > Social
  4. Set the auto-hide threshold: when a media item receives a certain number of reports, it is automatically hidden and sent to the moderation queue
  5. BuddyPress notifications fire automatically for reactions, comments, and mentions when BuddyPress is active

Reactions

WPMediaVerse uses a custom reactions system stored in a dedicated database table, separate from WordPress post meta.

REST API

Method Endpoint Description
GET /mvs/v1/media/{id}/reactions Get reaction counts for a media item
POST /mvs/v1/media/{id}/reactions Add or change your reaction
DELETE /mvs/v1/media/{id}/reactions Remove your reaction

Action: mvs_reaction_added

Fires when a reaction is added. BuddyPress integration uses this to send notifications.

do_action( 'mvs_reaction_added', $media_id, $user_id, $reaction_type );

Comments

WPMediaVerse uses a custom comments system stored in a dedicated table, separate from WordPress's wp_comments.

Comment section below a media item

REST API

Method Endpoint Description
GET /mvs/v1/media/{id}/comments List comments on a media item
POST /mvs/v1/media/{id}/comments Post a new comment
PUT /mvs/v1/media/{id}/comments/{comment_id} Edit a comment
DELETE /mvs/v1/media/{id}/comments/{comment_id} Delete a comment

Action: mvs_comment_created

do_action( 'mvs_comment_created', $comment_id, $media_id, $user_id );

BuddyPress integration uses this to record activity and send notifications.

Favorites

Users can save media items to their favorites or to a named collection.

REST API

Method Endpoint Description
POST /mvs/v1/media/{id}/favorite Add to favorites
DELETE /mvs/v1/media/{id}/favorite Remove from favorites
GET /mvs/v1/favorites List the current user's favorites

Follows

Users can follow other users to see their public media in a feed.

REST API

Method Endpoint Description
POST /mvs/v1/users/{id}/follow Follow a user
DELETE /mvs/v1/users/{id}/follow Unfollow a user
GET /mvs/v1/users/{id}/followers List a user's followers
GET /mvs/v1/users/{id}/following List who a user follows

Mentions

Users can @mention each other in comments. Mentioned users receive a notification.

Action: mvs_mentions_created

do_action( 'mvs_mentions_created', $mentioned_user_ids, $comment_id );

Sharing

The ShareService provides share URL generation for media items. Shareable links respect the media's privacy level - private media returns a 403 when accessed via a share link by an unauthorized user.

Reports

Users can report inappropriate media content.

REST API

Method Endpoint Description
POST /mvs/v1/media/{id}/report Submit a content report

When the number of reports for a single media item reaches the Auto-Hide Threshold (set in AI & Moderation settings), the media is automatically hidden and added to the moderation queue.

Direct Messages

Included in Free - This feature is available in the free version of WPMediaVerse.

Send private messages, share photos, record voice notes, and have real conversations - all without leaving your media community.

What You Can Do

  • Send a private message to any member directly from their profile
  • Attach photos, files, or share a media item from the gallery straight into chat
  • Record and send short voice messages
  • React to individual messages with any emoji
  • See typing indicators and read receipts in real time
  • Mute noisy conversations, pin important ones, or archive old chats
  • Search across all your messages to find anything instantly
  • Control who can message you: everyone, followers only, mutual followers only, or nobody

How It Works (for Users)

Starting a Conversation

  1. Visit any member's profile page
  2. Click Message - the chat panel opens in the bottom-right corner with that conversation ready
  3. Type your message and press Enter to send

To start a conversation without visiting a profile:

  1. Click the chat icon at the bottom of any page
  2. Click the compose icon inside the chat panel
  3. Search for the member by name or username and select them

Sending a Voice Message

  1. Open a conversation
  2. Click the microphone icon in the message bar
  3. Hold to record your message, then release to send
  4. The recipient sees a playable audio clip in the conversation

Sharing a Photo in Chat

  1. Open a conversation
  2. Click the media icon (photo frame) in the message bar
  3. Browse your uploaded media and click any item to share it directly into the chat

Managing Conversations

Action How to do it
Mute Open the conversation menu and click Mute - notifications are suppressed
Pin Click Pin to keep the conversation at the top of your list
Archive Click Archive to hide it from the main list. Find archived chats under the Archive tab
Search Click the search icon in the panel header and type any word or phrase

Message Requests

If a member has restricted who can message them to followers only, your message goes to their Requests tab rather than their main inbox. They can accept or decline the request.

  • Accept - Your conversation moves to their main inbox and messaging continues normally
  • Decline - The request is removed. You are not notified of the decline

Chat panel with message requests

For Site Owners

  1. Go to Media > Settings > Social to configure site-wide DM defaults
  2. Set who can send DMs by default: everyone, followers, mutual followers, or nobody
  3. Set a minimum account age (in days) to prevent new accounts from sending DMs
  4. Users can override their own DM access setting from their account settings
  5. The chat panel appears automatically at the bottom of every page for logged-in users - no shortcode needed
  6. To adjust how often the chat checks for new messages, set the polling interval (default: 3 seconds)

Database Tables

Table Purpose
mvs_conversations One row per conversation, stores metadata (muted, pinned, archived state per participant)
mvs_conversation_participants Maps users to conversations (supports future group chat expansion)
mvs_messages Individual messages with content, type, read status, and soft-delete flag
mvs_message_reactions Emoji reactions attached to individual messages

Opening a Conversation

There are three ways to start or open a conversation:

  • From a profile page - Click the Message button on any user's profile. The chat panel opens with that conversation pre-loaded. No searching required.
  • From the chat panel - Click the compose icon inside the chat panel and search by username or display name.
  • Deep links - Link directly to a conversation or user using a URL fragment:
Fragment Opens
#mvs-chat/{conversationId} A specific conversation by ID
#mvs-chat/user/{userId} The conversation with a specific user (creates one if none exists)

Message button on a user profile card

Chat Panel Features

Feature Description
Text messages Plain text with @mention support
File attachments Attach any file type within the site's allowed MIME list
Media sharing Share a WPMediaVerse media item directly into a conversation
Voice messages Record and send short audio clips
Emoji reactions React to individual messages with any emoji
Typing indicators Shows a live indicator when the other user is typing
Read receipts Delivered and read timestamps shown per message
Message deletion Delete your own messages (content replaced with "This message was deleted")

Chat panel showing a conversation with media share and reactions

Conversation Management

Action How
Mute Suppress notifications for a conversation without leaving it
Pin Keep a conversation at the top of the conversation list
Archive Hide a conversation from the main list; accessible via the Archive tab
Search Full-text search across all your messages via the search icon in the panel header

Privacy Settings

Each user controls their DM privacy from their account settings. Admins set the site-wide defaults at Media > Settings > Social.

Option Key Values
Who can message me mvs_dm_access everyone, followers, mutual, nobody
Minimum account age mvs_dm_min_age Integer (days). Prevents newly registered accounts from sending DMs.
Show online status mvs_show_online_status 1 (visible) or 0 (hidden)

When mvs_dm_access is set to nobody, the Message button is hidden on that user's profile.

Transport

The chat panel polls the REST API on a configurable interval to fetch new messages. The polling interval is set in milliseconds via the mvs_dm_poll_interval option (default: 3000). Define it as a constant to prevent admin overrides:

// wp-config.php
define( 'MVS_DM_POLL_INTERVAL', 2000 );

REST API

Base URL: /wp-json/mvs/v1/

All endpoints require a logged-in user. Pass the X-WP-Nonce header with a nonce from wp_create_nonce( 'wp_rest' ).

POST /conversations

Start a new conversation.

Body:

{ "participant_id": 42 }

Response: 201 Created with the new conversation object, or 200 OK with the existing conversation if one already exists between the two users.


GET /me/conversations

List all conversations for the current user. Excludes archived conversations unless ?include_archived=1 is passed.

Parameters:

Parameter Type Default Description
include_archived int 0 Set to 1 to include archived conversations
per_page int 20 Conversations per page
page int 1 Page number

GET /conversations/{id}/messages

List messages in a conversation. The current user must be a participant.

Parameters:

Parameter Type Default Description
per_page int 30 Messages per page
before int (none) Return messages with ID lower than this value (for pagination)

POST /conversations/{id}/messages

Send a message. The current user must be a participant and must satisfy the recipient's mvs_dm_access setting.

Body (JSON or multipart/form-data for file attachments):

Field Required Description
content No Text content
type No Message type: text (default), file, media, voice
media_id No WPMediaVerse media ID when type=media
file No File upload when type=file or type=voice
parent_id No Message ID to reply to. Creates a threaded reply visible under the parent message.

Response: 201 Created with the new message object.


POST /messages/upload

Upload a file to use as a DM attachment. Returns an attachment token to pass as file in a subsequent POST /conversations/{id}/messages call.

Body: multipart/form-data with a file field.

Response: 200 OK

{ "attachment_token": "att_abc123", "url": "https://yoursite.com/...", "mime_type": "image/jpeg" }

PATCH /conversations/{id}

Update conversation state for the current user.

Body:

Field Type Description
muted bool Set to true to mute, false to unmute
pinned bool Set to true to pin, false to unpin
archived bool Set to true to archive, false to restore

Response: 200 OK with the updated conversation object.


DELETE /conversations/{id}

Delete a conversation for the current user. The conversation remains visible to the other participant.

Response: 204 No Content


DELETE /messages/{id}

Soft-delete a message. Requires ownership of the message. The message record is kept but its content is replaced and is_deleted is set to 1.


DELETE /messages/{id}/unsend

Hard-delete a message. Only available within the edit window (default: 5 minutes after sending). Requires ownership of the message. The message record is permanently removed.

Response: 204 No Content


GET /me/messages/unread-count

Return the total number of unread messages across all conversations for the current user. Used to update the chat panel badge.

Response:

{ "unread_count": 4 }

POST /messages/{id}/reactions

Add or change a reaction on a message.

{ "emoji": "❤️" }

To remove a reaction, call this endpoint again with the same emoji.


Actions and Filters

mvs_dm_before_send

Fires before a message is saved. Use this to validate, block, or transform message content.

add_action( 'mvs_dm_before_send', function( $message_data, $conversation_id, $sender_id ) {
    // Inspect or modify $message_data before it is written.
}, 10, 3 );

mvs_dm_message_sent

Fires after a message is successfully saved.

do_action( 'mvs_dm_message_sent', $message_id, $conversation_id, $sender_id );

mvs_dm_poll_interval

Filter the polling interval in milliseconds delivered to the front end.

add_filter( 'mvs_dm_poll_interval', function( $ms ) {
    return 5000; // Poll every 5 seconds.
} );

Privacy & Access Control

Free + Pro - Core functionality is included free. Features marked with (Pro) require WPMediaVerse Pro.

WPMediaVerse provides 6 privacy levels for media items, albums, and collections. Access checks run on every REST API call and on the explore archive query.

Privacy Levels

Level Value Who Can View
Public public Everyone, including logged-out visitors
Members Only members Any logged-in WordPress user
Friends friends BuddyPress friends of the media owner (requires BuddyPress active)
Group group Members of a specific BuddyPress group (requires BuddyPress active)
Private private Only the media owner and users with moderate_mvs_media
Custom custom A specific list of user IDs defined via access grants

Owners and Moderators Always Have Access

Media owners (the post_author) and users with the moderate_mvs_media capability bypass all privacy checks. They can view all media regardless of its privacy level.

Setting Privacy on Upload

Set the privacy field when creating media via REST API:

curl -X POST https://yoursite.com/wp-json/mvs/v1/media \
  -H "X-WP-Nonce: NONCE" \
  -F "file=@photo.webp" \
  -F "privacy=friends"

For group privacy, also include group_id:

  -F "privacy=group" \
  -F "group_id=42"

Changing Privacy After Upload

curl -X PUT https://yoursite.com/wp-json/mvs/v1/media/123 \
  -H "X-WP-Nonce: NONCE" \
  -H "Content-Type: application/json" \
  -d '{"privacy": "private"}'

Custom Access Grants

For custom privacy, grant access to specific users:

curl -X POST https://yoursite.com/wp-json/mvs/v1/media/123/access \
  -H "X-WP-Nonce: NONCE" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": 55,
    "expires_at": "2026-01-01T00:00:00Z"
  }'

Access grants can have optional expiry dates. Expired grants are cleaned up via wp mvs cleanup-expired or via cron.

Signed URLs for Private Files

For media stored with a non-public privacy level, WPMediaVerse can generate time-limited signed URLs:

curl https://yoursite.com/wp-json/mvs/v1/media/123/signed-url \
  -H "X-WP-Nonce: NONCE"

Response:

{
  "url": "https://yoursite.com/wp-content/uploads/wpmediaverse/2025/03/photo.jpg?token=abc123&expires=1743000000",
  "expires_at": "2025-03-27T13:00:00Z"
}

The signed URL TTL defaults to 3600 seconds (1 hour) and is configurable in Media > Settings > General > Signed URL Expiry.

Filtering Privacy Access in Code

Use the mvs_privacy_can_view filter to extend or override access logic:

add_filter( 'mvs_privacy_can_view', function( $result, $media_id, $user_id, $privacy ) {
    // Grant access to premium subscribers regardless of privacy level.
    if ( null === $result && wcs_user_has_subscription( $user_id, '', 'active' ) ) {
        return true;
    }
    return $result;
}, 10, 4 );

Return null to let the built-in logic run. Return true or false to override it.

Explore Archive Privacy Filtering

On the explore archive (/media/), WPMediaVerse applies automatic privacy filtering via a posts_where filter:

  • Logged-out users see only public media.
  • Logged-in non-moderators see public, members media, and their own media (any privacy level).
  • Moderators (moderate_mvs_media capability) see all media.

AI Moderation

Free + Pro - Core functionality is included free. Features marked with (Pro) require WPMediaVerse Pro.

WPMediaVerse integrates with OpenAI Vision (GPT-4o or GPT-4o Mini) to automatically analyze and moderate uploaded media. WPMediaVerse Pro adds support for Google Vision and AWS Rekognition.

AI moderation result on a media post

How AI Analysis Works

When Auto-Analyze Uploads is enabled, WPMediaVerse sends each uploaded image to OpenAI immediately after storage. The analysis returns:

  • A natural-language description of the image
  • Suggested tags (comma-separated keywords)
  • Safety categories with confidence scores (adult content, violence, etc.)

The description is saved to the mvs_media post's content. If Auto-Apply Tags is enabled, the suggested tags are assigned to the mvs_tag taxonomy.

How AI Moderation Works

When Auto-Moderate Uploads is enabled, WPMediaVerse checks the AI safety scores against configurable thresholds. If a policy violation is detected, the action defined in When AI Flags Content is applied:

Action What Happens
Flag for review Media stays published but appears in the moderation queue
Hide Media's _mvs_privacy is set to private
Reject Media's post_status is set to draft

All moderation actions fire the mvs_media_moderated action hook.

Triggering Analysis Manually

Site administrators can re-analyze any media item from the moderation queue or via the REST API:

curl -X POST https://yoursite.com/wp-json/mvs/v1/moderation/123/analyze \
  -H "X-WP-Nonce: NONCE"

Approving or Rejecting from the Moderation Queue

  1. Go to Media > Moderation Queue.
  2. Review the flagged item and its AI analysis result.
  3. Click Approve to publish or Reject to move to draft.

Moderation queue item with approve and reject buttons

Budget Control

Set a monthly spending cap at Media > Settings > AI & Moderation > Monthly AI Budget ($). WPMediaVerse tracks estimated spending per call and stops making AI calls when the budget is reached.

To check current spending:

wp mvs stats

Registering a Custom AI Provider (Developer)

Implement the AIProviderInterface and register your provider:

use WPMediaVerse\Services\AIProviderInterface;

class MyCustomProvider implements AIProviderInterface {

    public function get_id(): string {
        return 'my_provider';
    }

    public function is_available(): bool {
        return (bool) get_option( 'my_provider_api_key' );
    }

    public function analyze( int $media_id ): array|WP_Error {
        // Return array with 'description', 'tags', 'flags'.
    }
}

add_action( 'mvs_register_ai_providers', function( $ai_service ) {
    $ai_service->register_provider( new MyCustomProvider() );
} );

Moderation Status Meta

Meta Key Values Description
_mvs_moderation_status approved, pending, flagged, rejected Current moderation status
_mvs_ai_description string AI-generated description
_mvs_ai_tags string Comma-separated AI-suggested tags
_mvs_ai_flags serialized array Safety categories with scores

Shortcodes

Included in Free - This feature is available in the free version of WPMediaVerse.

WPMediaVerse provides 12 shortcodes for embedding media features in pages, posts, and classic editor content.

[mvs_gallery]

Displays a filterable media grid. Columns and items-per-page come from Media > Settings > Display and cannot be overridden by shortcode attributes.

[mvs_gallery]
[mvs_gallery type="image" category="nature" tag="summer" orderby="date"]
Attribute Default Description
type (all types) Filter by media type: image, video, or audio
category (all) Filter by mvs_category slug
tag (all) Filter by mvs_tag slug
user_id (all authors) Filter to a single author. Pair with orderby="popular" for a "Top media by this member" embed.
orderby date Sort order: date, title, views, popular, reactions, or random.
order desc Sort direction: asc or desc.

[mvs_upload]

Displays the frontend file upload form. The form is only functional for logged-in users.

[mvs_upload]
[mvs_upload max_files="5" show_privacy="true"]
Attribute Default Description
max_files 10 Maximum number of files selectable at once
show_privacy true Show privacy level selector on the form

This shortcode fires the mvs_before_upload_form action before rendering.

[mvs_album]

Displays a single album by ID.

[mvs_album id="123"]
[mvs_album id="123" columns="4" show_title="true" show_description="false"]
Attribute Default Description
id (required) Album post ID
columns 3 Grid columns
show_title true Show album title
show_description true Show album description

[mvs_player]

Embeds a single media item in an interactive player.

[mvs_player id="456"]
[mvs_player id="456" autoplay="false" loop="false" download="false"]
Attribute Default Description
id (required) Media post ID
autoplay false Start playback automatically (audio/video)
loop false Loop the media after it finishes
download false Show a download button

[mvs_stats]

Displays site-wide media statistics.

[mvs_stats]
[mvs_stats views="true" downloads="true" reactions="true" top="true" top_count="5"]
Attribute Default Description
views true Show total view count
downloads true Show total download count
reactions true Show total reaction count
top true Show the top media list
top_count 5 Number of items in the top media list

[mvs_dashboard]

Displays a personal media dashboard for the logged-in user. Shows the user's own uploads, albums, and stats. Redirects to login if the user is not logged in.

[mvs_dashboard]

No configurable attributes. The dashboard uses the dashboard-view Interactivity API block store.

[mvs_collection]

Displays a collection by ID. Works for both manual and smart collections.

[mvs_collection id="789"]
[mvs_collection id="789" columns="3" per_page="20"]
Attribute Default Description
id (required) Collection post ID
columns 3 Grid columns
per_page 20 Maximum items to show

[mvs_profile_edit]

Displays a profile edit form for the logged-in user, allowing them to update their first name, last name, display name, bio, and avatar. Redirects to login if not logged in.

[mvs_profile_edit]

No configurable attributes. The form is powered by the mvs/profile-edit Interactivity API store and saves to /mvs/v1/profile.

Profile edit form with avatar upload and name fields

[mvs_explore_feed]

Embeds the explore archive - infinite-scroll public media feed with filter chips and the new search autocomplete dropdown. Use this when you want the explore experience on a custom page rather than the dedicated archive route.

[mvs_explore_feed]

No configurable attributes. Display options inherit from Media > Settings > Display.

[mvs_lock_overlay]

Renders a privacy lock overlay for a single media item. If the current user has access, the overlay falls through and renders the player or image inline. If they do not, the overlay shows the configured restriction message.

[mvs_lock_overlay media_id="456"]
Attribute Default Description
media_id (required) Media post ID to evaluate access against.

[mvs_member_photos]

Renders a member's media grid. Auto-resolves the user - explicit user_id attribute first, then the BuddyPress displayed user, then the post author, then the current user - so the same shortcode works on profile pages, member-specific landing pages, and author archives.

Added in 1.2.0.

[mvs_member_photos]
[mvs_member_photos user_id="42"]
Attribute Default Description
user_id (auto-detect) Force a specific user. Leave empty to use the four-step resolution chain.

[mvs_pdf_viewer]

Embeds a PDF using the browser-native viewer (the #view=FitH URL fragment). No third-party JS, no licensing concerns.

Added in 1.2.0.

[mvs_pdf_viewer id="123"]
[mvs_pdf_viewer id="123" height="800" show_toolbar="0"]
Attribute Default Description
id (required) Media ID of the PDF.
height 600 Viewer height in pixels. Range: 200-1400.
show_toolbar 1 Show or hide the browser PDF toolbar. 1 to show, 0 to hide.

Gutenberg Blocks

Included in Free - This feature is available in the free version of WPMediaVerse.

WPMediaVerse registers 9 Gutenberg blocks under the WPMediaVerse block category. All blocks use the WordPress Interactivity API for reactive front-end behavior without a separate JavaScript framework.

Gutenberg block inserter showing the WPMediaVerse block category

Block List

Block Name Handle Description
Media Upload mvs/media-upload Frontend file upload form with drag-and-drop
Media Grid mvs/media-grid Filterable, paginated media gallery grid
Media Player mvs/media-player Single-item audio/video player with controls
Album Viewer mvs/album-viewer Displays a single album's media in a grid
Member Photos mvs/member-photos Auto-detects the user (explicit userId → BuddyPress displayed user → post author → current user) and renders their media grid
PDF Viewer mvs/pdf-viewer Browser-native PDF embed using the #view=FitH URL fragment. Configurable height, optional toolbar, five distinct empty states
Media Stats mvs/media-stats Site-wide or per-user media statistics
Explore Feed mvs/explore-feed Infinite-scroll explore feed (all public media) with search autocomplete
Lock Overlay mvs/lock-overlay Paywall/restriction overlay for any block

Story Viewer is not in this list of 9. Its block source ships in the plugin, and the server-side StoryService exists, but as of 1.5.0 the block is intentionally not registered in the inserter - the Story create-flow UI and the Story REST endpoint have not landed yet, so the block would surface empty for site owners. See Story Viewer Block below for the current status.

Interactivity API Architecture

Each block ships with a view.js module registered via wp_enqueue_script_module(). Blocks communicate through shared state in the mvs interactivity store. The shared-ui module provides common utilities (REST fetching, nonce management) shared by all blocks.

Using Blocks in Templates

All registered blocks can be used in Full Site Editing (FSE) templates, template parts, and block patterns. They are fully compatible with query blocks and block themes.

Media Upload Block

Media Upload block in the editor

Block Settings:

  • Max Files per Upload (default: 10)
  • Show Privacy Selector (default: on)

Upload modal polish (1.2.0):

  • Preview tiles show the filename (truncated tastefully) and a per-tile remove (×) button.
  • Audio files render with a dedicated audio fallback icon instead of a broken thumbnail.
  • A row of eight popular tag pills appears below the tags input - click to append, no duplicates.

Media Grid Block

Media Grid block showing filter controls and grid layout

Block Settings:

  • Media Type Filter (image/video/audio/all)
  • Category Filter
  • Tag Filter
  • Sort Order - date, title, popular, views, reactions, random
  • Sort Direction - asc / desc
  • User ID filter (userId) - restrict the grid to a single author
  • Lightbox (default: on)
  • Show Reactions (default: on)

Grid columns and pagination inherit from Media > Settings > Display.

Sorting (1.2.0): Most Popular and Most Reactions sorts join the mvs_media_stats table to rank by aggregate engagement. Random sort reshuffles each page load.

Media Player Block

Block Settings:

  • Media ID (required - use the picker to select)
  • Autoplay (default: off)
  • Loop (default: off)
  • Show Download Button (default: off)

Album Viewer Block

Block Settings:

  • Album (select from a dropdown of your albums)
  • Columns override
  • Show Title / Show Description

Story Viewer Block (Not Yet Registered)

Status at 1.5.0: the mvs/story-viewer block source ships inside the plugin (src/blocks/story-viewer/) and the server-side StoryService can already create stories and return the active set, but the block is deliberately left out of the inserter. It is not one of the 9 registered blocks above, so you will not find it in the block library.

Why it is held back: there is no story create-flow in the upload UI and no public Story REST endpoint yet, so registering the viewer would only show site owners an empty block by default. The full experience - a way to mark an upload as a story, tap-to-advance navigation in a circular-avatar carousel, and the REST endpoint that powers it - is still on the roadmap. When that create-flow lands, the block flips on and joins the registered list.

What the block will do once enabled: render active stories (is_story media that has not passed story_expires_at) as a horizontal carousel of circular avatars. Planned attributes are count (number of stories, default 10) and avatar size (default 64 px).

Member Photos Block

The Member Photos block renders a single member's media grid. The block resolves the user automatically using a four-step fallback chain so the same block works in profile templates, member-specific pages, author archives, and BuddyPress profile tabs without per-page wiring.

Resolution order:

  1. Explicit userId attribute on the block
  2. BuddyPress displayed user (when on a BP profile)
  3. Post author (when in a single-post context)
  4. Current logged-in user

Block Settings:

  • User ID (optional - leave empty to auto-detect)
  • Items Per Page
  • Sort Order - same options as Media Grid

PDF Viewer Block

The PDF Viewer block embeds a PDF using the browser's native viewer via the #view=FitH URL fragment - no third-party JS, no licensing concerns.

Block Settings:

  • Media ID (required - pick a PDF media item)
  • Height (200-1400 px, default 600)
  • Show Toolbar (default: on)

Empty states: the block emits five distinct empty states - no media selected, wrong media type, missing file, access denied, and a generic fallback.

Media Stats Block

Block Settings:

  • Show Views (default: on)
  • Show Downloads (default: on)
  • Show Reactions (default: on)
  • Show Top Media (default: on)
  • Top Media Count (default: 5)

Explore Feed Block

The Explore Feed block provides an infinite-scroll feed of all public media. It supports URL-based filtering via ?mvs_tag=slug and ?s=search-term query parameters.

Search autocomplete (1.2.0): the search input now shows a type-ahead dropdown - top eight title matches, debounced 250 ms, full keyboard navigation (Arrow keys, Enter, Escape) and ARIA combobox semantics for screen reader users.

Lock Overlay Block

The Lock Overlay block wraps any other block content and shows a restriction message to users who do not meet access criteria. Configure access rules via the Access Control REST API.

GDPR & Privacy Compliance

Included in Free - This feature is available in the free version of WPMediaVerse.

WPMediaVerse integrates with the WordPress privacy tools built into Tools > Export Personal Data and Tools > Erase Personal Data. No configuration is required. The integration is active whenever the plugin is active.

The privacy functionality lives in GDPRService.php.

What Gets Exported

When an administrator runs a personal data export for a user, WPMediaVerse adds the following data groups to the export ZIP:

Data Group What Is Included
Media uploads File URLs, titles, descriptions, privacy level, upload date
Comments All comments the user posted on media items
Reactions Each reaction (emoji, media item, timestamp)
Favorites Each media item the user saved as a favorite
Direct messages All conversation participants and message content
Follow relationships List of users the subject follows and users following the subject

The export respects the WordPress standard format. Each group appears as a named section in the downloadable HTML and JSON files.

What Gets Erased

When an administrator runs a personal data erasure for a user, WPMediaVerse removes:

  • All media items uploaded by that user (files and database records)
  • All comments posted by that user on media items
  • All reactions and favorites
  • All direct message conversations and messages where the user is a participant
  • All follow relationships (both directions)

Erasure is permanent and cannot be undone. Media items that belong to BuddyPress groups are also removed.

Before erasing, WordPress asks the user to confirm the request via email. WPMediaVerse erasure only runs after that confirmation is received.

Privacy Policy Text

WPMediaVerse registers suggested privacy policy text via wp_add_privacy_policy_content(). The suggestion appears in the Privacy Policy Guide at Settings > Privacy > Privacy Policy Guide.

The suggested text describes:

  • What media metadata is collected and stored
  • How direct messages are stored and for how long
  • What follow data is retained
  • How to request export or erasure

You are not required to use the suggested text verbatim. Review it and incorporate the relevant parts into your site's privacy policy.

Developer Notes

Adding Custom Data to the Export

Use the mvs_privacy_export_data filter to add plugin-extension data to the exporter:

add_filter( 'mvs_privacy_export_data', function( $data, $user ) {
    $data['my_extension'] = [
        'group_id'    => 'my-extension-data',
        'group_label' => __( 'My Extension Data', 'my-extension' ),
        'items'       => get_my_extension_data_for_user( $user->ID ),
    ];
    return $data;
}, 10, 2 );

Hooking into Erasure

Use the mvs_privacy_before_erase action to perform additional cleanup before WPMediaVerse deletes user data:

add_action( 'mvs_privacy_before_erase', function( $user_id ) {
    // Remove any extension data for this user.
    delete_user_meta( $user_id, 'my_extension_meta' );
} );

User Blocking & Reporting

Included in Free - This feature is available in the free version of WPMediaVerse.

WPMediaVerse includes a blocking system that prevents specific users from interacting with you and a reporting system that lets users flag abusive content or accounts for moderator review.

User profile page showing Block User and Report User options

Blocking a User

You can block a user from two places:

  • From their profile page - Open the action menu on the user's profile card and select Block User.
  • From the report flow - After submitting a report against a user, you are offered the option to also block them.

What Blocking Does

When you block a user, they cannot:

  • View your media items (media items are hidden from them on all browse pages and their direct URLs return a 403)
  • Send you direct messages (your profile shows no Message button for them; any existing conversation is locked)
  • Follow you (the Follow button is removed for them)
  • Comment on your media
  • React to your media

Blocking is one-directional. You can still view the blocked user's public media unless you also choose to hide it.

Blocks are stored per-user and do not require any admin action.

Confirmation dialog after blocking a user

Unblocking a User

Go to Account Settings > Blocked Users to see your full block list and remove individual blocks. You can also unblock via the REST API.

User Reporting

Reporting a user notifies the site moderators. It does not automatically take any action on the reported account.

To report a user, open the action menu on their profile card and select Report User. Choose a reason from the dropdown and optionally add a note.

Report Reasons

Value Label
spam Spam or fake account
inappropriate Inappropriate behavior
harassment Harassment or bullying
impersonation Impersonating someone
other Other

Reports appear in Media > Moderation Queue under the Users tab. Moderators with the moderate_mvs_media capability can review, dismiss, or act on reports.

Media Reporting

Any logged-in user can report a media item from the media card or the media detail page using the flag icon.

Report Reasons

Value Label
spam Spam or misleading
inappropriate Sexually inappropriate
copyright Copyright violation
harassment Harassment or hate speech
other Other

When a media item accumulates reports equal to the Auto-Hide Threshold set in Media > Settings > AI & Moderation, it is automatically hidden and added to the moderation queue.

Media card showing the report flag icon

REST API

Base URL: /wp-json/mvs/v1/

All endpoints require a logged-in user. Pass the X-WP-Nonce header with a nonce from wp_create_nonce( 'wp_rest' ).

POST /users/{id}/block

Block a user.

Response: 200 OK

{ "blocked": true }

DELETE /users/{id}/block

Unblock a user.

Response: 200 OK

{ "blocked": false }

GET /me/blocked

List users blocked by the current user.

Parameters:

Parameter Type Default Description
per_page int 20 Blocked users per page
page int 1 Page number

Response: Array of user objects with id, name, and avatar_url.


POST /users/{id}/report

Report a user account.

Body:

Field Required Description
reason Yes One of: spam, inappropriate, harassment, impersonation, other
note No Optional free-text note visible to moderators (max 500 characters)

Response: 201 Created

{ "report_id": 17, "status": "pending" }

POST /media/{id}/report

Report a media item.

Body:

Field Required Description
reason Yes One of: spam, inappropriate, copyright, harassment, other
note No Optional free-text note (max 500 characters)

Response: 201 Created

{ "report_id": 24, "status": "pending" }

Actions and Filters

mvs_user_blocked

Fires after a user is blocked.

add_action( 'mvs_user_blocked', function( $blocker_id, $blocked_id ) {
    // Perform additional cleanup or logging.
}, 10, 2 );

mvs_user_reported

Fires after a user report is submitted.

add_action( 'mvs_user_reported', function( $report_id, $reported_user_id, $reporter_id, $reason ) {
    // Notify a Slack channel, send to an external moderation service, etc.
}, 10, 4 );

mvs_media_reported

Fires after a media report is submitted.

add_action( 'mvs_media_reported', function( $report_id, $media_id, $reporter_id, $reason ) {
    // Custom post-report logic.
}, 10, 4 );

mvs_block_query_args

Filter the WP_Query arguments used to exclude blocked-user content from browse pages.

add_filter( 'mvs_block_query_args', function( $args, $viewer_id ) {
    // Adjust how blocked content is filtered.
    return $args;
}, 10, 2 );

Image Optimization

Included in Free - This feature is available in the free version of WPMediaVerse.

WPMediaVerse automatically reduces image file sizes at upload time. No extra plugin is required. Originals are never made larger: if re-encoding produces no gain, the original file is kept untouched.

What It Does

When a JPEG, PNG, or GIF is uploaded:

  1. The file is re-encoded at high quality with embedded camera metadata stripped.
  2. The result is compared to the original. If it is smaller, the smaller file is committed. If not, the original is kept.
  3. A WebP copy is generated alongside the original and every thumbnail size (on by default).
  4. An AVIF copy can optionally be generated for even smaller file sizes (off by default; slower to encode).

Animated GIFs are detected and skipped from the lossless re-encode step. Their frames are preserved.

How Browsers Receive Images

WPMediaVerse uses a <picture> element with progressive fallback. Browsers receive the most efficient format they support:

  1. AVIF (if enabled and available)
  2. WebP (if enabled and available)
  3. Original JPEG/PNG/GIF (always served as a fallback)

This format negotiation applies across the explore grid, BuddyPress activity stream, the media dashboard, single media pages, and the lightbox. No JavaScript is required; it is handled by native browser behavior.

Settings

Access these settings at Media > Settings > Storage.

Setting Default Description
Compress uploaded images On Re-encodes JPEG, PNG, and GIF originals to remove camera metadata and reduce file size. Works alongside EWWW, Imagify, Smush, and ShortPixel.
Create WebP copies for faster loading On Saves a WebP copy next to every original and thumbnail. WebP files are typically 25 to 35 percent smaller than JPEG.
Create AVIF copies for the smallest possible files Off Saves an AVIF copy next to every original and thumbnail. AVIF is around 30 to 50 percent smaller than WebP. Encoding is much slower than WebP. Requires Imagick with libheif, or GD on PHP 8.1+ with libavif.

Toggling any setting takes effect on the next upload. Settings do not retroactively process existing media.

Admin: Optimization Column

The Optimization column on the Media > All Media list shows the result for each image:

Badge Meaning
-23% (green) The original was re-encoded and reduced by that percentage.
WebP ready (green) No lossless gain on the original, but a WebP copy was generated.
No lossless gain (grey) Re-encoding did not reduce the file and WebP was not generated.
Not optimized The image has not been through the optimization pipeline yet.

Each image row also has two row actions: Optimize (re-runs the optimization pipeline for that image) and Details (opens a detail page at ?page=mvs-media&view=details&media_id=N showing file sizes, savings percentage, and variant URLs).

Bulk Optimization and WP-CLI

To optimize images uploaded before 1.3.0, use WP-CLI:

wp mvs optimize <id>
wp mvs optimize-bulk

Both commands are resume-safe. See the WP-CLI reference for all available options.

For Developers

To replace the built-in optimizer with an external service (EWWW, Imagify, Smush, ShortPixel, or a custom compressor), hook into the mvs_optimize_image filter. See the Hooks and Filters reference for the full signature and examples.

BuddyPress Integration

Profile tabs, group media, activity feed, and notifications.

BuddyPress Integration Overview

Included in Free - WPMediaVerse is the most complete media solution for BuddyPress communities. Integration is optional - the plugin works standalone on any WordPress site, but when BuddyPress is active, it unlocks profile tabs, group media, activity stream, and notifications automatically.

WPMediaVerse integrates with BuddyPress to add media features directly into the BuddyPress social layer. The integration is automatic - no configuration needed. It activates when BuddyPress is active and gracefully skips all BP-specific code when BuddyPress is not installed.

Requirements

  • BuddyPress 12.0+
  • WPMediaVerse 1.0.0+

What the Integration Adds

BuddyPress Component Feature Added
Activity Records activity on media upload, comment, and album additions
Member Profiles Adds a Media tab showing the member's uploads
Groups Adds a Media tab in group navigation
Notifications Sends notifications for reactions, comments, and @mentions
Activity Post Form Adds a media attach button to the activity update form

Activation Check

The integration loads via BuddyPressIntegration::init(), which is called on plugins_loaded. Every feature check begins with:

if ( ! function_exists( 'buddypress' ) ) {
    return;
}

Individual features additionally check bp_is_active( 'activity' ), bp_is_active( 'groups' ), and bp_is_active( 'notifications' ) before hooking.

Multisite Compatibility

The BuddyPress integration works on WordPress Multisite networks. Activity and notifications are scoped to the site where BuddyPress is installed. Media uploaded on a subsite is recorded in that subsite's activity stream.

Profile Media Tab

Included in Free - WPMediaVerse is the most complete media solution for BuddyPress communities. Integration is optional - the plugin works standalone on any WordPress site, but when BuddyPress is active, it unlocks profile tabs, group media, activity stream, and notifications automatically.

When BuddyPress is active, WPMediaVerse adds a Media tab to every user's BuddyPress profile page.

BuddyPress member profile with Media tab active

Tab Location

The tab appears in the main BuddyPress profile navigation at:

/members/{username}/media/

It is added via bp_setup_nav at priority 100.

What the Tab Shows

The Media tab displays the profile owner's published media items in a paginated grid. Privacy filtering applies:

  • Visitors see only the profile owner's public media.
  • Logged-in users see public and members-only media.
  • BuddyPress friends see public, members-only, and friends media.
  • The profile owner sees all their own media.
  • Moderators (moderate_mvs_media) see all media.

Media tab content showing grid of photos with privacy badges

Profile URL Pattern

WPMediaVerse also registers a standalone media profile URL:

/media/@{username}/
/media/@{username}/page/2/

These URLs are handled by the TemplateLoader and use the explore.php template, filtered to the specific user.

User Profile Edit Page

Logged-in users can edit their avatar and profile details at:

/media/edit-profile/

Or use the [mvs_profile_edit] shortcode on any page.

Disabling the Profile Tab

To remove the profile tab without disabling the entire integration:

remove_action( 'bp_setup_nav', array( $integration, 'add_profile_tab' ), 100 );

Since $integration is not easily accessible externally, the cleaner approach is to use the tab's existence to skip rendering:

add_action( 'bp_setup_nav', function() {
    // Remove the WPMediaVerse media nav item from BP.
    bp_core_remove_nav_item( 'media' );
}, 200 );

Group Media Tab

Included in Free - WPMediaVerse is the most complete media solution for BuddyPress communities. Integration is optional - the plugin works standalone on any WordPress site, but when BuddyPress is active, it unlocks profile tabs, group media, activity stream, and notifications automatically.

When BuddyPress Groups is active, WPMediaVerse adds a Media tab to every BuddyPress group.

BuddyPress group page with Media tab

Tab Location

The tab appears in the group navigation at:

/groups/{group-slug}/media/

It is registered via bp_setup_nav at priority 100, conditional on bp_is_active( 'groups' ).

What the Tab Shows

The group media tab displays media that was:

  • Uploaded with privacy=group and group_id={this-group-id}.
  • Reassigned to the group via the mvs_media_group_assigned action.

Privacy applies within the group: only group members can view the tab content.

Assigning Media to a Group

When uploading, set the privacy to group and provide the group_id:

curl -X POST https://yoursite.com/wp-json/mvs/v1/media \
  -H "X-WP-Nonce: NONCE" \
  -F "file=@photo.webp" \
  -F "privacy=group" \
  -F "group_id=42"

This stores _mvs_privacy=group and _mvs_group_id=42 on the media post.

Group Activity Integration

Media uploaded to a group fires the mvs_media_group_assigned action:

do_action( 'mvs_media_group_assigned', $media_id, $group_id );

The BuddyPress integration listens to this action and re-scopes the upload's activity item from the member component to the groups component, so it appears in the group's activity stream rather than the member's personal stream.

BuddyPress group activity stream with media upload

Group Activity in the Activity Post Form

When a group member uses the BP activity post form inside a group, the Attach Media button (added by WPMediaVerse) automatically assigns uploaded media to that group.

Activity Stream Media

Included in Free - WPMediaVerse is the most complete media solution for BuddyPress communities. Integration is optional - the plugin works standalone on any WordPress site, but when BuddyPress is active, it unlocks profile tabs, group media, activity stream, and notifications automatically.

WPMediaVerse records media events as BuddyPress activity items and enhances existing activity with media thumbnails and inline video players.

Activity Types Registered

WPMediaVerse registers two custom activity action types:

Action Type Component Label
mvs_media_upload wpmediaverse Media Uploads
mvs_media_upload groups Group Media Uploads
mvs_comment wpmediaverse Media Comments

These appear in the BuddyPress activity filter dropdown.

When Activity Is Recorded

Event Action Hook Activity Recorded
Media uploaded via UploadService mvs_media_uploaded "Username uploaded [media title]" with thumbnail
Media published via admin/WP-CLI publish_mvs_media Fallback activity without thumbnail
Comment posted on media mvs_comment_created "Username commented on [media title]"
Media added to an album mvs_album_items_added Updates existing upload activity to reference the album
Media assigned to a group mvs_media_group_assigned Reassigns activity to the group component
Bulk album upload mvs_album_items_added One grouped activity for all files in the same upload action - see below

Bulk album upload activity grouping (1.2.0)

When a user uploads multiple files at once via the album upload modal, WPMediaVerse emits one grouped activity entry for the whole batch instead of one entry per file:

Username uploaded 3 photos to album Portrait Series - with a 3-thumbnail grid

The mechanism: the upload modal sends ?album_upload=1 with each per-file POST /media request. The mvs_media_uploaded listener records a skip flag on the activity row, suppressing per-media activity creation. After the JS link call finishes, mvs_album_items_added fires once with the bundled media IDs and the listener emits a single grouped bp_activity_add with the thumbnail grid as content. Single-file album uploads still produce a per-photo activity (no bundling needed); ad-hoc photo posts via the activity composer keep their existing one-row-per-post behaviour.

Activity Format

BuddyPress activity item showing a media upload

Upload activities use the format:

Username uploaded a [type] - [media title]

For group uploads:

Username uploaded a photo in the group [Group Name] - [media title]

Thumbnail Injection

WPMediaVerse injects media thumbnails into activity items in two ways:

  1. bp_get_activity_content_body filter (priority 0) - transforms activity content to include an image tag.
  2. bp_activity_entry_content action - injects thumbnails for activities with empty content (common for imported media).

Inline video players are injected for video media via inject_video_player_in_activity.

Attaching Media via the Activity Post Form

The Attach Media button appears in the BuddyPress activity post form for members and inside groups.

BuddyPress activity post form with Attach Media button

Users select previously uploaded media or upload new files inline. The media IDs are attached to the activity via bp_activity_posted_update / bp_groups_posted_update.

Allowed HTML Tags in Activity

WPMediaVerse extends the bp_activity_allowed_tags filter to allow its custom HTML attributes (data-mvs-*, data-wp-*) to pass through BP's kses filter without being stripped.

BuddyPress Notifications

Included in Free - WPMediaVerse is the most complete media solution for BuddyPress communities. Integration is optional - the plugin works standalone on any WordPress site, but when BuddyPress is active, it unlocks profile tabs, group media, activity stream, and notifications automatically.

When BuddyPress Notifications is active, WPMediaVerse sends in-app notifications for media social events.

Notification Types

Event Action Hook Who Gets Notified
Someone reacts to your media mvs_reaction_added Media owner
Someone comments on your media mvs_comment_created Media owner
Someone @mentions you in a comment mvs_mentions_created Each mentioned user

Notification Registration

WPMediaVerse registers wpmediaverse as a BuddyPress notification component via the bp_notifications_get_registered_components filter.

Notification format strings are registered via:

add_filter( 'bp_notifications_get_notifications_for_user', ... );

Notification Format

Notifications appear in the BuddyPress notification bell with these formats:

Type Format
Reaction Username reacted to your photo [media title]
Comment Username commented on your media [media title]
Mention Username mentioned you in a comment on [media title]

BuddyPress notification dropdown showing WPMediaVerse notifications

Notification Filters (BP Nouveau)

In BuddyPress Nouveau, notification filter links are registered via bp_nouveau_notifications_init_filters to allow users to filter their notification list by WPMediaVerse notifications.

Reading Notifications via REST API

curl https://yoursite.com/wp-json/mvs/v1/notifications \
  -H "X-WP-Nonce: NONCE"

This returns WPMediaVerse-specific notifications. For the full BuddyPress notification list, use the BP REST API.

Marking Notifications as Read

curl -X PUT https://yoursite.com/wp-json/mvs/v1/notifications/123 \
  -H "X-WP-Nonce: NONCE" \
  -H "Content-Type: application/json" \
  -d '{"is_new": false}'

Real-Time Notification Polling

WPMediaVerse includes a REST polling transport (RestPollingTransport) that the frontend uses to poll /mvs/v1/notifications for new notifications without requiring WebSockets.

1.2.0 update - single notification surface

When BuddyPress is active, every WPMediaVerse notification is mirrored to BuddyPress via bp_notifications_add_notification, and the standalone dashboard .mvs-notification-bell markup is suppressed. This means BP-active sites see one bell - the BP nav bell - instead of two competing bells rendering the same notifications.

This is automatic. No setting to flip, no filter to add. If BuddyPress is deactivated, the standalone WPMediaVerse bell returns automatically.

Pro Features

Layout modes, cloud storage, video, quotas, and advanced privacy.

WPMediaVerse Pro

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

WPMediaVerse Pro extends the free plugin with advanced layout modes, cloud storage, video processing, AI providers, quota management, and granular privacy controls.

WPMediaVerse Pro license key entry screen

Requirements

  • WPMediaVerse (free) 1.0.0 or higher installed and activated
  • WordPress 6.5+
  • PHP 8.0+
  • MySQL 5.7+ or MariaDB 10.4+

Installation

  1. Install and activate the free WPMediaVerse plugin first.
  2. Go to Plugins > Add New Plugin > Upload Plugin.
  3. Upload the wpmediaverse-pro.zip file and click Install Now.
  4. Click Activate Plugin.
  5. Go to Media > License and enter your license key.
  6. Click Activate License.

Media License settings page with activation status

Pro features activate immediately after a valid license is confirmed. No site reload is required.

Updating Pro

When an update is available, WordPress shows it in the standard Plugins screen. The updater requires a valid active license. If your license has expired, download the latest ZIP from your account at wbcomdesigns.com and upload it manually.

1.2.0: All Pro features now Gutenberg blocks

Every Pro feature now ships as a first-class Gutenberg block - drop-in, configurable in the editor, no shortcodes-as-the-only-option. Each block has a matching [mvs_pro_*] shortcode for classic editor and template-tag use.

Block Handle Notes
Tournament mvs/pro-tournament Configurable tournamentId attribute.
Tournaments List mvs/pro-tournaments-list Lists active tournaments.
Challenge mvs/pro-challenge Configurable challengeId attribute.
Challenges List mvs/pro-challenges-list Lists active and upcoming challenges.
Battle mvs/pro-battle Configurable battleId attribute.
Battles Active mvs/pro-battles-active Currently running battles.
Instagram Feed mvs/pro-instagram-feed Instagram-style square grid layout.
Flickr Feed mvs/pro-flickr-feed Flickr-style justified rows layout.
Pinterest Feed mvs/pro-pinterest-feed Pinterest-style masonry layout.
Dribbble Feed mvs/pro-dribbble-feed Dribbble-style card grid layout.
Leaderboard mvs/pro-leaderboard Top performers across competitions.
Compete Hub mvs/pro-compete-hub Combined challenges + battles + tournaments dashboard.

Layout flexibility: because each layout (Instagram, Flickr, Pinterest, Dribbble) is its own block, admins can mix layouts on different pages - e.g. a Pinterest feed on the home page and an Instagram feed on a member directory - instead of being locked to one site-wide layout setting.

MigrationPage admin restructure

The migration tool admin page is now a generic shell that hosts per-platform cards (rtMedia, MediaPress, BuddyBoss). Two pre-existing detection bugs were fixed in the same pass: the Imported count was always 0 regardless of actual progress, and the MediaPress dedup query was running against an undefined $wpdb. Migrations now report accurate counts and skip already-imported items correctly.

Pro Feature Categories

Category Page What It Adds
Layout Modes layout-modes.md Instagram, Pinterest, Flickr, and Dribbble feed layouts
Cloud Storage cloud-storage.md Amazon S3 and BunnyCDN storage drivers
Video Transcoding video-transcoding.md Async FFmpeg transcoding to 720p/480p/360p and HLS
Video Chapters video-chapters.md Chapter markers and resume playback
Auto-Captions auto-captions.md OpenAI Whisper transcription and WebVTT captions
Watermarking watermarking.md GD-based text and logo watermarks on media
Video Analytics video-analytics.md Play event tracking, heatmaps, and retention reports
AI Providers ai-providers.md Google Cloud Vision and AWS Rekognition support
Quotas quotas.md Per-user storage and count limits with membership integration
Advanced Privacy advanced-privacy.md Multi-level privacy, presets, album inheritance, and bulk updates
Connected Accounts connected-accounts.md Connect Flickr to import/export photos and auto-push new uploads

User Reports (Pro Moderation)

Pro adds a Reports view that surfaces user-submitted abuse reports on media and members - the complaints your community files, as opposed to the free Moderation Queue, which is about AI/auto-flagged content awaiting an approve/reject decision.

  • Where: MediaVerse > Moderation, in the User Reports tab. (It is also reachable directly at admin.php?page=mvs-reports.)
  • Who: any user with the moderate_mvs_media capability.
  • What it lists: every report row - date, reporter, target type (media or user), the target, the reason code, and a details excerpt. A status filter switches between Pending, Resolved, and Dismissed, each with a live count.
  • Actions: on a pending report you can Resolve (mark handled) or Dismiss (no action needed). Both are nonce-protected and capability-checked.

Note for the parent agent: this section documents the Pro Reports admin page inline. If the docs site wants it discoverable on its own, it could later be split into a dedicated pro-features/reports.md page - the content above is self-contained and ready to lift out.

License Management

Setting Location Description
License Key Media > License Your product license key from wbcomdesigns.com
Activation Status Media > License Shows active, inactive, or expired
Deactivate License Media > License Release the activation to use on another site

A single license activates one site. Purchase additional activations from your account dashboard if you need to run Pro on multiple sites.

Layout Modes

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Transform your media community's look with one click - choose the visual style that fits your audience, from Instagram-style grids to Pinterest masonry boards.

What You Can Do

  • Switch between four distinct visual layouts without touching code
  • Match your community's purpose: photo sharing, discovery, portfolios, or design showcases
  • Override the global layout for any individual gallery using a shortcode attribute
  • Conditionally switch layouts by page context using the mvs_pro_feed_layout filter

The Four Layouts at a Glance

Mode Best For
Instagram Photo-sharing communities, daily uploads, stories
Pinterest Inspiration boards, discovery-focused sites
Flickr Photography portfolios, camera clubs
Dribbble Design showcases, creative portfolios

How to Switch Layouts (for Site Owners)

  1. Go to Media > Settings > Display
  2. Find the Feed Layout option and select your preferred mode
  3. Click Save Settings - the explore page, all profile media tabs, and gallery shortcodes update immediately

The setting is stored in the mvs_pro_feed_layout option.

What Changes for Users When You Switch Layouts

  • The explore/browse page re-renders in the new layout style
  • All user profile media tabs switch to the new layout automatically
  • The lightbox updates: Flickr mode shows EXIF camera data in the sidebar; Instagram shows the swipe carousel
  • Stories appear above the grid in Instagram mode only

Layout mode selector in Settings showing all four options


Choosing a Layout Mode

The selected mode applies to the explore archive, user profile media tab, and any [mvs_gallery] shortcode or Media Grid block that does not override it with a layout attribute.


Instagram Mode

Perfect for photo-sharing communities and daily uploads.

Instagram layout with recent uploaders bar and vertical card feed

The Instagram layout renders a vertical card feed. Each post shows the author avatar, full-width photo, reaction/comment/share buttons, like count, caption, and inline comment box - identical to the Instagram experience.

  • Recent uploaders appear as circular avatars above the feed (links to their profiles)
  • Each card has heart, comment, share, and bookmark buttons
  • Clicking "Expand" opens the lightbox with the full media detail view
  • Other users' posts show a "Following" button in the card header

Feed template: templates/feed/instagram.php Profile template: templates/profile/instagram.php


Pinterest Mode

Ideal for inspiration boards, discovery-focused sites, and mixed-format content.

Pinterest masonry layout with cards of varying heights

The Pinterest layout uses a masonry algorithm that preserves each image's original proportions. Cards include the media title and a truncated description below the image.

  • Column count is controlled by the Grid Columns display setting (2-4)
  • Hovering a card reveals a save-to-collection button
  • Infinite scroll loads the next page of results automatically

Feed template: templates/feed/pinterest.php Profile template: templates/profile/pinterest.php


Flickr Mode

Best for photography portfolios, camera clubs, and image-quality-focused communities.

Flickr justified gallery layout with full-width rows

The Flickr layout uses a justified gallery algorithm: images in each row are resized to fill the full container width while maintaining a consistent row height. The default row height is 200px and is configurable per shortcode.

  • Clicking an image opens the lightbox with EXIF data displayed in the sidebar
  • Profile page shows a filmstrip-style contact sheet view

Feed template: templates/feed/flickr.php Profile template: templates/profile/flickr.php

Shortcode override:

[mvs_gallery layout="flickr" row_height="240"]
Attribute Default Description
layout (site setting) Force a specific layout for this shortcode instance
row_height 200 Target row height in pixels (Flickr mode only)

Dribbble Mode

Great for design showcases, creative portfolios, and high-resolution work.

Dribbble layout showing large shot thumbnails in a 2-column grid

The Dribbble layout presents media as large portfolio shots in a 2-column grid. Each card shows the title, view count, and reaction count on hover. This layout is optimised for high-resolution PNG and GIF files.

  • Animated GIFs play on hover
  • Profile page shows featured work prominently at the top before the full grid

Feed template: templates/feed/dribbble.php Profile template: templates/profile/dribbble.php


Overriding the Layout Per Shortcode

You can force any layout mode on a per-shortcode basis without changing the global setting:

[mvs_gallery layout="pinterest"]
[mvs_gallery layout="flickr" row_height="180"]
[mvs_gallery layout="dribbble"]

The layout attribute accepts: instagram, pinterest, flickr, dribbble.

Overriding the Layout in Code

add_filter( 'mvs_pro_feed_layout', function( $layout ) {
    // Force Flickr layout on archive pages.
    if ( is_post_type_archive( 'mvs_media' ) ) {
        return 'flickr';
    }
    return $layout;
} );

Cloud Storage

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Stop storing media on your web server - offload every photo and video to Amazon S3 or BunnyCDN for faster delivery, lower server load, and global CDN performance.

Pluggable Storage Architecture

WPMediaVerse separates where media records live (always in your WordPress database) from where files are stored (local, S3, or BunnyCDN). Every upload goes through a StorageDriverInterface - a clean abstraction that means:

  • Switch drivers anytime - Change from local to S3 in one setting. Existing files continue to serve from their original location; new uploads go to the new driver.
  • Build your own driver - Developers can register custom drivers (Google Cloud Storage, DigitalOcean Spaces, Wasabi) by implementing the StorageDriverInterface. See Custom Storage Drivers.
  • Signed URLs for private media - Cloud drivers generate time-limited signed URLs so private and members-only media stays protected even when served from a public CDN.
  • No lock-in - Media metadata stays in your WordPress database regardless of storage driver. Switch providers without losing any data.

Why Use Cloud Storage

  • Your WordPress server no longer stores or serves media files - reducing disk usage and bandwidth costs
  • Files are served from edge locations closest to each visitor, so images load faster worldwide
  • S3 and BunnyCDN both scale to millions of files without any WordPress configuration changes
  • Private media gets signed URLs so only authorized users can access the actual file - even with cloud storage

How Media Serving Works in 1.4.0

Location-based serving

Each media item is served from where it is actually stored, based on its storage location and privacy setting at the time of the request. The active storage driver setting only controls where new uploads go - it does not affect files that were uploaded previously.

This means:

  • Switching the active driver (for example, from local to S3) does not break any existing media. Files already on S3 keep serving from S3. Files on local disk keep serving through the plugin's /serve route.
  • Enabling a cloud integration for the first time does not affect older uploads - they continue working as before.
  • Public media stored on cloud serves directly from the CDN (no WordPress request involved). Local media serves through the plugin's /serve proxy route.

Private media stays local

Only public media is eligible for cloud storage. Media with any other privacy setting (members-only, friends-only, private, or group) is always stored on the local server disk. This applies to the original file, all thumbnails, and all generated image variants (WebP, AVIF).

There is effectively one storage location per media item at any time: either cloud (for public media) or local (for everything else).

Per-request access check for private media

Every request to the /serve route for non-public media re-verifies the requesting user's view permission (can_view). A signed URL does not act as a transferable bearer token for private media. If the viewer no longer has access, the request returns 403 - even with a valid, unexpired signed URL. Public media uses bearer-style URLs (cacheable and shareable) because they carry no access restriction.

Cloudflare R2 requires a public domain

If you use Cloudflare R2 and have not configured a public domain (r2.dev subdomain or custom domain) on your bucket, WPMediaVerse will not emit the raw *.r2.cloudflarestorage.com API URL. That endpoint is never publicly readable. Instead, the plugin falls back to serving the file from the local copy via /serve.

To enable true CDN serving from R2, configure a public domain for your bucket in the Cloudflare R2 dashboard (either the r2.dev subdomain or your own custom domain), then enter that hostname in the CDN Domain field in Storage settings.

"Serve public cloud media directly" setting retired

The Serve public cloud media directly checkbox (mvs_cloud_direct_public_urls) has been removed from the settings UI in 1.4.0. Direct CDN serving for public cloud media is now automatic. You do not need to enable any toggle. The underlying option is retained in the database for back-compatibility, but it has no effect on serving behavior.

If you had this checkbox enabled before upgrading, no action is needed - behavior is the same or better.


Setting Up Amazon S3

  1. Log into your AWS Console and create an S3 bucket
  2. Create an IAM user with the required permissions (see IAM Policy below) and save the access key and secret key
  3. In WordPress, go to Media > Settings > Storage
  4. Set Storage Driver to Amazon S3
  5. Enter your bucket name, region, access key ID, and secret access key
  6. If you use CloudFront or a custom domain, enter the hostname in the CDN Domain field
  7. Click Test Connection - WPMediaVerse Pro uploads a small test file and reads it back to confirm everything works
  8. Click Save Settings - all new uploads now go directly to S3

S3 configuration fields in Storage settings

Setting Up BunnyCDN

  1. Log into your BunnyCDN dashboard and create a Storage Zone
  2. Note your storage zone name, API key, region, and pull zone hostname
  3. In WordPress, go to Media > Settings > Storage
  4. Set Storage Driver to BunnyCDN
  5. Enter your storage zone name, API key, region, and CDN hostname
  6. Click Test Connection to verify
  7. Click Save Settings - all new uploads now go to BunnyCDN

Cloud Storage settings panel showing driver selector

Choosing a Storage Driver

Go to Media > Settings > Storage and set the Storage Driver option. The value is stored in the mvs_storage_driver option.

Value Driver
local Default WordPress uploads directory (no Pro required)
s3 Amazon S3
bunny BunnyCDN

Only one driver is active at a time. Switching drivers does not migrate existing files - previously uploaded files remain at their original URLs and continue to serve from their original location.


Amazon S3

S3 configuration fields in Storage settings

Settings

Option Option Key Description
S3 Bucket mvs_pro_s3_bucket The name of your S3 bucket
S3 Region mvs_pro_s3_region AWS region code, e.g. us-east-1
Access Key ID mvs_pro_s3_access_key Your AWS IAM access key ID
Secret Access Key mvs_pro_s3_secret_key Your AWS IAM secret access key
CDN Domain mvs_pro_s3_cdn_domain Optional CloudFront or custom domain for file URLs

Storing Credentials in wp-config.php

Instead of saving credentials to the database, define them as constants in wp-config.php:

define( 'MVS_PRO_AWS_ACCESS_KEY', 'AKIAIOSFODNN7EXAMPLE' );
define( 'MVS_PRO_AWS_SECRET_KEY', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' );

When these constants are defined, WPMediaVerse Pro uses them instead of the database values. The admin fields show a placeholder indicating constants are in use.

Required IAM Policy

Your IAM user needs at minimum:

{
  "Effect": "Allow",
  "Action": [
    "s3:PutObject",
    "s3:GetObject",
    "s3:DeleteObject",
    "s3:ListBucket"
  ],
  "Resource": [
    "arn:aws:s3:::your-bucket-name",
    "arn:aws:s3:::your-bucket-name/*"
  ]
}

CDN Domain

If you serve your bucket through CloudFront or a custom domain, enter the hostname (without trailing slash) in the CDN Domain field. WPMediaVerse Pro replaces the default S3 URL with this domain for all generated file URLs.


BunnyCDN

BunnyCDN configuration fields in Storage settings

Settings

Option Option Key Description
Storage Zone mvs_pro_bunny_zone Your BunnyCDN storage zone name
API Key mvs_pro_bunny_api_key Your BunnyCDN API key
Region mvs_pro_bunny_region Storage region: de, ny, la, sg, syd
CDN Hostname mvs_pro_bunny_cdn_hostname Your pull zone hostname, e.g. media.yoursite.b-cdn.net

Regions

Value Location
de Falkenstein, Germany (default)
ny New York, USA
la Los Angeles, USA
sg Singapore
syd Sydney, Australia

Testing the Connection

After saving settings, click Test Connection in the Storage settings panel. WPMediaVerse Pro uploads a small test file, reads it back, then deletes it. The result (success or error message) appears inline without a page reload.

Storage settings panel showing connection test result

If the test fails, verify your credentials, bucket name, and that the IAM or API key has sufficient permissions.


File Path Structure

Files are stored under the same path structure used for local uploads:

wpmediaverse/YYYY/MM/filename.ext

For S3 this becomes s3://your-bucket/wpmediaverse/YYYY/MM/filename.ext. For BunnyCDN it becomes a path within your storage zone.

Signed URLs with Cloud Storage

When media privacy is not public, WPMediaVerse Pro generates signed URLs through the /serve proxy route. The proxy re-verifies view permission on every request - signed URLs for non-public media do not grant transferable access. S3 presigned URLs and BunnyCDN token authentication are used for migration and admin operations, not for end-user delivery of private media.


Developer: Filtering Public Cloud URLs

The public-cloud serving behavior can be adjusted using three filters. Full parameter details are in the Developer Guide: Hooks and Filters.

Filter What it controls
mvs_serve_public_cloud_direct Return false to force all media back through the /serve proxy instead of emitting direct CDN URLs
mvs_public_cloud_thumbnail_url Rewrite or replace the direct CDN URL for a public cloud-hosted thumbnail
mvs_public_cloud_file_url Rewrite or replace the direct CDN URL for a public cloud-hosted original file

Video Transcoding

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Give your community professional-quality video playback with automatic quality selection - viewers on fast connections get 720p, mobile users on slower connections get 360p, automatically.

What Users See

When a user plays a video, they see a quality selector in the player corner showing available options: 720p, 480p, and 360p. The player automatically selects the best quality for their connection and lets them override it manually. Playback starts faster because the video streams progressively rather than requiring a full download.

While transcoding is in progress after upload, the video plays at its original uploaded resolution. Once transcoding completes (typically within a few minutes), the quality selector appears.

Single media page showing quality selector in the video player

What Admins Need

  • FFmpeg installed on the server and accessible from PHP. Verify this by running which ffmpeg via SSH.
  • Action Scheduler 3.0+ (bundled with WooCommerce, or install as a standalone plugin from wordpress.org)

Shared hosting plans often do not have FFmpeg. VPS and dedicated servers typically do. Contact your host if you are unsure.

Enabling Transcoding

  1. Confirm FFmpeg is installed on your server: run which ffmpeg via SSH and note the path
  2. Go to Media > Settings > Video
  3. Enable Transcode Uploaded Videos
  4. If your FFmpeg path differs from /usr/bin/ffmpeg, update the FFmpeg Path field
  5. Click Save Settings
  6. All new video uploads will now be queued for transcoding automatically

Video settings tab with transcode toggle and FFmpeg path

Requirements

  • FFmpeg installed on the server and accessible from PHP (verify with which ffmpeg via SSH)
  • Action Scheduler 3.0+ (bundled with WooCommerce or installable as a standalone plugin)

Enabling Transcoding

Go to Media > Settings > Video and enable Transcode Uploaded Videos. The setting is stored in the mvs_pro_transcode_enabled option.

Option Option Key Default Description
Transcode Uploaded Videos mvs_pro_transcode_enabled 0 Enable async FFmpeg transcoding on upload
FFmpeg Path mvs_pro_ffmpeg_path /usr/bin/ffmpeg Absolute path to the FFmpeg binary

Video settings tab with transcode toggle and FFmpeg path

Output Formats

Each uploaded video is transcoded to:

Resolution Bitrate Format
720p ~2500 kbps MP4 (H.264 + AAC)
480p ~1000 kbps MP4 (H.264 + AAC)
360p ~500 kbps MP4 (H.264 + AAC)

An HLS playlist (master.m3u8) is also generated, referencing all three resolutions. The player loads the HLS stream by default and falls back to direct MP4 at the highest available resolution for browsers without HLS support.

File Storage

Transcoded files are stored at:

/wp-content/uploads/mvs-transcodes/{media_id}/
  720p.mp4
  480p.mp4
  360p.mp4
  master.m3u8
  720p/
  480p/
  360p/

If cloud storage is configured, the transcoded files are uploaded to the same bucket or storage zone using the same path structure.

Post Meta

Meta Key Values Description
_mvs_transcode_status pending, processing, complete, failed Current transcoding job status
_mvs_transcodes serialized array URLs to each transcoded file and the HLS playlist

REST API

Base URL: /wp-json/mvs-pro/v1/

GET /media/{id}/transcodes

Get the transcoding status and file URLs for a media item.

Response:

{
  "status": "complete",
  "transcodes": {
    "720p": "https://example.com/wp-content/uploads/mvs-transcodes/123/720p.mp4",
    "480p": "https://example.com/wp-content/uploads/mvs-transcodes/123/480p.mp4",
    "360p": "https://example.com/wp-content/uploads/mvs-transcodes/123/360p.mp4",
    "hls": "https://example.com/wp-content/uploads/mvs-transcodes/123/master.m3u8"
  }
}

POST /media/{id}/transcodes

Trigger transcoding manually. Requires ownership or edit_others_mvs_media. Use this to re-transcode a file after correcting the FFmpeg path.

Response: 202 Accepted with the current status object.

Transcoding Queue

Action Scheduler processes the transcoding queue in the background. You can monitor jobs at Tools > Scheduled Actions (if WooCommerce is active) or at the Action Scheduler standalone admin page. Filter by the mvs_pro_transcode hook to see only transcoding jobs.

Troubleshooting

If transcoding fails, check:

  1. FFmpeg is installed: run ffmpeg -version via SSH.
  2. The path in Media > Settings > Video > FFmpeg Path matches the which ffmpeg output.
  3. The PHP process has permission to invoke the binary (disable_functions in php.ini does not block it).
  4. The mvs-transcodes directory inside wp-content/uploads is writable by the web server user.

Failed jobs set _mvs_transcode_status to failed. Re-trigger via the REST API endpoint above or by editing the media item in WP Admin and clicking Re-Transcode.

Video Chapters

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

WPMediaVerse Pro lets you add chapter markers to video files and tracks each viewer's resume position so they can pick up where they left off.

Video player showing chapter markers on the progress bar

How Chapters Work

Chapter markers appear on the video player's progress bar as clickable tick marks. A chapter list panel opens alongside the player, showing each chapter title and its timestamp. Clicking a chapter jumps the video to that position.

Chapter data is stored per media item via the REST API and rendered into the player on page load.


REST API

Base URL: /wp-json/mvs-pro/v1/

GET /media/{id}/chapters

Retrieve the chapter list for a media item.

Response:

{
  "chapters": [
    { "time": 0, "title": "Introduction" },
    { "time": 145, "title": "Main Topic" },
    { "time": 312, "title": "Conclusion" }
  ]
}

time is in seconds from the start of the video.

PUT /media/{id}/chapters

Replace the chapter list for a media item. Requires ownership or edit_others_mvs_media.

Body:

{
  "chapters": [
    { "time": 0, "title": "Introduction" },
    { "time": 145, "title": "Main Topic" },
    { "time": 312, "title": "Conclusion" }
  ]
}

Sending an empty array ("chapters": []) removes all chapters from the media item.

Response: 200 OK with the updated chapter list.


Resume Playback

WPMediaVerse Pro tracks the furthest playback position reached by each authenticated user. When a user returns to a video they have partially watched, the player offers a Resume from X:XX prompt.

Resume positions are stored server-side, not in browser storage, so they persist across devices.

REST API

GET /media/{id}/resume

Get the resume position for the current authenticated user.

Response:

{
  "position": 187,
  "updated_at": "2025-03-28T09:14:00Z"
}

Returns 404 if no resume position exists for this user and media item.

POST /media/{id}/resume

Save or update the resume position. The player calls this endpoint as the video plays.

Body:

{ "position": 187 }

position is in seconds. The endpoint accepts updates only when the new position is greater than the stored one, preventing backwards seeks from overwriting progress.

Response: 200 OK.

DELETE /media/{id}/resume

Clear the resume position for the current user. This is called when the user watches to the end of the video or manually dismisses resume tracking.

Response: 204 No Content.


Adding Chapters via WP Admin

  1. Go to Media > All Media and open the media item you want to edit.
  2. Scroll to the Chapters meta box.
  3. Click Add Chapter, enter the timestamp (in M:SS or H:MM:SS format) and a title.
  4. Repeat for each chapter.
  5. Click Update.

Chapters meta box in the media edit screen

Importing Chapters from a File

You can import chapters from a plain text file using the WP-CLI command:

wp mvs chapters import --media_id=123 --file=/path/to/chapters.txt

The file format is one chapter per line: HH:MM:SS Chapter Title. Lines starting with # are ignored.

Auto-Captions

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

WPMediaVerse Pro transcribes video and audio files using the OpenAI Whisper API and attaches the result as a WebVTT caption file. Captions are served with the media player and can be edited via the REST API.

Video player with captions visible and CC button

Requirements

  • An OpenAI API key with access to the Whisper transcription endpoint
  • The media file must be a supported audio or video format (MP3, MP4, M4A, WAV, WEBM, OGG)

Enabling Auto-Captions

Go to Media > Settings > Video and configure the following options.

Option Option Key Default Description
Auto-Generate Captions mvs_pro_captions_auto 0 Automatically transcribe each uploaded video or audio file
Whisper API Key mvs_pro_whisper_api_key (empty) Your OpenAI API key

Auto-captions settings section with API key field

When Auto-Generate Captions is on, WPMediaVerse Pro queues a transcription job via Action Scheduler immediately after a file is stored. The caption file is saved once the Whisper API responds.

Caption File Storage

WebVTT files are stored at:

/wp-content/uploads/mvs-captions/{media_id}/captions.vtt

If cloud storage is active, the VTT file is also uploaded alongside the media file.

REST API

Base URL: /wp-json/mvs-pro/v1/

GET /media/{id}/captions

Retrieve the caption file content and metadata for a media item.

Response:

{
  "status": "complete",
  "language": "en",
  "vtt_url": "https://example.com/wp-content/uploads/mvs-captions/123/captions.vtt",
  "generated_at": "2025-03-28T10:00:00Z"
}

status can be none, pending, complete, or failed.

POST /media/{id}/captions

Trigger Whisper transcription manually, or upload a custom VTT file.

To trigger automatic transcription (no body required):

curl -X POST https://yoursite.com/wp-json/mvs-pro/v1/media/123/captions \
  -H "X-WP-Nonce: NONCE"

To upload your own VTT file (multipart/form-data):

Field Required Description
file Yes A .vtt file
language No BCP 47 language code, e.g. en, fr, de

Response: 202 Accepted when queuing transcription, 201 Created when uploading a custom file.

PUT /media/{id}/captions

Replace the caption content with edited VTT text. Use this to correct transcription errors.

Body (JSON):

{
  "content": "WEBVTT\n\n00:00:00.000 --> 00:00:04.000\nHello and welcome.\n\n00:00:04.500 --> 00:00:09.000\nToday we are covering...",
  "language": "en"
}

Response: 200 OK with the updated caption metadata.

DELETE /media/{id}/captions

Remove the caption file and reset the caption status to none.

Response: 204 No Content.

WebVTT Format

WPMediaVerse Pro always stores captions in WebVTT format. If you upload an SRT file, it is automatically converted to VTT before saving.

Example VTT file:

WEBVTT

00:00:00.000 --> 00:00:04.000
Hello and welcome to this video.

00:00:04.500 --> 00:00:09.000
Today we are covering the main topic.

Editing Captions in WP Admin

  1. Open the media item in Media > All Media.
  2. Scroll to the Captions meta box.
  3. The VTT content is displayed in an editable text area.
  4. Make your corrections and click Update.

Captions meta box in the media edit screen

Watermarking

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

WPMediaVerse Pro applies text or logo watermarks to image files using PHP's GD library. Watermarks are applied at download time so your stored originals are never modified.

Watermark settings panel showing text and logo options

Requirements

  • PHP GD extension enabled (verify with phpinfo() - look for the gd section)
  • For logo watermarks: a PNG file with a transparent background is recommended

Enabling Watermarks

Go to Media > Settings > Display > Watermark and configure the options below.

Option Option Key Default Description
Enable Watermarking mvs_pro_watermark_enabled 0 Apply a watermark to downloaded image files
Watermark Type mvs_pro_watermark_type text text to overlay a text string, logo to overlay an image
Watermark Text mvs_pro_watermark_text (empty) The text to overlay (used when type is text)
Watermark Image mvs_pro_watermark_image_id (empty) WordPress attachment ID of the logo image
Position mvs_pro_watermark_position bottom-right Where on the image to place the watermark
Opacity mvs_pro_watermark_opacity 70 Watermark opacity, 0 (transparent) to 100 (opaque)

Watermark position selector showing position options

Watermark Positions

Value Location
top-left Top-left corner with padding
top-right Top-right corner with padding
center Centred on the image
bottom-left Bottom-left corner with padding
bottom-right Bottom-right corner with padding

Text Watermarks

When Watermark Type is text, WPMediaVerse Pro renders the string in Watermark Text using a bundled TrueType font. You can change the font by replacing assets/fonts/watermark.ttf in the Pro plugin directory, or by using the filter:

add_filter( 'mvs_pro_watermark_font_path', function( $path ) {
    return '/path/to/your-font.ttf';
} );

Font size scales automatically based on the longest dimension of the output image.

Logo Watermarks

When Watermark Type is logo, select an image from your WordPress Media Library using the Watermark Image field. WPMediaVerse Pro resizes the logo to 15% of the output image's width before compositing. Override that ratio:

add_filter( 'mvs_pro_watermark_logo_ratio', function( $ratio ) {
    return 0.10; // 10% of image width.
} );

How Watermarks Are Applied

Watermarks are applied dynamically when a user requests a download. The watermarked image is streamed directly to the browser - it is not cached to disk. The original file in storage is never changed.

If you want to apply watermarks to existing media in bulk, use WP-CLI:

wp mvs watermark apply --all
wp mvs watermark apply --media_id=123

Bypassing Watermarks

Users with the manage_options capability and the media owner always download the original file without a watermark. To grant additional roles watermark-free downloads:

add_filter( 'mvs_pro_skip_watermark', function( $skip, $media_id, $user_id ) {
    if ( user_can( $user_id, 'upload_files' ) ) {
        return true;
    }
    return $skip;
}, 10, 3 );

Video Analytics

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

WPMediaVerse Pro records play events for every video, builds per-video heatmaps, and provides a dashboard showing retention and engagement metrics.

Video analytics dashboard showing play counts and retention curve

How Event Tracking Works

The player fires events to the REST API as viewers interact with a video. Events are rate-limited to one event per second per session to prevent flooding. Anonymous viewers are tracked by session ID; authenticated users are tracked by user ID.

Tracked Events

Event Fired When
play Playback starts or resumes
pause Playback is paused
seek User jumps to a new position
complete Playback reaches the end of the video
buffer Player enters a buffering state

Events are written to the mvs_play_events database table.

Database Table: mvs_play_events

Column Type Description
id bigint Auto-increment primary key
media_id bigint The mvs_media post ID
user_id bigint WordPress user ID, or 0 for anonymous
session_id varchar Unique session identifier
event varchar Event type: play, pause, seek, complete, buffer
position float Playback position in seconds when the event fired
created_at datetime UTC timestamp

Settings

Option Option Key Default Description
Data Retention mvs_pro_analytics_retention_days 365 Days to keep raw event rows before pruning

Go to Media > Settings > Analytics to change the retention period. A daily WP-Cron job deletes rows older than the configured limit.

Analytics settings panel showing retention days field

REST API

Base URL: /wp-json/mvs-pro/v1/

POST /media/{id}/analytics/event

Record a single play event. Authentication is not required - anonymous events are accepted.

Body:

{
  "event": "play",
  "position": 0,
  "session_id": "abc123xyz"
}

session_id should be a stable string generated client-side per browser session (e.g. a UUID stored in sessionStorage).

Rate limiting: Returns 429 Too Many Requests if more than one event per second is received for the same session.

Response: 204 No Content.

GET /media/{id}/analytics/heatmap

Get the heatmap data for a video: how often each second of the video has been viewed.

Response:

{
  "media_id": 123,
  "duration": 360,
  "heatmap": [42, 41, 40, 39, 38, 12, 12, 11, ...]
}

heatmap is an array where each index is a second of the video and the value is the view count for that second.

GET /media/{id}/analytics/retention

Get the audience retention curve: the percentage of viewers still watching at each point in the video.

Response:

{
  "media_id": 123,
  "retention": [100, 98, 97, 95, 60, 58, ...]
}

Each value is a percentage (0-100). Index 0 always starts at 100.

GET /media/{id}/analytics/dashboard

Get aggregated statistics for a media item. Requires ownership or moderate_mvs_media.

Response:

{
  "media_id": 123,
  "total_plays": 310,
  "unique_viewers": 205,
  "completion_rate": 0.42,
  "avg_watch_time": 148,
  "peak_concurrent": 12
}

avg_watch_time is in seconds. completion_rate is a decimal between 0 and 1.

Viewing Analytics in WP Admin

Open a media item in Media > All Media, then click the Analytics tab in the edit screen. You can see the retention curve, heatmap, and summary statistics for the last 7, 30, or 90 days.

Analytics tab on the media edit screen with retention curve

AI Providers

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

WPMediaVerse Pro adds Google Cloud Vision and AWS Rekognition as AI analysis providers alongside the built-in OpenAI Vision option. All three providers support auto-tagging and content moderation.

AI and Moderation settings tab showing provider selector

Supported Providers

Provider Auto-Tag Content Moderation Configuration Location
OpenAI Vision (free + Pro) Yes Yes Media > Settings > AI & Moderation
Google Cloud Vision Yes Yes Media > Settings > AI & Moderation > Google Vision
AWS Rekognition Yes Yes Media > Settings > AI & Moderation > AWS Rekognition

Only one provider is active at a time. Select it under Media > Settings > AI & Moderation > Provider.


Google Cloud Vision

Requirements

  • A Google Cloud project with the Cloud Vision API enabled
  • An API key or service account with roles/cloudvision.user

Settings

Option Option Key Description
Vision API Key mvs_pro_google_vision_key Google Cloud API key

Google Vision settings section with API key field

What Google Vision Returns

  • Labels: Object and scene descriptions, mapped to mvs_tag terms when Auto-Apply Tags is on
  • Safe Search: Likelihood scores for adult, violence, racy, medical, and spoof categories
  • Web Detection: Public web entities that match the image (used as supplemental tags)

AWS Rekognition

Requirements

  • AWS account with the Rekognition service enabled in your chosen region
  • IAM user or role with rekognition:DetectLabels and rekognition:DetectModerationLabels permissions

Settings

Option Option Key Description
Access Key ID mvs_pro_aws_access_key AWS IAM access key ID
Secret Access Key mvs_pro_aws_secret_key AWS IAM secret access key
Region mvs_pro_aws_region AWS region, e.g. us-east-1

AWS Rekognition settings section

What Rekognition Returns

  • Labels: Hierarchical object and scene labels with confidence scores, mapped to mvs_tag terms
  • Moderation Labels: Content categories (nudity, violence, drugs, etc.) with confidence scores used by the moderation threshold settings

Circuit Breaker

WPMediaVerse Pro implements a circuit breaker for all AI providers. If a provider returns 5 consecutive errors (network timeout, invalid API key, rate limit), the circuit opens and no further API calls are made for 5 minutes. This prevents quota exhaustion during outages.

When the circuit is open:

  • New uploads skip AI analysis and are marked with _mvs_ai_status = skipped
  • The circuit half-opens after 5 minutes and retries a single request
  • If the retry succeeds, the circuit closes and normal operation resumes
  • If the retry fails, the circuit stays open for another 5 minutes

You can reset the circuit manually from WP Admin at Media > Settings > AI & Moderation > Reset Circuit Breaker, or via WP-CLI:

wp mvs ai reset-circuit

Auto-Tagging Behaviour

All providers map their returned labels to mvs_tag taxonomy terms. Terms are created if they do not exist. Confidence thresholds control which labels are applied:

Setting Option Key Default Description
Min Tag Confidence mvs_pro_ai_tag_confidence 70 Minimum provider confidence score (0-100) to apply a tag
Max Tags Per Item mvs_pro_ai_max_tags 10 Cap on the number of tags applied per media item

Content Moderation Thresholds

Each safety category has an independent threshold. When a category's confidence score meets or exceeds the threshold, the moderation action configured in When AI Flags Content is applied.

Configure thresholds at Media > Settings > AI & Moderation > Moderation Thresholds.

Moderation threshold sliders for content categories

Quotas

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Limit free users to 50 photos, give premium members unlimited uploads - quota packages let you build a tiered media community where upgrades have real value.

What Users See

When a user goes to upload media, a usage widget appears at the top of the upload page. It shows a progress bar for each limit in their active package: how many images they have used out of their allowed total, the same for video and audio, and overall storage used versus their storage cap. When a limit is reached, the upload form shows a friendly message explaining which limit was hit and how to upgrade.

Upload page with quota usage widget

Use Cases

  • Free tier: 50 photos, 5 videos, 100 MB storage. Upgrade to Pro for unlimited.
  • Club membership: 500 photos, 50 videos, 5 GB storage for paying members.
  • Content creator plan: Unlimited photos and videos, 50 GB storage.

Setting Up Quotas (for Site Owners)

  1. Go to Media > Quota Packages and click Add New Package
  2. Enter a name (e.g., "Free", "Pro Member", "Content Creator")
  3. Set the limits for image count, video count, audio count, and total storage. Use -1 for unlimited
  4. Click Save
  5. Go to the Assignments tab to map the package to a membership level or user role
  6. Set a Default Package at Media > Quota Packages > Settings - this applies to users with no membership

Repeat to create as many packages as your membership tiers require.

Add New Package screen with count and storage limits

Membership Integrations

WPMediaVerse Pro detects active membership plugins and maps their levels to quota packages automatically.

Plugin How to Map
MemberPress Media > Quota Packages > Assignments > MemberPress tab
Paid Memberships Pro Media > Quota Packages > Assignments > PMPro tab
WooCommerce Memberships Media > Quota Packages > Assignments > WooCommerce tab

When a user holds multiple memberships, WPMediaVerse Pro applies the highest limits across each dimension independently.


How Quotas Work (Technical)

When a user uploads a file, WPMediaVerse Pro checks the user's active quota package before accepting the upload. If the upload would exceed any limit in the package, the API returns 403 Forbidden with the error code mvs_quota_exceeded.

Users with the manage_options capability are never subject to quota limits.

Database Tables

mvs_quota_packages

Stores the quota package definitions.

Column Type Description
id bigint Primary key
name varchar Human-readable package name
max_image_count bigint Maximum image files allowed, -1 for unlimited
max_video_count bigint Maximum video files allowed, -1 for unlimited
max_audio_count bigint Maximum audio files allowed, -1 for unlimited
max_storage_bytes bigint Total storage cap across all file types, -1 for unlimited
created_at datetime UTC creation timestamp

mvs_credit_log

Logs every quota deduction and addition for auditing and rollback on delete.

Column Type Description
id bigint Primary key
user_id bigint WordPress user ID
media_id bigint Related mvs_media post ID
type varchar image, video, or audio
bytes bigint File size in bytes (positive = deduct, negative = restore)
reason varchar upload, delete, admin_adjust
created_at datetime UTC timestamp

REST API

Base URL: /wp-json/mvs-pro/v1/

GET /quota/user/{id}

Get the current quota usage and limits for a user. Requires ownership or manage_options.

Response:

{
  "user_id": 5,
  "package": "Pro Member",
  "usage": {
    "image_count": 48,
    "video_count": 3,
    "audio_count": 1,
    "storage_bytes": 524288000
  },
  "limits": {
    "max_image_count": 500,
    "max_video_count": 50,
    "max_audio_count": 50,
    "max_storage_bytes": 5368709120
  }
}

storage_bytes and max_storage_bytes are raw byte values. Divide by 1073741824 to display in GB.

GET /quota/packages

List all quota packages. Requires manage_options.

Response:

{
  "packages": [
    {
      "id": 1,
      "name": "Free",
      "max_image_count": 50,
      "max_video_count": 5,
      "max_audio_count": 5,
      "max_storage_bytes": 524288000
    }
  ]
}

POST /quota/adjust

Manually adjust a user's usage (for migrations or corrections). Requires manage_options.

Body:

{
  "user_id": 5,
  "type": "image",
  "bytes": -2097152,
  "reason": "admin_adjust"
}

A negative bytes value restores storage credit. A positive value deducts it.

Response: 200 OK with the updated usage object.

Frontend Usage Widget

A usage summary widget appears on the upload page and the user's media dashboard. It shows a progress bar for each limit in the user's active package.

Upload page with quota usage widget

To disable the widget on the upload page:

add_filter( 'mvs_pro_show_quota_widget', '__return_false' );

Advanced Privacy

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

WPMediaVerse Pro extends the free plugin's 6 privacy levels with album-level inheritance, per-user presets, and bulk privacy updates.

Privacy selector on the upload page showing all six levels

Privacy Levels

The six levels available in both free and Pro versions:

Level Value Who Can View
Public public Everyone including logged-out visitors
Members Only members Any logged-in WordPress user
Friends friends BuddyPress friends of the owner (requires BuddyPress)
Group group Members of a specific BuddyPress group (requires BuddyPress)
Private private Only the owner and users with moderate_mvs_media
Custom custom A defined list of user IDs via access grants

Pro adds multi-level inheritance, presets, and bulk management on top of these levels.


Album-Level Inheritance

When a media item is inside an album, it can inherit the album's privacy level instead of using its own. Enable this behaviour per album using the inherit_privacy flag.

When inherit_privacy is true:

  • The media item's own _mvs_privacy value is ignored
  • Access checks use the parent album's privacy level
  • Changing the album's privacy instantly updates visibility for all items that inherit from it

To enable inheritance when creating or updating an album:

curl -X PUT https://yoursite.com/wp-json/mvs/v1/albums/55 \
  -H "X-WP-Nonce: NONCE" \
  -H "Content-Type: application/json" \
  -d '{"inherit_privacy": true, "privacy": "members"}'

Album edit screen showing the Inherit Privacy toggle


REST API

Base URL: /wp-json/mvs-pro/v1/

GET /media/{id}/privacy

Get the current privacy settings for a media item.

Response:

{
  "media_id": 123,
  "privacy": "friends",
  "inherited_from_album": false,
  "album_id": null
}

PUT /media/{id}/privacy

Update the privacy level for a single media item. Requires ownership or edit_others_mvs_media.

Body:

{
  "privacy": "group",
  "group_id": 42
}

group_id is required when privacy is group.

Response: 200 OK with the updated privacy object.

PUT /media/privacy/bulk

Update privacy for multiple media items in one request. Requires ownership of all items or edit_others_mvs_media.

Body:

{
  "media_ids": [101, 102, 103],
  "privacy": "private"
}

Items the authenticated user does not own are skipped. The response lists which IDs were updated and which were skipped.

Response:

{
  "updated": [101, 102],
  "skipped": [103]
}

GET /privacy/presets

List the current user's saved privacy presets.

Response:

{
  "presets": [
    {
      "id": 1,
      "name": "Close friends only",
      "privacy": "friends",
      "is_default": true
    }
  ]
}

POST /privacy/presets

Save a new privacy preset for the current user.

Body:

{
  "name": "Close friends only",
  "privacy": "friends",
  "is_default": true
}

Setting is_default to true makes this preset the pre-selected option on the upload form. Only one preset can be the default; saving a new default clears the flag from the previous one.

Response: 201 Created with the new preset object.


Bulk Privacy Updates

Site administrators can update privacy in bulk from Media > All Media:

  1. Select media items using the checkboxes.
  2. Open the Bulk Actions dropdown.
  3. Select Change Privacy.
  4. Choose the target privacy level and click Apply.

Media list table with bulk actions dropdown

This uses the same PUT /media/privacy/bulk endpoint internally and respects the same ownership rules.


User Privacy Presets

Users can save their preferred privacy level as a named preset from the upload page. The preset they mark as default is automatically selected each time they open the upload form.

Presets are stored as user meta. They are personal and not visible to other users or administrators.

Upload form showing preset selector dropdown


Developer Filter

Use mvs_privacy_can_view (available in the free plugin) to extend access logic. Pro privacy checks run through the same filter:

add_filter( 'mvs_privacy_can_view', function( $result, $media_id, $user_id, $privacy ) {
    // Grant access to users with a custom capability.
    if ( null === $result && user_can( $user_id, 'mvs_vip_access' ) ) {
        return true;
    }
    return $result;
}, 10, 4 );

Return null to let built-in logic run. Return true or false to override it.

Connected Accounts

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Connect an external photo platform to WPMediaVerse and move media in both directions - import existing photos and albums into your media library, and auto-push new uploads back out to that platform. In 1.5.0 the only built-in connector is Flickr, with full import, export, album browsing, and metadata sync support.

The connector framework is pluggable, so additional platforms can be added by developers in the future. This page covers the Flickr connector as it ships today.

What the Flickr Connector Does

Once a member connects their Flickr account, they can:

  • Import photos and videos from their Flickr photostream or from a specific album (set) into WPMediaVerse.
  • Export (auto-push) - automatically copy every new WPMediaVerse upload to their Flickr account.
  • Browse albums - pick a Flickr album to import from or push exports into.
  • Sync metadata - pull the latest title, description, tags, and privacy from Flickr back into the local media record (delta sync).

Privacy is mapped both ways. A Flickr "public/friends/family/private" visibility maps to the matching WPMediaVerse privacy level on import, and the member can choose how WPMediaVerse privacy maps back to Flickr on export.


Two Levels of Setup

There are two separate roles in this feature, and it helps to keep them apart:

  1. Site owner (admin) - enables the feature and optionally provides a plugin-level Flickr app key/secret so members can connect with one click. This is done once, in MediaVerse > Settings.
  2. Member (user) - connects their own Flickr account through an OAuth flow on the same settings page, then imports/exports their photos. Each user authorizes their own account; the connection (access token) is stored per user.

Step 1 - Enable Connected Accounts (Admin)

  1. In WordPress admin, go to MediaVerse > Settings.
  2. In the settings sidebar, open the Connected Accounts tab (under the Advanced group).
  3. Check Enable Platform Connectors feature.
  4. Click Save Changes.

When the feature is disabled, no connectors load and the connector REST endpoints are not registered.


Providing a built-in Flickr API key/secret lets your members connect with a single Connect with Flickr button - they never need their own developer key. This is the smoothest experience for most sites.

Create a Flickr app

  1. Sign in at Flickr and go to flickr.com/services/apps/create.
  2. Choose Apply for a non-commercial key (or commercial, depending on your use).
  3. Give the app a name and description, agree to the API terms, and submit.
  4. Flickr shows you a Key and a Secret - copy both.

Enter the credentials in WPMediaVerse

  1. Back in MediaVerse > Settings > Connected Accounts.
  2. Paste the key into Flickr Plugin API Key.
  3. Paste the secret into Flickr Plugin API Secret.
  4. Click Save Changes.

The secret field is masked. When you re-save the settings page without re-typing it, the stored secret is preserved - an empty submit will not wipe it.

If you leave these two fields empty, the one-click Connect with Flickr button is hidden and members must supply their own Flickr key instead (see "Members using their own key" below).


Step 3 - Connect a Flickr Account (Member)

The connect flow runs on the same Connected Accounts settings tab. Each registered platform shows as a card.

With the plugin-level key (one click)

  1. On the Flickr card, click Connect with Flickr.
  2. You are redirected to Flickr's authorization page. Sign in if needed and click OK, I'll authorize it.
  3. Flickr redirects back to WPMediaVerse. The card now shows Connected as @yourusername.

Members using their own key

If your site has no plugin-level key - or a member wants a dedicated rate limit - they can use their own Flickr app:

  1. On the Flickr card, click Use your own API key (recommended for heavy usage).
  2. Create a Flickr app at flickr.com/services/apps (same steps as above) and copy the Key and Secret.
  3. Enter the Flickr API Key and Flickr API Secret in the expanded fields.
  4. Click Connect with My Key and complete the Flickr authorization.

A member using their own key gets the note "Using: Your own key (dedicated rate limit)" on the connected card; a member on the plugin key sees "Using: Plugin key (shared)".

How the connection is stored

The OAuth authorization returns an access token that is encrypted before being saved to that user's profile. Flickr does not expose a token-revocation endpoint, so disconnecting clears the locally stored token and validation cache. The actual API key/secret never reaches the browser.


Step 4 - Import Photos and Albums

After connecting, the member can browse and import their Flickr media.

  1. On the connected Flickr card, open the import dialog.
  2. Choose to browse the whole photostream or a single album (set).
  3. Select the photos to import.
  4. Confirm the import.

For each photo, WPMediaVerse:

  • Downloads the best available image size from Flickr.
  • Creates a media item in your library through the standard upload pipeline (so quotas, watermarking, and AI features all apply).
  • Copies the title, description, tags, and Flickr privacy (mapped to WPMediaVerse privacy).
  • Records the Flickr photo ID so the same photo is never imported twice - re-importing an already-imported photo is reported as "skipped".

The import result reports per-item outcomes: imported, skipped (already present), or failed (with an error message).


Step 5 - Export and Auto-Push New Uploads

Exporting copies a WPMediaVerse media item up to the member's Flickr account.

Auto-push

On a connected Flickr card, the member can toggle Auto-push new uploads > Enable. With this on, every new WPMediaVerse upload by that member is automatically exported to Flickr. When Action Scheduler is available the export runs in the background; otherwise it runs inline at upload time.

Default privacy on Flickr

The card has a Default privacy on Flickr selector that controls how exported media is made visible on Flickr:

Option Effect on Flickr
Match WPMediaVerse Use the media item's own privacy, mapped to Flickr
Public Always public on Flickr
Friends Visible to Flickr friends
Friends + Family Visible to Flickr friends and family
Private Private on Flickr

If a member set a default album, exported photos are also added to that Flickr album. Like import, export deduplicates - a media item already exported to Flickr is reported as "skipped" rather than uploaded again.


Step 6 - Sync Metadata (Delta Sync)

The Flickr connector supports delta sync: pulling the latest title, description, tags, and privacy from Flickr back into the matching local media records. This is useful when a member edits photo metadata on Flickr and wants WPMediaVerse to reflect those edits. Sync only touches media that originated from (or was exported to) Flickr for that member, and records a "last synced" timestamp.


Test the Connection

A connected card has a Test Connection button. It runs a live check against Flickr's identity endpoint (flickr.test.login) and confirms the stored token still works. The result is cached for 15 minutes to avoid hammering the API. If the token has been revoked on Flickr's side, the test reports the failure and the member can reconnect.


Disconnecting

Click Disconnect on a connected card to remove the local connection. This clears the encrypted tokens, the cached account identity, the auto-push and default-privacy preferences, and the validation cache. Because Flickr has no remote revoke endpoint, you may also want to revoke WPMediaVerse from your Flickr account's connected apps page if you want Flickr's side cleared too.


Settings

These options live on MediaVerse > Settings > Connected Accounts and are stored in wp_options.

Setting Option key Default Description
Enable Platform Connectors feature mvs_connectors_enabled 0 (off) Master switch for the whole Connected Accounts feature. When off, no connectors load and the REST endpoints are not registered.
Flickr Plugin API Key mvs_pro_connector_flickr_app_key (empty) Site-wide Flickr app key for one-click member connection. Leave empty to require each member to supply their own.
Flickr Plugin API Secret mvs_pro_connector_flickr_app_secret (empty) Site-wide Flickr app secret. Masked field - an empty re-save keeps the existing value.

Per-member preferences (auto-push toggle, default privacy, the member's own key, and the OAuth tokens) are stored in user meta, not site options, so each member's Flickr connection is independent.


Troubleshooting

The "Connect with Flickr" button is missing. The plugin-level Flickr key/secret are empty. Either add them under Connected Accounts (Step 2) or have the member use Use your own API key instead.

No connector cards appear at all. The feature is off. Enable Platform Connectors feature and save (Step 1).

"Flickr OAuth token mismatch or session expired." The connect flow took longer than the 5-minute request-token window, or it was started in one browser tab and finished in another. Start the connect again from the Flickr card.

Imported photos look low-resolution. WPMediaVerse imports the best size Flickr exposes for that photo. Photos uploaded to Flickr at small sizes, or with download restrictions, may not offer a high-resolution original.

Test Connection still shows a stale result after reconnecting. The validation result is cached for 15 minutes. Disconnecting clears that cache; after a fresh connect the next Test Connection reflects the new token.

Auto-push isn't sending new uploads. Confirm the member has Auto-push new uploads enabled on the connected card, and that the media is owned by that connected member. Background exports rely on WordPress cron / Action Scheduler running on your site.


Developer Notes

The connector framework is pluggable. Each connector implements WPMediaVersePro\Connectors\ConnectorInterface and registers itself on the mvs_connectors filter, keyed by a slug. The REST surface lives under mvs-pro/v1/connectors:

Route Method Purpose
/connectors GET List connectors and per-user connection state
/connectors/{id}/connect POST Start the OAuth flow (returns a redirect URL)
/connectors/{id}/disconnect POST Remove the stored connection
/connectors/{id}/status GET Live connection validation
/connectors/{id}/photos GET Browse remote photos (paginated)
/connectors/{id}/albums GET Browse remote albums
/connectors/{id}/import POST Import remote photos by ID
/connectors/{id}/export POST Export local media by ID
/connectors/{id}/sync POST Delta-sync metadata for linked media

Read and initiate routes require the user to be logged in; browse, import, export, and sync routes require the user to be connected to that connector.

Gamification

Photo challenges, battles, tournaments, points, and streaks.

Gamification Overview

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

WPMediaVerse Gamification turns your media community into a competitive platform. Users earn XP, enter photo competitions, challenge each other to battles, and boost their media visibility - all integrated with the wb-gamification plugin's XP and reward engine.

Gamification requires WPMediaVerse Pro and the wb-gamification plugin (active and configured). All gamification features are disabled by default. Enable each feature individually at Media > Settings > Gamification.

Gamification overview in WPMediaVerse admin settings

Requirements

  • WPMediaVerse Pro 1.0.0+
  • wb-gamification plugin (active)
  • WordPress 6.5+
  • PHP 7.4+

Feature Types

Feature Description Setting Key
Photo Challenges Themed weekly competitions with voting mvs_challenges_enabled
Photo Battles 1v1 head-to-head media matchups mvs_battles_enabled
Tournaments Single-elimination bracket competitions mvs_tournaments_enabled
Media Boosts Spend points to increase media visibility mvs_boosts_enabled
Upload Streaks Track consecutive daily uploads mvs_streaks_enabled

Competition Types

All three competition types share a unified database schema. Each type follows its own lifecycle, but they share the same tables.

Challenges

Themed weekly competitions. Admin creates a challenge with a theme, entry window, and voting window. Users submit a photo, community votes, winners earn XP prizes.

Battles

1v1 matchups. A challenger picks a photo and invites an opponent. Both submit photos, the community votes, the system resolves the winner.

Tournaments

Single-elimination brackets for 4 to 64 participants. Users register, the system seeds the bracket, rounds proceed by community vote until a winner is determined.

Database Schema

Table Purpose
mvs_competitions Master record for all challenges, battles, and tournaments
mvs_competition_entries User photo submissions for any competition type
mvs_competition_matches Individual head-to-head pairings (battles and tournament rounds)
mvs_competition_votes Per-user votes on entries or matches
mvs_boosts Active and expired media boost records

The mvs_competitions.type column distinguishes between challenge, battle, and tournament records.

XP Integration

WPMediaVerse registers 14 gamification actions with the wb-gamification manifest. These actions fire automatically as users interact with competitions.

wb-gamification manifest showing WPMediaVerse actions and XP values

XP is awarded via do_action( 'wbgam_award_xp', $user_id, $action_slug, $reference_id ). The manifest file ships with the plugin and can be filtered using mvs_gamification_manifest.

Frontend Pages

URL Description
/media/challenges/ Browse and enter photo challenges
/media/battles/ View active and completed battles
/media/tournaments/ Browse tournaments and view brackets
/compete/ Unified competition hub showing all active competitions

The My Media dashboard adds Challenges, Battles, and Tournaments tabs showing the logged-in user's entries, results, and active matches.

My Media dashboard with competition tabs

Scheduled Actions

All competition lifecycle transitions run via Action Scheduler on an hourly recurrence. No competition state changes happen in real time - transitions occur at the next hourly tick after a deadline passes.

Scheduled Action Trigger Condition
mvs_activate_challenges Challenge start date reached
mvs_close_challenge_entries Entry deadline reached
mvs_finalize_challenges Voting deadline reached
mvs_resolve_expired_battles Battle vote deadline reached
mvs_start_tournaments Tournament registration deadline reached
mvs_resolve_tournament_matches Match vote deadline reached
mvs_expire_boosts Boost impression target or duration reached
mvs_check_streaks Daily at 2 AM - break streaks for missed uploads

Action Scheduler must be running for gamification to function. If you use a managed host that blocks WP-Cron, configure Action Scheduler with a server-level cron trigger.

Photo Challenges

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Run weekly themed photo competitions - your community submits their best shots, votes for their favorites, and the top three photographers win XP prizes.

What You Can Do (as a User)

  • Browse active challenges and see the current theme
  • Enter your best photo for any active challenge
  • Vote for your favorite entries during the voting window
  • Track your standing and see the final rankings with winner badges
  • Earn XP for participating, even if you do not place in the top three

How It Works (for Users)

  1. Go to Media > Challenges on your site
  2. Click the Active tab to see the current challenge theme (e.g., "Golden Hour Photography")
  3. Click Enter Challenge - a media picker opens showing your uploaded photos
  4. Select one of your photos or upload a new one, add an optional caption, and click Submit
  5. Your entry appears in the challenge gallery alongside other participants
  6. When the entry window closes, the challenge moves to the Voting tab
  7. Vote for your single favorite entry - you can only vote once per challenge
  8. When voting closes, open the Finalized tab to see the ranked results
  9. Winner badges (1st, 2nd, 3rd) appear on the top entries and XP is awarded automatically to your account

Photo Challenges frontend page showing active challenge

For Site Owners

  1. Go to Media > Settings > Gamification and enable Photo Challenges
  2. Go to Media > Competitions > Challenge Manager and click Add Challenge
  3. Set a theme title, entry start date, entry end date, and voting end date
  4. Set XP prizes for 1st, 2nd, 3rd place and a participation XP amount for all entrants
  5. Click Save - the challenge appears on the frontend when the start date arrives
  6. To run challenges on autopilot without manual creation, enable Autopilot in the settings (see below)

Challenge Manager create form

Lifecycle

A challenge moves through four stages. All transitions are handled by Action Scheduler on an hourly schedule.

Stage Description
Scheduled Challenge is published but the start date has not arrived
Active Entry window is open - users can submit photos
Voting Entry window closed - community can vote on submissions
Finalized Voting closed - winners determined, XP awarded

Creating a Challenge

Go to Media > Competitions > Challenge Manager and click Add Challenge.

Challenge Manager create form

Field Description
Theme Title or topic for the challenge (e.g., "Golden Hour Photography")
Theme Library Pick from 25+ pre-built themes instead of writing one manually
Entry Start Date and time when photo submissions open
Entry End Date and time when submissions close
Voting End Date and time when voting closes
Max Entries Per User How many photos one user may submit
XP - 1st Place XP awarded to the top vote-getter
XP - 2nd Place XP awarded to the second-highest vote-getter
XP - 3rd Place XP awarded to the third-highest vote-getter
XP - Participation XP awarded to all entrants when the challenge finalizes

Autopilot

Autopilot creates and schedules challenges automatically so you do not need to create them manually each week.

Setting Key Description
Enable Autopilot mvs_autopilot_enabled Automatically create a new challenge when the current one enters Voting stage
Autopilot Day mvs_autopilot_day Day of the week to start each new challenge (0 = Sunday, 6 = Saturday)
Autopilot Hour mvs_autopilot_hour Hour of day (0-23, site timezone) to open entries

When autopilot runs, it picks the next unused theme from the Theme Library. Once all themes are used, it cycles back to the first theme.

Theme Library

The Theme Library ships with 25+ pre-built challenge themes. Go to Media > Competitions > Theme Library to browse, add, edit, or disable themes.

Theme Library grid showing theme cards with categories

Themes are categorized (Nature, Urban, Portrait, Abstract, etc.). You can add custom themes and assign them to any category.

Settings Reference

Setting Key Default
Enable Photo Challenges mvs_challenges_enabled Off
Enable Autopilot mvs_autopilot_enabled Off
Autopilot Day mvs_autopilot_day 1 (Monday)
Autopilot Hour mvs_autopilot_hour 9

REST API

Base URL: /wp-json/mvs-pro/v1/challenges

Method Endpoint Description
POST /challenges Create a new challenge. Requires manage_options.
GET /challenges/{id} Get challenge details including stage, entry count, and dates
POST /challenges/{id}/enter Submit a photo entry. Requires authentication.
GET /challenges/{id}/entries List submitted entries with vote counts
POST /challenges/{id}/vote Cast a vote for an entry. One vote per user.

POST /challenges/{id}/enter

{
  "media_id": 456,
  "caption": "Optional entry caption"
}

Returns 409 Conflict if the user has already reached max_entries_per_user for this challenge.

POST /challenges/{id}/vote

{
  "entry_id": 789
}

Returns 403 Forbidden if the challenge is not in Voting stage. Returns 409 Conflict if the user already voted.

Frontend Behavior

The /media/challenges/ page displays challenges in three tabs: Active, Voting, and Finalized.

Challenges page with tab navigation and challenge cards

  • Active tab - Shows the entry submission form when the user is logged in and has not yet reached the entry limit
  • Voting tab - Shows all entries as a grid with vote buttons; the user's vote is highlighted after casting
  • Finalized tab - Shows entries ranked by votes with winner badges for positions 1, 2, and 3

The media picker in the entry form lets users select from their existing uploaded media or upload a new photo directly.

Challenge entry submission form

Scheduled Actions

Action Hook Condition
mvs_activate_challenges Runs hourly - sets Scheduled challenges to Active when start date is past
mvs_close_challenge_entries Runs hourly - sets Active challenges to Voting when entry deadline is past
mvs_finalize_challenges Runs hourly - sets Voting challenges to Finalized, tallies votes, awards XP

Photo Battles

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Challenge any photographer on the site to a head-to-head photo duel - your best shot vs. theirs, side by side, with the community deciding the winner.

What You Can Do (as a User)

  • Challenge any member to a 1v1 photo battle directly from a media item page
  • Pick your strongest photo as your battle entry
  • Accept or decline incoming battle challenges from your notifications
  • Watch the live vote count during the voting period
  • See your win/loss record on your media dashboard

How It Works (for Users)

Challenging Someone

  1. Open any of your uploaded photos on the site
  2. Click Challenge to Battle
  3. Search for a member by username to challenge them
  4. Your photo is set as your battle entry and the invite is sent
  5. Your opponent receives a notification to accept or decline

After the Challenge is Accepted

  1. Both you and your opponent have 48 hours to confirm or change your battle photo
  2. Choose one photo from your uploads - click Submit My Photo
  3. Once both photos are submitted, the battle opens for community voting
  4. The VS layout shows both photos side by side with a vote button under each
  5. Any logged-in member (except participants) can cast one vote
  6. When the voting period ends, the winner is announced automatically and XP is awarded

Photo Battles frontend page showing VS layout

Viewing Your Battles

Go to My Media > Battles to see all your battles with win/loss/pending status. Click any battle to see the full vote breakdown.

For Site Owners

  1. Go to Media > Settings > Gamification and enable Photo Battles
  2. Battles are self-service - users challenge each other directly, no admin involvement required
  3. Submit and vote windows are currently 48 hours each
  4. Monitor all active battles from Media > Competitions

Lifecycle

Stage Description
Pending Challenger sent the invite - waiting for opponent response
Accepted Opponent accepted - both users can now submit their battle photo
Submitting Both users have 48 hours (default) to submit photos
Voting Both photos submitted - community votes for 48 hours (default)
Resolved Vote deadline passed - winner determined, XP awarded
Declined Opponent declined the challenge - battle closed
Expired Submit or vote deadline passed with incomplete participation

Starting a Battle

From any media item page, click Challenge to Battle. You can also start a battle from your My Media dashboard.

Media item page with Challenge to Battle button

  1. Select the photo you want to use as your battle entry
  2. Search for and select your opponent by username
  3. Submit the challenge invite

The opponent receives a WPMediaVerse notification and, if BuddyPress notifications are active, a BuddyPress notification.

Submitting a Photo

After the opponent accepts, both users have 48 hours to submit their battle photo. Each participant selects one photo from their media library or uploads a new one.

If either participant does not submit within the deadline, the battle expires and no XP is awarded.

Voting

Once both photos are submitted, the battle moves to Voting. Any logged-in user (not just participants) can vote for one photo. Participants cannot vote in their own battle.

Votes are recorded in mvs_competition_votes. Each user can cast one vote per battle.

Settings Reference

Setting Key Default
Enable Photo Battles mvs_battles_enabled Off

Submit and vote deadlines are currently fixed at 48 hours each. Configurable deadline settings are planned for a future release.

REST API

Base URL: /wp-json/mvs-pro/v1/battles

Method Endpoint Description
POST /battles Create a new battle challenge
GET /battles/{id} Get battle details, stage, and vote counts
POST /battles/{id}/accept Accept a battle invite. Opponent only.
POST /battles/{id}/decline Decline a battle invite. Opponent only.
POST /battles/{id}/submit Submit your battle photo
POST /battles/{id}/vote Cast a vote for a photo in this battle

POST /battles

{
  "challenger_media_id": 456,
  "opponent_user_id": 99
}

Returns 403 Forbidden if battles are disabled. Returns 400 Bad Request if the challenger does not own the specified media.

POST /battles/{id}/submit

{
  "media_id": 501
}

Returns 403 Forbidden if the authenticated user is not a participant in this battle. Returns 409 Conflict if this participant already submitted.

POST /battles/{id}/vote

{
  "entry_id": 789
}

Returns 403 Forbidden if the battle is not in Voting stage, or if the voter is a participant.

Frontend Behavior

The /media/battles/ page shows all battles visible to the logged-in user.

Battles browse page showing battle cards with VS layout

  • Active battles - Shows the VS card layout with both photos and vote buttons if the battle is in Voting stage
  • Pending battles - Shows a card with the invite status and accept/decline buttons for the opponent
  • Resolved battles - Shows final vote counts with a winner badge

The My Media > Battles tab shows only the user's own battles with win/loss/pending status.

My Media Battles tab showing battle history

Scheduled Actions

Action Hook Condition
mvs_resolve_expired_battles Runs hourly - resolves battles where the vote deadline has passed; expires battles where the submit deadline passed without both submissions

Tournaments

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Enter a single-elimination bracket competition - submit your best photo, survive each round of community voting, and claim the championship title.

What You Can Do (as a User)

  • Register for an open tournament with one of your photos
  • Watch the bracket fill up as other photographers register
  • Vote in active round matches - any member can vote (except in their own match)
  • Track your bracket position round by round
  • Earn XP for every match you win and a large XP prize if you win the tournament

How It Works (for Users)

Registering

  1. Go to Media > Tournaments on your site
  2. Find a tournament in the Registration stage and click Register
  3. Pick one photo from your uploads to represent you in the tournament
  4. Click Submit Registration - your photo appears in the participant list
  5. Once registration closes, the bracket is generated automatically

Following the Bracket

  1. Open the tournament detail page to see the full bracket visualization
  2. Completed rounds are shown in grey; the active round's matches are highlighted
  3. Each matchup shows two photos side by side with the current vote count
  4. Click Vote on the photo you think should advance - you get one vote per match
  5. You cannot vote in a match where you are a participant
  6. When all matches in a round are resolved, the next round opens automatically

What Happens If You Win or Lose

  • Win a match: You advance to the next round and earn Round Win XP
  • Lose a match: You are eliminated but keep any XP already earned
  • Win the tournament: You earn the Winner XP prize and a tournament champion badge
  • Reach the final but lose: You earn the Runner-Up XP prize

Tournament bracket visualization showing round progression

For Site Owners

  1. Go to Media > Settings > Gamification and enable Tournaments
  2. Go to Media > Competitions > Tournament Manager and click Add Tournament
  3. Set the title, bracket size (4, 8, 16, 32, or 64 participants), registration window, and match vote duration
  4. Set XP prizes for the winner, runner-up, and each round win
  5. Click Save - the tournament appears on the frontend when registration opens
  6. The bracket generates automatically when registration closes

Tournament Manager create form

Bracket Sizes

Supported sizes: 4, 8, 16, 32, 64 participants.

If registrations do not fill the bracket exactly, the system assigns byes to the highest-seeded empty slots. Bye recipients automatically advance to the next round without a match.

Seeding is random at bracket generation time.

Lifecycle

Stage Description
Registration Tournament is open - users can register with a photo
In Progress Registration closed - bracket generated, rounds underway
Finals Only two participants remain
Complete Winner determined, XP awarded to all placers

The system generates the bracket when the registration deadline passes (hourly Action Scheduler check). Each round's matches open for voting simultaneously. A new round begins after all matches in the current round are resolved.

Creating a Tournament

Go to Media > Competitions > Tournament Manager and click Add Tournament.

Tournament Manager create form

Field Description
Title Tournament name displayed to users
Bracket Size Maximum participants: 4, 8, 16, 32, or 64
Registration Opens Date and time users can start registering
Registration Closes Deadline for registrations - bracket generates after this
Match Vote Duration How long each round's matches are open for votes (in hours)
XP - Winner XP awarded to the tournament winner
XP - Runner-Up XP awarded to the finalist who loses
XP - Round Win XP awarded each time a participant wins a match

Settings Reference

Setting Key Default
Enable Tournaments mvs_tournaments_enabled Off

REST API

Base URL: /wp-json/mvs-pro/v1/tournaments

Method Endpoint Description
POST /tournaments Create a tournament. Requires manage_options.
GET /tournaments/{id} Get tournament details, stage, and participant count
POST /tournaments/{id}/register Register for a tournament with a photo
GET /tournaments/{id}/bracket Get the full bracket structure with all matches and results
POST /tournaments/{id}/vote Cast a vote in an active tournament match

POST /tournaments/{id}/register

{
  "media_id": 456
}

Returns 409 Conflict if the user is already registered. Returns 400 Bad Request if registration is closed or the bracket is full.

GET /tournaments/{id}/bracket

Returns the full bracket as a nested structure by round.

{
  "tournament_id": 12,
  "size": 16,
  "current_round": 2,
  "rounds": [
    {
      "round": 1,
      "matches": [
        {
          "match_id": 101,
          "entry_a": { "entry_id": 5, "media_id": 456, "user_id": 11, "votes": 14 },
          "entry_b": { "entry_id": 9, "media_id": 502, "user_id": 22, "votes": 8 },
          "winner_entry_id": 5,
          "status": "resolved"
        }
      ]
    }
  ]
}

POST /tournaments/{id}/vote

{
  "match_id": 201,
  "entry_id": 789
}

Returns 403 Forbidden if the match is not in the current active round. Returns 409 Conflict if the user already voted in this match. Participants cannot vote in matches they are part of.

Frontend Behavior

The /media/tournaments/ page lists all tournaments with their current stage and participant count.

Tournaments browse page showing tournament cards

Clicking a tournament opens the detail page with the bracket visualization. Active matches show vote buttons directly inside the bracket.

Tournament detail page with bracket

  • Registration stage - Shows a Register button and the current participant count relative to bracket size
  • In Progress - Shows the bracket with completed rounds greyed out and active round matches highlighted
  • Complete - Shows the full bracket with the winner highlighted at the top

The My Media > Tournaments tab shows tournaments the user is registered in, with their current bracket position.

Bye Handling

When registrations do not fill the bracket exactly, byes are assigned before round 1 begins. A bye appears in the bracket as an automatic win - the real participant advances and their slot shows "Bye" for the opposing side. Bye participants do not earn a Round Win XP award.

Scheduled Actions

Action Hook Condition
mvs_start_tournaments Runs hourly - generates brackets when registration deadline passes
mvs_resolve_tournament_matches Runs hourly - resolves matches where the vote deadline has passed; advances winners to next round; detects when all matches in a round are resolved and opens the next round

Media Boosts

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Spend your earned points to push a photo to the top of the Explore feed - get more eyes on your best work right when you want it seen.

What You Can Do (as a User)

  • Boost any of your photos to increase its visibility in the Explore feed
  • Choose how many impressions you want to buy (e.g., 500 views)
  • See a live point cost preview before committing
  • Track how many impressions your boost has delivered so far
  • Cancel an active boost at any time (points are not refunded)
  • Run boosts on multiple photos simultaneously

How It Works (for Users)

  1. Open one of your uploaded photos
  2. Click Boost - a panel shows your current point balance and the cost per 100 impressions
  3. Enter your target impression count (e.g., 500 impressions costs 250 points at the default rate)
  4. Click Confirm Boost - points are deducted immediately from your balance
  5. Your photo is now marked as boosted and appears at elevated positions in the Explore feed
  6. Return to the photo page anytime to see a progress bar showing impressions delivered vs. target
  7. When the impression target is reached (or after 30 days), the boost expires automatically and your photo returns to its normal organic rank

Media item page showing Boost button

Where Boosted Content Appears

Boosted photos appear throughout the Explore feed at regular intervals mixed in with organic content - they do not all cluster at the top. This means boosted photos reach a wider audience as visitors scroll, rather than just people who see the top of the page.

For Site Owners

  1. Go to Media > Settings > Gamification and enable Media Boosts
  2. Set the Max Impressions Per Boost (default: 5,000) to control how large a boost can be
  3. Set the Cost Per 100 Impressions (default: 50 points) to match your community's point economy
  4. Users must earn points through other gamification activities (uploads, challenges, streaks) before they can boost

How Boosts Work (Technical)

  1. The user clicks Boost on any media item they own
  2. They set an impression target (up to the site maximum)
  3. The system deducts points from their wb-gamification balance
  4. The media item is flagged as boosted in mvs_boosts
  5. The Explore feed query injects boosted items at a higher rank
  6. When the impression counter reaches the target, or the boost duration expires, the boost auto-expires and the media returns to organic ranking

A user can have multiple media items boosted simultaneously. One media item can only have one active boost at a time.

Boost Cost Calculation

Cost is based on the impression target:

cost = ceil( impression_target / 100 ) × mvs_boost_cost_per_100

Example: boosting for 500 impressions at the default cost of 50 points per 100 costs 250 points.

The point deduction fires via wb-gamification's point spend API at the moment the boost is created. If the user does not have enough points, the API returns an error and the boost is not created.

Settings Reference

Setting Key Default Description
Enable Media Boosts mvs_boosts_enabled Off Master toggle for the boosts feature
Max Impressions Per Boost mvs_boost_max_impressions 5000 The highest impression target a user can set
Cost Per 100 Impressions mvs_boost_cost_per_100 50 Points deducted per 100 impressions purchased

Database Table: mvs_boosts

Column Type Description
id bigint Primary key
media_id bigint The boosted media item
user_id bigint User who created the boost
target_impressions int Impression target set at boost creation
current_impressions int Running impression count
points_spent int Points deducted from user balance
status varchar active or expired
created_at datetime When the boost was created
expires_at datetime Hard expiry datetime (independent of impressions)

REST API

Base URL: /wp-json/mvs-pro/v1/media/{id}/boost

Method Endpoint Description
POST /media/{id}/boost Create a boost for a media item
GET /media/{id}/boost Get the active boost status for a media item
DELETE /media/{id}/boost Cancel an active boost. Points are not refunded.

POST /media/{id}/boost

{
  "target_impressions": 500
}

Returns 400 Bad Request if target_impressions exceeds mvs_boost_max_impressions. Returns 402 Payment Required if the user does not have enough points. Returns 409 Conflict if the media item already has an active boost.

GET /media/{id}/boost

{
  "active": true,
  "target_impressions": 500,
  "current_impressions": 143,
  "points_spent": 250,
  "expires_at": "2026-04-07T09:00:00Z"
}

Returns { "active": false } if no active boost exists.

Explore Feed Injection

Boosted media is injected into the Explore feed by a filter on the WPMediaVerse explore query. The injected items appear at regular intervals (every N organic items) rather than clustering at the top. The injection interval is not currently user-configurable.

Impression counts increment server-side when a boosted item is rendered in the feed. Impressions are tracked per page load, not per unique user.

Expiry

A boost expires when either condition is met:

  • current_impressions reaches target_impressions
  • The current time passes expires_at

The hard expiry datetime is set to 30 days after boost creation, regardless of the impression target.

Scheduled Actions

Action Hook Condition
mvs_expire_boosts Runs hourly - sets status = expired on boosts where the impression target is reached or expires_at has passed

Upload Streaks

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

Upload one photo a day and watch your streak grow - hit milestones to earn XP rewards, and use freeze tokens to protect your streak on days you miss.

What You Can Do (as a User)

  • Build a streak simply by uploading at least one photo or video each day
  • See your current streak count and your all-time best streak on your dashboard
  • Earn XP automatically when you hit streak milestones: 7 days, 30 days, 100 days, 365 days
  • Use freeze tokens to skip one missed day without losing your streak
  • Show off your streak badge next to your username across the site

How It Works (for Users)

Building Your Streak

  1. Upload any photo or video on your site - that counts as your streak day
  2. Your streak counter increments once per calendar day, no matter how many files you upload
  3. Come back the next day and upload again to keep your streak alive
  4. Your streak count and a flame badge appear on your profile and next to your username in comments

Streak Milestones

When your streak reaches a milestone, XP is awarded automatically to your account:

Milestone XP Awarded
7 days 50 XP
30 days 250 XP
100 days 1,000 XP
365 days 5,000 XP

Each milestone is awarded only once. If your streak breaks and you rebuild to 7 days again, no additional XP is awarded for that milestone.

User dashboard showing streak badge with flame icon

Using Freeze Tokens

If you miss a day, a freeze token automatically protects your streak. One token covers one missed day. If you have no tokens and miss a day, your streak resets to zero (your all-time best is never reset).

Freeze tokens are earned through other gamification rewards on the site. Admins can also grant them manually.

Where Your Streak Badge Appears

Once your streak reaches 3 days, a badge showing your streak count appears:

  • Next to your username on every media card you post
  • Next to your name in comments
  • On your profile page
  • In the member directory

For Site Owners

  1. Go to Media > Settings > Gamification and enable Upload Streaks
  2. Streaks run automatically - no manual management needed
  3. Grant freeze tokens to specific users from Users > Edit User > Streak Tokens in wp-admin
  4. The daily streak check runs at 2 AM site timezone via Action Scheduler - confirm Action Scheduler is processing jobs

How Streaks Work (Technical)

A streak increments by 1 when a user uploads at least one media item on a calendar day (site timezone). If the user uploads multiple times on the same day, the streak still only increments once.

If a user misses a day with no upload, the mvs_check_streaks cron job (runs daily at 2 AM, site timezone) resets their current streak to 0. The longest streak value is never reset.

A streak freeze token skips one missed day. If the user has a freeze token and misses a day, the cron job consumes one token and does not reset the streak.

User Meta Keys

Meta Key Type Description
_mvs_current_streak int Number of consecutive days with at least one upload
_mvs_longest_streak int The user's all-time highest streak
_mvs_last_upload_date string YYYY-MM-DD of the user's most recent upload (site timezone)
_mvs_streak_freezes int Number of unused freeze tokens

Milestone XP Rewards

XP is awarded once per milestone. If a user reaches day 30, they receive the 7-day and 30-day rewards. If their streak later breaks and they rebuild to day 7, no XP is awarded again for that milestone.

Milestone awards are tracked via a separate user meta key to prevent duplicate payouts.

Milestone XP Awarded
7 days 50 XP
30 days 250 XP
100 days 1,000 XP
365 days 5,000 XP

Streak milestone XP award notification

Streak Freeze Tokens

Freeze tokens are earned through wb-gamification rewards or granted manually by an admin. Users cannot purchase them directly with points.

When the mvs_check_streaks cron runs and finds a user missed yesterday:

  1. Check _mvs_streak_freezes
  2. If count is greater than 0 - decrement by 1, leave streak intact
  3. If count is 0 - reset _mvs_current_streak to 0

A freeze only protects against a single missed day. Two consecutive missed days break the streak even with a freeze token.

Streak Badge

A streak badge displays next to the username in media cards, comments, and the member directory when the user has an active streak of 3 days or more. The badge shows the current streak count.

Media card showing username with streak badge

The badge is suppressed if the user's streak is 0 or if the streaks feature is disabled.

Settings Reference

Setting Key Default
Enable Upload Streaks mvs_streaks_enabled Off

Scheduled Actions

Action Hook Schedule Description
mvs_check_streaks Daily at 2 AM (site timezone) Compares _mvs_last_upload_date to yesterday for every user with a streak greater than 0. Applies freezes or resets streaks. Awards XP for newly reached milestones.

The daily streak check runs via Action Scheduler, not WP-Cron. If Action Scheduler is not processing jobs, streaks will not break or award XP on time.

Gamification Admin Dashboard

Requires WPMediaVerse Pro - This feature is available exclusively in the Pro version.

The Gamification admin area is accessible at Media > Competitions. It provides a unified view of all active and pending competitions, plus dedicated managers for challenges, tournaments, and battles.

Gamification admin screens only appear when at least one gamification feature is enabled in Settings.

Competitions admin page showing the main table

Competitions Overview Table

The main Competitions page lists every challenge, battle, and tournament in a single table.

Column Description
Title Competition name - click to open the detail view
Type Challenge, Battle, or Tournament
Status Current lifecycle stage with a color-coded badge
Entries Number of participants or entries submitted
Created Date the competition was created
Start / End Active date range
Actions Quick action buttons for the row

Quick Actions:

  • View - Opens the frontend competition page in a new tab
  • Edit - Opens the admin edit form for this competition
  • Finalize - Manually force a competition to the Finalized/Resolved stage. Use this when the scheduled action has not run yet or to end a competition early.

You can filter the table by Type and Status using the dropdowns above the table.

Competitions table filter controls

Challenge Manager

Go to Media > Competitions > Challenge Manager to create and edit photo challenges.

Challenge Manager list view

The Challenge Manager list shows all challenges sorted by start date descending. Click Add Challenge to open the create form, or click any challenge title to edit it.

From the edit form you can:

  • Change the theme, dates, and XP prize amounts for a challenge that has not yet started
  • View the current entry count for a challenge that is Active or in Voting
  • Manually trigger finalization before the voting deadline

You cannot edit entry windows or XP prizes after a challenge reaches Active stage. Changing dates for an Active challenge requires manually editing the database.

Theme Library

Click Media > Competitions > Theme Library to manage the pool of challenge themes used by Autopilot.

Theme Library admin page with category filter

Column Description
Theme Name The challenge title displayed to users
Category Organizational category (Nature, Urban, Portrait, etc.)
Used Count How many times Autopilot has used this theme
Status Active (available to Autopilot) or Disabled

Add a custom theme by clicking Add Theme. Set the name, category, and optional description. Mark a theme as Disabled to exclude it from Autopilot selection without deleting it.

Tournament Manager

Go to Media > Competitions > Tournament Manager to create and manage tournaments.

Tournament Manager showing tournament list

From the Tournament Manager you can:

  • Create a new tournament with bracket size, dates, and XP prizes
  • View the generated bracket for any in-progress tournament
  • Manually advance a round if the scheduled action has not yet run
  • View per-match vote counts and participant photos

Clicking a tournament title opens the admin bracket view, which mirrors the frontend bracket but adds vote count detail and a manual resolve button per match.

Admin bracket view for a tournament

Battle Monitor

Go to Media > Competitions > Battle Monitor to oversee all active battles.

Battle Monitor table showing active battles

The Battle Monitor table includes every battle that is not in a terminal state (Declined, Expired, Resolved). Columns show:

Column Description
Challenger Username and photo thumbnail
Opponent Username and photo thumbnail
Stage Current stage (Pending, Accepted, Submitting, Voting)
Votes Vote count for each side (visible once in Voting stage)
Submit Deadline When the submit window closes
Vote Deadline When the vote window closes
Actions Resolve (force resolution) or Delete

Resolve disputes - If a battle is stuck due to a bug or edge case, use the Resolve button to manually set a winner or mark the battle as expired.

Gamification Settings Page

Go to Media > Settings > Gamification to configure all gamification features.

Gamification settings page showing feature toggles

Feature Toggles

Toggle Setting Key What It Controls
Enable Photo Challenges mvs_challenges_enabled Shows the challenges frontend page and enables the REST API routes
Enable Photo Battles mvs_battles_enabled Shows the battles frontend page and enables the REST API routes
Enable Tournaments mvs_tournaments_enabled Shows the tournaments frontend page and enables the REST API routes
Enable Media Boosts mvs_boosts_enabled Shows Boost buttons on media items and enables the REST API routes
Enable Upload Streaks mvs_streaks_enabled Activates streak tracking on media upload and the daily cron check

Autopilot Configuration

Setting Key Default
Enable Autopilot mvs_autopilot_enabled Off
Autopilot Day mvs_autopilot_day 1 (Monday)
Autopilot Hour mvs_autopilot_hour 9

XP Reward Amounts

These fields set the default XP prizes applied when creating new challenges and tournaments. Existing competitions are not affected when you change these values.

Field Description
Challenge 1st Place XP Default XP for challenge winner
Challenge 2nd Place XP Default XP for challenge runner-up
Challenge 3rd Place XP Default XP for third place
Challenge Participation XP Default XP for all entrants
Tournament Winner XP Default XP for tournament champion
Tournament Runner-Up XP Default XP for tournament finalist
Tournament Round Win XP Default XP per round win

Boost Configuration

Setting Key Default
Max Impressions Per Boost mvs_boost_max_impressions 5000
Cost Per 100 Impressions mvs_boost_cost_per_100 50

Saving the Settings page does not restart any scheduled actions. If you enable a feature that was previously disabled, existing scheduled actions will pick up the new state on the next hourly run.

Developer Guide

REST API, hooks, templates, storage drivers, WP-CLI, and migrations.

REST API Reference

Endpoints and hooks marked (Pro) require WPMediaVerse Pro. Everything documented below ships in the free plugin.

Base URL: /wp-json/mvs/v1/

All routes below use the mvs/v1 namespace (the messaging routes share the same namespace).

Authentication. Reads of public data are open. Every write - and every /me/* route - requires an authenticated user. Pass the X-WP-Nonce header with a nonce generated via wp_create_nonce( 'wp_rest' ) and send cookies with credentials: 'same-origin', or use a WordPress Application Password for non-browser clients.

Authorization model. Three levels are used throughout:

  • Public - no auth; privacy is enforced inside the query so private rows never leak.
  • Authenticated - any logged-in user (is_user_logged_in()).
  • Capability - a specific capability such as upload_mvs_media, moderate_mvs_media, or manage_mvs_access.

Rate limiting. Many routes are throttled per user/IP (the limit is noted where it is unusually tight). Exceeding a limit returns 429 Too Many Requests.


Media

GET /media

Auth: Public (privacy enforced in query). Rate-limited to 120/min.

List media items. Returns only rows the caller is allowed to see.

Parameters:

Parameter Type Default Description
page int 1 Page number
per_page int 20 Items per page (max: 100, filterable via mvs_rest_pagination_max)
media_type string (all) Filter by type: image, video, audio, document
author int (all) Filter by user ID
slug string (none) Fetch a single item by post slug
tag string (all) Filter by mvs_tag slug
category string (all) Filter by mvs_category slug
orderby string date Sort: date, trending, popular (filterable via mvs_feed_sort_options)
scope string public public or all (owner/privileged callers)
s string (none) Full-text search term
group_covers bool false Collapse gallery groups to a single cover item

Response:

{
  "items": [
    {
      "id": 123,
      "title": "Sunset Photo",
      "description": "",
      "media_type": "image",
      "file_url": "https://example.com/wp-content/uploads/wpmediaverse/2025/03/photo.webp",
      "privacy": "public",
      "author_id": 1,
      "album_id": null,
      "views": 42,
      "reactions_count": 5,
      "comments_count": 2,
      "created_at": "2025-03-27T12:00:00Z"
    }
  ],
  "total": 50,
  "pages": 5
}

POST /media

Auth: Capability - upload_mvs_media (or manage_options).

Upload a new media file.

Body (multipart/form-data):

Field Required Description
file Yes The file to upload
title No Media title (defaults to filename)
description No Text description
privacy No Privacy level (default: site setting)
group_id No BuddyPress group ID (required when privacy=group)
album_id No Add to this album after upload
is_story No true to mark as a story

Response: 201 Created with the new media object.

GET /media/{id}

Auth: Public (privacy check in permission callback). Returns 403 if the caller cannot view it, 404 if it does not exist.

Get a single media item.

PUT /media/{id}

Auth: Capability - owner with edit_mvs_medias, or edit_others_mvs_medias.

Update a media item.

Body (JSON):

{
  "title": "Updated Title",
  "description": "Updated description",
  "privacy": "private"
}

DELETE /media/{id}

Auth: Capability - owner with delete_mvs_medias, or delete_others_mvs_medias.

Delete a media item and its stored file.

POST /media/{id}/replace

Auth: Capability - same as PUT /media/{id} (edit permission).

Replace the underlying file of an existing media item while keeping its ID, comments, reactions, and stats. Send the new file as multipart/form-data with a file field.

POST /media/{id}/view

Auth: Public.

Record a view for the item. Increments the view counter and writes a row to mvs_media_views.

POST /media/{id}/download

Auth: Public. Rate-limited to 30/min.

Record a download event and increment mvs_media_stats.downloads. Refused with 403 when the global Allow Downloads toggle is off OR the per-media allow_download meta is '0'.

POST /media/{id}/share

Auth: Public. Rate-limited to 60/min.

Record a share event and increment mvs_media_stats.shares. Called by the lightbox Share button after a successful navigator.share / clipboard copy.

GET /media/{id}/access

Auth: Public.

Report whether the current user can view the item (resolves privacy rules and any access grants). Returns an access decision, not the file.

GET /media/{id}/group

Auth: Public.

Return every item that belongs to the same gallery/upload group as {id} (used to build multi-item lightboxes).

GET /media/{id}/signed-url

Auth: Authenticated with view access to the media.

Generate a time-limited signed URL for a private file. The signed URL points at /serve and carries an HMAC-SHA256 signature binding the request to a user, media ID, and expiry.

Parameter Type Default Description
download bool false Issue a download (attachment) URL
ttl int (setting) Override the signed-URL lifetime, in seconds

GET /serve

Auth: Public - the HMAC signature on the URL is the credential (analogue of an S3 pre-signed URL). For non-public media the handler also re-checks can_view per request.

Stream the underlying file (full file or a thumbnail variant) for a validated signed URL. Drains output buffers and disables zlib.output_compression before streaming so byte counts match Content-Length. Honours Range: headers for video/audio.

Param Required Description
mvs_id yes Media ID
mvs_uid yes User ID the URL was signed for (0 for anonymous public media)
mvs_exp yes Unix expiration timestamp
mvs_sig yes HMAC-SHA256 signature
mvs_size no large / medium / thumbnail / watermark to serve a variant
mvs_dl no When 1, sets Content-Disposition: attachment and increments the download counter

GET /me/media

Auth: Authenticated.

List the current user's own media, including private and pending items. Accepts the same parameters as GET /media.


Albums

GET /albums

Auth: Public (privacy enforced in query).

List albums. Supports page, per_page, author, orderby, order.

POST /albums

Auth: Authenticated with album-create permission.

Create an album.

{
  "title": "My Album",
  "description": "Optional",
  "privacy": "public"
}

GET /albums/{id}

Auth: Public (private albums 404 for non-owners).

Get an album with its media list.

PUT /albums/{id}

Auth: Authenticated - owner / edit permission.

Update an album.

DELETE /albums/{id}

Auth: Authenticated - owner / delete permission.

Delete an album (does not delete the media items it contains).

PUT /albums/{id}/reorder

Auth: Authenticated - owner / edit permission.

Reorder the items inside an album.

{ "order": [103, 101, 102] }

POST /albums/{id}/items

Auth: Authenticated - owner / edit permission.

Add media to an album.

{ "media_ids": [101, 102, 103] }

DELETE /albums/{id}/items/{media_id}

Auth: Authenticated - owner / edit permission.

Remove a single media item from an album.

PUT /albums/{id}/cover

Auth: Authenticated - owner / edit permission.

Set the album cover.

{ "media_id": 101 }

Collections

GET /collections

Auth: Authenticated. Returns the current user's collections.

POST /collections

Auth: Authenticated.

Create a collection (manual or smart).

{
  "title": "Nature",
  "type": "smart",
  "rules": { "tags": ["nature"], "media_type": "image" },
  "privacy": "public"
}

GET /collections/{id}

Auth: Public (privacy check in permission callback).

Get a collection with its resolved item list.

PUT /collections/{id}

Auth: Authenticated - owner only.

Update a collection.

DELETE /collections/{id}

Auth: Authenticated - owner only.

Delete a collection.

PUT /collections/{id}/rules

Auth: Authenticated - owner only.

Set the smart-collection rules used to resolve its items.

{ "rules": [ { "field": "tag", "value": "nature" } ] }

Reactions

All reaction operations live on a single route that varies by HTTP method.

GET /media/{id}/reactions

Auth: Public.

Get reaction counts grouped by type. When a logged-in user calls it, the response also indicates that user's own reaction.

POST /media/{id}/reactions

Auth: Authenticated.

Add or change your reaction. Note the field name is reaction_type.

{ "reaction_type": "love" }

DELETE /media/{id}/reactions

Auth: Authenticated.

Remove your reaction.


Comments

GET /media/{id}/comments

Auth: Public (visibility follows the parent media's privacy).

List comments. Supports page, per_page (max 100).

POST /media/{id}/comments

Auth: Authenticated. Use @username syntax for mentions.

{
  "content": "Great photo @jane!",
  "parent": 0,
  "from_activity": 0
}
Field Required Description
content Yes Comment text
parent No Parent comment ID for threaded replies (default 0)
from_activity No Source BuddyPress activity ID, when posted from the activity stream

PUT /media/{id}/comments/{comment_id}

Auth: Authenticated - owner (within the edit window) or moderate_mvs_media.

Edit a comment.

DELETE /media/{id}/comments/{comment_id}

Auth: Authenticated - owner or moderate_mvs_media.

Delete a comment.


Favorites

GET /media/{id}/favorite

Auth: Authenticated.

Return whether the current user has favorited the item.

POST /media/{id}/favorite

Auth: Authenticated.

Add the item to favorites (toggles on). Optional collection_id saves it into a specific collection.

DELETE /media/{id}/favorite

Auth: Authenticated.

Remove the item from favorites.

GET /me/favorites

Auth: Authenticated.

List the current user's favorites. Supports collection_id, page, per_page.


Access Control & Grants

These routes manage per-media access rules and direct user grants. All require the media owner or the manage_mvs_access capability.

GET /media/{media_id}/rules

Auth: Owner or manage_mvs_access.

List the access rules attached to a media item.

POST /media/{media_id}/rules

Auth: Owner or manage_mvs_access. Rate-limited to 30/min.

Replace the full rule set for a media item.

{
  "rules": [
    { "rule_type": "follower", "rule_value": "1" },
    { "rule_type": "purchase", "rule_value": "1", "price": 4.99, "currency": "USD" }
  ]
}

Each rule's rule_type must be one of AccessRulesService::RULE_TYPES.

DELETE /media/{media_id}/rules/{rule_id}

Auth: Owner or manage_mvs_access.

Delete a single access rule.

POST /media/{media_id}/grant

Auth: Owner or manage_mvs_access.

Grant a specific user access to the media.

{
  "user_id": 55,
  "source": "manual",
  "expires_at": "2026-01-01T00:00:00Z"
}

source defaults to manual and must be one of AccessRulesService::GRANT_SOURCES.

DELETE /media/{media_id}/grant/{user_id}

Auth: Owner or manage_mvs_access.

Revoke a user's grant.

GET /me/grants

Auth: Authenticated.

List the media the current user has been granted access to.

Parameter Type Default Description
per_page int 20 Items per page (max: 100)
page int 1 Page number
active_only bool true Exclude expired grants

Follows

POST /users/{id}/follow

Auth: Authenticated. Rate-limited to 30/min.

Follow a user.

DELETE /users/{id}/follow

Auth: Authenticated.

Unfollow a user.

GET /users/{id}/followers

Auth: Public.

List a user's followers (display name + avatar).

GET /users/{id}/following

Auth: Public.

List who a user follows.

GET /me/following

Auth: Authenticated.

List who the current user follows.

GET /me/followers

Auth: Authenticated.

List the current user's followers.


User Profile

GET /me/profile

Auth: Authenticated.

Get the current user's profile.

PUT /me/profile

Auth: Authenticated.

Update profile fields.

{
  "first_name": "Jane",
  "last_name": "Smith",
  "display_name": "jsmith",
  "description": "Photographer"
}

POST /me/avatar

Auth: Authenticated.

Upload a new profile avatar (multipart/form-data, file field).

DELETE /me/avatar

Auth: Authenticated.

Remove the custom avatar and revert to Gravatar.


Users

GET /users/{id}

Auth: Public. Rate-limited to 60/min.

Get a user's public profile: bio, avatar URL, follower/following counts, and public media count. user_login / user_registered are returned only to the user themselves or to admins (enumeration hardening).

GET /users/{id}/media

Auth: Public (privacy enforced in query).

List a user's visible media. Supports page, per_page.

GET /users/search

Auth: Public.

Search for users by display name or username.

Parameter Type Default Description
q string (required) Search term
per_page int 10 Results per page (max: 50)

Reports & Blocking

POST /media/{id}/report

Auth: Authenticated. Rate-limited to 10/min.

Submit a content report against a media item.

{
  "reason": "inappropriate",
  "details": "Optional explanation"
}

reason must be one of ReportService::REASONS.

POST /users/{id}/report

Auth: Authenticated.

Report a user. Same reason / details body as media reports.

POST /users/{id}/block

Auth: Authenticated.

Block a user.

DELETE /users/{id}/block

Auth: Authenticated.

Unblock a user.

GET /me/blocked

Auth: Authenticated.

List the users the current user has blocked.


Moderation

All moderation routes require the moderate_mvs_media capability.

GET /moderation/queue

List flagged / pending media items. Supports collection params (page, per_page).

GET /moderation/counts

Return queue counts (pending, flagged, etc.) for building badges and tabs.

POST /moderation/{id}/approve

Approve a media item.

POST /moderation/{id}/reject

Reject a media item. Optional reason string is recorded.

POST /moderation/{id}/analyze

Trigger AI analysis (description / tagging / safety) on a media item.

GET /ai/usage

Return AI usage / budget figures for the moderation dashboard.


Bulk Operations

POST /media/bulk

Auth: Authenticated with the relevant per-action capability. Rate-limited to 10/min, max 100 IDs per call.

Perform a bulk action on multiple media items.

{
  "action": "delete",
  "media_ids": [101, 102, 103]
}
Field Required Description
action Yes One of delete, move_to_album, change_privacy
media_ids Yes Array of media IDs (max 100)
album_id When action=move_to_album Destination album ID
privacy When action=change_privacy New privacy value

Stats

GET /media/{id}/stats

Auth: Public for public media; 403 for media the caller cannot view.

Per-item statistics (views, reactions, comments, downloads).

GET /me/stats

Auth: Authenticated.

Aggregate statistics across the current user's own media.


Tags

GET /tags

Auth: Public.

List / autocomplete mvs_tag terms.

Parameter Type Default Description
search string (none) Filter by name
per_page int 20 Results per page (max: 100)
orderby string name name or count

POST /tags

Auth: Capability - create_tag_permissions_check (any user who can upload media).

Create a new tag. Body: name (required), optional slug.

GET /tags/cloud

Auth: Public.

Return top tags with usage counts for a tag cloud. Optional limit (default 50, max 200).

POST /tags/merge

Auth: Capability - admin (moderate_mvs_media).

Merge one tag into another. All media on source_id are re-tagged with target_id and source_id is deleted.

{ "source_id": 12, "target_id": 7 }

PUT /tags/{id}

Auth: Capability - admin.

Rename a tag.

{ "name": "New Tag Name" }

DELETE /tags/{id}

Auth: Capability - admin.

Delete a tag.


Notifications

GET /me/notifications

Auth: Authenticated.

List the current user's notifications.

Parameter Type Default Description
per_page int 20 Items per page (max: 100)
page int 1 Page number
filter string all Filter set (e.g. all, unread)

The total count is returned in the X-WP-Total header.

GET /me/notifications/count

Auth: Authenticated.

Return the current user's unread notification count.

POST /me/notifications/read

Auth: Authenticated.

Mark notifications as read. Pass an ids array to mark specific notifications, or omit it to mark all as read.

{ "ids": [12, 13, 14] }

Admin

POST /admin/welcome/dismiss

Auth: Authenticated (per-user state).

Dismiss the admin welcome banner for the current user.


Activity Feed

GET /feed

Auth: Public (private events never appear; following scope is empty for anonymous callers).

Return the activity feed.

Parameter Type Default Description
scope string public public (all public media) or following (followed users)
per_page int 20 Items per page (max: 100)
page int 1 Page number

GET /users/{id}/activity

Auth: Public.

Return a user's public activity (uploads, album creations, reactions). Supports page, per_page.


Messaging

Direct-messaging routes share the mvs/v1 namespace. All require authentication. Conversations started by users you do not follow land in the Requests tab until accepted or declined.

GET /me/conversations

List the current user's conversations.

Parameter Type Default Description
tab string all all, unread, or requests
per_page int 20 Conversations per page (max: 50)
page int 1 Page number

POST /conversations

Start a new conversation.

{ "recipient_id": 42 }

Response: 201 Created with the new conversation object.

GET /conversations/{id}

Get a single conversation's metadata and participants.

PATCH /conversations/{id}

Update the current user's per-conversation preferences.

{
  "is_muted": true,
  "is_pinned": false,
  "is_archived": false
}

DELETE /conversations/{id}

Leave (soft-delete) the conversation for the current user.

GET /conversations/{id}/messages

List messages in a conversation (newest-first).

Parameter Type Default Description
per_page int 30 Messages per page (max: 100)
before int 0 Return messages with ID less than this (cursor pagination)

POST /conversations/{id}/messages

Send a message.

{
  "content": "Hey, love the photo!",
  "message_type": "text",
  "media_id": null,
  "attachment_id": null,
  "parent_id": null,
  "metadata": {}
}
Field Required Description
content Yes (unless an attachment/media is sent) Message text
message_type No text (default) or media
media_id No Attach an existing media post
attachment_id No Attach a file uploaded via POST /messages/upload
parent_id No Reply to this message ID
metadata No Arbitrary structured metadata

POST /conversations/{id}/read

Mark all messages in the conversation as read for the current user.

POST /conversations/{id}/typing

Send a typing-indicator event (no persistent storage; fires a real-time event only).

POST /conversations/{id}/accept

Accept a message request - moves the conversation from Requests to All.

POST /conversations/{id}/decline

Decline a message request - removes the conversation from the inbox.

DELETE /messages/{id}

Soft-delete a message for the current user (content hidden, record retained).

DELETE /messages/{id}/unsend

Hard-delete (unsend) a message. Only available within the edit window and only for the message owner.

POST /messages/{id}/reactions

Add an emoji reaction to a message.

{ "emoji": "heart" }

DELETE /messages/{id}/reactions

Remove your emoji reaction from a message.

POST /messages/upload

Upload an attachment for use in a DM. Returns a reference ID to pass as attachment_id (or media_id) when sending the message. Body: multipart/form-data with a single file field.

{ "media_id": 204, "url": "https://example.com/..." }

GET /me/messages/unread-count

Return the total unread message count for the current user.

{ "count": 3 }

GET /messages/poll

Long-poll for new messages. The server holds the connection open and responds as soon as a new message arrives or the timeout is reached.

Parameter Type Default Description
since int (required) Return messages with ID greater than this value
conversation_id int 0 Scope the poll to a single conversation

Error Responses

All errors follow the WP REST API error format:

{
  "code": "mvs_invalid_type",
  "message": "This file type is not allowed.",
  "data": { "status": 400 }
}

Common error codes:

Code Status Meaning
mvs_invalid_type 400 MIME type not in allowed list
mvs_file_too_large 400 File exceeds max upload size
mvs_blocked_extension 400 Dangerous file extension
mvs_no_ids 400 Bulk request had no media IDs
mvs_duplicate 409 Duplicate file (when duplicate_action=skip)
mvs_not_found 404 Resource not found
mvs_user_not_found 404 User not found
mvs_not_logged_in / mvs_unauthorized 401 Authentication required
mvs_forbidden / rest_forbidden 403 Access denied by privacy/capability rules
mvs_storage_failed 500 Storage driver error
(rate limit) 429 Too many requests

Pro REST API Reference

Endpoints marked (Pro) require WPMediaVerse Pro 1.5.0+.

Base URL: /wp-json/mvs-pro/v1/

WPMediaVerse Pro registers its own REST namespace, mvs-pro/v1, alongside the free mvs/v1 namespace. Pro must be active for any of these routes to be registered.

Authentication uses the same mechanism as the free API: pass an X-WP-Nonce header with a nonce from wp_create_nonce( 'wp_rest' ) and include credentials: 'same-origin' so the request is tied to the logged-in user. Application Passwords (WP 5.6+) work for mobile/headless clients.

Permission conventions used below:

  • Public - no authentication required (__return_true or open read). Some are rate-limited inside the service.
  • User - any logged-in user (is_user_logged_in).
  • Owner/Admin - the media owner or a user who can edit others' media.
  • Admin - requires manage_options or manage_mvs_settings (noted per route).
  • HMAC - no WordPress auth; request body is verified against an HMAC-SHA256 signature header.

Some feature areas only register their routes when the matching admin toggle is enabled (mvs_battles_enabled, mvs_challenges_enabled, mvs_tournaments_enabled, mvs_boosts_enabled, mvs_streaks_enabled, mvs_connectors_enabled). When a feature is disabled its routes are not registered.


Quota & Credits

User-facing quota summaries plus admin package/credit management and the signed external top-up webhook.

GET /me/quota

Return the current user's quota summary (per-type usage and limits for image, video, audio).

Auth: User


GET /me/quota/check

Lightweight pre-upload check: can the current user upload a given media type (and optional file size) right now?

Auth: User

Parameters:

Parameter Type Required Default Description
media_type string Yes - One of image, video, audio
file_size int No 0 Size in bytes, for storage-limit checks

Response:

{ "can_upload": true, "reason": "" }

GET /me/credits/history

Return the current user's credit transaction history.

Auth: User


GET /users/{user_id}/quota

Get a specific user's quota summary.

Auth: Admin


POST /users/{user_id}/package

Assign a quota package to a user.

Auth: Admin

Body:

Field Type Required Description
package_id int Yes Package to assign

POST /users/{user_id}/credits

Grant extra upload credits to a user for a specific media type.

Auth: Admin

Body:

Field Type Required Default Description
media_type string Yes - One of image, video, audio
amount int Yes - Credits to add (min 1)
note string No "" Optional ledger note

GET /packages

List all quota packages.

Auth: Admin


POST /packages

Create a quota package.

Auth: Admin

Body:

Field Type Required Default Description
name string Yes - Package name
image_limit int No 0 Image upload limit (0 = none)
video_limit int No 0 Video upload limit
audio_limit int No 0 Audio upload limit
storage_bytes int No 0 Storage cap in bytes
is_default bool No false Whether this is the default package

PUT /packages/{id}

Update a quota package.

Auth: Admin


DELETE /packages/{id}

Delete a quota package.

Auth: Admin


POST /credits/webhook

External credit top-up endpoint. Used by integrations that grant credits from an outside system.

Auth: HMAC - the request is not cookie/capability authenticated. The handler verifies the X-MVS-Signature header against hash_hmac( 'sha256', $body, $secret ) using hash_equals(). Requests with an invalid or missing signature are rejected.


Privacy

Advanced per-item and bulk privacy controls plus reusable per-user privacy presets.

PUT /media/{id}/privacy

Update the privacy level of a single media item. Pro-only because it supports the advanced levels (e.g. specific-people, album-inherit).

Auth: Owner/Admin

Body:

Field Type Required Default Description
privacy string Yes - Privacy level (validated against the configured set)
custom_users int[] No [] User IDs allowed to view, when privacy is "Specific People"
inherit_album bool No false When true, the item follows its album's privacy

POST /media/bulk-privacy

Apply the same privacy level to many media items at once.

Auth: User (per-item ownership is enforced inside the service)

Body:

Field Type Required Description
media_ids int[] Yes 1-100 media IDs to update
privacy string Yes Privacy level to apply to all selected items

GET /privacy/presets

List the current user's saved privacy presets.

Auth: User


POST /privacy/presets

Save a new privacy preset for reuse.

Auth: User

Body:

Field Type Required Default Description
name string Yes - Preset name (≤100 chars)
privacy string Yes - Privacy level to store
custom_users int[] No [] User IDs to include when privacy is "Specific People"

Video - Transcoding

FFmpeg-backed transcoding actions and admin queue/server inspection.

GET /media/{id}/transcodes

Retrieve stored transcode renditions and current job status for a video.

Auth: Owner/Admin

status values: queued, processing, failed, complete.


POST /media/{id}/transcode

Trigger a transcode job for a video. Defaults to the configured presets when none are supplied.

Auth: Owner/Admin

Body:

Field Type Required Description
presets string[] No Specific presets to run (each must be a known preset key)

DELETE /media/{id}/transcodes

Delete all transcoded files for a video.

Auth: Owner/Admin


GET /transcode/status

Admin overview of the transcode job queue.

Auth: Admin

Parameters:

Parameter Type Default Description
status string all One of queued, processing, failed, complete, all
per_page int 20 Jobs per page (1-100)
page int 1 Page number

GET /transcode/config

Report FFmpeg availability and the configured transcode presets.

Auth: Admin


Video - Chapters & Resume

GET /media/{id}/chapters

List chapter markers for a video.

Auth: User (must be able to read the media)


PUT /media/{id}/chapters

Replace all chapter markers for a video.

Auth: Owner/Admin (chapter-edit permission)

Body:

{
  "chapters": [
    { "time_seconds": 0,   "title": "Introduction" },
    { "time_seconds": 120, "title": "Main Feature", "thumbnail_url": "https://…/chap.webp" }
  ]
}

Each chapter requires time_seconds (int ≥ 0) and title (1-200 chars); thumbnail_url is optional.


GET /media/{id}/resume

Get the current user's saved resume position for a video.

Auth: User


POST /media/{id}/resume

Save the current playback position. Called periodically by the Pro player.

Auth: User

Body:

Field Type Required Description
position number Yes Seconds into the video (≥ 0)

DELETE /media/{id}/resume

Clear the current user's resume position for a video.

Auth: User


Captions

WebVTT caption retrieval, manual upload, Whisper generation, deletion, and job status.

GET /media/{id}/captions

Return caption metadata (VTT URL, language, provider, word count, duration, generated-at). Returns 404 when no captions exist.

Auth: User (must be able to read the media)


PUT /media/{id}/captions

Upload/replace a caption track by sending raw WebVTT content.

Auth: Owner/Admin

Body:

Field Type Required Description
vtt_content string Yes The full WebVTT document

POST /media/{id}/captions/generate

Queue an OpenAI Whisper transcription job to auto-generate captions.

Auth: Owner/Admin


DELETE /media/{id}/captions

Remove the caption track from a video/audio item.

Auth: Owner/Admin


GET /media/{id}/captions/status

Poll the status of a caption generation job.

Auth: User (must be able to read the media)


Analytics

Public play-event ingestion plus owner/admin analytics surfaces.

POST /media/{id}/events

Record a video play/heatmap event. Public and rate-limited inside the service; mirrors the free /media/{id}/view route.

Auth: Public (rate-limited)

Body:

Field Type Required Default Description
event_type string Yes - One of the allowed event types
position number Yes - Playhead position in seconds (≥ 0)
duration number No 0 Segment duration in seconds (≥ 0)
session_id string Yes - Player session identifier (1-64 chars)

GET /media/{id}/analytics

Full analytics for a single media item, including the heatmap buckets.

Auth: Owner/Admin

Parameters:

Parameter Type Default Description
bucket_count int 100 Heatmap resolution (10-500 buckets)

GET /analytics/top

Top media by engagement.

Auth: Admin

Parameters:

Parameter Type Default Description
period string 30d One of today, 7d, 30d, all
limit int 10 Number of items (1-100)
sort string engagement One of engagement, plays, completion

GET /analytics/overview

Site-wide analytics summary.

Auth: Admin


Boosts

Spend gamification points to promote a media item's visibility.

GET /boosts

List the current user's boosts.

Auth: User

Parameters:

Parameter Type Default Description
status string "" Filter by boost status
per_page int 20 Items per page
page int 1 Page number

X-WP-Total is returned in the response header.


POST /boosts

Create a boost for a media item. Returns 201 with the new boost_id.

Auth: User

Body:

Field Type Required Default Description
media_id int Yes - Media item to boost
impressions_target int No 500 Target number of impressions

GET /boosts/balance

Return the current user's point balance and the boost cost/limit settings.

Auth: User

Response:

{ "balance": 1200, "cost_per_100": 50, "max_impressions": 5000 }

Streaks

Requires mvs_streaks_enabled.

POST /streaks/buy-freeze

Purchase a streak-freeze token with gamification points. Fails with a 400 if the gamification plugin is inactive or the user lacks enough points.

Auth: User

Response:

{ "freezes": 3, "balance": 1100 }

Challenges

Requires mvs_challenges_enabled. Themed photo challenges with entries, voting, and finalized results.

GET /challenges

List challenges.

Auth: Public

Parameters:

Parameter Type Default Description
status string "" Filter by status
per_page int 20 Items per page (max via mvs_rest_pagination_max, default 100)
page int 1 Page number

POST /challenges

Create a challenge.

Auth: Admin

Body (key fields): title (required), description, theme, cover_media_id, start_date (required), end_date (required), voting_end_date (required), max_entries_per_user (default 1), and XP awards xp_1st / xp_2nd / xp_3rd / xp_participation.


GET /challenges/{id}

Get a single challenge.

Auth: Public


PUT /challenges/{id}

Update a challenge.

Auth: Admin


POST /challenges/{id}/cancel

Cancel a challenge.

Auth: Admin


GET /challenges/{id}/entries

List entries for a challenge.

Auth: Public

Parameters: per_page (default 20), page (default 1), orderby (default votes).


POST /challenges/{id}/entries

Submit an entry to a challenge.

Auth: User

Body:

Field Type Required Description
media_id int Yes Media item to enter

POST /challenges/{id}/entries/{entry_id}/vote

Vote for a challenge entry.

Auth: User


DELETE /challenges/{id}/entries/{entry_id}/vote

Remove the current user's vote from an entry.

Auth: User


GET /challenges/{id}/results

Get finalized results for a challenge.

Auth: Public


Battles

Requires mvs_battles_enabled. 1v1 photo battles.

GET /battles

List battles.

Auth: Public

Parameters: user_id (default 0), status (default ""), per_page (default 20), page (default 1).


POST /battles

Create (challenge a user to) a battle.

Auth: User

Body:

Field Type Required Default Description
opponent_id int Yes - The user being challenged
theme string No "" Optional battle theme

GET /battles/{id}

Get a single battle.

Auth: Public


POST /battles/{id}/accept

Accept a battle challenge.

Auth: User


POST /battles/{id}/decline

Decline a battle challenge.

Auth: User


POST /battles/{id}/submit

Submit a media entry for a battle.

Auth: User

Body:

Field Type Required Description
media_id int Yes Media item to submit

POST /battles/{id}/vote

Cast a vote in a battle.

Auth: User

Body:

Field Type Required Description
voted_for_id int Yes The user (battle side) being voted for

Tournaments

Requires mvs_tournaments_enabled. Single-elimination bracket tournaments.

GET /tournaments

List tournaments.

Auth: Public

Parameters: status (default ""), per_page (default 20), page (default 1).


POST /tournaments

Create a tournament.

Auth: Admin

Body (key fields): title (required), description, theme, cover_media_id, bracket_size (default 8), registration_start (required), registration_end (required), round_duration_hours (default 48), xp_round_win (default 150), xp_tournament_win (default 500).


GET /tournaments/{id}

Get tournament detail.

Auth: Public


POST /tournaments/{id}/register

Register the current user for a tournament.

Auth: User


DELETE /tournaments/{id}/register

Unregister the current user from a tournament.

Auth: User


GET /tournaments/{id}/bracket

Get the tournament bracket.

Auth: Public


GET /tournaments/{id}/participants

Get the tournament participants (display name + avatar only).

Auth: Public


POST /tournaments/{id}/matches/{match_id}/submit

Submit a media entry for a bracket match.

Auth: User

Body:

Field Type Required Description
media_id int Yes Media item to submit for the match

POST /tournaments/{id}/matches/{match_id}/vote

Vote in a bracket match.

Auth: User

Body:

Field Type Required Description
voted_for_id int Yes The participant being voted for

Competitions (read-only roll-up)

GET /competitions/active-summary

A single discovery roll-up combining the active challenge, open tournaments, and the most recent battles. Intentionally public so it can drive landing-page/widget discovery. When the requester is logged in, the response also includes their my_activity.

Auth: Public

Response:

{
  "active_challenge": { "id": 5, "title": "Black and White Week" },
  "open_tournaments": [],
  "recent_battles": []
}

There is no generic /competitions CRUD. Challenges, battles, and tournaments are managed through their own route groups above.


Connectors

Requires mvs_connectors_enabled. OAuth-based import/export connectors for external platforms (Flickr, etc.). Connector IDs are slugs (e.g. flickr).

GET /connectors

List all registered connectors with each one's connection state.

Auth: User


POST /connectors/{id}/connect

Initiate the OAuth flow for a connector.

Auth: User

Body:

Field Type Default Description
use_custom bool false Use user-supplied API credentials instead of plugin defaults
api_key string "" Custom API key (when use_custom is true)
api_secret string "" Custom API secret (when use_custom is true)
return_url string "" Where to redirect after the OAuth callback (defaults to the connectors admin page)

POST /connectors/{id}/disconnect

Revoke and remove the connection.

Auth: User (must be connected to this connector)


GET /connectors/{id}/status

Live validation of the connection.

Auth: User (must be connected)


GET /connectors/{id}/photos

Browse the remote platform's photos.

Auth: User (must be connected)

Parameters:

Parameter Type Default Description
page int 1 Page number
per_page int 20 Photos per page (1-30)
album_id string "" Restrict to a specific remote album/set

GET /connectors/{id}/albums

Browse the remote platform's albums.

Auth: User (must be connected)


POST /connectors/{id}/import

Import selected remote photos into the media library.

Auth: User (must be connected)

Body:

Field Type Required Description
photo_ids string[] Yes Remote photo IDs to import

POST /connectors/{id}/export

Export local media to the remote platform.

Auth: User (must be connected)

Body:

Field Type Required Description
media_ids int[] Yes Local attachment IDs to export

POST /connectors/{id}/sync

Run an incremental delta sync of recently changed items.

Auth: User (must be connected)


Admin

POST /admin/gamification-welcome/dismiss

Dismiss the gamification first-run welcome banner (site-wide option).

Auth: Admin (manage_mvs_settings)


Error Responses

Pro endpoints use the same error envelope as the free API:

{
  "code": "mvs_pro_rest_forbidden",
  "message": "You do not have permission to manage WPMediaVerse Pro settings.",
  "data": { "status": 403 }
}

Common Pro error codes:

Code Status Meaning
mvs_pro_rest_forbidden 403 Caller lacks the required Pro capability
mvs_pro_no_captions 404 No captions exist for the requested media
mvs_pro_privacy_update_failed 400 Privacy could not be updated (bad level or write failure)
mvs_gamification_unavailable 400 Gamification plugin not active (streak freeze)
mvs_insufficient_points 400 Not enough gamification points for the requested action

Hooks & Filters Reference

All WPMediaVerse hooks use the mvs_ prefix. Pro-only hooks require WPMediaVerse Pro to be active and are labeled (Pro). Hooks introduced in version 1.1 are labeled (New in 1.1).


Quick Reference

Hook Name Type Free/Pro Since
mvs_loaded action Free 1.0
mvs_pro_loaded action Pro 1.0
mvs_ai_providers action Free 1.0
mvs_theme_json filter Free 1.0
mvs_before_media_insert action Free 1.0
mvs_media_uploaded action Free 1.0 (signature extended in 1.2.3)
mvs_before_upload_form action Free 1.0
mvs_before_thumbnail_generation action Free 1.1
mvs_after_thumbnail_generation action Free 1.1
mvs_upload_args filter Free 1.0
mvs_allowed_file_types filter Free 1.1
mvs_max_upload_size filter Free 1.1
mvs_upload_directory filter Free 1.1
mvs_media_metadata filter Free 1.0
mvs_before_content action Free 1.0
mvs_after_content action Free 1.0
mvs_before_template_render action Free 1.1
mvs_after_template_render action Free 1.1
mvs_dashboard_before_content action Free 1.0
mvs_dashboard_tabs action Free 1.0
mvs_dashboard_panels action Free 1.0
mvs_dashboard_after_content action Free 1.0
mvs_locate_template filter Free 1.0
mvs_template_variables filter Free 1.1
mvs_body_classes filter Free 1.1
mvs_reserved_media_paths filter Free 1.0
mvs_before_explore_grid action Free 1.0
mvs_after_explore_grid action Free 1.1
mvs_feed_query_args filter Free 1.1
mvs_feed_sort_options filter Free 1.1
mvs_media_response filter Free 1.0
mvs_album_response filter Free 1.1
mvs_collection_response filter Free 1.1
mvs_rest_pagination_max filter Free 1.1
mvs_explore_query_args filter Free 1.0
mvs_parent_route filter Free 1.0
mvs_reaction_added action Free 1.0
mvs_reaction_removed action Free 1.0
mvs_favorite_added action Free 1.0
mvs_share_recorded action Free 1.0
mvs_media_group_assigned action Free 1.0
mvs_album_cover_set action Free 1.0
mvs_album_items_added action Free 1.0
mvs_tag_term_count filter Free 1.0
mvs_user_badge_html filter Free 1.0
mvs_reaction_toggled action Free 1.0
mvs_favorite_toggled action Free 1.0
mvs_comment_created action Free 1.0
mvs_mentions_created action Free 1.0
mvs_user_followed action Free 1.0
mvs_user_unfollowed action Free 1.0
mvs_media_shared action Free 1.0
mvs_report_submitted action Free 1.0
mvs_user_blocked action Free 1.0
mvs_tags_merged action Free 1.0
mvs_activity_types filter Free 1.0
mvs_activity_max_media filter Free 1.0
mvs_notification_created action Free 1.1
mvs_should_send_notification filter Free 1.1
mvs_notification_data filter Free 1.1
mvs_notification_types filter Free 1.0
mvs_notification_message filter Free 1.0
mvs_conversation_created action Free 1.0
mvs_message_sent action Free 1.0
mvs_message_request_accepted action Free 1.0
mvs_message_deleted action Free 1.0
mvs_message_reaction_added action Free 1.0
mvs_voice_message_sent action Free 1.0
mvs_conversation_read action Free 1.0
mvs_can_send_message filter Free 1.0
mvs_dm_access_level filter Free 1.0
mvs_dm_message_rate_limit filter Free 1.0
mvs_dm_convo_rate_limit filter Free 1.0
mvs_message_max_length filter Free 1.0
mvs_message_types filter Free 1.0
mvs_dm_allowed_file_types filter Free 1.0
mvs_messaging_poll_intervals filter Free 1.0
mvs_messaging_transport filter Free 1.0
mvs_show_online_status filter Free 1.0
mvs_dm_max_upload_size filter Free 1.0
mvs_settings_sidebar_after action Free 1.0
mvs_settings_before_save action Free 1.1
mvs_settings_render_{renderer} action Free 1.0
mvs_dashboard_widgets action Free 1.1
mvs_settings_sections filter Free 1.0
mvs_settings_group_labels filter Free 1.0
mvs_hide_submenu_slugs filter Free 1.0
mvs_moderation_tabs filter Free 1.0
mvs_stats_tabs filter Free 1.0
mvs_comment_edit_window filter Free 1.1
mvs_should_render_chat_panel filter Free 1.2
mvs_page_id_{slot} filter Free 1.2
mvs_user_data_purged action Free 1.2
mvs_media_flagged action Free 1.0
mvs_moderation_changed action Free 1.0
mvs_should_ai_analyze filter Free 1.1
mvs_ai_result filter Free 1.1
mvs_ai_moderation_result filter Free 1.1
mvs_openai_api_key filter Free 1.0
mvs_media_deleted action Free 1.0
mvs_watermark_invalidated action Free 1.0
mvs_watermarks_invalidated_all action Free 1.0
mvs_storage_driver filter Free 1.0
mvs_watermark_enabled filter Free 1.0
mvs_watermark_config filter Free 1.0
mvs_generate_watermark filter Free 1.0
mvs_cloud_thumbnail_url filter Free 1.3.0
mvs_cloudops_allow_non_public_to_cloud filter Free 1.3.0
mvs_filename_strategy filter Free 1.3.0
mvs_thumbnail_sizes filter Free 1.3.0
mvs_thumbnail_size_resolved filter Free 1.3.0
mvs_can_repair_thumb filter Free 1.2.3
mvs_repair_media_thumb filter Free 1.2.3
mvs_watermark_font_path filter Pro 1.0
mvs_webhook_sslverify filter Free 1.3.0
mvs_profile_updated action Free 1.0
mvs_avatar_uploaded action Free 1.0
mvs_avatar_deleted action Free 1.0
mvs_user_display_name filter Free 1.0
mvs_user_profile_url filter Free 1.0
mvs_profile_data filter Free 1.0
mvs_profile_update_fields filter Free 1.0
mvs_avatar_allowed_types filter Free 1.0
mvs_avatar_max_size filter Free 1.0
mvs_access_rule_created action Free 1.0
mvs_access_rule_deleted action Free 1.0
mvs_access_granted action Free 1.0
mvs_access_revoked action Free 1.0
mvs_story_created action Free 1.0
mvs_story_expired action Free 1.0
mvs_privacy_can_view filter Free 1.0
mvs_buddynext_active filter Free 1.0
mvs_pro_transcode_complete action Pro 1.0
mvs_pro_captions_generated action Pro 1.0
mvs_pro_transcode_presets filter Pro 1.1
mvs_pro_poster_frame filter Pro 1.1
mvs_pro_analytics_recorded action Pro 1.1
mvs_pro_analytics_event_data filter Pro 1.1
mvs_pro_analytics_summary filter Pro 1.1
mvs_layout_assets action Pro 1.0
mvs_before_layout_render action Pro 1.1
mvs_active_layout filter Pro 1.0
mvs_layout_modes filter Pro 1.0
mvs_layout_template_map filter Pro 1.0
mvs_layout_config filter Pro 1.1
mvs_pro_credits_added action Pro 1.0
mvs_pro_woo_package_assigned action Pro 1.0
mvs_pro_woo_package_reverted action Pro 1.0
mvs_pro_memberpress_package_assigned action Pro 1.0
mvs_pro_memberpress_package_reverted action Pro 1.0
mvs_pro_pmpro_package_assigned action Pro 1.0
mvs_pro_pmpro_package_reverted action Pro 1.0
mvs_quota_render_mapping_fields action Pro 1.0
mvs_quota_save_mapping action Pro 1.0
mvs_pro_before_quota_check filter Pro 1.1
mvs_pro_quota_source filter Pro 1.1
mvs_challenge_created action Pro 1.0
mvs_challenge_entry_submitted action Pro 1.0
mvs_challenge_finalized action Pro 1.0
mvs_battle_created action Pro 1.0
mvs_battle_accepted action Pro 1.0
mvs_battle_resolved action Pro 1.0
mvs_tournament_created action Pro 1.0
mvs_tournament_started action Pro 1.0
mvs_tournament_match_resolved action Pro 1.0
mvs_tournament_finalized action Pro 1.0
mvs_autopilot_challenge_created action Pro 1.0
mvs_autopilot_create_failed action Pro 1.0
mvs_autopilot_no_theme_available action Pro 1.0
mvs_autopilot_pool_reset action Pro 1.0
mvs_streak_milestone action Pro 1.0
mvs_challenge_winner_named action Pro 1.2.3
mvs_challenge_activated action Pro 1.5.0
mvs_challenge_voting_started action Pro 1.5.0
mvs_battle_cancelled action Pro 1.5.0
mvs_tournament_cancelled action Pro 1.5.0
mvs_tournament_updated action Pro 1.5.0
mvs_competition_status_changed action Pro 1.5.0
mvs_competitions_tick_ran action Pro 1.5.0
mvs_activate_scheduled_challenges action Pro 1.5.0
mvs_close_challenge_entries action Pro 1.5.0
mvs_finalize_expired_challenges action Pro 1.5.0
mvs_start_registered_tournaments action Pro 1.5.0
mvs_resolve_expired_matches action Pro 1.5.0
mvs_pro_leaderboard_xp_rows filter Pro 1.2.0
mvs_challenge_email_created_subject filter Pro 1.5.0
mvs_challenge_email_created_body filter Pro 1.5.0
mvs_challenge_email_entry_subject filter Pro 1.5.0
mvs_challenge_email_entry_body filter Pro 1.5.0
mvs_challenge_email_winner_subject filter Pro 1.5.0
mvs_challenge_email_winner_body filter Pro 1.5.0
mvs_challenge_email_participant_subject filter Pro 1.5.0
mvs_challenge_email_participant_body filter Pro 1.5.0
mvs_connectors filter Pro 1.5.0
mvs_media_imported action Pro 1.5.0
mvs_media_exported action Pro 1.5.0
mvs_optimize_image filter Free 1.3.0
mvs_optimize_jpeg_quality filter Free 1.3.0
mvs_webp_quality filter Free 1.3.0
mvs_avif_quality filter Free 1.3.0
mvs_ffmpeg_binary filter Free 1.3.0
mvs_default_video_poster_url filter Free 1.3.0
mvs_media_privacy_changed action Free 1.3.0
mvs_serve_public_cloud_direct filter Free 1.4.0
mvs_public_cloud_thumbnail_url filter Free 1.4.0
mvs_public_cloud_file_url filter Free 1.4.0
mvs_broadcast_thumbnail_ttl filter Free 1.5.0

Table of Contents

  1. Plugin Lifecycle
  2. Upload Pipeline
  3. Template System
  4. Explore & Feed
  5. REST API
  6. Social & Engagement
  7. Notifications
  8. Direct Messages
  9. Admin & Settings
  10. AI & Moderation
  11. Storage & Files
  12. User Profiles
  13. Access & Privacy
  14. BuddyPress Integration
  15. Video Processing (Pro)
  16. Analytics (Pro)
  17. Layout System (Pro)
  18. Quota System (Pro)
  19. Competitions (Pro)
  20. Connectors (Pro)
  21. Common Recipes

1. Plugin Lifecycle

mvs_loaded

Fires after the free plugin is fully initialized and the DI container is ready. Use this instead of plugins_loaded when you need access to WPMediaVerse services.

Parameters: none

/**
 * Bootstrap a third-party integration after MVS is ready.
 *
 * @since 1.0
 */
add_action( 'mvs_loaded', function() {
    // Safe to resolve services from the container here.
} );

mvs_pro_loaded (Pro)

Fires after WPMediaVerse Pro is fully initialized.

Parameters: none

/**
 * Run Pro-specific setup after the Pro plugin is ready.
 *
 * @since 1.0
 */
add_action( 'mvs_pro_loaded', function() {
    // Pro services are available here.
} );

mvs_ai_providers

Fires during plugin init to allow registering custom AI providers with the AI service.

Parameters:

Parameter Type Description
$ai_service AIService The AI service instance
/**
 * Register a custom AI provider.
 *
 * @since 1.0
 *
 * @param AIService $ai_service The AI service instance.
 */
add_action( 'mvs_ai_providers', function( $ai_service ) {
    $ai_service->register_provider( new MyCustomAIProvider() );
} );

Additional Lifecycle Filters

Filter Description Parameters Returns
mvs_theme_json Filter theme.json data passed to the frontend JS bundle $data (array) array

2. Upload Pipeline

mvs_media_uploaded

Fires after a new media post is created, stored, and indexed. This is the primary hook for post-upload processing - use it for gamification, activity feeds, external pipelines, and analytics.

Parameters:

Parameter Type Description
$media_id int The new mvs_media post ID
$file_data array File data. Keys: mime, file_path, file_url, file_size, file_type, file_hash, media_type, privacy, user_id, is_first (1.2.3+)
$user_id int Uploader user ID (1.2.3+)
$media_type string Resolved type: photo, video, audio, document (1.2.3+)

Backward compatibility: Listeners registered with accepted_args=1 or =2 continue to work unchanged - the new positional args are appended.

/**
 * Award gamification points on every upload, plus a one-time "first upload" badge.
 *
 * @since 1.2.3
 *
 * @param int    $media_id   The new media post ID.
 * @param array  $file_data  File data (now includes user_id and is_first).
 * @param int    $user_id    Uploader user ID.
 * @param string $media_type 'photo' | 'video' | 'audio' | 'document'.
 */
add_action( 'mvs_media_uploaded', function( int $media_id, array $file_data, int $user_id, string $media_type ) {
    wb_gamification_award( $user_id, 'mvs_media_uploaded', 10 );

    if ( ! empty( $file_data['is_first'] ) ) {
        wb_gamification_award_badge( $user_id, 'first_upload' );
    }
}, 10, 4 );

mvs_upload_args

Filters the upload arguments before file processing. Return a WP_Error to reject the upload.

Parameters:

Parameter Type Description
$args array Keys: mime, media_type, file_size, file_name
$user_id int Uploading user ID

Returns: array|WP_Error

/**
 * Reject uploads over 10 MB for subscribers.
 *
 * @since 1.0
 *
 * @param array $args    Upload arguments.
 * @param int   $user_id Uploading user ID.
 * @return array|WP_Error
 */
add_filter( 'mvs_upload_args', function( array $args, int $user_id ) {
    if ( user_can( $user_id, 'subscriber' ) && $args['file_size'] > 10 * MB_IN_BYTES ) {
        return new WP_Error( 'quota_exceeded', __( 'Upload limit exceeded for your plan.', 'wpmediaverse' ) );
    }
    return $args;
}, 10, 2 );

mvs_before_thumbnail_generation (New in 1.1)

Fires before WordPress multi_resize runs for a newly uploaded image. Use this to add or modify the sizes array.

Parameters:

Parameter Type Description
$media_id int Media post ID
$file_path string Absolute path to the uploaded file
$sizes array Size definitions passed to multi_resize
/**
 * Add a custom 800×600 thumbnail size before generation.
 *
 * @since 1.1
 *
 * @param int    $media_id  Media post ID.
 * @param string $file_path Absolute file path.
 * @param array  $sizes     Size definitions.
 */
add_action( 'mvs_before_thumbnail_generation', function( int $media_id, string $file_path, array $sizes ) {
    // Log or modify $sizes via reference if the hook passes by reference,
    // otherwise use mvs_after_thumbnail_generation to inspect results.
}, 10, 3 );

mvs_after_thumbnail_generation (New in 1.1)

Fires after all thumbnails are generated and stored in media meta.

Parameters:

Parameter Type Description
$media_id int Media post ID
$generated array Map of size slug to generated file data
$file_path string Absolute path to the source file
/**
 * Push newly generated thumbnails to an external CDN.
 *
 * @since 1.1
 *
 * @param int    $media_id  Media post ID.
 * @param array  $generated Generated thumbnail map.
 * @param string $file_path Source file path.
 */
add_action( 'mvs_after_thumbnail_generation', function( int $media_id, array $generated, string $file_path ) {
    foreach ( $generated as $size => $data ) {
        my_cdn_push( $data['path'] );
    }
}, 10, 3 );

Additional Upload Filters

Filter Description Parameters Since
mvs_before_media_insert Fires before the mvs_media post is created none 1.0
mvs_before_upload_form Fires before the upload form HTML renders none 1.0
mvs_allowed_file_types Filter allowed MIME types array $types (array) 1.1
mvs_max_upload_size Filter max upload size in bytes $max_size (int), $user_id (int) 1.1
mvs_upload_directory Filter upload subdirectory path $subdir (string), $user_id (int), $media_type (string) 1.1
mvs_media_metadata Filter extracted metadata before storage $metadata (array), $file_path (string), $media_id (int) 1.0

3. Template System

mvs_locate_template

Filters the resolved template file path. Use this to serve templates from a custom theme directory.

Parameters:

Parameter Type Description
$template string Resolved absolute file path
$template_name string Template filename (e.g., media-single.php)
$template_path string Subdirectory within the template directory

Returns: string

/**
 * Load MVS templates from the active theme's /mvs/ subfolder.
 *
 * @since 1.0
 *
 * @param string $template      Resolved template path.
 * @param string $template_name Template filename.
 * @return string
 */
add_filter( 'mvs_locate_template', function( string $template, string $template_name ) {
    $custom = get_stylesheet_directory() . '/mvs/' . $template_name;
    return file_exists( $custom ) ? $custom : $template;
}, 10, 2 );

mvs_dashboard_tabs

Fires inside the dashboard shortcode to allow registering custom tabs.

Parameters: none

/**
 * Register a custom "Collections" tab on the user dashboard.
 *
 * @since 1.0
 */
add_action( 'mvs_dashboard_tabs', function() {
    echo '<button class="mvs-tab" data-panel="collections">' . esc_html__( 'Collections', 'my-plugin' ) . '</button>';
} );

mvs_dashboard_panels

Fires inside the dashboard shortcode to allow registering custom panel content.

Parameters: none

/**
 * Render content for the custom "Collections" panel.
 *
 * @since 1.0
 */
add_action( 'mvs_dashboard_panels', function() {
    echo '<div id="mvs-panel-collections" class="mvs-panel">';
    // Panel content here.
    echo '</div>';
} );

Additional Template Hooks

Hook Type Description Parameters Since
mvs_before_content action Before main template content in full-page templates none 1.0
mvs_after_content action After main template content none 1.0
mvs_before_template_render action Before a template part renders $template_name, $args 1.1
mvs_after_template_render action After a template part renders $template_name, $args 1.1
mvs_dashboard_before_content action Before dashboard shortcode content none 1.0
mvs_dashboard_after_content action After dashboard shortcode content none 1.0
mvs_template_variables filter Filter template variables before render $args (array), $template_name (string) 1.1
mvs_body_classes filter Filter MVS body CSS classes $classes (array) 1.1
mvs_reserved_media_paths filter Filter reserved URL paths under /media/ $paths (array) 1.0

4. Explore & Feed

mvs_feed_query_args (New in 1.1)

Filters the query arguments used to fetch the media feed. Use this to add custom ordering, meta queries, or taxonomy filters.

Parameters:

Parameter Type Description
$query_args array WP_Query arguments for the feed
$request WP_REST_Request The incoming REST request

Returns: array

/**
 * Add a custom meta query to the media feed.
 *
 * @since 1.1
 *
 * @param array           $query_args Feed query arguments.
 * @param WP_REST_Request $request    Incoming REST request.
 * @return array
 */
add_filter( 'mvs_feed_query_args', function( array $query_args, $request ) {
    $query_args['meta_query'][] = [
        'key'     => '_featured',
        'value'   => '1',
        'compare' => '=',
    ];
    return $query_args;
}, 10, 2 );

mvs_feed_sort_options (New in 1.1)

Filters the available sort options shown in the explore feed UI.

Parameters:

Parameter Type Description
$options array Associative array of slug => label

Returns: array

/**
 * Add a "Most Commented" sort option to the explore feed.
 *
 * @since 1.1
 *
 * @param array $options Existing sort options.
 * @return array
 */
add_filter( 'mvs_feed_sort_options', function( array $options ) {
    $options['most_commented'] = __( 'Most Commented', 'my-plugin' );
    return $options;
} );

Additional Explore Hooks

Hook Type Description Parameters Since
mvs_before_explore_grid action Before the explore grid renders none 1.0
mvs_after_explore_grid action After the explore grid renders none 1.1

5. REST API

mvs_media_response

Filters the REST API response array for a single media item. Use this to add or remove fields before the response is sent.

Parameters:

Parameter Type Description
$data array REST response data array
$media_id int Media post ID

Returns: array

/**
 * Append custom post meta to the media REST response.
 *
 * @since 1.0
 *
 * @param array $data     REST response data.
 * @param int   $media_id Media post ID.
 * @return array
 */
add_filter( 'mvs_media_response', function( array $data, int $media_id ) {
    $data['location'] = get_post_meta( $media_id, '_location', true );
    return $data;
}, 10, 2 );

Additional REST Filters

Filter Description Parameters Default Since
mvs_album_response Filter album REST response $data (array), $album_id (int) Raw album data 1.1
mvs_collection_response Filter collection REST response $data (array), $collection_id (int) Raw collection data 1.1
mvs_rest_pagination_max Max per_page for media feed endpoint $maximum (int) 100 1.1
mvs_explore_query_args Filter the WP_Query args used by the /media/ explore template $query_args (array), $profile (WP_User|null) Template query 1.0
mvs_parent_route Filter the resolved parent route slug for a template context $parent (string), $context (string), $args (array) Resolved slug 1.0

6. Social & Engagement

mvs_comment_created

Fires when a comment is posted on a media item.

Parameters:

Parameter Type Description
$media_id int Media post ID
$user_id int Commenting user ID
$comment_id int New comment ID
$content string Comment text
$source string Source context: web, bp_activity, etc.
/**
 * Send a Slack alert when a comment is posted.
 *
 * @since 1.0
 *
 * @param int    $media_id   Media post ID.
 * @param int    $user_id    Commenter user ID.
 * @param int    $comment_id New comment ID.
 * @param string $content    Comment text.
 * @param string $source     Comment source.
 */
add_action( 'mvs_comment_created', function( int $media_id, int $user_id, int $comment_id, string $content, string $source ) {
    my_slack_notify( "New comment on media #{$media_id}" );
}, 10, 5 );

mvs_reaction_toggled

Fires when a user adds, changes, or removes a reaction on a media item.

Parameters:

Parameter Type Description
$media_id int Media post ID
$user_id int User who changed the reaction
$reaction_type string|null Reaction slug (e.g., love) or null if removed
$action string added, changed, or removed
/**
 * Award points when a reaction is added.
 *
 * @since 1.0
 *
 * @param int         $media_id      Media post ID.
 * @param int         $user_id       User ID.
 * @param string|null $reaction_type Reaction slug or null.
 * @param string      $action        add, change, or remove.
 */
add_action( 'mvs_reaction_toggled', function( int $media_id, int $user_id, $reaction_type, string $action ) {
    if ( 'added' === $action ) {
        my_award_points( get_post_field( 'post_author', $media_id ), 1 );
    }
}, 10, 4 );

mvs_user_followed

Fires when a follow relationship is created.

Parameters:

Parameter Type Description
$follower_id int User who followed
$following_id int User who was followed
/**
 * Send a "new follower" notification.
 *
 * @since 1.0
 *
 * @param int $follower_id  User who followed.
 * @param int $following_id User who was followed.
 */
add_action( 'mvs_user_followed', function( int $follower_id, int $following_id ) {
    my_send_follower_notification( $following_id, $follower_id );
}, 10, 2 );

Additional Social Hooks

Hook Type Description Parameters Since
mvs_reaction_added action Reaction added to media $media_id, $user_id, $reaction_type 1.0
mvs_reaction_removed action Reaction removed from media $media_id, $user_id 1.0
mvs_favorite_added action Favorite added (fires in addition to mvs_favorite_toggled) $media_id, $user_id 1.0
mvs_favorite_toggled action Favorite added or removed $media_id, $user_id, $action (added/removed) 1.0
mvs_share_recorded action Share recorded against a media item $media_id, $user_id 1.0
mvs_media_group_assigned action Media assigned to a BuddyPress group after upload $media_id, $group_id 1.0
mvs_album_cover_set action Album cover image set $album_id, $media_id 1.0
mvs_tag_term_count filter Filter the displayed media count for a tag term $count (int), $term_taxonomy_id (int) 1.0
mvs_user_badge_html filter Inject HTML for a user badge next to an author name (Pro streak/verified badges) $html (string), $user_id (int) 1.0
mvs_mentions_created action @mentions parsed from a comment $media_id, $mentioned_ids, $context, $comment_id 1.0
mvs_user_unfollowed action Follow relationship removed $follower_id, $following_id 1.0
mvs_media_shared action Media shared to external platform $media_id, $user_id, $platform 1.0
mvs_report_submitted action Content report filed $report_id, $reporter_id, $target_type, $target_id, $reason 1.0
mvs_user_blocked action User blocked another user $blocker_id, $blocked_id 1.0
mvs_tags_merged action Two tags merged $source_id, $target_id, $posts 1.0
mvs_activity_types filter Register activity feed types $types (array) 1.0
mvs_activity_max_media filter Max media items per activity post $count (int), default 6 1.0

7. Notifications

mvs_notification_created (New in 1.1)

Fires after a notification is stored in the database. Use this to push notifications to a custom channel.

Parameters:

Parameter Type Description
$notification_id int New notification record ID
$user_id int Recipient user ID
$type string Notification type slug (e.g., comment, follow)
$actor_id int User who triggered the notification
$media_id int Related media post ID (0 if not media-related)
/**
 * Send an email for comment notifications.
 *
 * @since 1.1
 *
 * @param int    $notification_id Notification record ID.
 * @param int    $user_id         Recipient user ID.
 * @param string $type            Notification type.
 * @param int    $actor_id        Triggering user ID.
 * @param int    $media_id        Related media ID.
 */
add_action( 'mvs_notification_created', function( int $notification_id, int $user_id, string $type, int $actor_id, int $media_id ) {
    if ( 'comment' !== $type ) {
        return;
    }
    $user  = get_userdata( $user_id );
    $actor = get_userdata( $actor_id );
    wp_mail(
        $user->user_email,
        __( 'New comment on your media', 'my-plugin' ),
        sprintf( __( '%s commented on your photo.', 'my-plugin' ), $actor->display_name )
    );
}, 10, 5 );

mvs_should_send_notification (New in 1.1)

Filters whether a notification should be stored and dispatched. Return false to suppress.

Parameters:

Parameter Type Description
$should_send bool Whether to send (default true)
$user_id int Recipient user ID
$type string Notification type slug
$actor_id int Triggering user ID
$media_id int Related media ID

Returns: bool

/**
 * Suppress notifications during a scheduled import.
 *
 * @since 1.1
 *
 * @param bool   $should_send Whether to send the notification.
 * @param int    $user_id     Recipient user ID.
 * @param string $type        Notification type.
 * @param int    $actor_id    Triggering user ID.
 * @param int    $media_id    Related media ID.
 * @return bool
 */
add_filter( 'mvs_should_send_notification', function( bool $should_send, int $user_id, string $type, int $actor_id, int $media_id ) {
    if ( get_transient( 'my_plugin_import_running' ) ) {
        return false;
    }
    return $should_send;
}, 10, 5 );

Additional Notification Filters

Filter Description Parameters Since
mvs_notification_data Filter notification data array before insert $data (array), $type (string) 1.1
mvs_notification_types Filter the list of allowed notification type slugs $types (array) 1.0
mvs_notification_message Override the rendered message label for a notification type. Return a non-null string to replace the default $label (string|null), $type (string), $actor_name (string), $media_title (string) 1.0

8. Direct Messages

mvs_can_send_message

Filters whether a user is allowed to send a DM to a recipient.

Parameters:

Parameter Type Description
$can bool Current permission result
$sender_id int Sending user ID
$recipient_id int Receiving user ID

Returns: bool

/**
 * Block DMs from unverified accounts.
 *
 * @since 1.0
 *
 * @param bool $can          Current permission.
 * @param int  $sender_id    Sender user ID.
 * @param int  $recipient_id Recipient user ID.
 * @return bool
 */
add_filter( 'mvs_can_send_message', function( bool $can, int $sender_id, int $recipient_id ) {
    if ( ! my_is_verified( $sender_id ) ) {
        return false;
    }
    return $can;
}, 10, 3 );

mvs_message_sent

Fires after a DM is stored in the database.

Parameters:

Parameter Type Description
$message_id int New message ID
$conversation_id int Conversation the message belongs to
$sender_id int Sending user ID
$recipient_ids int[] Array of recipient user IDs
/**
 * Push a DM via WebSocket after storage.
 *
 * @since 1.0
 *
 * @param int   $message_id      New message ID.
 * @param int   $conversation_id Conversation ID.
 * @param int   $sender_id       Sender user ID.
 * @param int[] $recipient_ids   Recipient user IDs.
 */
add_action( 'mvs_message_sent', function( int $message_id, int $conversation_id, int $sender_id, array $recipient_ids ) {
    my_websocket_push( $conversation_id, $message_id );
}, 10, 4 );

Additional DM Hooks

Hook Type Description Parameters Since
mvs_conversation_created action New DM conversation created $conv_id, $user_a, $participants 1.0
mvs_message_request_accepted action Message request accepted $conversation_id, $user_id 1.0
mvs_message_deleted action Message deleted $message_id, $user_id, $is_unsend 1.0
mvs_message_reaction_added action Emoji reaction added to a message $message_id, $user_id, $emoji 1.0
mvs_voice_message_sent action Voice message sent $message_id, $conversation_id, $duration 1.0
mvs_conversation_read action Conversation marked as read $conversation_id, $user_id 1.0
mvs_dm_access_level filter Override DM access check result $access, $sender_id, $recipient_id 1.0
mvs_dm_message_rate_limit filter Max messages/minute per user $limit (int), default 20 1.0
mvs_dm_convo_rate_limit filter Max new conversations/hour $limit (int), default 10 1.0
mvs_message_max_length filter Max message character length $length (int), default 2000 1.0
mvs_message_types filter Allowed message type slugs $types (array), default text, media_share, image, video, audio, voice, file, system 1.0
mvs_dm_allowed_file_types filter Allowed MIME types for DM file attachments $types (array), default image MIME list 1.0
mvs_messaging_poll_intervals filter Polling intervals (ms) for the chat client $intervals (array), keys active/list/background 1.0
mvs_messaging_transport filter Swap the messaging transport object (e.g. WebSocket instead of REST polling) $transport (TransportInterface) 1.0
mvs_show_online_status filter Filter online status visibility $show (bool), $viewer_id, $user_id 1.0
mvs_dm_max_upload_size filter Max DM attachment size in bytes $bytes (int), default 10 * MB_IN_BYTES 1.0

9. Admin & Settings

mvs_settings_before_save (New in 1.1)

Fires before settings are saved to the database. Use this to validate or transform settings values.

Parameters:

Parameter Type Description
$option_page string Settings page slug being saved
/**
 * Log settings saves for auditing.
 *
 * @since 1.1
 *
 * @param string $option_page Settings page slug.
 */
add_action( 'mvs_settings_before_save', function( string $option_page ) {
    error_log( "MVS settings page '{$option_page}' saved by user " . get_current_user_id() );
} );

mvs_dashboard_widgets (New in 1.1)

Fires after the built-in overview page widgets render. Use this to add custom stat cards to the admin overview.

Parameters: none

/**
 * Add a custom stat card to the MVS admin overview.
 *
 * @since 1.1
 */
add_action( 'mvs_dashboard_widgets', function() {
    $count = my_plugin_get_exports_count();
    echo '<div class="mvs-stat-card"><span class="mvs-stat-number">' . esc_html( $count ) . '</span><span class="mvs-stat-label">' . esc_html__( 'Exports', 'my-plugin' ) . '</span></div>';
} );

Additional Admin Hooks

Hook Type Description Parameters Since
mvs_settings_sidebar_after action After settings sidebar renders none 1.0
mvs_settings_render_{renderer} action Renders a custom settings section. The dynamic part is the section's renderer key (e.g. mvs_settings_render_pages) $section (array) 1.0
mvs_settings_sections filter Register settings sidebar sections $sections (array) 1.0
mvs_settings_group_labels filter Override settings group labels $labels (array) 1.0
mvs_hide_submenu_slugs filter Hide admin submenu slugs under the MVS menu $slugs (array) 1.0
mvs_moderation_tabs filter Filter the tabs shown on the moderation queue page $tabs (array) 1.0
mvs_stats_tabs filter Filter the tabs shown on the stats page $tabs (array) 1.0
mvs_comment_edit_window filter Seconds a user can still edit a comment after posting $seconds (int), default 15 * MINUTE_IN_SECONDS 1.1
mvs_should_render_chat_panel filter Whether the floating chat panel renders on the current request $render (bool), $visibility (string, one of everywhere/logged_in/bp_pages) 1.2
mvs_page_id_{slot} filter Override the resolved page ID for a plugin page slot (e.g. mvs_page_id_explore). The dynamic part is the slot slug $page_id (int), $slot (string) 1.2
mvs_user_data_purged action Fires after a user's MVS data is erased (GDPR / account deletion) $user_id (int) 1.2

10. AI & Moderation

mvs_moderation_changed

Fires when a media item's moderation status changes via the REST API or admin UI.

Parameters:

Parameter Type Description
$media_id int Media post ID
$status string New status: approved, rejected, flagged
$old_status string Previous moderation status
$user_id int Moderator user ID (0 if system-triggered)
/**
 * Notify the media owner of a moderation decision.
 *
 * @since 1.0
 *
 * @param int    $media_id   Media post ID.
 * @param string $status     New moderation status.
 * @param string $old_status Previous moderation status.
 * @param int    $user_id    Moderator user ID.
 */
add_action( 'mvs_moderation_changed', function( int $media_id, string $status, string $old_status, int $user_id ) {
    if ( 'rejected' === $status ) {
        $author_id = (int) get_post_field( 'post_author', $media_id );
        my_send_rejection_email( $author_id, $media_id );
    }
}, 10, 4 );

mvs_should_ai_analyze (New in 1.1)

Filters whether the AI pipeline should analyze a given media item. Return false to skip.

Parameters:

Parameter Type Description
$should_analyze bool Whether to analyze (default true)
$media_id int Media post ID

Returns: bool

/**
 * Skip AI analysis for video media.
 *
 * @since 1.1
 *
 * @param bool $should_analyze Whether to run AI analysis.
 * @param int  $media_id       Media post ID.
 * @return bool
 */
add_filter( 'mvs_should_ai_analyze', function( bool $should_analyze, int $media_id ) {
    if ( 'video' === get_post_meta( $media_id, '_mvs_media_type', true ) ) {
        return false;
    }
    return $should_analyze;
}, 10, 2 );

mvs_openai_api_key

Filters the OpenAI API key used for AI moderation and tagging. Use this to supply the key from a secrets manager.

Parameters:

Parameter Type Description
$key string API key from plugin settings

Returns: string

/**
 * Load the OpenAI API key from a wp-config.php constant.
 *
 * @since 1.0
 *
 * @param string $key API key from settings.
 * @return string
 */
add_filter( 'mvs_openai_api_key', function( string $key ) {
    return defined( 'OPENAI_API_KEY' ) ? OPENAI_API_KEY : $key;
} );

Additional AI & Moderation Hooks

Hook Type Description Parameters Since
mvs_media_flagged action AI flags a media item $media_id, $result (array) 1.0
mvs_ai_result filter Filter combined AI output $output (array), $media_id 1.1
mvs_ai_moderation_result filter Filter moderation result before flagging $result (array), $media_id 1.1

11. Storage & Files

mvs_storage_driver

Filters the active storage driver slug. Use this to switch drivers per environment without changing the database setting.

Parameters:

Parameter Type Description
$driver_slug string Current driver slug (e.g., local, s3, b2)

Returns: string

/**
 * Override the storage driver from a wp-config.php constant.
 *
 * @since 1.0
 *
 * @param string $driver Current driver slug.
 * @return string
 */
add_filter( 'mvs_storage_driver', function( string $driver ) {
    return defined( 'MVS_STORAGE_DRIVER' ) ? MVS_STORAGE_DRIVER : $driver;
} );

mvs_watermark_config

Filters the watermark configuration array before the watermark image is composed.

Parameters:

Parameter Type Description
$config array Keys: position, opacity, image_url, text

Returns: array

/**
 * Force bottom-right watermark at 30% opacity.
 *
 * @since 1.0
 *
 * @param array $config Watermark configuration.
 * @return array
 */
add_filter( 'mvs_watermark_config', function( array $config ) {
    $config['opacity']  = 0.3;
    $config['position'] = 'bottom-right';
    return $config;
} );

Image Optimization (1.3.0)

mvs_optimize_image

Extension point for external optimizers (EWWW, Imagify, Smush, ShortPixel). Fires once per file pass: once for the lossless re-encode, once for each WebP sibling, and once for each AVIF sibling. The filter runs before the built-in pass, so a returning listener can fully replace the result.

Parameters:

Parameter Type Description
$file_path string Absolute path to the file on local disk
$context array Keys: media_id (int), variant (string, e.g. original, original-webp), mime (string), user_id (int)

Returns: string|WP_Error - Return a file path string to replace the result. Return the same $file_path for an in-place edit. Return a WP_Error to log a warning and keep the original.

/**
 * Delegate JPEG optimization to EWWW Image Optimizer.
 *
 * @since 1.3.0
 *
 * @param string $file_path Absolute path to file.
 * @param array  $context   { media_id, variant, mime, user_id }.
 * @return string
 */
add_filter( 'mvs_optimize_image', function( string $file_path, array $context ) {
    if ( 'image/jpeg' !== $context['mime'] ) {
        return $file_path;
    }
    ewww_image_optimizer( $file_path );
    return $file_path;
}, 10, 2 );

mvs_optimize_jpeg_quality

Filters the JPEG re-encode quality used by the built-in lossless pass. Range 0-100. Setting to 100 produces a near-lossless re-encode that still strips EXIF.

Parameters:

Parameter Type Description
$quality int Default 92
$context array Keys: media_id, variant, mime, user_id

Returns: int

add_filter( 'mvs_optimize_jpeg_quality', function( int $quality, array $context ) : int {
    // Use 85 for thumbnails, 92 for originals.
    return str_contains( $context['variant'] ?? '', 'thumb' ) ? 85 : $quality;
}, 10, 2 );

mvs_webp_quality

Filters the WebP encoder quality. Range 0-100.

Parameters:

Parameter Type Description
$quality int Default 82
$context array Keys: media_id, variant, mime, user_id

Returns: int

add_filter( 'mvs_webp_quality', function( int $quality ) : int {
    return 75; // Smaller files, still visually lossless for most photos.
} );

mvs_avif_quality

Filters the AVIF encoder quality. Range 0-100. AVIF encoding is CPU-intensive; only runs when the mvs_generate_avif setting is enabled.

Parameters:

Parameter Type Description
$quality int Default 50
$context array Keys: media_id, variant, mime, user_id

Returns: int

add_filter( 'mvs_avif_quality', function( int $quality ) : int {
    return 40; // Lower = smaller file; AVIF retains quality well below 50.
} );

Video Poster (1.3.0)

mvs_ffmpeg_binary

Filters the ffmpeg binary path used for video poster extraction. The plugin auto-detects common paths (/opt/homebrew/bin/ffmpeg, /usr/local/bin/ffmpeg, /usr/bin/ffmpeg, /opt/ffmpeg/bin/ffmpeg). Use this filter on hosts with a non-standard install location.

Parameters:

Parameter Type Description
$binary string Resolved path or ffmpeg if auto-detect failed

Returns: string

add_filter( 'mvs_ffmpeg_binary', function( string $binary ) : string {
    return '/opt/custom/bin/ffmpeg';
} );

mvs_default_video_poster_url

Filters the fallback poster URL shown when ffmpeg could not generate a frame (e.g. ffmpeg not installed, corrupt video). The URL is used at render time only and is never stored in media meta.

Parameters:

Parameter Type Description
$url string Default: plugin-bundled SVG asset URL

Returns: string

add_filter( 'mvs_default_video_poster_url', function( string $url ) : string {
    return get_stylesheet_directory_uri() . '/assets/video-placeholder.webp';
} );

Cloud URL Serving (1.4.0)

mvs_serve_public_cloud_direct

Controls whether public media stored on a cloud driver is served via a direct CDN URL rather than through the plugin's /serve proxy. Default is true (direct). Set to false to force all traffic through the proxy (e.g. for request logging or header injection).

Parameters:

Parameter Type Description
$allowed bool Whether direct cloud serving is permitted. Default true
$media_id int Media ID

Returns: bool

add_filter( 'mvs_serve_public_cloud_direct', function( bool $allowed, int $media_id ) : bool {
    // Force all media through /serve for audit logging.
    return false;
}, 10, 2 );

mvs_public_cloud_thumbnail_url

Filters the direct CDN URL emitted for a cloud-hosted thumbnail. Use this to rewrite the URL to a custom domain (e.g. a CDN pull zone in front of S3) or return an empty string to fall back to the /serve proxy.

Parameters:

Parameter Type Description
$thumb_url string Cloud thumbnail URL
$media_id int Media ID
$size string Size key (e.g. thumb_large, thumb_medium)

Returns: string - Return empty string to force /serve proxy for this thumbnail.

add_filter( 'mvs_public_cloud_thumbnail_url', function( string $thumb_url, int $media_id, string $size ) : string {
    // Replace the raw S3 hostname with a CloudFront distribution.
    return str_replace( 'my-bucket.s3.amazonaws.com', 'cdn.example.com', $thumb_url );
}, 10, 3 );

mvs_public_cloud_file_url

Filters the direct CDN URL for a public media's original file. Companion to mvs_public_cloud_thumbnail_url for full-file reads. Return empty string to fall back to the /serve proxy.

Parameters:

Parameter Type Description
$file_url string Cloud file URL
$media_id int Media ID
$size string Always empty string for this filter (full-file context)

Returns: string

add_filter( 'mvs_public_cloud_file_url', function( string $file_url, int $media_id, string $size ) : string {
    return str_replace( 'my-bucket.s3.amazonaws.com', 'cdn.example.com', $file_url );
}, 10, 3 );

Broadcast Thumbnail Expiry (1.5.0)

mvs_broadcast_thumbnail_ttl

Sets how long, in seconds, a broadcast thumbnail access URL stays valid. These URLs are minted by MediaRepository::get_broadcast_thumbnail_url() for thumbnails embedded in long-lived surfaces such as notification emails and RSS feeds, where the link may be opened long after it was generated. The default is HOUR_IN_SECONDS (3600). The serve-time privacy check still runs on every request, so this filter only controls the link's validity window, not the access decision.

Raise the value on sites that cache those surfaces at the CDN for longer than an hour, but keep it as short as your caching allows.

Parameters:

Parameter Type Description
$ttl int Time-to-live in seconds. Default HOUR_IN_SECONDS (3600)
$media_id int Media ID the thumbnail belongs to
$size string Size key (e.g. thumb_large, thumb_medium)

Returns: int - The TTL in seconds.

add_filter( 'mvs_broadcast_thumbnail_ttl', function( int $ttl, int $media_id, string $size ) : int {
    // Widen to 6 hours on a site that caches notification emails / RSS at the CDN.
    return 6 * HOUR_IN_SECONDS;
}, 10, 3 );

Cloud, Thumbnails & Filenames

mvs_cloudops_allow_non_public_to_cloud

Controls whether a non-public (private/restricted) media item is allowed to be migrated to a cloud driver during a CloudOps migration. Default false - only public media is cloud-eligible, so private files stay on local disk. Return true to opt a specific item in.

Parameters:

Parameter Type Description
$allow bool Whether to allow the move. Default false
$media_id int Media ID
$privacy string Current privacy value
$to string Target driver slug

Returns: bool

add_filter( 'mvs_cloudops_allow_non_public_to_cloud', function( bool $allow, int $media_id, string $privacy, string $to ) : bool {
    // Allow "members" media to go to cloud, keep "private" local.
    return 'members' === $privacy ? true : $allow;
}, 10, 4 );

mvs_filename_strategy

Filters the stored-filename strategy used for new uploads. The built-in strategies are hashed (default) and original.

Parameters:

Parameter Type Description
$strategy string Resolved strategy slug
$user_id int Uploading user ID

Returns: string

add_filter( 'mvs_filename_strategy', function( string $strategy, int $user_id ) : string {
    return user_can( $user_id, 'manage_options' ) ? 'original' : $strategy;
}, 10, 2 );

Additional Storage Hooks

Hook Type Description Parameters Since
mvs_media_deleted action Media permanently deleted $media_id, $author_id 1.0
mvs_watermark_invalidated action Single watermark cache cleared $media_id 1.0
mvs_watermarks_invalidated_all action All watermark caches cleared none 1.0
mvs_watermark_enabled filter Enable/disable watermark per media item $enabled (bool), $media_id 1.0
mvs_generate_watermark filter Override watermark generation entirely $url, $media_id, $file_path, $file_url, $config 1.0
mvs_cloud_thumbnail_url filter Override the cloud URL stored for a generated thumbnail size at upload time. Return non-empty to use a custom URL $url (string, empty), $size_name (string), $media_id (int) 1.3.0
mvs_thumbnail_sizes filter Filter the size definitions array used for thumbnail generation $sizes (array) 1.3.0
mvs_thumbnail_size_resolved filter Filter the resolved default thumbnail size key $size (string) 1.3.0
mvs_can_repair_thumb filter Whether the "Repair thumbnail" admin row action is offered for a media item $can (bool), $media_id (int), $file_type (string), $file_path (string) 1.2.3
mvs_repair_media_thumb filter Let Pro / third parties regenerate thumbnails during an admin repair. Return the count of regenerated size-variants $regenerated (int), $media_id (int), $context (array: file_type, file_path) 1.2.3
mvs_watermark_font_path (Pro) filter Path to the TTF font used for text watermarks $path (string, empty), $config (array) 1.0
mvs_webhook_sslverify filter Whether outgoing webhook requests verify SSL (default off on local environments) $verify (bool), $url (string) 1.3.0

12. User Profiles

mvs_user_profile_url

Filters the public profile URL for a user. Override this to point at BuddyPress, a custom route, or any external profile system.

Parameters:

Parameter Type Description
$url string Default MVS profile URL
$user_id int User ID

Returns: string

/**
 * Use the WordPress author URL as the MVS profile URL.
 *
 * @since 1.0
 *
 * @param string $url     Default profile URL.
 * @param int    $user_id User ID.
 * @return string
 */
add_filter( 'mvs_user_profile_url', function( string $url, int $user_id ) {
    return get_author_posts_url( $user_id );
}, 10, 2 );

mvs_user_display_name

Filters a user's display name everywhere MVS renders it. Pro uses this internally to append a streak badge.

Parameters:

Parameter Type Description
$name string Current display name
$user_id int User ID

Returns: string

/**
 * Append a verified checkmark to display names.
 *
 * @since 1.0
 *
 * @param string $name    Current display name.
 * @param int    $user_id User ID.
 * @return string
 */
add_filter( 'mvs_user_display_name', function( string $name, int $user_id ) {
    if ( my_is_verified( $user_id ) ) {
        $name .= ' ✓';
    }
    return $name;
}, 10, 2 );

Additional Profile Hooks

Hook Type Description Parameters Since
mvs_profile_updated action Profile saved $user_id, $fields (array) 1.0
mvs_avatar_uploaded action Avatar uploaded $user_id, $attachment_id 1.0
mvs_avatar_deleted action Avatar removed $user_id 1.0
mvs_profile_data filter Filter profile data in REST response $data (array), $user_id 1.0
mvs_profile_update_fields filter Filter allowed profile update fields $fields (array), $user_id 1.0
mvs_avatar_allowed_types filter Filter allowed avatar MIME types $types (array), $user_id 1.0
mvs_avatar_max_size filter Max avatar file size in bytes $bytes (int), default 2 * MB_IN_BYTES 1.0

13. Access & Privacy

mvs_privacy_can_view

Filters the privacy access check result. Return null to use the built-in check, true to grant, or false to deny.

Parameters:

Parameter Type Description
$result bool|null Current result (null = use default logic)
$media_id int Media post ID
$user_id int Viewing user ID (0 for anonymous)
$privacy string Media privacy level slug

Returns: bool|null

/**
 * Grant access to group members using a custom group check.
 *
 * @since 1.0
 *
 * @param bool|null $result   Current access result.
 * @param int       $media_id Media post ID.
 * @param int       $user_id  Viewing user ID.
 * @param string    $privacy  Privacy level.
 * @return bool|null
 */
add_filter( 'mvs_privacy_can_view', function( $result, int $media_id, int $user_id, string $privacy ) {
    if ( 'group' === $privacy && my_user_in_media_group( $media_id, $user_id ) ) {
        return true;
    }
    return $result;
}, 10, 4 );

mvs_media_privacy_changed

Fires when a media item's privacy level changes. Fires from both MediaRepository::set() (single-field update path) and MediaRepository::update() (bulk update path). Does not fire when a row is first inserted.

Parameters:

Parameter Type Description
$media_id int Media ID
$new_privacy string New privacy value (e.g. public, members, friends, private)
$old_privacy string Previous privacy value
/**
 * Sync BuddyPress activity visibility when a media item is made private.
 *
 * @since 1.3.0
 *
 * @param int    $media_id    Media ID.
 * @param string $new_privacy New privacy value.
 * @param string $old_privacy Previous privacy value.
 */
add_action( 'mvs_media_privacy_changed', function( int $media_id, string $new_privacy, string $old_privacy ) {
    if ( 'public' === $old_privacy && 'public' !== $new_privacy ) {
        // Hide related BuddyPress activity from sitewide stream.
        my_plugin_hide_bp_activity_for_media( $media_id );
    }
}, 10, 3 );

Additional Access Hooks

Hook Type Description Parameters Since
mvs_access_rule_created action Access rule created for a media item $rule_id, $media_id, $rule_type, $rule_value 1.0
mvs_access_rule_deleted action Access rule removed $rule_id, $media_id, $rule_type 1.0
mvs_access_granted action User granted access to restricted media $grant_id, $media_id, $user_id, $source 1.0
mvs_access_revoked action User access revoked $media_id, $user_id 1.0
mvs_story_created action Story published $media_id, $user_id, $expires_at (signature changed in 1.2.3) 1.0
mvs_album_items_added action Media added to an album $album_id, $actor_id, $media_ids, $added (signature changed in 1.2.3) 1.0
mvs_story_expired action Story expired and removed $media_id 1.0

14. BuddyPress Integration

These hooks only fire when BuddyPress is active. Use mvs_buddynext_active to detect whether the BuddyNext integration layer is running.

mvs_buddynext_active

Filters whether the BuddyNext integration is considered active. Override this if you need to force or suppress the integration.

Parameters:

Parameter Type Description
$active bool Auto-detected active state

Returns: bool

/**
 * Force BuddyNext integration off in a staging environment.
 *
 * @since 1.0
 *
 * @param bool $active Auto-detected state.
 * @return bool
 */
add_filter( 'mvs_buddynext_active', function( bool $active ) {
    return defined( 'WP_STAGING' ) ? false : $active;
} );

15. Video Processing (Pro)

mvs_pro_transcode_complete (Pro)

Fires when all transcode presets for a video have finished processing.

Parameters:

Parameter Type Description
$media_id int Media post ID
$results array Per-preset result map
$final_status string Overall status: complete, partial, failed
/**
 * Notify the uploader when video transcoding is done.
 *
 * @since 1.0
 *
 * @param int    $media_id     Media post ID.
 * @param array  $results      Per-preset results.
 * @param string $final_status Overall transcode status.
 */
add_action( 'mvs_pro_transcode_complete', function( int $media_id, array $results, string $final_status ) {
    if ( 'complete' === $final_status ) {
        $author_id = (int) get_post_field( 'post_author', $media_id );
        my_send_transcode_ready_email( $author_id, $media_id );
    }
}, 10, 3 );

mvs_pro_transcode_presets (Pro) (New in 1.1)

Filters the transcode quality presets before encoding starts. Use this to add, remove, or modify quality levels.

Parameters:

Parameter Type Description
$presets array Array of preset definitions (slug, bitrate, resolution)
$media_id int Media post ID

Returns: array

/**
 * Remove the 4K preset to save storage costs.
 *
 * @since 1.1
 *
 * @param array $presets  Transcode preset definitions.
 * @param int   $media_id Media post ID.
 * @return array
 */
add_filter( 'mvs_pro_transcode_presets', function( array $presets, int $media_id ) {
    return array_filter( $presets, fn( $p ) => '2160p' !== $p['slug'] );
}, 10, 2 );

Additional Video Hooks

Hook Type Description Parameters Since
mvs_pro_captions_generated action Whisper transcription saved as WebVTT $media_id, $vtt_url 1.0
mvs_pro_poster_frame filter Filter video poster frame URL $poster_url, $media_id, $file_path 1.1

16. Analytics (Pro)

mvs_pro_analytics_event_data (Pro) (New in 1.1)

Filters analytics event data before it is stored. Return false to skip recording the event entirely.

Parameters:

Parameter Type Description
$event_data array Event payload (user_id, timestamp, meta)
$media_id int Media post ID
$event_type string Event type slug (e.g., view, download)

Returns: array|false

/**
 * Strip PII from analytics events before storage.
 *
 * @since 1.1
 *
 * @param array  $event_data Event data payload.
 * @param int    $media_id   Media post ID.
 * @param string $event_type Event type slug.
 * @return array|false
 */
add_filter( 'mvs_pro_analytics_event_data', function( $event_data, int $media_id, string $event_type ) {
    unset( $event_data['ip_address'] );
    return $event_data;
}, 10, 3 );

Additional Analytics Hooks

Hook Type Description Parameters Since
mvs_pro_analytics_recorded action Analytics event stored $media_id, $event_type, $user_id 1.1
mvs_pro_analytics_summary filter Filter dashboard analytics summary $summary (array), $media_id, $date_range 1.1

17. Layout System (Pro)

mvs_active_layout (Pro)

Filters the active layout slug. Use this to switch layouts programmatically (e.g., per page or per user role).

Parameters:

Parameter Type Description
$slug string Active layout slug from the database setting

Returns: string

/**
 * Show the Pinterest layout for mobile visitors.
 *
 * @since 1.0
 *
 * @param string $slug Active layout slug.
 * @return string
 */
add_filter( 'mvs_active_layout', function( string $slug ) {
    return wp_is_mobile() ? 'pinterest' : $slug;
} );

Additional Layout Hooks

Hook Type Description Parameters Since
mvs_layout_assets action After layout CSS/JS is enqueued $layout_instance, $slug 1.0
mvs_before_layout_render action Before layout template loads $layout_slug, $template_name 1.1
mvs_layout_modes filter Register custom layout modes $modes (slug => class) 1.0
mvs_layout_template_map filter Override template file mapping $map (array), $layout_instance 1.0
mvs_layout_config filter Filter layout configuration $config (array), $slug (string) 1.1

18. Quota System (Pro)

mvs_pro_quota_source (Pro) (New in 1.1)

Filters the quota package assigned to a user. Use this to integrate a custom membership or LMS plugin.

Parameters:

Parameter Type Description
$package array|null Package definition or null for the site default
$user_id int User ID

Returns: array|null

/**
 * Assign quota from a custom LMS based on course enrollment.
 *
 * @since 1.1
 *
 * @param array|null $package Quota package definition.
 * @param int        $user_id User ID.
 * @return array|null
 */
add_filter( 'mvs_pro_quota_source', function( $package, int $user_id ) {
    $course_id = my_lms_get_active_course( $user_id );
    if ( $course_id ) {
        return my_lms_get_quota_for_course( $course_id );
    }
    return $package;
}, 10, 2 );

mvs_pro_before_quota_check (Pro) (New in 1.1)

Fires before quota enforcement runs. Return a WP_Error to reject the action before quota is checked.

Parameters:

Parameter Type Description
$args array Quota check args (media_type, count)
$user_id int User ID

Returns: array|WP_Error

/**
 * Block uploads during a scheduled maintenance window.
 *
 * @since 1.1
 *
 * @param array $args    Quota check arguments.
 * @param int   $user_id User ID.
 * @return array|WP_Error
 */
add_filter( 'mvs_pro_before_quota_check', function( $args, int $user_id ) {
    if ( get_option( 'my_plugin_maintenance_mode' ) ) {
        return new WP_Error( 'maintenance', __( 'Uploads are paused for maintenance.', 'my-plugin' ) );
    }
    return $args;
}, 10, 2 );

Additional Quota Hooks

Hook Type Description Parameters Since
mvs_pro_credits_added action Credits added to user quota $user_id, $media_type, $amount, $source 1.0
mvs_pro_woo_package_assigned action WooCommerce order assigns a quota package $user_id, $product_id, $package_id, $order_status 1.0
mvs_pro_woo_package_reverted action WooCommerce order cancelled, package reverted $user_id, $default, $order_status 1.0
mvs_pro_memberpress_package_assigned action MemberPress membership assigns package $user_id, $membership_id, $package_id 1.0
mvs_pro_memberpress_package_reverted action MemberPress membership expired, reverted $user_id, $default 1.0
mvs_pro_pmpro_package_assigned action PMPro level assigns package $user_id, $level_id, $package_id 1.0
mvs_pro_pmpro_package_reverted action PMPro level cancelled, reverted $user_id, $default 1.0
mvs_quota_render_mapping_fields action Admin quota page renders mapping UI none 1.0
mvs_quota_save_mapping action Admin saves quota mapping none 1.0

19. Competitions (Pro)

Challenges

Hook Type Description Parameters Since
mvs_challenge_created action Challenge created $competition_id, $args, $created_by 1.0
mvs_challenge_entry_submitted action User submits an entry $challenge_id, $user_id, $media_id 1.0
mvs_challenge_activated action A scheduled challenge transitions to active $challenge_id 1.5.0
mvs_challenge_voting_started action A challenge closes entries and opens voting $challenge_id 1.5.0
mvs_challenge_winner_named action Fires once per top-3 rank when a challenge is finalized - fires before mvs_challenge_finalized $challenge_id, $user_id, $rank (1, 2, or 3) 1.2.3
mvs_challenge_finalized action Voting ends, winners determined $challenge_id, $results 1.0
mvs_competition_status_changed action Any competition (challenge/battle/tournament) changes status $competition_id, $old_status, $new_status 1.5.0
/**
 * Award scaled XP per rank without parsing the private $results shape.
 *
 * @since 1.2.3
 *
 * @param int $challenge_id Challenge competition ID.
 * @param int $user_id      Winning user ID.
 * @param int $rank         1, 2, or 3.
 */
add_action( 'mvs_challenge_winner_named', function( int $challenge_id, int $user_id, int $rank ) {
    $scale = array( 1 => 500, 2 => 250, 3 => 100 );
    if ( isset( $scale[ $rank ] ) ) {
        my_award_xp( $user_id, $scale[ $rank ] );
    }
}, 10, 3 );

Battles

Hook Type Description Parameters Since
mvs_battle_created action Battle created $competition_id, $challenger_id, $opponent_id 1.0
mvs_battle_accepted action Opponent accepts battle $battle_id, $user_id 1.0
mvs_battle_resolved action Battle voting ends, winner determined $battle_id, $winner_id, $loser_id 1.0
mvs_battle_cancelled action Battle cancelled $battle_id 1.5.0

Tournaments

Hook Type Description Parameters Since
mvs_tournament_created action Tournament created $competition_id, $args, $created_by 1.0
mvs_tournament_started action Registration closes, bracket generated $tournament_id 1.0
mvs_tournament_match_resolved action Single bracket match resolved $match_id, $winner_id 1.0
mvs_tournament_finalized action Tournament ends, champion crowned $competition_id, $champion_id 1.0
mvs_tournament_updated action Tournament settings updated $tournament_id, $args 1.5.0
mvs_tournament_cancelled action Tournament cancelled $tournament_id 1.5.0

Autopilot

Hook Type Description Parameters Since
mvs_autopilot_challenge_created action Autopilot creates a weekly challenge $competition_id, $theme 1.0
mvs_autopilot_create_failed action Autopilot failed to create a challenge $error, $theme 1.0
mvs_autopilot_no_theme_available action All themes in pool have been used none 1.0
mvs_autopilot_pool_reset action Theme pool recycled $pool 1.0

Streaks

Hook Type Description Parameters Since
mvs_streak_milestone action User reaches a streak milestone (7, 30, 100, 365 days) $user_id, $days, $xp_awarded 1.0
/**
 * Send a congratulations email at streak milestones.
 *
 * @since 1.0
 *
 * @param int $user_id    User ID.
 * @param int $days       Streak day count reached.
 * @param int $xp_awarded XP awarded for this milestone.
 */
add_action( 'mvs_streak_milestone', function( int $user_id, int $days, int $xp_awarded ) {
    $user = get_userdata( $user_id );
    wp_mail(
        $user->user_email,
        sprintf( __( 'You reached a %d-day streak!', 'my-plugin' ), $days ),
        sprintf( __( 'Congratulations! You earned %d XP.', 'my-plugin' ), $xp_awarded )
    );
}, 10, 3 );

Competition Scheduler

The competitions cron tick (CompetitionsScheduler) fires these process-stage actions on each run. They take no per-item parameters - listen to them to run side-effects around the lifecycle batch, or call them directly to force a stage. mvs_competitions_tick_ran reports the wall-clock time of the completed tick.

Hook Type Description Parameters Since
mvs_activate_scheduled_challenges action Activate challenges whose start time has passed none 1.5.0
mvs_close_challenge_entries action Close entry submission for challenges that hit the entry deadline none 1.5.0
mvs_finalize_expired_challenges action Finalize challenges whose voting window has ended none 1.5.0
mvs_start_registered_tournaments action Start tournaments whose registration window closed none 1.5.0
mvs_resolve_expired_matches action Resolve battle/tournament matches past their deadline none 1.5.0
mvs_competitions_tick_ran action Fires at the end of every scheduler tick $timestamp (int, time()) 1.5.0

Challenge Email Templates

Each of these filters lets you override the subject or body of a challenge notification email. All are string filters; return the modified string. The trailing parameters give you the context to personalize the copy.

Filter Description Parameters Since
mvs_challenge_email_created_subject Subject of the "challenge created" email $subject (string), $challenge_id (int), $args (array), $created_by (int) 1.5.0
mvs_challenge_email_created_body Body of the "challenge created" email $body (string), $challenge_id (int), $args (array), $created_by (int) 1.5.0
mvs_challenge_email_entry_subject Subject of the "entry received" email $subject (string), $challenge_id (int), $user_id (int), $media_id (int) 1.5.0
mvs_challenge_email_entry_body Body of the "entry received" email $body (string), $challenge_id (int), $user_id (int), $media_id (int) 1.5.0
mvs_challenge_email_winner_subject Subject of the "you won" email $subject (string), $challenge_id (int), $user_id (int), $rank (int) 1.5.0
mvs_challenge_email_winner_body Body of the "you won" email $body (string), $challenge_id (int), $user_id (int), $rank (int) 1.5.0
mvs_challenge_email_participant_subject Subject of the "challenge ended" email to non-winning participants $subject (string), $challenge_id (int), $user_id (int) 1.5.0
mvs_challenge_email_participant_body Body of the "challenge ended" participant email $body (string), $challenge_id (int), $user_id (int) 1.5.0

mvs_pro_leaderboard_xp_rows (Pro)

Supplies leaderboard rows for the xp metric. The Pro leaderboard renderer defers to this filter (it owns no XP store of its own), so a gamification plugin returns the ranked rows here. Return an array of ['user_id' => int, 'score' => int] rows.

Parameters:

Parameter Type Description
$rows array Default empty array
$per_page int Row limit
$window string Time window slug (e.g. all, month, week)

Returns: array

add_filter( 'mvs_pro_leaderboard_xp_rows', function( array $rows, int $per_page, string $window ) : array {
    return my_gamification_top_xp( $per_page, $window ); // [ ['user_id'=>1,'score'=>500], ... ]
}, 10, 3 );

21. Connectors (Pro)

Pro's import/export connectors (e.g. Flickr) register through mvs_connectors and fire import/export actions as media moves in and out.

mvs_connectors

Registers connector instances. Return your connector keyed by its slug.

Parameters:

Parameter Type Description
$connectors array Map of slug => connector instance. Default empty

Returns: array

add_filter( 'mvs_connectors', function( array $connectors ) : array {
    $connectors['my_service'] = new My_Service_Connector();
    return $connectors;
} );

Additional Connector Hooks

Hook Type Description Parameters Since
mvs_media_imported action A media item was imported from a connector $media_id (int), $connector (string, e.g. flickr), $remote_id (string) 1.5.0
mvs_media_exported action A media item was exported to a connector $media_id (int), $connector (string), $remote_id (string) 1.5.0

22. Common Recipes

Recipe 1: Custom upload size per user role

Give editors 50 MB and subscribers 10 MB using mvs_max_upload_size.

/**
 * Set upload limits based on user role.
 *
 * @since 1.1
 *
 * @param int $max_size Default max size in bytes.
 * @param int $user_id  Uploading user ID.
 * @return int
 */
add_filter( 'mvs_max_upload_size', function( int $max_size, int $user_id ) {
    if ( user_can( $user_id, 'edit_others_posts' ) ) {
        return 50 * MB_IN_BYTES;
    }
    if ( user_can( $user_id, 'read' ) ) {
        return 10 * MB_IN_BYTES;
    }
    return $max_size;
}, 10, 2 );

Recipe 2: Add a custom "Most Commented" sort option

Register the sort option with mvs_feed_sort_options and apply the ordering with mvs_feed_query_args.

/**
 * Register the "Most Commented" sort option.
 *
 * @since 1.1
 *
 * @param array $options Existing sort options.
 * @return array
 */
add_filter( 'mvs_feed_sort_options', function( array $options ) {
    $options['most_commented'] = __( 'Most Commented', 'my-plugin' );
    return $options;
} );

/**
 * Apply the "most_commented" ordering to the feed query.
 *
 * @since 1.1
 *
 * @param array           $query_args WP_Query arguments.
 * @param WP_REST_Request $request    Incoming REST request.
 * @return array
 */
add_filter( 'mvs_feed_query_args', function( array $query_args, $request ) {
    if ( 'most_commented' === $request->get_param( 'sort' ) ) {
        $query_args['orderby'] = 'comment_count';
        $query_args['order']   = 'DESC';
    }
    return $query_args;
}, 10, 2 );

Recipe 3: Skip AI analysis for videos

Use mvs_should_ai_analyze to prevent AI processing for video media types.

/**
 * Skip AI analysis for video media.
 *
 * @since 1.1
 *
 * @param bool $should_analyze Whether to run AI analysis.
 * @param int  $media_id       Media post ID.
 * @return bool
 */
add_filter( 'mvs_should_ai_analyze', function( bool $should_analyze, int $media_id ) {
    return 'video' !== get_post_meta( $media_id, '_mvs_media_type', true );
}, 10, 2 );

Recipe 4: Send an email for comment notifications

Use mvs_notification_created to dispatch an email when a comment notification is created.

/**
 * Email the media owner when someone comments on their upload.
 *
 * @since 1.1
 *
 * @param int    $notification_id Notification record ID.
 * @param int    $user_id         Recipient user ID.
 * @param string $type            Notification type slug.
 * @param int    $actor_id        Triggering user ID.
 * @param int    $media_id        Related media post ID.
 */
add_action( 'mvs_notification_created', function( int $notification_id, int $user_id, string $type, int $actor_id, int $media_id ) {
    if ( 'comment' !== $type ) {
        return;
    }

    $recipient = get_userdata( $user_id );
    $actor     = get_userdata( $actor_id );
    $media_url = get_permalink( $media_id );

    wp_mail(
        $recipient->user_email,
        __( 'New comment on your photo', 'my-plugin' ),
        sprintf(
            /* translators: 1: commenter name, 2: media URL */
            __( '%1$s commented on your photo. View it here: %2$s', 'my-plugin' ),
            $actor->display_name,
            $media_url
        )
    );
}, 10, 5 );

Recipe 5: Custom quota from an LMS plugin (Pro)

Use mvs_pro_quota_source to assign quota packages based on LearnDash course enrollment.

/**
 * Map LearnDash course enrollment to MVS quota packages.
 *
 * @since 1.1
 *
 * @param array|null $package Current quota package or null for site default.
 * @param int        $user_id User ID.
 * @return array|null
 */
add_filter( 'mvs_pro_quota_source', function( $package, int $user_id ) {
    // Check if the user is enrolled in the "Pro Creator" course (ID: 123).
    if ( function_exists( 'sfwd_lms_has_access' ) && sfwd_lms_has_access( 123, $user_id ) ) {
        return [
            'photo_limit' => 500,
            'video_limit' => 50,
            'label'       => 'Pro Creator',
        ];
    }
    return $package;
}, 10, 2 );

Recipe 6: Add custom fields to the media REST response

Use mvs_media_response to append custom post meta or computed values to the media API response.

/**
 * Add location and EXIF data to the media REST response.
 *
 * @since 1.0
 *
 * @param array $data     REST response data array.
 * @param int   $media_id Media post ID.
 * @return array
 */
add_filter( 'mvs_media_response', function( array $data, int $media_id ) {
    $data['location']  = get_post_meta( $media_id, '_mvs_location', true );
    $data['camera']    = get_post_meta( $media_id, '_mvs_exif_camera', true );
    $data['focal_len'] = get_post_meta( $media_id, '_mvs_exif_focal_length', true );
    return $data;
}, 10, 2 );

Recipe 7: Add a custom thumbnail size

Use mvs_before_thumbnail_generation to inject an additional size into the generation pipeline.

/**
 * Generate an 800×600 "hero" thumbnail for every upload.
 *
 * Note: Register the size with add_image_size() so WordPress
 * creates it via multi_resize. Hook into upload_dir or the
 * upload pipeline to inject it into MVS-managed sizes.
 *
 * @since 1.1
 */
add_action( 'init', function() {
    add_image_size( 'mvs-hero', 800, 600, true );
} );

/**
 * Log when MVS thumbnail generation begins.
 *
 * @since 1.1
 *
 * @param int    $media_id  Media post ID.
 * @param string $file_path Absolute path to uploaded file.
 * @param array  $sizes     Size definitions passed to multi_resize.
 */
add_action( 'mvs_before_thumbnail_generation', function( int $media_id, string $file_path, array $sizes ) {
    // Inspect $sizes; the mvs-hero size is included automatically
    // once registered with add_image_size().
    error_log( 'Generating thumbnails for media #' . $media_id );
}, 10, 3 );

Recipe 8: Redirect profile URLs to BuddyPress

Use mvs_user_profile_url to point all MVS profile links at the BuddyPress member profile.

/**
 * Use the BuddyPress member profile URL for all MVS profile links.
 *
 * @since 1.0
 *
 * @param string $url     Default MVS profile URL.
 * @param int    $user_id User ID.
 * @return string
 */
add_filter( 'mvs_user_profile_url', function( string $url, int $user_id ) {
    if ( function_exists( 'bp_core_get_user_domain' ) ) {
        return bp_core_get_user_domain( $user_id );
    }
    return $url;
}, 10, 2 );

Template Overrides

Endpoints and hooks marked (Pro) require WPMediaVerse Pro.

WPMediaVerse uses a template loading system that checks your active theme before falling back to plugin templates. This lets you fully customize media page layouts without modifying the plugin.

How It Works

The TemplateLoader class calls locate_template() to check these locations in order:

  1. Child theme: wp-content/themes/child-theme/wpmediaverse/template-name.php
  2. Parent theme: wp-content/themes/parent-theme/wpmediaverse/template-name.php
  3. Plugin default: wp-content/plugins/wpmediaverse/templates/template-name.php

Creating a Theme Override

Create a wpmediaverse/ directory inside your theme and copy the template file you want to modify:

wp-content/themes/your-theme/
└── wpmediaverse/
    ├── media-single.php       # Single media item page
    ├── album.php              # Single album page
    ├── collection.php         # Single collection page
    ├── explore.php            # Media archive / explore page
    └── profile-edit.php       # Profile edit page

Available Templates

File Used For
media-single.php Single mvs_media post page
album.php Single mvs_album post page
collection.php Single mvs_collection post page
explore.php mvs_media and mvs_album archive, taxonomy archives, and /media/@username/ profile pages
profile-edit.php /media/edit-profile/ endpoint

Available Partials

Template partials are located in templates/partials/ and loaded with TemplateLoader::get_template():

WPMediaVerse\Core\TemplateLoader::get_template( 'partials/media-card.php', array(
    'media_id' => $post->ID,
) );

Using TemplateLoader in Custom Code

use WPMediaVerse\Core\TemplateLoader;

// Load a template with data.
TemplateLoader::get_template( 'media-single.php', array(
    'media_id' => 123,
    'show_reactions' => true,
) );

// Just locate the path (without loading).
$path = TemplateLoader::locate( 'explore.php' );

Getting Media URLs in a Template (MediaUrl)

When you write a custom template you almost always need a media URL - a thumbnail for a grid card, or the full file for a lightbox. Do not hand-build these URLs from wp_upload_dir() and do not call SignedUrlService directly. A raw upload path breaks the moment a site enables cloud storage or marks media private, and calling the signing service directly means re-implementing the privacy gate.

Since 1.5.0 the read-side facade WPMediaVerse\Core\MediaUrl is the single entry point. It resolves the active storage driver, runs the privacy check, and returns either a signed /serve URL or a direct CDN URL - whichever is correct for that media's privacy and the current driver.

use WPMediaVerse\Core\MediaUrl;

// Thumbnail URL for the current viewer (size: large | medium | thumb).
$thumb = MediaUrl::thumb( $media_id, 'large' );

// Full original file URL for the current viewer.
$full = MediaUrl::file( $media_id );

if ( $thumb ) {
    printf(
        '<img src="%s" alt="%s" loading="lazy" />',
        esc_url( $thumb ),
        esc_attr( get_the_title( $media_id ) )
    );
}

Both methods return an empty string when the service isn't ready (very early bootstrap) or when the viewer's identity is rejected by the privacy gate - always guard the return value before printing, as shown above.

Public static methods

namespace WPMediaVerse\Core;

final class MediaUrl {

    // Signed /serve URL for a thumb variant. $size: large|medium|thumb.
    // $ttl 0 = service default. $user_id null = current user; 0 = broadcast surface.
    // $skip_privacy is forced to false when the resolved user id is 0.
    public static function thumb(
        int $media_id,
        string $size = 'large',
        int $ttl = 0,
        ?int $user_id = null,
        bool $skip_privacy = true
    ): string;

    // Signed /serve URL for the full original file.
    public static function file( int $media_id, ?int $user_id = null ): string;

    // The meta key holding a variant's stored URL.
    // e.g. ('large') -> 'thumb_large'; ('large','webp') -> 'thumb_large_webp'.
    public static function variant_meta_key(
        string $size,
        string $format = VariantSpec::FORMAT_PRIMARY
    ): string;

    // The meta key holding a variant's driver-agnostic relative path
    // (the URL key with a `_path` suffix). e.g. ('large') -> 'thumb_large_path'.
    public static function variant_path_meta_key(
        string $size,
        string $format = VariantSpec::FORMAT_PRIMARY
    ): string;
}

The two *_meta_key() helpers are the single source of truth for the size+format → meta-key mapping. Use them instead of hardcoding 'thumb_large_webp'-style strings when you need to read variant meta yourself:

use WPMediaVerse\Core\MediaUrl;
use WPMediaVerse\Services\VariantSpec;

$webp_key = MediaUrl::variant_meta_key( 'large', VariantSpec::FORMAT_WEBP ); // 'thumb_large_webp'
$webp_url = get_post_meta( $media_id, $webp_key, true );

Note: TemplateHelpers::get_thumb_url() is now a one-line delegate to MediaUrl::thumb(), so existing templates that already use it keep working unchanged - MediaUrl is simply the canonical name to reach for in new code.

Filtering the Template Path

You can override any template path using the mvs_locate_template filter:

add_filter( 'mvs_locate_template', function( string $template, string $name, string $path ) {
    // Use a completely different directory for all WPMediaVerse templates.
    $override = WP_CONTENT_DIR . '/my-media-templates/' . $name;
    return file_exists( $override ) ? $override : $template;
}, 10, 3 );

BuddyX Theme Integration

WPMediaVerse adds the mvs-page and no-sidebar CSS body classes to all WPMediaVerse pages. The BuddyX theme (and any theme that handles these classes) renders these pages full-width without a sidebar.

Pages that receive these classes:

  • Single mvs_media, mvs_album, mvs_collection posts
  • mvs_media and mvs_album archives
  • mvs_tag and mvs_category taxonomy pages
  • /media/edit-profile/ endpoint
  • /media/@username/ profile pages
  • Any page whose ID matches an mvs_page_* option (e.g., the page containing [mvs_dashboard])

Shortcode Context in Block Templates

When a shortcode renders a block template (via Shortcodes::render_block_template()), the variable $mvs_shortcode_context is set to true. Block render.php files should check this variable before calling get_block_wrapper_attributes(), which causes a PHP warning outside a block context:

if ( empty( $mvs_shortcode_context ) ) {
    $wrapper_attrs = get_block_wrapper_attributes();
}

Custom Storage Drivers

Endpoints and hooks marked (Pro) require WPMediaVerse Pro.

WPMediaVerse uses a pluggable storage driver system. The free plugin ships with a Local driver (stores files in the WordPress uploads directory). WPMediaVerse Pro adds Amazon S3 and BunnyCDN drivers. You can implement your own driver by implementing the StorageDriverInterface.

StorageDriverInterface

namespace WPMediaVerse\Services;

interface StorageDriverInterface {

    /**
     * Store a file.
     *
     * @param string $source_path  Absolute local path of the temporary file.
     * @param string $dest_path    Relative destination path (e.g., "2025/03/photo.webp").
     * @return bool True on success.
     */
    public function store( string $source_path, string $dest_path ): bool;

    /**
     * Delete a file.
     *
     * @param string $path Relative path.
     * @return bool True on success.
     */
    public function delete( string $path ): bool;

    /**
     * Get the public URL for a file.
     *
     * @param string $path Relative path.
     * @return string Full URL.
     */
    public function url( string $path ): string;

    /**
     * Check if a file exists.
     *
     * @param string $path Relative path.
     * @return bool
     */
    public function exists( string $path ): bool;

    /**
     * Get the absolute filesystem path for a stored file.
     *
     * @since 1.1.0
     *
     * @param string $path Relative path.
     * @return string Absolute file path.
     */
    public function get_full_path( string $path ): string;

    /**
     * Download a stored file to a local destination path.
     *
     * REQUIRED for two flows:
     *   1. The `wp mvs migrate-storage` CLI command - copies media between
     *      drivers without losing files.
     *   2. Thumbnail generation in cloud mode - images stored on S3/BunnyCDN
     *      must be pulled to a local temp file before `wp_get_image_editor()`
     *      can resize them.
     *
     * A local driver returns true immediately if `$path` and `$local_dest`
     * resolve to the same file (no-op). Cloud drivers stream the remote object
     * into `$local_dest`. Return false on any download failure (the caller is
     * responsible for retries / cleanup of partial writes).
     *
     * @since 1.2.2
     *
     * @param string $path       Relative path of the source file.
     * @param string $local_dest Absolute local filesystem path to write to.
     * @return bool True on success.
     */
    public function download( string $path, string $local_dest ): bool;
}

All seven methods are required. A driver that does not implement every method will fail PHP's interface contract at load time.

Implementing a Custom Driver

use WPMediaVerse\Services\StorageDriverInterface;

class MyS3CompatibleDriver implements StorageDriverInterface {

    private string $bucket;
    private string $endpoint;

    public function __construct() {
        $this->bucket   = get_option( 'my_storage_bucket' );
        $this->endpoint = get_option( 'my_storage_endpoint' );
    }

    public function store( string $source_path, string $dest_path ): bool {
        // Upload $source_path to $this->bucket/$dest_path.
        // Return true on success.
    }

    public function delete( string $path ): bool {
        // Delete $this->bucket/$path.
    }

    public function url( string $path ): string {
        return $this->endpoint . '/' . $this->bucket . '/' . $path;
    }

    public function exists( string $path ): bool {
        // Check if object exists in bucket.
    }

    public function get_full_path( string $path ): string {
        // For remote drivers, this may return the URL or a temp local path.
        return $this->url( $path );
    }

    public function download( string $path, string $local_dest ): bool {
        // Stream $this->bucket/$path into the local file $local_dest.
        // Return true on success, false on any failure.
    }
}

Registering Your Driver

StorageService::get_driver() resolves the active driver by passing the configured driver name through the singular mvs_storage_driver filter and expecting a driver instance back. The filter receives two arguments - the current driver (null until something supplies one) and the configured driver name - and your callback returns your driver instance only when the name matches your slug, otherwise it returns $driver unchanged:

add_filter( 'mvs_storage_driver', function( $driver, string $name ) {
    return 'my_s3_compatible' === $name ? new MyS3CompatibleDriver() : $driver;
}, 10, 2 );

If no filter returns a StorageDriverInterface instance, StorageService falls back to the built-in LocalDriver.

This is exactly how WPMediaVerse Pro registers its own drivers - its callback switches on $name and returns the matching driver (s3, bunnycdn, r2, dospaces):

// Simplified from WPMediaVerse Pro.
add_filter( 'mvs_storage_driver', function( $driver, string $name ) {
    switch ( $name ) {
        case 's3':
            return new S3StorageDriver();
        case 'bunnycdn':
            return new BunnyCDNStorageDriver();
        default:
            return $driver;
    }
}, 10, 2 );

Then set the active driver to your slug so StorageService picks it:

wp option update mvs_storage_driver my_s3_compatible

(Or add your slug to the settings-page dropdown via a separate filter so site owners can switch to it from the admin UI.)

Local Driver Reference

The built-in local driver stores files at:

{wp_upload_dir['basedir']}/wpmediaverse/YYYY/MM/filename.ext

Files are served from:

{wp_upload_dir['baseurl']}/wpmediaverse/YYYY/MM/filename.ext

get_full_path() returns the absolute filesystem path, which is used by services like WatermarkService and AIService that need to read the file from disk.

Signed URLs and Private Delivery

Your driver does not generate signed URLs - that is SignedUrlService's job. The flow is:

  1. A read-side caller asks Core\MediaUrl::thumb() / ::file() (or SignedUrlService directly) for a URL.
  2. SignedUrlService::generate() / ::generate_thumbnail() runs the privacy check (PrivacyService::can_view()), then either:
    • returns a signed /serve proxy URL (HMAC-signed query params: media ID, viewer user ID, expiry, signature) for gated/private media, or
    • returns a direct driver URL for public media on a cloud driver, so the browser hits the CDN edge instead of WordPress.
  3. When the browser requests a /serve URL, SignedUrlService::serve() re-validates the signature and re-checks can_view() per request, then streams the bytes by reading the file from your driver (via get_full_path() / download()).

There is no mvs_generate_signed_url filter. The public extension points that actually exist let a cloud driver substitute its own CDN/presigned URL for the public-media direct path:

Filter Args When it fires
mvs_serve_public_cloud_direct (bool $enabled, int $media_id) Gate the "serve public cloud media directly" behavior on/off per media.
mvs_public_cloud_thumbnail_url (string $url, int $media_id, string $size) Final say on the public thumbnail URL for cloud-hosted media - return a presigned/CDN URL here.
mvs_public_cloud_file_url (string $url, int $media_id, string $context) Final say on the public full-file URL for cloud-hosted media.
// Substitute a presigned URL for your driver's public thumbnails.
add_filter( 'mvs_public_cloud_thumbnail_url', function( string $url, int $media_id, string $size ) {
    if ( 'my_s3_compatible' !== get_option( 'mvs_storage_driver' ) ) {
        return $url;
    }
    // Build the relative variant path and sign it with your SDK.
    $rel = get_post_meta( $media_id, 'thumb_' . $size . '_path', true );
    return $rel ? my_generate_presigned_url( $rel, 3600 ) : $url;
}, 10, 3 );

Private and restricted media is never eligible for the direct-cloud path - StorageService::get_driver_for_privacy() keeps non-public media on local disk, and /serve re-checks can_view() on every request. So these filters only affect public media.

WP-CLI Commands

Endpoints and hooks marked (Pro) require WPMediaVerse Pro.

WPMediaVerse registers all its commands under the wp mvs namespace.

wp mvs stats

Display plugin statistics in a table.

wp mvs stats

Output:

+-------------------+-------+
| Metric            | Value |
+-------------------+-------+
| Published Media   | 342   |
| Albums            | 18    |
| Total Views       | 9841  |
| Total Reactions   | 512   |
| Total Favorites   | 203   |
| DB Version        | 5     |
| Plugin Version    | 1.0.0 |
+-------------------+-------+

wp mvs migrate

Run or check database migrations.

# Run all pending migrations.
wp mvs migrate

# Check if migrations are needed (dry run).
wp mvs migrate --check

Options:

Option Description
--check Only check if migrations are needed; do not run them

Examples:

# Typical update workflow:
wp mvs migrate --check
wp mvs migrate

wp mvs prune-views

Delete old per-view tracking records to keep the wp_mvs_media_views table from growing indefinitely.

# Prune views older than 90 days (default).
wp mvs prune-views

# Prune views older than 30 days.
wp mvs prune-views --days=30

# Preview how many rows would be deleted.
wp mvs prune-views --dry-run

# Combine options.
wp mvs prune-views --days=30 --dry-run

Options:

Option Default Description
--days=<days> 90 Retain records newer than this many days
--dry-run off Show count without deleting

wp mvs cleanup-expired

Remove expired custom access grants from the wp_mvs_access_grants table.

# Cleanup with default batch size (100 grants at a time).
wp mvs cleanup-expired

# Use a larger batch size for faster cleanup on large sites.
wp mvs cleanup-expired --batch-size=500

Options:

Option Default Description
--batch-size=<size> 100 Number of grants to process per batch

wp mvs reindex

Rebuild the wp_mvs_media_index table from all published mvs_media posts. Run this if the index becomes out of sync (e.g., after a direct database import or migration from another plugin).

# Reindex with default batch size.
wp mvs reindex

# Use smaller batches to reduce memory usage on large sites.
wp mvs reindex --batch-size=50

Options:

Option Default Description
--batch-size=<size> 100 Number of posts to process per batch

The command outputs progress as it processes each batch and shows a final count of indexed items.


wp mvs cache-flush

Flush all WPMediaVerse caches - both object cache groups and plugin-managed transients. Run this after making direct database changes or when stale data is suspected.

wp mvs cache-flush

Output:

Success: WPMediaVerse caches flushed.

wp mvs moderation-stats

Display the current moderation queue statistics broken down by status.

wp mvs moderation-stats

Output:

+----------+-------+
| Status   | Count |
+----------+-------+
| Pending  | 14    |
| Approved | 3281  |
| Rejected | 57    |
+----------+-------+

wp mvs generate-video-thumbnails

Generate poster frames for video media items that don't have a thumb_large meta entry yet. Uses ffmpeg to extract a frame at the second mark of each video. Idempotent - videos that already have a poster are skipped unless --force is passed.

Recommended after upgrading to 1.2.0 to backfill posters for any video uploaded before the fix. Without a poster, video media renders a black frame in the lightbox until playback starts; with a poster, the first frame appears immediately.

# Backfill missing posters (skips videos that already have one).
wp mvs generate-video-thumbnails

# Dry-run to count what would be processed.
wp mvs generate-video-thumbnails --dry-run

# Re-extract posters even when they already exist (e.g. after switching ffmpeg builds).
wp mvs generate-video-thumbnails --force

Requirements:

  • ffmpeg available on the host system (check with which ffmpeg).

Options:

Option Default Description
--force off Overwrite existing thumb_large posters
--dry-run off Count eligible videos without writing to disk or DB

Added in 1.2.0.


wp mvs backfill-activity-thumbnails

Backfill BuddyPress activity thumbnails for media items that were imported without them (e.g., bulk imports or migrations from another plugin). Processes items in batches to avoid memory exhaustion.

# Backfill with default batch size.
wp mvs backfill-activity-thumbnails

# Use a smaller batch on memory-constrained hosts.
wp mvs backfill-activity-thumbnails --batch-size=25

# Preview what would be updated without writing to the database.
wp mvs backfill-activity-thumbnails --dry-run

Options:

Option Default Description
--batch-size=<size> 50 Number of activity records to process per batch
--dry-run off Count eligible records without updating them

wp mvs sync-activity-privacy

Recompute the BuddyPress activity hide_sitewide flag for every existing media activity. Walks each mvs_media_upload activity plus every activity_update carrying _mvs_media_ids meta and recomputes hide_sitewide from the linked media's effective privacy (media + parent album, most-restrictive wins). Run once after upgrading to bring legacy activity rows in line with the privacy-sync behaviour. Idempotent - re-runs only touch rows that have actually drifted.

Requires BuddyPress with the Activity component active; the command exits early otherwise.

# Preview which activity rows would change.
wp mvs sync-activity-privacy --dry-run

# Apply the recomputed flags.
wp mvs sync-activity-privacy

Options:

Option Default Description
--dry-run off Show what would change without writing to the database

wp mvs relocalize-private

Heal pre-1.4.0 non-public media rows whose stored URL meta still points at a prior cloud bucket. When media uploaded to a cloud driver (S3 / BunnyCDN / R2 / DigitalOcean Spaces) was later switched to a restricted privacy level (members / friends / private / group / custom), the URL meta was not relocalized, so SignedUrlService 403s on every read because the cloud URL fails its wpmediaverse/ containment check. The 1.4.0 listener fixes this going forward; this command heals older rows (Basecamp #9925110293).

Safe to re-run - rows that already have local URLs are skipped.

Added in 1.4.0.

# Report what would change without writing.
wp mvs relocalize-private --dry-run

# Heal all affected rows.
wp mvs relocalize-private

# Inspect a single media row.
wp mvs relocalize-private --media-id=47

Options:

Option Default Description
--dry-run off Report what would change without writing
--media-id=<id> 0 (all) Only inspect this single media row
--limit=<n> 0 (all) Stop after inspecting this many candidate rows

wp mvs migrate-storage

Migrate every stored media file from one storage driver to another. Idempotent - re-running skips media already present on the destination. Only public media is migrated by default (non-public media stays local to preserve privacy). The active mvs_storage_driver option is NOT flipped automatically; flip it manually via wp option update once the run completes cleanly.

Added in 1.3.0.

# Dry run: report what would move without touching files.
wp mvs migrate-storage --from=local --to=s3 --dry-run

# Migrate but keep source copies for a verification window.
wp mvs migrate-storage --from=local --to=s3 --keep-source

# Full migration.
wp mvs migrate-storage --from=local --to=s3

# Migrate a single media row for testing.
wp mvs migrate-storage --from=local --to=s3 --media-id=42

Options:

Option Default Description
--from=<driver> required Source driver slug: local, s3, bunnycdn
--to=<driver> required Destination driver slug. Must differ from --from
--dry-run off Walk the media list and report without transferring files
--keep-source off Skip post-verify source-side deletion
--media-id=<id> 0 (all) Migrate one specific media row
--limit=<n> 0 (all) Stop after this many rows
--include-non-public off Also migrate non-public media (only when cloud bucket is private)

Note: s3 and bunnycdn drivers require WPMediaVerse Pro.


wp mvs cloud-thumbs-backfill

Push thumbnail variants to the active cloud driver for images uploaded before 1.3.0 (when cloud-side thumbnails landed). Downloads each original from cloud, regenerates three size variants locally, and uploads each variant. Only processes public images whose thumbnail meta still points at a local URL.

Run wp mvs migrate-storage first so original files are already on cloud.

Added in 1.3.0.

# Dry run.
wp mvs cloud-thumbs-backfill --dry-run

# Process all eligible images.
wp mvs cloud-thumbs-backfill

# Limit for testing.
wp mvs cloud-thumbs-backfill --limit=100

# Single media row.
wp mvs cloud-thumbs-backfill --media-id=42

Options:

Option Default Description
--dry-run off Report what would migrate without downloading or uploading
--media-id=<id> 0 (all) Process a single media row
--limit=<n> 0 (all) Stop after this many rows

Requirements: Active driver must not be local. Pro plugin required for s3/bunnycdn.


wp mvs cleanup-local

Delete local copies of media that are verified on the active cloud driver. Use after a wp mvs migrate-storage --keep-source run once the cloud driver has been confirmed to serve all media correctly.

Irreversible. The command verifies each file exists on cloud before deleting the local copy; a failed verification keeps the local file. Only public media is processed.

Added in 1.3.0.

# Preview what would be deleted.
wp mvs cleanup-local --dry-run

# Delete originals and thumbnail variants.
wp mvs cleanup-local

# Keep local thumbnail variants; delete originals only.
wp mvs cleanup-local --keep-thumbs

# Single media row.
wp mvs cleanup-local --media-id=42

Options:

Option Default Description
--dry-run off Walk the candidate list and report without deleting
--media-id=<id> 0 (all) Process a single media row
--limit=<n> 0 (all) Stop after this many rows
--keep-thumbs off Delete only the original file; keep local thumbnail variants

wp mvs optimize

Optimize a single media row: re-encode the original and emit WebP (and optionally AVIF) siblings. Resume-safe via the _mvs_optimized_at meta sentinel - already-optimized media is skipped unless --force is passed.

Added in 1.3.0.

wp mvs optimize 42
wp mvs optimize 42 --include-variants --force

Synopsis:

wp mvs optimize <media_id> [--include-variants] [--force]

Options:

Option Default Description
<media_id> required Media row ID to optimize
--include-variants off Also re-process every thumbnail variant
--force off Re-run even when _mvs_optimized_at is already set

Output: Reports bytes before and after, percent saved, and variant count.


wp mvs optimize-bulk

Bulk-optimize every image in the library matching the given filters. Resume-safe: rows already marked with _mvs_optimized_at are skipped automatically. Rows that previously failed (marked _mvs_optimize_failed) are also skipped unless --include-failed is passed.

Added in 1.3.0.

# Process all unoptimized images, including variants.
wp mvs optimize-bulk --include-variants

# JPEG only, dry run.
wp mvs optimize-bulk --mime=image/jpeg --dry-run

# Cap to first 500 rows.
wp mvs optimize-bulk --limit=500

# Skip first 200 rows (manual offset resume).
wp mvs optimize-bulk --offset=200

Options:

Option Default Description
--limit=<n> 0 (all) Cap rows processed
--offset=<n> 0 Skip the first N rows (manual resume)
--mime=<types> all images Comma-separated MIME types to include (e.g. image/jpeg,image/png)
--media-type=<type> all Restrict to one media_type column value (e.g. photo)
--include-variants off Process thumbnail variants alongside the original
--dry-run off Report what would be processed without writing
--include-failed off Re-process rows previously marked as failed

Output: Reports processed/skipped/failed counts and total bytes saved.


wp mvs competitions tick (Pro)

Run one competitions scheduler tick immediately, instead of waiting for the recurring Action Scheduler job. A tick fires every competition transition hook once - activating scheduled challenges, closing challenge entries, finalizing expired challenges, starting registered tournaments, and resolving expired matches - so any challenge or tournament whose deadline has passed advances right away. Useful for debugging on a site where Action Scheduler / WP-Cron is not firing, or to force an immediate state advance after editing competition rows.

Requires WPMediaVerse Pro with a competition feature enabled (challenges, tournaments, or battles).

wp mvs competitions tick

This command takes no options or arguments.

Output:

Success: Competitions tick executed.

wp mvs competitions recompute (Pro)

Force the one-shot competitions catch-up migration to run again. Clears the internal migration flag, re-fires every transition hook once, and re-marks the flag as done. Use this when competition DB rows are edited by hand and you need the scheduler to re-derive their state. Reports how many non-finalized, non-cancelled competitions remain after the pass.

Requires WPMediaVerse Pro with a competition feature enabled.

wp mvs competitions recompute

This command takes no options or arguments.

Output:

Success: Recomputed 3 competition(s).

Scheduling Maintenance with WP-CLI Cron

Add these commands to your server cron for automated maintenance:

# /etc/cron.d/wpmediaverse

# Prune old views weekly.
0 2 * * 0 www-data wp --path=/var/www/html mvs prune-views --days=90

# Cleanup expired access grants daily.
0 3 * * * www-data wp --path=/var/www/html mvs cleanup-expired

Exit Codes

All commands follow WP-CLI conventions:

  • 0 - success
  • 1 - error (WP_CLI::error())

Migration Tools

Endpoints and hooks marked (Pro) require WPMediaVerse Pro.

WPMediaVerse Pro includes WP-CLI commands to import media from three popular WordPress media plugins. Each command reads the source plugin's data, maps it to WPMediaVerse's mvs_media post type and wp_mvs_media_index table, and preserves the original upload dates, author attribution, and file URLs.

WPMediaVerse Pro is required. The migration commands are not available in the free plugin.

Always run with --dry-run first to review what will be imported before committing changes.

Updated in 1.2.0: the Tools > Migration admin page is now a generic shell that hosts per-platform cards (rtMedia, MediaPress, BuddyBoss). Each platform owns its own detection, batch-run, dedup, and progress logic via a WPMediaVersePro\Integrations\<Platform>\MigrationAdmin class. Two pre-existing detection bugs were fixed in the same pass: the Imported count was always 0 regardless of actual progress, and the MediaPress dedup query was running against an undefined $wpdb. All three CLI importers (import-rtmedia, import-mediapress, import-buddyboss) extend the new AbstractBatchImporter base class - same flag set, same batched-fetch loop, same dedup behaviour.


wp mvs import-rtmedia

Import media from rtMedia.

The command reads rtm_media table records and their associated meta, creates mvs_media posts with matching post dates and authors, and inserts index rows.

# Preview the import without making any changes.
wp mvs import-rtmedia --dry-run

# Run the full import.
wp mvs import-rtmedia

# Use a smaller batch size to reduce memory usage on large sites.
wp mvs import-rtmedia --batch-size=50

Options:

Option Default Description
--dry-run off Preview counts and field mapping; do not write any data
--batch-size=<n> 100 Number of rtMedia records to process per batch
--skip-existing on Skip media whose original attachment ID has already been imported
--album-map on Recreate rtMedia albums as WPMediaVerse albums and re-associate media

What is mapped:

rtMedia field WPMediaVerse destination
media_author post_author on mvs_media post
media_date post_date on mvs_media post (original date preserved)
attachment_id Resolved to file URL; stored in mvs_media_index.file_url
activity_id Stored as _mvs_source_activity_id post meta for reference
Album membership Re-created as WPMediaVerse album relationships
Media type Mapped to image, video, or audio based on MIME type

Dry-run output example:

Found 842 rtMedia records.
842 would be created (0 skipped as duplicates).
Albums: 14 would be created.
Run without --dry-run to execute.

wp mvs import-mediapress

Import media from MediaPress.

The command queries mpp_media custom posts and their gallery associations, then creates matching mvs_media posts and index rows.

# Preview the import.
wp mvs import-mediapress --dry-run

# Run the full import.
wp mvs import-mediapress

# Limit to a specific MediaPress gallery.
wp mvs import-mediapress --gallery-id=12

Options:

Option Default Description
--dry-run off Preview counts; do not write any data
--batch-size=<n> 100 Number of MediaPress media posts to process per batch
--gallery-id=<id> (all) Import only media belonging to this MediaPress gallery
--skip-existing on Skip media whose mpp_media post ID has already been imported

What is mapped:

MediaPress field WPMediaVerse destination
post_author post_author on mvs_media post
post_date post_date on mvs_media post (original date preserved)
_mpp_media_manager_id Resolved to file URL via attachment
Gallery Re-created as WPMediaVerse album
mpp_media_type Mapped to image, video, or audio
Privacy (gallery-level) Mapped to WPMediaVerse privacy: mpp-publicpublic, mpp-membersmembers, mpp-friendsfollowers, mpp-privateprivate

wp mvs import-buddyboss

Import media from BuddyBoss Platform (BuddyBoss Media component).

The command reads BuddyBoss media entries stored as custom posts and creates matching mvs_media posts.

# Preview the import.
wp mvs import-buddyboss --dry-run

# Run the full import.
wp mvs import-buddyboss

# Import only a specific media type.
wp mvs import-buddyboss --type=video

Options:

Option Default Description
--dry-run off Preview counts; do not write any data
--batch-size=<n> 100 Number of BuddyBoss media posts to process per batch
--type=<type> (all) Limit import to photo, video, or document
--skip-existing on Skip media whose BuddyBoss post ID has already been imported
--include-albums on Re-create BuddyBoss albums as WPMediaVerse albums

What is mapped:

BuddyBoss field WPMediaVerse destination
post_author post_author on mvs_media post
post_date post_date on mvs_media post (original date preserved)
bb_media_id attachment Resolved to file URL; stored in mvs_media_index.file_url
Album membership Re-created as WPMediaVerse album relationships
Group ID (_bb_media_group_id) Stored as _mvs_bp_group_id post meta for BuddyPress integration
Privacy Mapped from BuddyBoss privacy levels to WPMediaVerse equivalents

Post-Migration Steps

After running any import command, run the following to ensure the index is consistent and all view stats are initialised:

wp mvs reindex
wp mvs migrate

Check imported media at Media > All Media in wp-admin. Filter by the original author to verify counts match.

wp-admin Media list filtered by imported author


Preserving Original File URLs

All three import commands store the original file URL in mvs_media_index.file_url without moving or copying the files. Your existing URLs continue to work. If you later change the storage driver in WPMediaVerse Pro (e.g., moving to Amazon S3), use wp mvs migrate-storage to batch-transfer files.


Handling Import Errors

If a batch fails, the command outputs the failing record IDs and continues. Review errors with:

wp mvs import-rtmedia 2>&1 | tee import-log.txt

Check wp-content/debug.log for detailed error traces when WP_DEBUG_LOG is enabled:

// wp-config.php
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );

Exit Codes

All import commands follow WP-CLI conventions:

  • 0 - completed (including dry-run)
  • 1 - fatal error (source plugin tables not found, Pro license inactive, etc.)

Something unclear? Open a support ticket →

Buy WPMediaVerse