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

Publisher Laravel Package

orchestra/publisher

Orchestra Publisher is a Laravel package for managing and publishing package assets and resources, helping move files into public directories and integrate cleanly with Laravel’s asset publishing workflow.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with a hard pin. The only release evidence on record is v3.5.0, a single-line changelog bumping support to Laravel 5.5. Do not let Composer float the major; treat every Laravel version above 5.5 as unverified until you've run the publish flow end-to-end on your target.

composer require orchestra/publisher:"^3.5"

This line predates package auto-discovery (or at least predates the package declaring it), so wire it manually in config/app.php:

'providers' => [
    Orchestra\Publisher\PublisherServiceProvider::class,
],
'aliases' => [
    'Publisher' => Orchestra\Publisher\Facades\Publisher::class,
],

Read three files before writing integration code. In the installed vendor directory:

  • src/Publisher.php — the chainable implementation; this is the API surface. Method names moved across 2.x → 3.x (extensions(), foundation(), publishes(), publish(), queue(), flush()), so verify the exact signatures against your pinned tag rather than copying a snippet from a blog post or an old Stack Overflow answer.
  • src/PublisherServiceProvider.php — how the singleton and path resolvers are bound. Tells you what is safe to rebind.
  • src/Contracts/ — the interface to type-hint against, so a future replacement only touches your bindings.

First use case: one operator command instead of three vendor:publish calls. The package earns its keep when a module ships config, views, and public assets and you want them published in a single programmatic call:

use Orchestra\Publisher\Facades\Publisher;

Publisher::extensions('acme/reports')
    ->publishes([
        __DIR__.'/../resources/config' => config_path('acme/reports'),
        __DIR__.'/../resources/views'  => base_path('resources/views/vendor/acme/reports'),
        __DIR__.'/../public'           => public_path('vendor/acme/reports'),
    ], 'acme-reports')
    ->publish();

Wrap it in php artisan acme:install so the operator never types the publisher API. That is the whole premise of the package: a registered publish set, declared programmatically, executed in one call.

Implementation Patterns

One manifest per package, consumed by every flow. Do not scatter $this->publishes() and Artisan::call() across service providers and commands. Put the resource map in a single PublishManifest (or a static method on the package provider) and have install, upgrade, and repair all read from it:

final class AcmeReportsManifest
{
    public static function all(): array
    {
        return [
            'acme-reports-config' => [
                __DIR__.'/../resources/config' => config_path('acme/reports'),
            ],
            'acme-reports-assets' => [
                __DIR__.'/../resources/views' => base_path('resources/views/vendor/acme/reports'),
                __DIR__.'/../public'           => public_path('vendor/acme/reports'),
            ],
        ];
    }
}

When a new resource kind appears, one file changes and every command inherits it. This is also your escape hatch if you later fork or replace the dependency.

Split publish sets by ownership, not by folder. The distinction that matters operationally is who is allowed to edit the file after it lands:

Group Examples Safe to force on upgrade?
Code-owned public assets, views, compiled stubs Yes
Operator-owned config, translations No — diff or report instead

Install registers everything without force (existing user-edited files survive). Upgrade registers everything but forces only the code-owned tags. Repair exposes --tag so an operator re-publishes one broken group in isolation. Expose --force and --tag as explicit options on your command; do not make callers learn the publisher API.

Sequence migrations explicitly. The publisher copies files; it has no lifecycle awareness. Any install flow that ships migrations must chain the migration run itself, and fail loudly if it doesn't:

$this->publishManifest($only = null);

if ($this->option('migrate')) {
    Artisan::call('migrate', [
        '--path'  => 'database/migrations/vendor'acme-reports',
        '--force' => true,
    ]);
}

Otherwise you ship a perfectly published set of migrations that never ran — and the failure surfaces only at runtime, in production.

Reset state between batches. The publisher is a singleton with mutable accumulated state. If one command runs two install flows, or you're under a long-lived worker, a second batch can inherit the first batch's registrations. Call the reset (flush() on 3.x — confirm against src/Publisher.php) between batches, and treat "one publisher instance per logical publish operation" as the rule.

Route extension-aware paths deliberately. Publisher::extensions($vendor.'/'.$package) resolves destinations against Orchestra's extension conventions. This is the only reason to take the dependency over a hand-rolled helper. If every destination in your app is plain config_path() / public_path(), you are paying a version constraint and a maintenance liability for a thin wrapper — write the ~50-line helper yourself. Keep the calls behind your manifest either way so the swap costs one class.

Test the filesystem, not the publisher. Feature-test your install command with a temp base path and assert on what landed:

$this->artisan('acme:install', ['--force' => true]);
$this->assertFileExists(config_path('acme/reports/api.php'));
$this->assertFileDoesNotExist(public_path('vendor/acme/reports/.DS_Store'));

The negative assertion matters more than the positive one — accidentally shipped files reach production and nobody notices.

Keep it off the request path. Publishing is deploy-time and provisioning-time work. A publisher call inside a controller, a model observer, or a queued job means filesystem writes on a runtime path with no idempotency guarantee and no rollback. If you find one there, that's a design bug, not a convenience.

Gotchas and Tips

Silent no-ops are the number-one trap. Publishing inherits vendor:publish semantics: existing files are skipped without force. Your upgrade command exits 0, the deploy goes green, and the new views never shipped. Surface what was published in command output — per-tag file counts at minimum — and fail or warn when an expected file was skipped.

--force needs a governance policy, not just a flag. Once a config file is on disk an operator may have edited it. Blind force on upgrade clobbers those edits and the operator finds out when their API key is gone. Split by ownership (see the table above) and, for operator-owned groups, compare hashes and report drift rather than overwriting.

Published config permanently shadows package defaults. After publishing, defaults shipped in the package no longer apply. Every new config key you add in a later release is invisible until someone re-publishes or merges. Two mitigations: document that published config is versioned state, and merge defaults at read time in your provider (array_merge($defaults, config('acme/reports', []))) rather than trusting the published file to be current.

Tag collisions fail quietly and confusingly. Tags are global to the publish registry. Two packages both registering config will interleave their file maps and the result is order-dependent. Namespace every tag with vendor and package: acme-reports-config, never config. Debug with php artisan vendor:publish --list — the publisher's registrations go through the framework registry, so your tags appear there. That listing is also the fastest way to confirm your provider actually booted before you start debugging paths.

Path resolution throws at publish time, not boot time. If extensions() expects Orchestra's extension manifest and your app doesn't have one, the failure surfaces during a deploy step, on the upgrade path, in production. Exercise the exact publish set in a staging deploy — not just a unit test with a mocked publisher — before it reaches an operator.

Verify the API surface for your tag, every time. Methods were renamed across the 2.x → 3.x line. A snippet that works on one tag will silently do the wrong thing (or nothing) on another. Read src/Publisher.php once per upgrade.

Treat upstream as unmaintained and budget accordingly. A one-line changelog for Laravel 5.5, zero dependents, no adoption signal: you own the version bumps and any framework-compatibility patches. Keep every publish declaration behind your own PublishManifest abstraction so the day you replace or fork this dependency, the change is confined to one or two classes instead of scattered across every extension. If your roadmap depends on partner onboarding velocity, don't make this package a single point of failure for it.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo