aysnc / wordpress-dynamic-media
Dynamic media for WordPress.
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:wordpress-plugin
pkg:composer/aysnc/wordpress-dynamic-media
Requires
- php: ^8.3
Requires (Dev)
- aysnc/wordpress-php-cs-fixer: ^0.3.0
- aysnc/wordpress-phpcs: ^0.1.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^9.6
- roots/wordpress: *
- squizlabs/php_codesniffer: ^3.13.5
- wp-phpunit/wp-phpunit: ^6.9
- yoast/phpunit-polyfills: ^2.0
This package is not auto-updated.
Last update: 2026-01-16 22:34:22 UTC
README
Automatically transform WordPress media URLs into dynamic, optimized URLs powered by image transformation services like Cloudinary.
Requirements
- PHP 8.3+
- WordPress 6.2+
- A Cloudinary account with auto-upload mapping configured
Installation
composer require aysnc/wordpress-dynamic-media
The plugin auto-activates through Composer's wordpress-plugin type. If your setup doesn't support that, activate it
manually in wp-admin.
What It Does
When you upload an image to WordPress, it gets stored at a URL like this:
https://example.com/wp-content/uploads/2024/03/hero-image.jpg
With this plugin active, that same image is served through Cloudinary with on-the-fly transformations:
https://res.cloudinary.com/your-cloud/images/w_800,h_600,c_fill/my-site/2024/03/hero-image/hero-image.jpg
The plugin hooks into WordPress's image handling at multiple levels, so this happens automatically - no need to change your templates or content.
Here's what gets transformed:
wp_get_attachment_image()and related functions- Responsive
srcsetattributes - Images embedded in post/page content
- Any code using
image_downsize() - REST API media endpoints (
/wp/v2/media)
REST API Support
The plugin transforms URLs in REST API responses by default. When you fetch media via /wp/v2/media, both source_url
and all size URLs in media_details.sizes are transformed.
This is enabled by default. To disable it globally:
add_filter( 'aysnc_wordpress_dynamic_media_config', function () { return [ 'rest_api_enabled' => false, ]; } );
Or disable it per-request (useful for admin/editor contexts):
add_filter( 'aysnc_wordpress_dynamic_media_rest_enabled', function ( bool $enabled, WP_REST_Request $request, WP_Post $attachment ): bool { // Disable for authenticated requests (likely editor) if ( is_user_logged_in() ) { return false; } return $enabled; }, 10, 3 );
Generating URLs
The real power is in generating dynamic URLs on demand. Given any attachment ID, you can build a URL with whatever transformations you need.
The Basics
These parameters work across all adapters:
use Aysnc\WordPress\DynamicMedia\Media; // Basic resize $url = Media::get_dynamic_url( $attachment_id, [ 'width' => 800, 'height' => 600, ] ); // Hard crop (fills the exact dimensions) $url = Media::get_dynamic_url( $attachment_id, [ 'width' => 400, 'height' => 400, 'hard_crop' => true, ] );
Adapter-Specific Transforms
Need more than dimensions? The transform array lets you pass parameters directly to your adapter:
// Cloudinary: auto-optimize with face detection $url = Media::get_dynamic_url( $attachment_id, [ 'width' => 400, 'height' => 400, 'hard_crop' => true, 'transform' => [ 'quality' => 'auto', 'fetch_format' => 'auto', 'gravity' => 'face', ], ] ); // Cloudinary: low-quality placeholder for lazy loading $placeholder = Media::get_dynamic_url( $attachment_id, [ 'width' => 100, 'transform' => [ 'effect' => 'blur:1000', 'quality' => 30, ], ] );
On portability: width, height, and hard_crop work across all adapters - the plugin translates these for each
service. The transform array does not. It's passed directly to your adapter, untouched.
This is intentional. Image services have different capabilities - Cloudinary's gravity: 'face' has no equivalent in
every provider. Rather than maintain a leaky abstraction, we give you direct access. The trade-off: if you switch
adapters, any code using transform needs to be updated.
Best practice: Don't call Media::get_dynamic_url() with transform scattered throughout your codebase. Wrap it in
your own function:
function get_image_url( int $id, array $args = [] ): string { return Media::get_dynamic_url( $id, $args ); }
That way if you change adapters - or just want to tweak your transforms - you have one place to update instead of hunting through templates.
Configuration
The plugin needs to know your Cloudinary details. Add this filter to your theme or a mu-plugin:
add_filter( 'aysnc_wordpress_cloudinary_config', function () { return [ 'cloud_name' => 'your-cloud-name', 'auto_mapping_folder' => 'your-auto-upload-folder', ]; } );
That's the minimum config. The auto_mapping_folder should match the folder name you set up in Cloudinary's auto-upload
settings.
Full Configuration Options
| Option | Default | Description |
|---|---|---|
| cloud_name | (required) | Your Cloudinary cloud name |
| auto_mapping_folder | (required) | The folder configured in Cloudinary's auto-upload mapping |
| domain | res.cloudinary.com | Custom domain if you're using a CNAME |
| default_hard_crop | fill | Cloudinary crop mode for hard-cropped images |
| default_soft_crop | fit | Cloudinary crop mode for proportionally-scaled images |
Using Environment Variables
Since configuration happens through a filter, you can pull values from wherever makes sense for your setup:
add_filter( 'aysnc_wordpress_cloudinary_config', function () { return [ 'cloud_name' => getenv( 'CLOUDINARY_CLOUD_NAME' ), 'auto_mapping_folder' => getenv( 'CLOUDINARY_FOLDER' ), 'domain' => getenv( 'CLOUDINARY_DOMAIN' ) ?: 'res.cloudinary.com', ]; } );
Hooks & Filters
aysnc_wordpress_dynamic_media_config
Global plugin configuration.
add_filter( 'aysnc_wordpress_dynamic_media_config', function (): array { return [ 'rest_api_enabled' => true, // Enable REST API transformation (default: true) ]; } );
aysnc_wordpress_dynamic_media_rest_enabled
Control REST API transformation on a per-request basis. Receives the request and attachment objects for context.
add_filter( 'aysnc_wordpress_dynamic_media_rest_enabled', function ( bool $enabled, WP_REST_Request $request, WP_Post $attachment ): bool { // Skip transformation for specific attachments if ( get_post_meta( $attachment->ID, '_skip_dynamic_media', true ) ) { return false; } return $enabled; }, 10, 3 );
Parameters:
$enabled- Whether REST API transformation is enabled (default:true)$request- The REST request object$attachment- The attachment post object
aysnc_wordpress_cloudinary_config
Configure the Cloudinary adapter. See Configuration above.
aysnc_wordpress_cloudinary_args
Modify transformation arguments before the Cloudinary URL is built. Use this for site-wide settings like auto-optimization:
add_filter( 'aysnc_wordpress_cloudinary_args', function ( array $args, int $attachment_id ): array { // Apply auto quality and format to all images $args['transform']['quality'] = 'auto'; $args['transform']['fetch_format'] = 'auto'; return $args; }, 10, 2 );
This filter is Cloudinary-specific. If you switch adapters, you'll replace this with the equivalent for your new service.
Supported transformation parameters:
| Parameter | Cloudinary | Parameter | Cloudinary |
|---|---|---|---|
| width | w | height | h |
| crop | c | gravity | g |
| quality | q | fetch_format | f |
| effect | e | opacity | o |
| radius | r | angle | a |
| background | b | border | bo |
| overlay | l | underlay | u |
| dpr | dpr | zoom | z |
| aspect_ratio | ar | flags | fl |
| progressive | fl_progressive | named_transformation | t |
See Cloudinary's transformation reference for the complete list.
aysnc_wordpress_dynamic_media_url
Modify the final dynamic URL before it's returned. Works with any adapter.
add_filter( 'aysnc_wordpress_dynamic_media_url', function ( string $url, int $attachment_id, array $args ): string { // Log all generated URLs error_log( "Dynamic URL for {$attachment_id}: {$url}" ); return $url; }, 10, 3 );
Parameters:
$url- The generated URL$attachment_id- WordPress attachment ID$args- Transformation arguments (width, height, hard_crop, etc.)
aysnc_wordpress_dynamic_media_srcset_dimensions
Adjust dimensions for individual srcset entries.
add_filter( 'aysnc_wordpress_dynamic_media_srcset_dimensions', function ( array $dimensions, int $attachment_id, array $image_meta, string $image_src ): array { // Force soft crop for all srcset images $dimensions['hard_crop'] = false; return $dimensions; }, 10, 4 );
Parameters:
$dimensions- Array withwidth,height, and optionallyhard_crop$attachment_id- WordPress attachment ID$image_meta- WordPress attachment metadata$image_src- Original image source URL
aysnc_wordpress_dynamic_media_content_image_src
Override the URL for images in post content. Return a string to use your custom URL, or null to let the plugin
generate one.
add_filter( 'aysnc_wordpress_dynamic_media_content_image_src', function ( ?string $src, int $attachment_id, ?string $original_src, $width, $height, string $size ): ?string { // Skip transformation for full-size images if ( $size === 'full' ) { return $original_src; } return null; // Let the plugin handle it }, 10, 6 );
Parameters:
$src- Current source (null on first pass)$attachment_id- WordPress attachment ID$original_src- The originalsrcattribute value$width- Image width attribute$height- Image height attribute$size- Image size name extracted from class (e.g.,large,thumbnail)
aysnc_wordpress_cloudinary_upload_url
Override the base upload URL used to determine the path within Cloudinary. Useful for multisite or custom upload configurations.
add_filter( 'aysnc_wordpress_cloudinary_upload_url', function ( string $upload_url ): string { // Use a consistent URL for multisite return 'https://example.com/wp-content/uploads'; } );
Custom Adapters
The plugin uses an adapter pattern, so you can add support for other image services:
use Aysnc\WordPress\DynamicMedia\Adapter; use Aysnc\WordPress\DynamicMedia\Adapters\MediaAdapter; class ImgixAdapter implements MediaAdapter { public static function get_dynamic_url( int $id, array $args ): string { $original_url = wp_get_attachment_url( $id ); // Build your Imgix URL here $imgix_url = 'https://your-source.imgix.net/' . basename( $original_url ); if ( ! empty( $args['width'] ) ) { $imgix_url .= '?w=' . $args['width']; } // Handle $args['transform'] for Imgix-specific params return $imgix_url; } } // Register and activate your adapter add_action( 'after_setup_theme', function () { Adapter::register( 'imgix', new ImgixAdapter() ); Adapter::set( 'imgix' ); }, 20 ); // Priority 20 to run after default registration
Switching Adapters
If you have multiple adapters registered, you can switch between them:
Adapter::set( 'cloudinary' ); Adapter::set( 'imgix' );
Pausing the Plugin
Need to temporarily disable transformations? Maybe for debugging or a specific request:
use Aysnc\WordPress\DynamicMedia\Plugin; Plugin::pause(); // Disable transformations // ... do something with original URLs ... Plugin::pause( false ); // Re-enable
Development
Setup
composer install npm install
Running Tests
npm run test:php
Code Quality
composer lint # PHP CodeSniffer composer format # PHP CS Fixer composer static-analysis # PHPStan (level max)
Full Test Suite
npm run lint:test # Runs lint, tests, and static analysis