PHPackages                             aysnc/wordpress-dynamic-media - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Image &amp; Media](/categories/media)
4. /
5. aysnc/wordpress-dynamic-media

ActiveWordpress-plugin[Image &amp; Media](/categories/media)

aysnc/wordpress-dynamic-media
=============================

Dynamic media for WordPress.

1.0.0(5mo ago)11MITPHPPHP ^8.3CI passing

Since Jan 16Pushed 5mo agoCompare

[ Source](https://github.com/Aysnc-Labs/wordpress-dynamic-media)[ Packagist](https://packagist.org/packages/aysnc/wordpress-dynamic-media)[ RSS](/packages/aysnc-wordpress-dynamic-media/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (8)Versions (2)Used By (0)

WordPress Dynamic Media
=======================

[](#wordpress-dynamic-media)

[![GitHub Actions](https://github.com/Aysnc-Labs/wordpress-dynamic-media/actions/workflows/test.yml/badge.svg)](https://github.com/Aysnc-Labs/wordpress-dynamic-media/actions/workflows/test.yml/badge.svg)[![Maintenance](https://camo.githubusercontent.com/9f9520637e3eaf34c2a45cb16bd6adf93e17366bb9c706a7d20b003e653afbcd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4163746976656c792532304d61696e7461696e65642d7965732d677265656e2e737667)](https://camo.githubusercontent.com/9f9520637e3eaf34c2a45cb16bd6adf93e17366bb9c706a7d20b003e653afbcd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4163746976656c792532304d61696e7461696e65642d7965732d677265656e2e737667)

Automatically transform WordPress media URLs into dynamic, optimized URLs powered by image transformation services like Cloudinary.

Requirements
------------

[](#requirements)

- PHP 8.3+
- WordPress 6.2+
- A Cloudinary account with [auto-upload mapping](https://cloudinary.com/documentation/fetch_remote_images#auto_upload_remote_resources)configured

Installation
------------

[](#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
------------

[](#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 `srcset` attributes
- Images embedded in post/page content
- Any code using `image_downsize()`
- REST API media endpoints (`/wp/v2/media`)

REST API Support
----------------

[](#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
---------------

[](#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

[](#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

[](#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
-------------

[](#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

[](#full-configuration-options)

OptionDefaultDescriptioncloud\_name(required)Your Cloudinary cloud nameauto\_mapping\_folder(required)The folder configured in Cloudinary's auto-upload mappingdomainres.cloudinary.comCustom domain if you're using a CNAMEdefault\_hard\_cropfillCloudinary crop mode for hard-cropped imagesdefault\_soft\_cropfitCloudinary crop mode for proportionally-scaled images### Using Environment Variables

[](#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 &amp; Filters
-------------------

[](#hooks--filters)

### `aysnc_wordpress_dynamic_media_config`

[](#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`

[](#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`

[](#aysnc_wordpress_cloudinary_config)

Configure the Cloudinary adapter. See [Configuration](#configuration) above.

---

### `aysnc_wordpress_cloudinary_args`

[](#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:**

ParameterCloudinaryParameterCloudinarywidthwheighthcropcgravitygqualityqfetch\_formatfeffecteopacityoradiusrangleabackgroundbborderbooverlaylunderlayudprdprzoomzaspect\_ratioarflagsflprogressivefl\_progressivenamed\_transformationtSee [Cloudinary's transformation reference](https://cloudinary.com/documentation/transformation_reference) for the complete list.

---

### `aysnc_wordpress_dynamic_media_url`

[](#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`

[](#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 with `width`, `height`, and optionally `hard_crop`
- `$attachment_id` - WordPress attachment ID
- `$image_meta` - WordPress attachment metadata
- `$image_src` - Original image source URL

---

### `aysnc_wordpress_dynamic_media_content_image_src`

[](#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 original `src` attribute 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`

[](#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
---------------

[](#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

[](#switching-adapters)

If you have multiple adapters registered, you can switch between them:

```
Adapter::set( 'cloudinary' );
Adapter::set( 'imgix' );
```

### Pausing the Plugin

[](#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
-----------

[](#development)

### Setup

[](#setup)

```
composer install
npm install
```

### Running Tests

[](#running-tests)

```
npm run test:php
```

### Code Quality

[](#code-quality)

```
composer lint            # PHP CodeSniffer
composer format          # PHP CS Fixer
composer static-analysis # PHPStan (level max)
```

### Full Test Suite

[](#full-test-suite)

```
npm run lint:test        # Runs lint, tests, and static analysis
```

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance70

Regular maintenance activity

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

169d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/80964230?v=4)[aysnc](/maintainers/aysnc)[@aysnc](https://github.com/aysnc)

---

Top Contributors

[![junaidbhura](https://avatars.githubusercontent.com/u/2512525?v=4)](https://github.com/junaidbhura "junaidbhura (20 commits)")

---

Tags

cdncloudinarydynamic-imagesimage-optimizationimage-processingon-the-flyperformancewordpresswordpress-plugin

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/aysnc-wordpress-dynamic-media/health.svg)

```
[![Health](https://phpackages.com/badges/aysnc-wordpress-dynamic-media/health.svg)](https://phpackages.com/packages/aysnc-wordpress-dynamic-media)
```

###  Alternatives

[goat1000/svggraph

Generates SVG graphs

135911.1k3](/packages/goat1000-svggraph)[gravatarphp/gravatar

Gravatar URL builder which is most commonly called as a Gravatar library

16653.6k2](/packages/gravatarphp-gravatar)[rsoury/wp-imgix

Rewrites WordPress image URLs to use ImgIX

167.2k](/packages/rsoury-wp-imgix)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
