> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Package public assets and updates

> Examine Laravel 13's publishing process and learn, from a long-term maintenance perspective, how to distribute JavaScript and CSS, how tags select paths, how the overwrite options work, and how to republish on Composer updates.

Updating your package's JavaScript or CSS does not automatically change files that have already been copied into the application's `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](/en/advanced/package-development) 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.

```text theme={null}
courier/
├── public/
│   ├── courier.css
│   └── courier.js
└── src/
    └── CourierServiceProvider.php
```

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../public' => public_path('vendor/courier'),
            ], 'courier-assets');
        }
    }
}
```

For the initial publish, specify the provider and tag explicitly.

```bash theme={null}
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider" --tag=courier-assets
```

In this example, `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.

```blade theme={null}
<link rel="stylesheet" href="{{ asset('vendor/courier/courier.css') }}">
<script src="{{ asset('vendor/courier/courier.js') }}" defer></script>
```

If you distribute ES modules or another format, adjust how they are loaded to match. `asset()` is a helper that generates URLs; it does not build, publish, or generate content-based file names.

<Warning>
  Place only build artifacts that are safe to expose in the source directory. In this example, the destination is `public`, which is accessible from the web. Do not include configuration files or internal data in the same publish group.
</Warning>

## 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.

| Option | Selected publish paths |
| - | - |
| `--tag=courier-assets` | Paths from every provider that registered that tag |
| `--provider="Acme\Courier\CourierServiceProvider"` | All paths registered by that provider |
| Both provider and tag | The intersection of that provider's paths and that tag's paths |
| `--all` | Publish paths from all providers |

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.

<Tip>
  Use a package-specific asset tag in update instructions so that users' configuration and views are not overwritten. Specifying only the provider with `--force` can also target that provider's configuration and views.
</Tip>

## Choose the right republish option

File publishing and directory publishing in `VendorPublishCommand` 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.

| Option | Files missing at the destination | Files existing at the destination |
| - | - | - |
| None | Added | Kept |
| `--force` | Added | Overwritten |
| `--existing` | Not added | Overwritten |
| `--existing --force` | Not added | Overwritten |

`--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.

```bash theme={null}
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider" --tag=courier-assets --force
```

<Warning>
  `--force` does not merge differences; it also overwrites users' edits. Keep user-customizable CSS separate from package-managed artifacts, for example by loading it as a separate file. Use a different update policy from the one for configuration and view customizations.
</Warning>

### 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 the `post-update-cmd` of `composer.json`.

```json theme={null}
{
    "scripts": {
        "post-update-cmd": [
            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
        ]
    }
}
```

This is an application-side script. Package auto-discovery itself does not update published files. In existing applications the script may have been changed or removed, so check the consuming application's configuration.

To join this update path, change the second argument of the earlier `publishes()` call to an array and register the same assets under two tags.

```php theme={null}
$this->publishes([
    __DIR__.'/../public' => public_path('vendor/courier'),
], ['courier-assets', 'laravel-assets']);
```

`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.

<Info>
  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.
</Info>

## Keep PHP and asset versions aligned in deployments

```mermaid theme={null}
flowchart TD
    A["Update the package"] --> B["Register publish paths"]
    B --> C["vendor:publish --force with the package tag<br>or the laravel-assets update script"]
    C --> D["Add new files<br>Overwrite existing files"]
    D --> E["Clean up the specified old files<br>Apply browser and CDN caching policy"]
    E --> F["Confirm PHP and assets run on the same version"]
```

`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, `--force` updates 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

<Columns cols={2}>
  <Card title="Package auto-discovery internals" icon="magnifying-glass" href="/en/advanced/package-discovery">
    Understand the differences between Composer updates, provider discovery, and file publishing.
  </Card>

  <Card title="Overriding and updating package views" icon="eye" href="/en/advanced/package-views">
    Review maintenance policies for templates that users customize.
  </Card>

  <Card title="Integrating package caches with optimize" icon="gears" href="/en/advanced/package-optimization">
    Learn about package caches, which are managed separately from file publishing.
  </Card>

  <Card title="Package Version Compatibility Management" icon="code-branch" href="/en/advanced/package-versioning">
    Treat changes to publish destinations and distribution formats as part of your compatibility contract.
  </Card>
</Columns>

## Primary sources

* [Laravel official documentation: Public assets](https://github.com/laravel/docs/blob/13.x/packages.md#public-assets)
* [Laravel official documentation: Publishing file groups](https://github.com/laravel/docs/blob/13.x/packages.md#publishing-file-groups)
* [Laravel Framework v13.35.0: ServiceProvider](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php) — `publishes()`, `addPublishGroup()`, `pathsToPublish()`, and `pathsForProviderAndGroup()`.
* [Laravel Framework v13.35.0: VendorPublishCommand](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Foundation/Console/VendorPublishCommand.php) — Selection scope, overwrite conditions, and copying within directories.
* [Laravel 13 official application skeleton: composer.json](https://github.com/laravel/laravel/blob/13.x/composer.json) — Republishing `laravel-assets` via `post-update-cmd`.


## Related topics

- [Laravel Package Development](/en/advanced/package-development.md)
- [Advanced Topics](/en/advanced/index.md)
- [Laravel Package Skeleton — the official starter template for packages](/en/blog/package-skeleton-introduction.md)
- [Vite and Asset Bundling](/en/vite.md)
- [Package route registration and caching](/en/advanced/package-routes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.