0% прочитано

Laravel Media Usage Tracking: Preventing Broken References When Images Are Deleted

In ICanUp, one Media asset may be used as a Post cover, News cover, Post Series cover, or in other content. A media_id alone is not enough once assets can be replaced or deleted. This article explains why I track Media usage, validate selected assets on the server, synchronize old and new references, and prevent permanent deletion from leaving broken references behind.

18 вересня 2026 р. 10 хв читанняLaravel

When the ICanUp Media Library started serving more than one part of the application, the simple model of "upload a file and store its ID" stopped being enough.

One image may become a Post cover, News cover, or Post Series cover. Another may be used inside content. Later, an administrator may replace or delete it.

At that point, the important question is no longer only about the file itself: does the system know where the asset is being used and what happens to those references when it is deleted?

A reliable Media Library needs to know not only which files exist, but also which entities currently depend on them.
Laravel Media Usage flow showing one Media asset referenced by Post, News, Post Series and Content Blocks, with usage tracking protecting references during replacement and deletion
One Media asset may have several usage contexts. Replacing or deleting it requires updating both the asset lifecycle and its content references.

The Simplest Implementation Works Until the First Delete

A cover image can begin with a simple nullable foreign key:

PHP - Store Media as a cover
$post->image_media_id = $media->id;$post->save();

That may be sufficient while Media is never replaced or deleted.

Once the application has a real media library, however, the asset gains a lifecycle:

Media lifecycle
upload  |  vselect  |  vuse in content  |  +-- replace  |  +-- soft delete  |  +-- permanent delete

media_id Does Not Tell Me Where the Asset Is Used

The Media table tells me that an asset exists.

It does not by itself tell me whether the asset is currently used as:

  • a Post cover;
  • a News cover;
  • a Post Series cover;
  • a Content Block image;
  • another supported usage context.

That is why the system needs a separate concept of Media usage.

I Separate the Media Asset from Its Usage

One Media asset with multiple usage contexts
Media  |  +-- Post / IMAGE  |  +-- News / IMAGE  |  +-- PostSeries / IMAGE  |  +-- ContentBlock / IMAGE

Media describes the asset itself.

Usage describes the fact that a particular entity uses that asset in a particular context.

Those are different responsibilities, and separating them makes replacement and deletion predictable.

I Do Not Trust the Media ID Submitted by the Form

The Media Picker can display images only and hide deleted assets. That is good UX, not a server-side guarantee.

A request can be modified manually, so the backend validates Media again.

PHP - Validate Media again on the server
$media = Media::query()    ->findOrFail($mediaId); if (! $media->isImage()) {    throw ValidationException::withMessages([        'image_media_id' => 'Selected media must be an image.',    ]);}
Filtering inside the Media Picker does not replace backend validation. Missing, deleted, or wrong-type Media must not become valid simply because the client submitted its ID.

Replacing an Image Changes Two References

When a cover changes from Media A to Media B, storing the new foreign key is only part of the operation.

For usage tracking, two things happen:

Replacing Media
Before: Post  |  +--> Media A  After: Post  |  +--> Media B Media A usage: removeMedia B usage: add

If only image_media_id is changed while the old usage remains, Media A will appear to be used forever even though the Post no longer depends on it.

That Is Why I Synchronize Usage Instead of Only Adding It

In ICanUp, a dedicated Media usage service owns this responsibility.

Conceptually, the call looks like this:

PHP - Synchronize old and new Media usage
$changedMediaIds = $mediaUsageService    ->syncSingleForContext(        $post,        $oldMediaId,        $newMediaId,        MediaUsageContextEnum::IMAGE,    );

The important word for me is sync.

The service receives the previous and next state and can make usage reflect the entity's real reference.

A Shared Service Keeps Media Rules Out of Every Module

Without a shared layer, every module would start solving the same problems independently:

  • how to register new usage;
  • how to remove old usage;
  • how to define the context;
  • how to update Media state after references change;
  • how deletion should behave.

Post, News, and Post Series would quickly end up with different rules.

I want content modules to describe what changed, while the Media layer handles usage consistently.

Usage Tracking Is Useful for More Than Deletion

Task

Why usage matters

Delete Media

Know which entities may be affected

Replace Media

Remove the old usage and create the new one

Admin UI

Show whether the asset is currently used

Audit

Find exactly where an asset is referenced

Cleanup

Avoid deleting a needed file as supposedly unused

Soft Delete and Permanent Delete Mean Different Things

I treat temporary removal and permanent deletion as different operations.

A soft-deleted Media asset can become unavailable for new selections while the record still exists and the system can retain its history and usage information.

Permanent deletion is stronger: active references to that Media ID must not be left behind.

Permanent Delete Must Not Leave a Dangling Foreign Key

The worst result looks like this:

Broken reference after deletion
news.image_media_id = 123 media.id = 123        |        X permanently deleted Result:News still points to media 123,but media 123 no longer exists.

Permanent deletion therefore needs to account for known references.

For a nullable cover relationship, clearing the reference before or as part of deletion can be the safe result.

