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

Openapi Merge Laravel Package

marcelthole/openapi-merge

marcelthole/openapi-merge is a PHP package for combining multiple OpenAPI specification files into one merged document. Split large API specs into manageable parts and merge them back, simplifying documentation and code generation workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a build-time CLI/utility for combining multiple OpenAPI 3.x files (yaml/json) into a single spec. It is not a Laravel package: there is no service provider, no facade, no publishable config, and nothing to register in config/app.php. Treat it the way you'd treat any dev-only binary such as php-cs-fixer.

composer require --dev marcelthole/openapi-merge

First look here, in order:

  1. vendor/bin/openapi-merge --help — the console entrypoint is where 90% of real usage lives.
  2. The OpenApiMerge\Merge\Merger, OpenApiMerge\File\FileReader and the writer classes under src/ if you need to call it programmatically.
  3. The resolver classes under src/Resolver/ — this is what decides how $refs between files are followed, and it's the part that bites people.

The canonical first use case for a Laravel app: you keep per-domain spec fragments (docs/openapi/users.yaml, docs/openapi/billing.yaml, …) plus a hand-written docs/openapi/base.yaml holding info, servers and shared components, and you merge them into public/docs/openapi.yaml for Swagger UI / Redoc / L5-Swagger / Scramble-free consumers.

vendor/bin/openapi-merge merge -f docs/openapi/base.yaml -f docs/openapi/users.yaml -f docs/openapi/billing.yaml -o public/docs/openapi.yaml

Get that working by hand before wiring it into Composer scripts or CI.

Implementation Patterns

Wrap it in an Artisan command, not a shell call. Calling the binary from a Composer script is fine locally but brittle in CI (CWD, PATH, exit code swallowing). A thin command that delegates to the package API gives you logging, config, and a --check mode:

// app/Console/Commands/MergeOpenApi.php
protected $signature = 'openapi:merge {--check : Fail if the committed spec is stale}';

Wire the file list from config/openapi.php (a plain config array — you own it, the package doesn't ship one) so module authors add a fragment by adding a line, not by editing a shell script.

Config-file driven merges. For more than three fragments, keep a docs/openapi/merge.yaml listing inputs and the output path and commit it. It makes the merge reproducible across machines and readable in review, and avoids 12-line shell commands drifting between developers' terminals.

Base file first, always. The first input typically supplies info, servers, security and tags. Decide deliberately which file is the base and document it — otherwise info.version in the output is whatever file happened to be listed first in someone's local invocation.

Namespace your components. Name schemas Users.User, Billing.Invoice (or UserResource / InvoiceResource) rather than User. Merging two #/components/schemas/User definitions from different modules is the single most common cause of "the output is wrong but no error was raised."

Run it on a pre-commit or CI gate. Generate into a file you commit, and fail the pipeline if regeneration produces a diff. That keeps spec changes visible in PRs alongside the code that caused them:

php artisan openapi:merge && git diff --exit-code public/docs/openapi.yaml

Combine with vendor specs when needed. Pulling a third-party or legacy spec in alongside your own paths is a legitimate use, but do it with an explicit path prefix strategy so you don't shadow your own routes.

Feed the artifact downstream. Whatever you point at the merged file (L5-Swagger's paths.docs, a static Redoc page, an upload to Stoplight) should read the generated file, never the fragments.

Gotchas and Tips

The 2.6.0 change is a constraint widening, not a behaviour change. It only allows symfony/console ^8. Laravel 11/12 currently pin symfony/console ^7, so on a modern Laravel app this release does nothing for you — and if you try to force symfony/console ^8 into a Laravel app to "use" it, Composer will fight you. Let the framework resolve console; upgrade this package for the other fixes and move on. The only practical beneficiaries are standalone CLI consumers and future Laravel versions.

No Laravel integration means no magic. No auto-discovery, no php artisan vendor:publish, no container bindings, no config. If you want it ergonomic, you build that layer yourself — that's the whole "implementation pattern" above.

$ref resolution is CWD-sensitive. Relative references resolve against the file that contains the ref, but if you run the binary from a different directory than your developer expecting (very easy in a Makefile or Docker WORKDIR), external refs into ../common/schemas.yaml break. Prefer paths relative to a fixed root and run the command from the project root consistently.

YAML parsing traps. PHP YAML parsers turn bare yes/no/on/off into booleans, and version: 3.0 into a float. Always quote: openapi: '3.0.3' and version: '1.0'. Symptom is a confusing validation error rather than a parse error.

Silent override on collisions. When two fragments define the same path or component key, one wins. Order in the input list is the tiebreak. Add a post-merge sanity check (assert expected operation count / that a known schema exists) rather than trusting the output blindly.

Non-deterministic output kills the CI diff gate. If key ordering shifts between runs, git diff --exit-code gives false failures. Verify stability by running the merge twice and diffing before you adopt the gate.

Validation happens at read time. Invalid fragments throw when read (the package validates against the OpenAPI schema), so failures point at the input file, not the merge step. Read the exception message's file path first.

Extension points. If you need custom reference behaviour (e.g. resolving refs over HTTP, or from a package), the resolver layer is the place to hook in; a custom writer is where you'd add path-prefixing or post-processing. Both are more maintainable than post-processing the YAML with string replacement.

Keep it out of the request cycle. Merging is a build/CI step. Never call it from a controller or a service provider's boot() — it's filesystem-heavy and will show up in your latency graphs.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin