Examples
Selecting only the operations owned by each service, by tag
A common case (issue #100) is merging several services but taking only the operations each one owns, identified by a tag:
{
"inputs": [
{ "inputFile": "service1/swagger.json", "operationSelection": { "includeTags": ["Service1"] } },
{ "inputFile": "service2/swagger.json", "operationSelection": { "includeTags": ["Service2"] } },
{ "inputFile": "service3/swagger.json", "operationSelection": { "includeTags": ["Service3"] } }
],
"output": "./dist/service.output.swagger.json"
}Three things worth knowing about how this behaves:
- Untagged operations are excluded.
includeTagsis an allow-list, so an operation with no tags at all does not survive it. If a service has operations you want that aren't tagged, either tag them upstream or useexcludeTagsto remove what you don't want instead. - A partially-filtered path keeps its remaining operations. Filtering is per operation; the path itself survives as long as one of its operations does.
- The top-level
tagsarray is only pruned byexcludeTags.includeTagsdeliberately leaves it alone, so a tag you filtered in keeps its description.
Selecting operations by path instead of by tag
Not every input's tags are under your control — some generators don't let you customise them at all — and two services can legitimately share a tag while only some of the operations under it should survive. includePaths / excludePaths select by where an operation lives in the document instead:
{
"inputs": [
{
"inputFile": "admin-service/swagger.json",
"operationSelection": { "excludePaths": [{ "path": "/admin/users", "method": "get" }] }
},
{
"inputFile": "internal-service/swagger.json",
"operationSelection": { "excludePaths": [{ "path": "/internal/*" }] }
}
],
"output": "./dist/service.output.swagger.json"
}pathsupports a*wildcard, matched the same wayincludeTags/excludeTagsare:*matches any run of characters (including none), nothing else is special, and the match is anchored at both ends —/admin/*matches/admin/usersbut not/other/admin/users. A literal.or other regex-looking character (/v1.2/status) is matched literally, not interpreted.methodis optional. Omit it to match every method on that path. Give a single method ("get") or a list (["get", "post"]) to narrow it — including a 3.2additionalOperationscustom verb like"PURGE", matched case-sensitively. Standard methods are lowercase in a parsed OpenAPI document (get, notGET) — a selector must match that spelling.- Selectors match this input's own original path, before
pathModificationruns. Write the selector against the path as it appears in that input's own file, not the path it'll have in the merged output. - If an operation matches both an
includePathsand anexcludePathsselector, exclusion wins — the same precedenceincludeTags/excludeTagsalready have. See Configuration reference →operationSelectionprecedence for the full rule when tag and path selectors are combined. includePathson a document with 3.1webhooksdrops every webhook operation, unless one of your selectors happens to match the webhook's event name — the same allow-list behaviourincludeTagsalready has for untagged webhooks. If you need to keep webhooks while filtering paths, tag the webhook operations and useincludeTagsinstead, or useexcludePaths(which doesn't have this effect).
Combining several inputs with dispute resolution
{
"inputs": [
{ "inputFile": "./gateway.swagger.json" },
{
"inputFile": "./jira.swagger.json",
"pathModification": { "stripStart": "/rest", "prepend": "/jira" },
"operationSelection": { "includeTags": ["included"] },
"description": { "append": true, "title": { "value": "Jira", "headingLevel": 2 } }
},
{
"inputFile": "./confluence.swagger.yaml",
"dispute": { "prefix": "Confluence" },
"pathModification": { "prepend": "/confluence" },
"operationSelection": { "excludeTags": ["excluded"] }
}
],
"output": "./output.swagger.json"
}Here, jira.swagger.json's paths lose their /rest prefix and gain a /jira one; only operations tagged included are kept; and its description is appended to the merged info.description under a level-2 "Jira" heading. confluence.swagger.yaml gets a /confluence prefix, drops operations tagged excluded, and any component name it shares with another input is disambiguated with a Confluence prefix.
Recreating x-tagGroups's merge as configuration
ReDoc uses the x-tagGroups extension to organise its sidebar into named groups of tags. By default this tool treats every x- extension as first-wins, so merging two inputs that each declare x-tagGroups would keep only the first one's groups — extensionMergeStrategies fixes that:
{
"inputs": [
{ "inputFile": "./user-service.json" },
{ "inputFile": "./admin-service.json" }
],
"output": "./output.swagger.json",
"extensionMergeStrategies": {
"x-tagGroups": {
"kind": "array",
"strategy": "union-by-key",
"key": "name",
"item": {
"kind": "object",
"strategy": "merge",
"fields": {
"tags": { "kind": "array", "strategy": "concat-unique" }
}
}
}
}
}Given
// user-service.json
{ "x-tagGroups": [{ "name": "User", "tags": ["listUsers", "updateUser"] }] }
// admin-service.json
{ "x-tagGroups": [
{ "name": "User", "tags": ["deleteUser"] },
{ "name": "Admin", "tags": ["getAuditLog"] }
] }the merged output's x-tagGroups is
[
{ "name": "User", "tags": ["listUsers", "updateUser", "deleteUser"] },
{ "name": "Admin", "tags": ["getAuditLog"] }
]The User groups from both inputs combine into one entry carrying every tag from both; Admin, seen only once, passes through unchanged; and both appear in the order they were first seen. Without extensionMergeStrategies configured, the output would instead be exactly user-service.json's original array — admin-service.json's groups, and its addition to User, silently discarded.
See Configuration reference → extensionMergeStrategies for the full node reference this example draws from, including what a scalar, plain concat, sortBy, and error node do.