﻿---
title: Changelog configuration reference
description: The changelog configuration file contains settings to make the creation of changelog files and bundles more consistent and repeatable. By default, it's...
url: https://docs-v3-preview.elastic.dev/elastic/docs-builder/pull/3906/data/release-notes/configure-ref
products:
  - Elastic Docs Builder
---

# Changelog configuration reference
The changelog configuration file contains settings to make the creation of changelog files and bundles more consistent and repeatable.

By default, it's named `docs/changelog.yml`.
For the most up-to-date changelog configuration options and examples, refer to [changelog.example.yml](https://github.com/elastic/docs-builder/blob/main/config/changelog.example.yml).
These are the main configuration sections:
<table>
  | Section                   | Purpose                                                                           |
  |---------------------------|-----------------------------------------------------------------------------------|
  | [bundle](#bundle)         | Sets directory paths, GitHub defaults, and named profiles for bundling.           |
  | [extract](#extract)       | Configures automatic extraction of release notes and issues from PR descriptions. |
  | [filename](#filename)     | Controls how `changelog add` names generated files.                               |
  | [lifecycles](#lifecycles) | Specifies allowed lifecycle values.                                               |
  | [pivot](#pivot)           | Maps GitHub labels to changelog types, areas, and products.                       |
  | [products](#products)     | Defines available products and defaults.                                          |
  | [rules](#rules)           | Filters which PRs create changelogs and which changelogs appear in bundles.       |
</table>


## Bundle

Configures directory paths, GitHub repository defaults, and named profiles for bundle operations.
These settings are separate from `rules.bundle` filtering.
Refer to:
- [Basic bundle settings](#bundle-basic)
- [Bundle descriptions](#bundle-descriptions)
- [Bundle profiles](#bundle-profiles)


### Basic settings

Controls bundle-level behavior.
These settings are relevant to one or all of the `changelog bundle`, `changelog gh-release`, and `changelog remove` commands.
<table>
  | Setting                           | Description                                                                                                                                                                                                                                                                                                                                                     |
  |-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
  | `bundle.branch`                   | Branch whose CDN changelog entries (`changelog/{org}/{repo}/{branch}/...`) are sourced from when bundling (default: `main`). Refer to [Entry sourcing](#bundle-entry-sourcing).                                                                                                                                                                                 |
  | `bundle.directory`                | Input directory containing changelog YAML files (default: `docs/changelog`).                                                                                                                                                                                                                                                                                    |
  | `bundle.infer_missing_changelogs` | When `true`, git-ref bundling synthesizes in-memory changelog entries from GitHub PR metadata when no matching changelog YAML is found on the CDN (or in the local folder when local sourcing is forced). Default `false`: unmatched PRs are warned and omitted like `--prs`. Override with `--infer` or a profile-level setting.                               |
  | `bundle.link_allow_repos`         | List of `owner/repo` pairs whose PR/issue links are preserved. When set (including empty `[]`), links to unlisted repos become `# PRIVATE:` sentinels.                                                                                                                                                                                                          |
  | `bundle.output_directory`         | Output directory for bundled files (default: `docs/releases`).                                                                                                                                                                                                                                                                                                  |
  | `bundle.owner`                    | Default GitHub repository owner (for example, `elastic`). Also the org segment of uploaded changelog-entry keys (`changelog/{org}/{repo}/{branch}/...`) and CDN entry sourcing.                                                                                                                                                                                 |
  | `bundle.release_dates`            | When `true`, bundles include a `release-date` field (default: true).                                                                                                                                                                                                                                                                                            |
  | `bundle.repo`                     | Default GitHub repository name (for example, `elasticsearch`). Used by the `{changelog}` directive to generate correct PR and issue links, and to scope uploaded changelog-entry keys (`changelog/{org}/{repo}/{branch}/...`) and CDN entry sourcing. Only needed when the product ID doesn't match the GitHub repository name (or to override the git remote). |
  | `bundle.use_local_changelogs`     | When `true`, always source entries from the local folder and never from the CDN (default: `false`). Refer to [Entry sourcing](#bundle-entry-sourcing).                                                                                                                                                                                                          |
</table>

<important>
  When `bundle.link_allow_repos` is omitted, no link filtering occurs.
  - For private repos, set it to `[]` or add related public repos to the list.
  - For public repos, add your `owner/repo` to the list at a minimum.
</important>


### Entry sourcing

`changelog bundle` reads the individual changelog entries it aggregates either from the local folder or from the public CDN. Entries are stored on the CDN per **authoring org/repo/branch** (`changelog/{org}/{repo}/{branch}/...`), not per product, so CDN sourcing keys off the resolvable authoring org/repo/branch rather than the bundle's target products.
The authoring repo is resolved with the same precedence as `changelog upload`: `--repo` > `bundle.repo` in `changelog.yml` > the git remote origin. The owner is resolved from `--owner` > `bundle.owner` (default `elastic`), and the branch from `--branch` > `bundle.branch` (default `main`).
Sourcing is decided per run:
- **Local folder.** Used when `bundle.use_local_changelogs: true`, when `--force-local` is passed, when `--directory` is passed, or when the authoring repo cannot be resolved. The folder must contain the changelog files.
- **CDN (default when a repo resolves).** Used when the authoring repo resolves, local sourcing is not forced, and a CDN base URL is configured (`DOCS_BUILDER_CHANGELOG_CDN`, defaulting to the public distribution). The command fetches `changelog/{org}/{repo}/{branch}/registry.json` and the entries it lists, then applies the bundle's own product/PR/issue/file filters to the downloaded set. Path-list / `--files` filters match CDN entries by file name, so the listed paths do not need to exist locally.

Use `--force-local` for uncommon ad hoc runs that need the local folder without editing `changelog.yml` — including path-list / `--files` runs that should read freshly authored files from disk instead of the CDN.
Because entries are org/repo/branch-scoped, one repository can produce a bundle for a shared product (for example, `cloud-serverless`) while sourcing its own entries from `changelog/{org}/{repo}/{branch}/`, without that product appearing in the repository's `docset.yml`. The `{changelog}` directive's `:cdn:` mode still consumes product-scoped *bundles*, so a repository that also renders its own release notes declares each product under `release_notes` as before.

### Bundle descriptions

You can add introductory text to bundles using the `description` field. This text appears at the top of rendered changelogs, after the release heading but before the entry sections.
When using profiles, you can provide this information in:
- `bundle.description`: Default description for all profiles
- `bundle.profiles.<name>.description`: Profile-specific description (overrides the default)

Bundle descriptions support these `{version}`, `{lifecycle}`, `{owner}`, and `{repo}` substitution variables.
When using `{version}` or `{lifecycle}`, you must provide the necessary version argument in the command (for example, `bundle profile 9.2.0`) or define `output_products` in your configuration file.
For complex descriptions with multiple paragraphs, lists, and links, use YAML literal block scalars with the `|` (pipe) syntax:
```yaml
bundle:
  description: |
    This release includes significant improvements:
    
    - Enhanced performance
    - Bug fixes and stability improvements
    - New features for better user experience
    
    For security updates, go to [security announcements](https://example.com/docs).
    
    Download the release binaries: https://github.com/{owner}/{repo}/releases/tag/v{version}
```

The `|` (pipe) preserves line breaks and is ideal for Markdown-formatted text. Avoid using `>` (greater than) for descriptions as it folds line breaks into spaces, making lists and paragraphs difficult to format correctly.

### Bundle profiles

Named profiles enable you to run commands repeatedly with consistent options.
They work with both `changelog bundle` and `changelog remove` commands.
These settings are located in the `bundle.profiles.<name>` section of the configuration file:
<definitions>
  <definition term="description">
    Overrides the global [bundle.description](#bundle-descriptions).
  </definition>
  <definition term="hide_features">
    Feature IDs to mark as hidden in the bundle.
  </definition>
  <definition term="When the bundle is rendered, entries with matching feature-id values are commented out.">
  </definition>
  <definition term="infer_missing_changelogs">
    Overrides [bundle.infer_missing_changelogs](#bundle-basic).
  </definition>
  <definition term="When true, git-ref bundling synthesizes entries from GitHub PR metadata for PRs with no matching changelog YAML on the CDN (or in the local folder when local sourcing is forced). Default is omit-with-warning. CLI --infer turns this on for a single run.">
  </definition>
  <definition term="output">
    The output filename pattern for the bundle file.
  </definition>
  <definition term="Supports {version} and {lifecycle} placeholders.">
  </definition>
  <definition term="When not set, the output path falls back in order to: bundle.output_directory/changelog-bundle.yaml (if bundle.output_directory is configured), then changelog-bundle.yaml in the input directory.">
  </definition>
  <definition term="Setting this is recommended so each profile produces a distinctly named file rather than overwriting the default.">
  </definition>
  <definition term="Example: "elasticsearch/{version}.yaml"">
  </definition>
  <definition term="output_products">
    The bundle's `products` metadata, which affects the bundle rules that are applied and the product and version titles that ultimately appear in documentation.
  </definition>
  <definition term="Supports {version} and {lifecycle} placeholders.">
  </definition>
  <definition term="When not set, the products array is derived from the individual changelog files matched by the filter. This often produces multiple product entries (one per unique product/target/lifecycle combination across all matched files), which may not reflect a single clean release identity.">
  </definition>
  <definition term="When set, the products array in the bundle is exactly the value you specify, replacing anything that would be derived from the matched changelogs. Use this to publish a single, authoritative product entry with a specific version and lifecycle.">
  </definition>
  <definition term="The {lifecycle} placeholder is substituted at runtime with the inferred lifecycle. For source: github_release profiles this comes from the release tag suffix. For standard profiles it comes from the version argument. Refer to changelog bundle cli command > Lifecycle inference.">
  </definition>
  <definition term="If you omit lifecycle from the pattern (for example, "elasticsearch {version}"), the lifecycle field is omitted from the products array entirely.">
  </definition>
  <definition term="Example: "elasticsearch {version} {lifecycle}" or "elasticsearch {version} ga" to hardcode GA regardless of tag. The same omit, derive, and hardcode patterns apply to date-based targets (for example, "cloud-serverless {version}", "cloud-serverless {version} {lifecycle}", or "cloud-serverless {version} preview" for exceptional non-GA date releases).">
  </definition>
  <definition term="Refer to changelog bundle cli command > Product format.">
  </definition>
  <definition term="owner">
    Overrides [bundle.owner](#bundle-basic).
  </definition>
  <definition term="products">
    Derive the list of changelogs by matching their `products` values (equivalent to the `--input-products` command option).
  </definition>
  <definition term="Not used when the source of truth for the release is a promotion report, URL list file, or source: github_release; in those cases this setting is ignored.">
  </definition>
  <definition term="Supports {version} and {lifecycle} placeholders that are substituted at runtime.">
  </definition>
  <definition term="The value "* * *" is equivalent to the --all command option.">
  </definition>
  <definition term="Example: "elasticsearch {version} {lifecycle}"">
  </definition>
  <definition term="Refer to changelog bundle cli command > Product format.">
  </definition>
  <definition term="release_dates">
    Overrides [bundle.release_dates](#bundle-basic).
  </definition>
  <definition term="repo">
    Overrides [bundle.repo](#bundle-basic).
  </definition>
  <definition term="Required when source: github_release is used and bundle.repo is not set.">
  </definition>
  <definition term="source">
    Derive the list of changelogs from the specified source.
  </definition>
  <definition term="Only github_release is currently supported (equivalent to the --release-version command option), which means a PR list is fetched from the GitHub release identified by the version argument.">
  </definition>
  <definition term="Requires repo to be set at the profile or bundle level.">
  </definition>
  <definition term="Mutually exclusive with products.">
  </definition>
  <definition term="Example: source: github_release">
  </definition>
</definitions>

Example profile usage:
```bash
docs-builder changelog bundle elasticsearch-release 9.2.0
docs-builder changelog remove elasticsearch-release 9.2.0
```


## Extract

Controls how the `changelog add` command extracts information from PR descriptions and titles.
<table>
  | Setting                      | Description                                                                                                                                                    |
  |------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
  | `extract.issues`             | Auto-extract linked issues/PRs from descriptions (default: `true`).                                                                                            |
  | `extract.release_notes`      | Auto-extract descriptions from GitHub (default: `true`).                                                                                                       |
  | `extract.strip_title_prefix` | Remove square-bracket prefixes from PR titles; strip a single hyphen separator or colon after the prefix when it is followed by whitespace (default: `false`). |
  When `extract.issues` is `true`, the system looks for patterns like "Fixes #123" in PR bodies (when you're creating changelogs from PRs) or "Fixed by #123" in issue bodies (when you're creating changelogs from issues).When `extract.release_notes` is `true`, the system looks for content like this in the PR or issue description:
  - `Release Notes: ...`
  - `Release-Notes: ...`
  - `release notes: ...`
  - `Release Note: ...`
  - `Release Notes - ...`
  - `## Release Note` (as a markdown header)
  The extracted release note text is used in the changelog `description`.When `extract.strip_title_prefix` is `true`:
  - The separator hyphen is removed only when at least one bracket prefix was stripped; PR titles that intentionally start with `-` followed by whitespace and have no bracket prefix are left unchanged.
  - Titles that still begin with `-`, `*`, `+`, an en dash (U+2013), or an em dash (U+2014) are surrounded in quotes so they're not parsed as list markers.
  - The title cleanup only occurs when the title is derived from GitHub. If you specify `--title` explicitly, that title is used as-is without any prefix stripping.


  ## FilenameControls how the `changelog add` command generates changelog file names.

  <table>
    | Value                 | Description                                                                    |
    |-----------------------|--------------------------------------------------------------------------------|
    | `timestamp` (default) | Use Unix timestamp with title slug (for example, `1735689600-fix-search.yaml`) |
    | `pr`                  | Use the PR number (for example, `12345.yaml`)                                  |
    | `issue`               | Use the issue number (for example, `67890.yaml`)                               |
  </table>
</table>


## Lifecycles

Specifies the allowed lifecycle values for your changelogs.
<table>
  | Value          | Description                                                                                                        |
  |----------------|--------------------------------------------------------------------------------------------------------------------|
  | `preview`      | Tech previews are time-boxed evaluations to help us gather early feedback from customers on key upcoming features. |
  | `beta`         | Beta features are no longer supported but some products might have beta releases.                                  |
  | `ga`           | General availability features are stable, scalable, and production-ready.                                          |
  | `experimental` | The experimental phase exists to enable rapid iteration on new features.                                           |
</table>

<note>
  The full list of possible values is defined in [Lifecycle.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/Lifecycle.cs).
</note>

You can specify lifecycles as a comma-separated string (`"preview, beta, ga"`) or as a YAML list.

## Pivot

Specifies the allowed area, type, and subtype values for your changelogs.
Also controls how the `changelog add` command maps GitHub labels to various changelog fields.
<table>
  | Setting           | Description                                                                                                                                                                                                                                   |
  |-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
  | `pivot.areas`     | Lists the valid area values. Optionally maps area names to GitHub labels (for example, `"Search Relevance": ":Search Relevance/Search"`).                                                                                                     |
  | `pivot.features`  | Maps `feature-id` values to GitHub labels. When a PR or issue has a matching label, `changelog add` sets `feature-id` unless you pass `--feature-id`. If there are multiple feature labels, the first match is used and a warning is emitted. |
  | `pivot.highlight` | Defines labels that set the `highlight` flag on changelogs.                                                                                                                                                                                   |
  | `pivot.products`  | Maps product IDs (and optionally version and lifecycle) to GitHub labels.                                                                                                                                                                     |
  | `pivot.types`     | Lists the valid type values (at a minimum, `feature`, `bug-fix`, and `breaking-change`). Optionally maps types to GitHub labels (for example, `bug-fix: ">bug"`). You can also optionally define breaking change subtypes.                    |
</table>

<note>
  Type and subtype values must match the available values defined in [ChangelogEntryType.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/ChangelogEntryType.cs) and [ChangelogEntrySubtype.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/ChangelogEntrySubtype.cs).
</note>

The following example demonstrates some GitHub label mappings in the `pivot` section of the changelog configuration file:
```yaml
pivot:
  types:
    # Example mappings - customize based on your label naming conventions
    breaking-change:
      labels: ">breaking, >bc"
    bug-fix: ">bug"
    enhancement: ">enhancement"
  areas:
    # Example mappings - customize based on your label naming conventions
    Autoscaling: ":Distributed Coordination/Autoscaling"
    "ES|QL": ":Search Relevance/ES|QL"
  products:
    'elasticsearch':
      - ":product/elasticsearch"
    'cloud-serverless':
      - ":product/serverless"
  features:
    'feature:new-search-api':
      - "feature-flag:new-search-api"
      - ":Feature/NewSearchApi"
```


## Products

Specifies the allowed product values and the default values used by the `changelog add` command.
<note>
  The product values must exist in [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml). All products in the catalog are valid for changelogs, including those that have `public-reference` disabled.
</note>

<table>
  | Setting              | Description                                                                                           |
  |----------------------|-------------------------------------------------------------------------------------------------------|
  | `products.available` | List of allowed product IDs. Empty list or omitted means all products from `products.yml` are allowed |
  | `products.default`   | List of default products when `--products` is not specified.                                          |
</table>

Example:
```yaml
products:
  available: ["elasticsearch", "cloud-serverless"]
  default:
    - product: elasticsearch
      lifecycle: ga
```


## Rules

Provides two key ways to customize the changelog workflow:
- `rules.create` defines GitHub labels that turn changelog creation on or off
- `rules.bundle` defines changelog types, areas, or product combinations that are included or excluded from release bundles

Understanding these rules is essential for managing large-scale changelog workflows with multiple products and release patterns.

### `rules.match`

Defines global default match behavior for all rules.

| Value           | Description                                                                                                                            |
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------|
| `any` (default) | Match if ANY item in the relevant changelog field matches an item in the configured list                                               |
| `all`           | Match only if ALL items in the relevant changelog field match items in the configured list (subset semantics; see product/area tables) |
| `conjunction`   | Match only if every item in the configured list appears in the relevant changelog field (logical AND over the list)                    |

If a lower-level `match` or `match_areas` is specified (for example, `rules.create.match` or `rules.bundle.match_areas`), it overrides the inherited value.

### `rules.create`

Defines the PR or issue labels that affect the creation of changelogs.
These rules affect the `docs-builder changelog add` command when it's pulling information from GitHub (for example using `--prs` or `--issues` command options).
These settings are located in the `rules.create` section of the configuration file:

| Setting    | Type   | Description                                                                   |
|------------|--------|-------------------------------------------------------------------------------|
| `exclude`  | string | Comma-separated list of labels that prevent changelog creation.               |
| `include`  | string | Comma-separated list of labels that enable changelog creation.                |
| `match`    | string | Override `rules.match` for create rules. Values: `any`, `all`, `conjunction`. |
| `products` | map    | Product-specific create rules.                                                |

<important>
  You cannot specify both `exclude` and `include`.
</important>

Product keys in `rules.create.products` can be a single product ID or a comma-separated list of product IDs (for example, `'elasticsearch, kibana'`).
Product-specific rules **override** the global create rules entirely.
```yaml
rules:
  create:
    exclude: ">non-issue"
    products:
      'elasticsearch, kibana':
        exclude: ">test"
      cloud-serverless:
        exclude: ">non-issue, ILM"
```

For more context, go to [Create changelogs > Control changelog creation](/elastic/docs-builder/pull/3906/data/release-notes/create#rules).

### `rules.bundle`

Controls which changelogs are included in bundles.
These rules are applied by the `docs-builder changelog bundle` and `docs-builder changelog gh-release` commands **after** the primary filter (`--prs`, `--issues`, `--all`, or `--input-products`) has identified the relevant changelogs and **before** the bundle is written.
<tip>
  The input stage (gathering entries) and bundle filtering stage (filtering for output) are conceptually separate.
</tip>

These settings are located in the `rules.bundle` section of the configuration file:

| Setting            | Type           | Description                                                                                                                                   |
|--------------------|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| `exclude_areas`    | string or list | Changelog areas to exclude from the bundle.                                                                                                   |
| `exclude_products` | string or list | Changelog products to exclude from the bundle.                                                                                                |
| `exclude_types`    | string or list | Changelog types to exclude from the bundle.                                                                                                   |
| `include_areas`    | string or list | Changelog areas to include in the bundle.                                                                                                     |
| `include_products` | string or list | Changelog products to include in the bundle.                                                                                                  |
| `include_types`    | string or list | Changelog types to include in the bundle.                                                                                                     |
| `match_areas`      | string         | Override `rules.match` for area matching. Values: `any`, `all`, `conjunction`.                                                                |
| `match_products`   | string         | Override `rules.match` for product matching. Values: `any`, `all`, `conjunction`.                                                             |
| `products`         | map            | Per-product type/area filter overrides. Refer to [Changelog configuration reference > Product-specific bundle rules](#rules-bundle-products). |

<important>
  You cannot specify both `exclude_products` and `include_products`, both `exclude_types` and `include_types`, or both `exclude_areas` and `include_areas`. You can mix exclude and include across different fields (for example, `exclude_types` with `include_areas`).
</important>

For examples, go to [Changelog configuration reference > Product matching behavior](#example-product-matching).
The way bundle rules are applied can be broken down into three "modes":
<definitions>
  <definition term="1 — No filtering">
    This mode applies when there is no effective bundle rule.
  </definition>
  <definition term="No changelogs are filtered out of the bundle based on their product, type, or area.">
  </definition>
  <definition term="2 — Global rules">
    This mode applies when there's at least one global bundle rule set and the `rules.bundle.products` section is absent or empty (`products: {}`).
  </definition>
  <definition term="Global rules are evaluated against each changelog's own products, type, and areas.">
  </definition>
  <definition term="There is no single product used for the rule context and no disjoint exclusion.">
  </definition>
  <definition term="For changelogs with missing or empty products, global product "include" or "exclude" lists are skipped with a warning (global type and area rules still apply). Note that such changelogs still fail bundle validation, which requires products on every entry.">
  </definition>
  <definition term="3 — Product-specific rules">
    This mode applies when the `rules.bundle.products` section is not absent or empty.
  </definition>
  <definition term="The bundle uses a single product for its rule context.">
  </definition>
  <definition term="Disjoint changelogs are excluded and changelogs with missing or empty products are excluded with a warning.">
  </definition>
  <definition term="Global rules.bundle are not used for filtering.">
  </definition>
  <definition term="Refer to Changelog configuration reference > Product-specific bundle rules.">
  </definition>
</definitions>

<important>
  In **Mode 2** (global-only `rules.bundle`), `match_products: any` with `include_products` is meaningful: each changelog is evaluated on its own, so OR-style inclusion (for example, elasticsearch **or** kibana) works without per-product rules.In **Mode 3** (non-empty `rules.bundle.products`), `include_products` can **only** affect changelogs that already contain the rule context product.
  It cannot pull in changelogs that are disjoint from that context.
  Refer to [Changelog configuration reference > Product-specific bundle rules](#rules-bundle-products).
</important>

Changelog commands emit a hint when both global bundle fields and a non-empty `products` map are present because global keys are ignored in Mode 3.
The `changelog bundle` and `changelog gh-release` commands also emit informational messages when rules cause changelogs to be omitted from the bundle.

## Advanced rule examples

The following sections provide details and examples for some of the more complicated bundle rule scenarios.

### Area matching behavior

The following table demonstates the impact of area-related bundle rules:

| Config                              | Changelog `areas`                | `match_areas` | Result                                                       |
|-------------------------------------|----------------------------------|---------------|--------------------------------------------------------------|
| `exclude_areas: [Internal]`         | `[Search, Internal]`             | `any`         | **Excluded** ("Internal" matches)                            |
| `exclude_areas: [Internal]`         | `[Search, Internal]`             | `all`         | **Included** (not all areas are in the exclude list)         |
| `exclude_areas: [Search, Internal]` | `[Search]`                       | `conjunction` | **Included** ("Internal" is not in the changelog)            |
| `exclude_areas: [Search, Internal]` | `[Search, Internal, Monitoring]` | `conjunction` | **Excluded** (every listed exclude area is in the changelog) |
| `include_areas: [Search]`           | `[Search, Internal]`             | `any`         | **Included** ("Search" matches)                              |
| `include_areas: [Search]`           | `[Search, Internal]`             | `all`         | **Excluded** ("Internal" is not in the include list)         |
| `include_areas: [Search, Internal]` | `[Search, Internal]`             | `conjunction` | **Included** (every listed include area is in the changelog) |
| `include_areas: [Search, Internal]` | `[Search]`                       | `conjunction` | **Excluded** ("Internal" is missing from the changelog)      |

As described in [match settings](#rules-match), the `conjunction` value means every area in the config list must appear in the changelog.
<tip>
  There is one difference between how these rules are applied in global rules ("mode 2") and product-specific rules ("mode 3"). In the latter case, there's an exceptional "pass-through" scenario that skips per-product "type" and "area" rules and does not fall back to global rules. Refer to [Changelog configuration reference > Product-specific bundle rules](#rules-bundle-products).
</tip>


### Product matching behavior

The following table demonstates the impact of global ("Mode 2") bundle rule [match settings](#rules-match):

| `rules.bundle` setting                        | Changelog `products`                | `match_products` value | Result                                                         |
|-----------------------------------------------|-------------------------------------|------------------------|----------------------------------------------------------------|
| `exclude_products: [cloud-enterprise]`        | `[cloud-enterprise, kibana]`        | `any`                  | **Excluded** ("cloud-enterprise" matches)                      |
| `exclude_products: [cloud-enterprise]`        | `[cloud-enterprise, kibana]`        | `all`                  | **Included** (not all products are in the exclude list)        |
| `exclude_products: [kibana, observability]`   | `[kibana]`                          | `conjunction`          | **Included** (not every exclude list item is in the changelog) |
| `exclude_products: [kibana, observability]`   | `[kibana, observability]`           | `conjunction`          | **Excluded** (every exclude list item is in the changelog)     |
| `include_products: [elasticsearch]`           | `[elasticsearch, kibana]`           | `any`                  | **Included** ("elasticsearch" matches)                         |
| `include_products: [elasticsearch]`           | `[elasticsearch, kibana]`           | `all`                  | **Excluded** ("kibana" is not in the include list)             |
| `include_products: [elasticsearch, security]` | `[elasticsearch, security, kibana]` | `conjunction`          | **Included** (every listed include ID is on the changelog)     |
| `include_products: [elasticsearch, security]` | `[elasticsearch]`                   | `conjunction`          | **Excluded** ("security" is missing from the changelog)        |

The impact of the `match_products` setting differs depending on the mode.
For product-specific ("Mode 3") details, refer to [Changelog configuration reference > Product-specific bundle rules](#rules-bundle-products).
In practice, most changelogs have a single product, so `any` (the default) and `all` behave identically for them.
The difference only matters for changelogs with multiple products.

### Product-specific bundle rules

This section provides more detailed information about "mode 3" product-specific bundle rules.
Product keys in `rules.bundle.products` can be a single product ID or a comma-separated list of product IDs (for example, `'elasticsearch, kibana'`) if you want an identical set of rules for multiple products.
When you create a bundle, only a single product-specific bundle rule can apply (we cannot combine multiple products' rules).
A single _rule-context product_ must therefore be determined for each bundle.
In cases where a bundle is associated with multiple products, a single rule-context product is automatically determined as follows:
- If the `--output-products` (CLI) or `output_products` (profile) option is specified, it's the first product alphabetically from that list
- Otherwise, it's the first product alphabetically from all products aggregated from changelogs that passed the primary filter (`--all`, `--prs`, `--issues`, or `--input-products`) to be candidates for inclusion in the bundle.

The bundle's rule-context product affects how rules are applied to each changelog, as follows:
- If a changelog contains the rule-context product in its `products` list and product-specific rules exist for that product, the rules are applied and the changelog is included or excluded accordingly.
- If a changelog does not contain the rule-context product in its `products` list it is _disjoint_ from the rule context and **excluded** from the bundle.
- If there are no `products` in the changelog or it's empty, a warning occurs and it's **excluded** the changelog.

<warning>
  There is another possible scenario where a changelog contains the rule-context product in its `products` but there is no product-specific rule for that product in the changelog configuration file. In that case the changelog is **included** without applying global `rules.bundle` filters. This is referred to as a "pass-through" scenario.
  It is recommended that you avoid this situation by:
  - Creating bundles with only a single product ID in `--output-products` (CLI) or `output_products` (profile) and ensuring that you have an appropriate product-specific bundle rule, or
  - Ensuring that you have product-specific bundle rules for all the possible product values in your changelogs.
</warning>

For example, if you create a bundle with `--output-products "kibana 9.3.0, security 9.3.0"` the derived rule-context product is `kibana` and it affects the changelogs as follows:

| Changelog `products` | Contains `kibana`? | Result (Mode 3)                                                   |
|----------------------|--------------------|-------------------------------------------------------------------|
| `[kibana]`           | Yes                | Use `kibana` per-product rules if defined; otherwise pass-through |
| `[security]`         | No                 | **Excluded** (disjoint)                                           |
| `[kibana, security]` | Yes                | Use `kibana` per-product rules if defined; otherwise pass-through |
| `[elasticsearch]`    | No                 | **Excluded** (disjoint)                                           |
| `[]` (empty/missing) | No                 | **Excluded** with warning                                         |

For release notes that need different rule behavior per product, run separate `changelog bundle` invocations with a single product in `--output-products` (or a profile whose `output_products` resolves to one product).
Alternatively, use **Mode 2** (global-only `rules.bundle`) when you need OR-style product inclusion without disjoint exclusion.
You can define all the same area, product, and type rules (such as `include_products`, `exclude_types`, `include_areas`, and `match_areas`) as described in [Changelog configuration reference > rules.bundle](#rules-bundle).
However, in this context, `include_products` can **only** affect changelogs that already contain the rule context product.
It cannot pull in changelogs that are disjoint from that context.
<important>
  Product-specific rules override the global bundle rules entirely.
  You must repeat global constraints in the product-specific bundle rules section if you still need them in that context.
</important>

The following example demonstrates how per-product rules replace global rules:
```yaml
rules:
  bundle:
    exclude_types: [docs]
    exclude_areas: [Internal]
    products:
      kibana:
        include_areas: [UI]
```

The result when the rule context product is `kibana` is as follows:
- Changelogs that have only `elasticsearch` product IDs are excluded as disjoint.
- Changelogs that have `kibana` product IDs have the appropriate product-specific rules applied. Global `exclude_types` and `exclude_areas` rules are not applied. Add `exclude_types` under `kibana` if you still need to exclude `docs` types.