public directory. To avoid a state where only the PHP code is on the new version while the browser keeps using the old assets, you need to decide who owns the published destination and how it gets updated.
This page assumes you are familiar with the package development basics and organizes distribution and maintenance design around Laravel 13’s publishing process. The implementation was checked against laravel/framework v13.35.0.
Publishing is neither a build nor a sync
ServiceProvider::publishes() registers a source and a destination. The actual file copying is done by vendor:publish. It does not transpile JavaScript, build CSS, or add entries to the application’s Vite configuration.
If your package distributes prebuilt files, use a structure like the following.
public/vendor/courier/courier.css and courier.js are created. If your design distributes them as plain CSS and JavaScript, you can reference them from Blade as follows.
asset() is a helper that generates URLs; it does not build, publish, or generate content-based file names.
Tags are not a provider-specific namespace
ServiceProvider registers publish paths in an array keyed by provider class and in an array keyed by tag. The tag array is shared across all providers, so using a generic tag such as public can also pull in other packages.
When both are specified,
pathsForProviderAndGroup() uses array_intersect_key() with source paths as keys. It is not a mechanism for switching to a different destination depending on the tag. Avoid designs that register the same source multiple times to give it separate destinations for different purposes.
--tag can be specified multiple times, in which case each tag is published in turn. Because --all returns early at the start of the selection process, adding --provider or --tag alongside it does not narrow the selection.
Choose the right republish option
File publishing and directory publishing inVendorPublishCommand decide whether to copy based on whether the destination file exists and which options are given. The following table describes the behavior for regular asset files that exist in the source.
--existing is not an option that protects edits. It overwrites existing files, but does not publish files added in the new version. For updates where the JavaScript needs newly added files, --existing alone may leave the artifacts incomplete.
If your contract is that the package manages the destination and users do not edit it directly, run the following after updating.
Deleted files remain at the destination
moveManagedFiles() in directory publishing iterates over files in the source and writes them. There is no logic that looks for files that exist only at the destination and deletes them. Even --force does not perform a full directory sync.
For example, if the new version removes legacy.js, public/vendor/courier/legacy.js remains if the old version was already published. The same applies to renamed files: the file with the old name remains, so record deleted and renamed files, along with changes to references, in your release notes.
If you provide steps for removing old files, list specifically the files the package owns. Do not provide steps that delete an entire directory that may contain users’ own files.
Joining laravel-assets means agreeing to be overwritten
The official Laravel 13 application skeleton includes the following script in thepost-update-cmd of composer.json.
publishes() call to an array and register the same assets under two tags.
laravel-assets is not a tag with special copy behavior. Because the skeleton’s script publishes that tag with --force, files that join it become targets for overwriting on Composer updates. Do not register configuration or views that users edit.
Automatic updates depend on the application having the script, that event being run, and the provider registering the publish paths. So that updates work even in deployments that do not meet these conditions, document a republish command that uses your package-specific tag.
Keep PHP and asset versions aligned in deployments
config:cache and view:cache do not rewrite published JavaScript or CSS. If your design keeps serving files at the same URL after publishing, browser or CDN caches may serve stale content. Include the application’s delivery policy in your update steps, such as URLs that reflect the distributed version or cache invalidation.
Check the following combinations for each release.
- In an application where nothing has been published yet, all required prebuilt files are published.
- With the old version already published,
--forceupdates existing files and adds new ones. - Asset updates do not overwrite users’ configuration, views, or custom CSS.
- Handling of deleted and renamed files is documented, and no references to the old version remain.
- The new content is delivered at the actual delivery URL, and the PHP and browser-side logic work together.
Related pages
Package auto-discovery internals
Understand the differences between Composer updates, provider discovery, and file publishing.
Overriding and updating package views
Review maintenance policies for templates that users customize.
Integrating package caches with optimize
Learn about package caches, which are managed separately from file publishing.
Package Version Compatibility Management
Treat changes to publish destinations and distribution formats as part of your compatibility contract.
Primary sources
- Laravel official documentation: Public assets
- Laravel official documentation: Publishing file groups
- Laravel Framework v13.35.0: ServiceProvider —
publishes(),addPublishGroup(),pathsToPublish(), andpathsForProviderAndGroup(). - Laravel Framework v13.35.0: VendorPublishCommand — Selection scope, overwrite conditions, and copying within directories.
- Laravel 13 official application skeleton: composer.json — Republishing
laravel-assetsviapost-update-cmd.