Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/cloak-labs/cloakwp-media-library-state/archive/refs/heads/main.zipWordPress Media Library state that survives the things that usually wipe it: reloading the grid, closing an attachment details modal, and reopening a media picker after you’ve already hit Load more a bunch of times.
If you’ve ever scrolled five hundred images deep in an ACF image field, picked something, closed the modal, decided it was wrong, opened it again — and found yourself back at the top with all the previously loaded images gone — this is for that. Same pain shows up with galleries, featured image, and Gutenberg media pickers. The grid on upload.php has a milder version of it: filters and Load more depth disappear on refresh.
Configured in PHP. No settings UI, no admin notices, no premium upsells.
What it does
Media Library grid (upload.php)
- Each Load more writes
?media_pages=Ninto the current tab URL viahistory.replaceState(no Back-button spam). - Active filters (type, date, search, and custom filters created via
cloakwp/corepackage'sLibraryFilters) are kept in the same URL alongsidemedia_pages. - Reloading the tab — or opening the URL elsewhere — restores filters and fetches the previously loaded depth in one request, then Load more continues from there.
- When
media_pages > 1after a reload, the page scrolls to the bottom (where Load more lives). - Clicking an item (opening the attachment modal) still appends
?item={id}to the URL (core behaviour); closing that modal restores your prior grid URL params (filters +media_pages), instead of core’s bareupload.php/?search=reset.
Media modals (block editor, ACF, featured image)
Opening the Media Library from Gutenberg, an ACF Image / Gallery / File field, or Featured Image remembers the following, per instance, for the current editor session:
- Load more depth
- Active library filters
- Scroll-to-selection on reopen: featured image scrolls to the current featured attachment; ACF galleries scroll to the last selected image; image fields scroll to their current value when set
Instances are scoped so they don’t bleed into each other:
| Opener | Scope |
|---|---|
| ACF Image / Gallery / File | That field instance (repeater / flexible rows included) |
| Featured image | One shared featured-image key |
Other Gutenberg / wp.media pickers |
Selected block clientId, or a generic select fallback |
State is in-memory for the editor session. It does not survive a full page reload of the editor.
Install paths
1. Composer (must-use plugin) — recommended
composer require cloakwp/media-library-state
Package type is wordpress-muplugin. With composer/installers configured, that installs to:
wp-content/mu-plugins/media-library-state/
(Your project may map that path differently — e.g. Bedrock uses public/app/mu-plugins/.)
Important: WordPress core only auto-loads PHP files directly in mu-plugins/. It does not load plugins nested in subdirectories like mu-plugins/media-library-state/media-library-state.php. You need an autoloader (or a tiny stub) for subdirectory must-use plugins.
Recommended: Roots Bedrock Autoloader — it scans mu-plugins/*/*.php for plugin headers and includes them. Ships with Bedrock; usable in any WordPress project as roots/bedrock-autoloader. Once loaded, this package shows under Plugins → Must-Use (not the toggleable Plugins list).
Without an autoloader, add a one-line stub at the mu-plugins root:
<?php
// wp-content/mu-plugins/media-library-state-loader.php
require WPMU_PLUGIN_DIR . '/media-library-state/media-library-state.php';
If you never call MediaLibraryState::make()->register(), the plugin bootstrap starts with defaults on init priority 1. See Fluent API for optional config.
2. Traditional plugin install (download as a zip)
For sites that don’t use Composer — install it like any other WordPress plugin:
- Open the GitHub repository page.
- Click the green Code button, then Download ZIP.
- Unzip the file. You’ll get a folder named something like
cloakwp-media-library-state-main. - Rename that folder to
media-library-state(optional but keeps the Plugins list tidy). - Install it in either way:
- WordPress admin: Plugins → Add New → Upload Plugin → choose the zip (re-zip the renamed folder if you renamed it) → Install Now → Activate, or
- Manually: upload the
media-library-statefolder intowp-content/plugins/on your server (via FTP/SFTP or your host’s file manager), then go to Plugins and click Activate.
Same defaults as the Composer path. Developers can still override config via fluent register() or the config filter (below). No mu-plugin autoloader needed.
Fluent API
MediaLibraryState::make()
->queryVar('media_pages') // URL param on upload.php grid
->maxPages(10) // cap restored first-query size (pages × 80 items)
->persistUrl(true) // write media_pages + filters into the grid URL
->persistModals(true) // remember Load more / filters / scroll per modal instance
->register();
Config filter
add_filter('cloakwp/media-library-state/config', function ($config) {
return $config->withMaxPages(5);
});
JavaScript API
When the admin script is enqueued, it exposes window.cloakwpMediaLibraryState:
// After the listing props have been reset, sync the persisted URL / modal snapshot.
if (window.cloakwpMediaLibraryState && typeof cloakwpMediaLibraryState.clearFilters === 'function') {
cloakwpMediaLibraryState.clearFilters(library.props); // or {} to drop every filter param
}
clearFilters(propsSource) removes filter query params (and media_pages) from the upload.php grid URL via history.replaceState, and resets the in-memory filter snapshot for the open modal. It does not change Backbone library props — the caller (e.g. cloakwp/core Clear) should reset those first, then pass the resulting props so the URL matches. mode, item, and other reserved core params are left alone.
How restore works
Media Library loads in pages (typically 80 items). Reopening at “page 5” by clicking Load more four times is slow and fragile. Instead, the first query-attachments request is inflated to posts_per_page × N, then the per-page size is restored so subsequent Load more calls page normally.
On the grid, N comes from ?media_pages=. In modals, N comes from the in-memory map for that instance key.
Out of scope
- List-view pagination (core already has
paged) - Persisting modal state across full editor reloads
Architecture
src/Core/ # Config
src/Plugin/ # Assets enqueue
MediaLibraryState.php # Fluent facade
resources/js/ # Query inflate + URL / modal persistence
Development
composer install
composer test
License
LGPL-3.0-only