Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Media Command Laravel Package

wp-cli/media-command

WP-CLI media command for WordPress: import local files or STDIN as attachments, set featured images, regenerate thumbnails in bulk, replace existing attachment files, fix EXIF orientation, and list all registered image sizes—streamlining media management from the command line.

View on GitHub
Deep Wiki
Context7

Getting Started

wp media ships in the standard WP-CLI Phar, so on most sites there is nothing to install. Verify and read the source of truth before scripting anything:

wp media --help
wp help media regenerate
wp media image-size        # confirms which sizes actually exist on this site

If you are on a Composer-managed WP-CLI install, add the package explicitly (composer require wp-cli/media-command) or, for a global PHAR, wp package install wp-cli/media-command. Check the bundle with wp package list | grep media — a stale bundled copy is a common reason a flag like --image_size or wp media replace appears "missing".

Look first at wp media image-size. It prints the registered sizes, dimensions, and crop behavior, and it is the fastest way to answer "why is there no medium_large thumbnail?" before you reach for regenerate.

The first use case worth wiring up is a content migration: import assets and attach them to existing posts in one step.

# Import a directory of assets and attach each to its post as the featured image
wp media import ~/migration/images/*.jpg --post_id=123 --title="Hero" --alt="Hero image" --featured_image

The second is the routine post-theme-change job: after adding or changing add_image_size() calls, regenerate only what is affected rather than the whole library.

wp media regenerate --image_size=hero --image_size=card --yes

Implementation Patterns

Import → capture ID → attach. --porcelain is the integration point for shell pipelines. It defaults to the attachment ID and can emit url:

ATTACHMENT_ID="$(wp media import ~/hero.png --porcelain)"
wp post meta update 123 _thumbnail_id "$ATTACHMENT_ID"

# Or fetch the URL for downstream use
wp media import https://example.com/logo.png --porcelain=url

--featured_image only makes sense with --post_id (or --post_name); for bulk assignment, import once with --porcelain, then fan out with xargs as the README demonstrates.

Batch regeneration by ID list. The confirmation prompt and unbounded single-process run are the two operational hazards. Pass IDs to skip the prompt entirely (confirmation only appears when no IDs are given), and batch with xargs so failures are localized:

wp post list --post_type=attachment --post_mime_type=image/jpeg --format=ids \
  | xargs -n 50 wp media regenerate

# Or a numeric range
seq 1000 2000 | xargs wp media regenerate

Targeted size work instead of full rebuilds. --image_size is repeatable and prunes the working set:

wp media regenerate 123 124 125 --image_size=thumbnail --image_size=medium
wp media regenerate --only-missing --yes      # fill gaps, touch nothing else
wp media regenerate --delete-unknown --yes    # drop orphaned sizes only

Prune as a two-phase release step. prune deletes generated files but leaves attachment metadata intact, which is safe because regenerate can rebuild registered sizes. It will not rebuild sizes that are no longer registered, so --remove-abandoned is the destructive flag — treat it as opt-in and never combine it with a config where a plugin temporarily unregisters sizes.

wp media prune --image_size=hero --yes                 # narrow, safe
wp media prune --remove-abandoned --yes                # irreversible for unregistered sizes

Replace instead of re-import. wp media replace <id> <file> preserves the attachment ID, post associations, and permalink structure — the right tool for swapping a client's low-res photo without breaking references. --porcelain makes it scriptable.

Orientation cleanup after migrations. Phone-sourced libraries routinely arrive with broken EXIF orientation. Always dry-run first, because the command rewrites files:

wp media fix-orientation --dry-run
wp media fix-orientation 63 64 65

Local-dev and CI patterns. In a seed script, prefer --skip-copy when pointing at fixtures you want to keep in the repo (no copying, no wp_unique_filename() suffixing), and --destination-dir when you need uploads to land somewhere other than the default month folder:

wp media import ./fixtures/*.jpg --skip-copy --preserve-filetime

--preserve-filetime is genuinely useful for deterministic date-based upload paths in fixtures; remote URLs always use the current time.

Piping binary content. wp media import - reads STDIN, which is the clean way to import from a remote source without a temp file:

curl -sL https://example.com/image.jpg | wp media import - --title="From CDN" --porcelain

Note the attachment gets the filename STDIN, so pass --file_name and --title when the resulting name matters.

Gotchas and Tips

Multisite is not implicit. wp media regenerate --yes on a multisite install only touches the current site. Loop with --url or use wp site list --field=url | xargs -I % wp media regenerate --url=% --yes. Same for import and prune.

Memory and time limits bite on real libraries. regenerate loads each image through GD or Imagick in a single PHP process. On a few thousand images expect it to exhaust memory_limit or hit max_execution_time. Raise limits per-invocation (wp --php-args='-d memory_limit=512M' media regenerate ...) and batch by ID. If you see silent skips, --debug surfaces the underlying error.

GD vs Imagick changes output. Whether thumbnails regenerate correctly for formats like WebP, AVIF, or CMYK JPEGs depends entirely on the compiled image editor and its version. wp eval 'var_dump(wp_image_editor_choose());' tells you which one WordPress picked. If a specific size quietly fails, that is the first thing to check — not the command.

--skip-copy has no safety net. Files stay where they are, are not passed through wp_unique_filename(), and --destination-dir is ignored. Delete the source directory and you have broken attachments with valid metadata. Use it only for fixtures or symlinked media.

prune and regenerate touch the filesystem, so permissions matter. Run as the same user that owns wp-content/uploads, or the web server will 404 thumbnails that the CLI thinks it wrote. A regenerate that reports success while the browser shows broken images is almost always ownership rather than the command.

Offloaded media will fight you. If uploads live in S3/CloudFront via a plugin, regenerate operates on local paths and the plugin's own sync logic. Confirm whether the plugin hooks into regeneration before assuming CLI behavior propagates to the CDN; otherwise regenerate locally and let the plugin push, or use the plugin's WP-CLI commands.

--update-attachment-refs is the quiet hero and the quiet risk. Post content often hardcodes a -300x200.jpg URL. Without --update-attachment-refs, regenerating after a size change leaves dead links in content. With it, you are rewriting post_content across the site. Dry-run or database snapshot first, then:

wp media regenerate --yes --update-attachment-refs

--skip-delete accumulates files. It leaves old thumbnails in place, which is correct when external systems hotlink them, but it means filenames accumulate and disk usage grows with each size change. Audit periodically with wp media prune.

Debugging attachment state is a one-liner. When a thumbnail is missing, the CLI output is not the whole story — inspect the stored metadata:

wp post meta get 760 _wp_attachment_metadata --format=json
wp eval 'print_r(wp_get_attachment_metadata(760));'
ls -la wp-content/uploads/2024/06/

If sizes lacks the key, the size was not registered at generation time; if the key exists but the file doesn't, the file was pruned or the write failed.

Confirmation prompts break automation. Any command run without explicit IDs will prompt. Always pass --yes in scripts, and set NO_COLOR=1 plus WP_CLI_TEST_QUIET=1 in CI so output is readable and non-interactive.

Aliases belong in wp-cli.yml. For a project with staging and production, define @staging/@prod aliases once rather than hand-typing --ssh=/--url= on destructive commands; the muscle memory difference between wp media prune --yes locally and on production is worth eliminating structurally.

Extending it. The commands are thin wrappers over core functions (media_handle_sideload, wp_generate_attachment_metadata, wp_get_registered_image_subsizes), so custom bulk logic — conditional regeneration, per-post featured-image sweeps, size-by-size rollouts — is usually better as wp eval-file calling those functions than as shell gymnastics around regenerate. For package contributions, the workflow is Behat acceptance tests in features/, composer phpcs -- <file> and composer phpstan per change, and composer behat -- features/<name>.feature:<line> for a single scenario. Remember README.md is generated via wp scaffold package-readme, so edit the command docblocks, not the README.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core