PHP - Example cleanup for a nullable Media reference
$news->image_media_id = null;$news->save();
The exact deletion policy depends on the context. One relation may be safely set to null, another may block deletion, and another may require replacement. The important part is making the policy explicit.

A Foreign Key Helps but Does Not Describe All Media Usage

A database foreign key is extremely useful for a direct field such as image_media_id.

A nullable FK with an appropriate deletion rule can prevent some broken references automatically.

A complete Media Library usually has more usage patterns than one column in one table, however.

  • direct foreign keys;
  • images inside Content Blocks;
  • multiple Media assets in one context;
  • polymorphic references;
  • future modules that also consume Media.

Database constraints and usage tracking therefore complement each other instead of replacing each other.

Deleting the File and Deleting the Media Record Are Different Operations

There is another layer: the physical file in storage.

If the file is removed first and reference cleanup later fails, the database can retain a Media record whose file no longer exists.

The permanent-deletion workflow therefore needs an explicit order just like database changes do.

Controlled permanent-delete flow
1. Resolve usages2. Apply deletion policy3. Clean references4. Remove Media record5. Remove physical file at the safe point

After-Commit Events Are Useful for Derived Media State

After usage changes, other parts of the system may need to refresh derived state or cached information.

I do not want to announce a successful change before the database transaction has actually committed.

PHP - Dispatch Media usage events after commit
DB::afterCommit(    static function () use (        $mediaUsageDispatcher,        $changedMediaIds,    ): void {        $mediaUsageDispatcher->dispatch(            array_values(                array_unique($changedMediaIds)            )        );    });

This reduces the risk of external reactions occurring while the transaction itself later rolls back.

The Media Picker and the Server Have Different Responsibilities

Media Picker

Laravel backend

Shows a convenient asset list

Verifies the Media actually exists

Filters asset types

Validates the allowed type

Hides deleted assets

Rejects deleted Media

Helps the user select a file

Protects the domain contract

The UI can make the correct choice easier. The server must make the incorrect choice impossible.

I Want to Know About Usage Before Delete Is Pressed

Usage tracking is valuable before a destructive operation happens.

If an asset is used in several places, the admin interface can expose those dependencies before permanent deletion.

Example usage information
Media: architecture-cover.png Used by:- Post cover- News cover- Post Series cover

Deletion is no longer a blind action.

The administrator can see that the resource has dependencies while the backend remains the final safety boundary.

Unused Media Also Needs a Reliable Definition

Over time, a Media Library collects assets that are genuinely no longer referenced.

Deleting them because "no Post points to this Media" is unsafe.

The asset may still be used by News, a series, or another content type.

A usage layer makes "unused" a system-wide concept instead of a module-specific guess.

Tests Need to Cover the Whole Lifecycle

Scenario

Expected result

Valid image Media selected

Reference and usage are created

Media does not exist

Request is rejected

Media is deleted

New usage is rejected

Media is not an image

Image context rejects it

Cover is replaced

Old usage is removed and new usage is created

Cover is cleared

Old Media usage is removed

Permanent delete

No active broken reference remains

The same Media is used by multiple entities

Removing one usage does not remove the others

One Media Asset Can Have More Than One Usage

This distinction matters.

If one image is used by two pieces of content, replacing the first cover does not make the asset unused.

Multiple usages of one Media asset
Media 75  |  +-- Post 18 / IMAGE  |  +-- News 4 / IMAGE Remove Post 18 usage        |        v Media 75 is still used by News 4

What I Do Not Do with Media References

  • Do not trust a Media ID only because the Media Picker submitted it.
  • Do not allow deleted Media to be used in new content.
  • Do not allow the wrong Media type to be used as an image.
  • Do not add a new usage without removing the previous one during replacement.
  • Do not classify Media as unused after checking only one module.
  • Do not remove the physical file without considering the record and its dependencies.
  • Do not leave permanent deletion without an explicit reference policy.
  • Do not rely on the client interface alone for Media Library integrity.

My Short Media Usage Checklist

  • Separate the Media asset from the fact that it is being used.
  • Define supported usage contexts.
  • Validate Media existence on the server.
  • Validate the Media type for the requested context.
  • Reject new usage of deleted Media.
  • Synchronize old and new usage during replacement.
  • Handle clearing nullable Media fields correctly.
  • Treat soft delete and permanent delete as different operations.
  • Resolve all known usages before permanent deletion.
  • Do not leave dangling foreign keys or other broken references.
  • Do not classify an asset as unused until the complete Media Usage layer has been checked.
  • Cover replacement, deletion, and multiple usages with automated tests.

Conclusion

The ICanUp Media Library stopped being only a file store once the same Media assets became reusable across different kinds of content.

Storing a media_id is easy. The harder part is knowing who currently depends on that Media, how to change the relationship safely during replacement, and what must happen during deletion.

I therefore separate the Media asset from its usage, validate selected Media again on the server, and synchronize previous and new references through a shared service.

Soft deletion and permanent deletion have different consequences, and a permanent delete must not leave active references to a resource that no longer exists.

For me, a reliable Media Library does not begin with upload. It begins with answering two questions: where is this file used, and what happens to every one of those places if the file is replaced or deleted?