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

# Topics

> Learn how topics work in Novu and how they help you organize and target groups of subscribers efficiently for broadcasts, announcements, and segmented alerts.

In Novu, a *topic* is a way to group subscribers under a shared label so that you can broadcast a message to many users with a single workflow trigger. This fan-out mechanism removes the need to manage individual subscriber IDs for each notification, streamlining scenarios like product announcements, feature rollouts, or status updates.

## Topics identifiers

Each topic is uniquely identified by a topic key, a permanent, internal reference in your system. You can also assign a name to help describe its purpose, for example, "Task Updates" or "Comment Thread Subscribers".

The `topicKey` must be unique and cannot be changed after creation; Novu enforces this uniqueness behind the scenes. Example: `task:taskId` or `post:postId`.

Common use cases for topics:

* Notifying all members of a specific team or project
* Messaging users who commented on a post
* Updating subscribers to a specific product or feature

## How topics fit into Novu's model

Novu’s notification system is built around workflows, which are triggered for one or more recipients. Topics act as a special type of recipient, representing a group of subscribers instead of an individual.

When you trigger a workflow using a topic:

* The `to` field accepts the topic’s key.
* Novu looks up [topic subscriptions](/platform/subscription) on that topic whose [Context](/platform/concepts/contexts) matches the trigger (exact match).
* For each matching subscription, Novu evaluates optional JSON Logic conditions against the trigger **payload**.
* A separate workflow event is created for each subscriber whose subscription passes.

Topics let you target large groups with a single API call, removing the need to loop through subscribers or send multiple workflow triggers.

<Note>
  Topics don’t replace individual targeting. You can still trigger workflows for specific subscribers or arrays of subscribers when needed.
</Note>

## Topic subscriptions and Context scoping

Adding a subscriber to a topic creates a **topic subscription**, the subscriber’s opt-in to receive notifications sent to that `topicKey`. Subscriptions support:

* **JSON Logic conditions**: deliver only when trigger **payload** fields match (for example, `payload.tier` is `premium`). Conditions do not support subscriber profile, Context data, or step results. See [Manage topic subscriptions](/platform/subscription/manage-topic-subscriptions).
* **Context scoping**: isolate subscriptions per tenant, app, or region using the same [Context](/platform/concepts/contexts) metadata you pass on workflow triggers.

Context scoping uses **exact-match** filtering, the same rules as the [Inbox](/platform/inbox/configuration/inbox-with-context):

| Context on subscription   | Context on trigger        | Included in fan-out? |
| ------------------------- | ------------------------- | -------------------- |
| `{ tenant: "acme-corp" }` | `{ tenant: "acme-corp" }` | Yes                  |
| None (default)            | None (default)            | Yes                  |
| `{ tenant: "acme-corp" }` | None (default)            | No                   |
| None (default)            | `{ tenant: "acme-corp" }` | No                   |

Both the subscription and the trigger must carry the same Context keys and values. Omitting Context on either side means the **default** (no Context) scope, not a wildcard that matches all Contexts.

A subscriber can hold multiple subscriptions on the same topic (up to 10), each with a different `identifier`, condition, or Context scope. Use the [Topics v2 API](/api-reference/topics/create-topic-subscriptions), [`@novu/js`](/platform/sdks/javascript#subscriptions), or [`@novu/react`](/platform/subscription/quickstart) to create and manage them.

## Dynamic and decoupled grouping

Once a topic is created, you can assign or remove subscribers at any time. Subscribers don’t need to know they belong to a topic, and it doesn’t affect their personal notification preferences.

Topics are dynamic and reflect real-time states. For example, a topic might include:

* Users who are currently watching a post
* Team members assigned to a project

This makes topics especially useful for modeling dynamic relationships in your application.

## Scalability and limits

Topics are designed for high-volume, high-efficiency use cases:

* Each topic supports up to 100,000 subscribers.
* A separate workflow event is created for each subscriber when a workflow is triggered to the topic

## Autogenerated topics

Novu supports on-the-fly topic creation. If you attempt to add subscribers to a topic key that doesn't exist for the selected environment and organization, Novu will automatically create a new topic named `Autogenerated-<TOPIC_KEY>` and assign the subscribers to it. This auto-generated topic can then be renamed and managed just like any other topic created manually.

<Note>
  Topics can be managed from [Novu dashboard](https://dashboard.novu.co/topics) or using [Topics APIs](/api-reference/topics/topic-schema)
</Note>

## Trigger workflow

As mentioned above, a workflow can be triggered to a topic in the same way as it is triggered to subscribers.

### To a single topic

To trigger a workflow to a topic, use `to` field with type `Topic` and topicKey.

<Tabs>
  <Tab title="Node.js">
    ```ts theme={null}
    import { Novu } from "@novu/api"

    const novu = new Novu({ secretKey: "<YOUR_SECRET_KEY_HERE>", });

    await novu.trigger({
      to: { type: "Topic", topicKey: "topic_key" },
      workflowId: "workflow_identifier",
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import novu_py
    from novu_py import Novu

    with Novu(secret_key=os.getenv("NOVU_SECRET_KEY", "")) as novu:
        novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
            workflow_id="workflow_identifier",
            to={"type": "Topic", "topic_key": "topic_key"},
        ))
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "os"

        novugo "github.com/novuhq/novu-go"
        "github.com/novuhq/novu-go/models/components"
    )

    s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

    res, err := s.Trigger(context.Background(), components.TriggerEventRequestDto{
        WorkflowID: "workflow_identifier",
        To: components.CreateToTopicPayloadDto(components.TopicPayloadDto{
            Type:     components.TriggerRecipientsTypeEnumTopic,
            TopicKey: "topic_key",
        }),
    }, nil)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    use novu;
    use novu\Models\Components;

    $sdk = novu\Novu::builder()
        ->setSecurity('<YOUR_SECRET_KEY_HERE>')
        ->build();

    $sdk->trigger(
        triggerEventRequestDto: new Components\TriggerEventRequestDto(
            workflowId: 'workflow_identifier',
            to: new Components\TopicPayloadDto(
                topicKey: 'topic_key',
                type: Components\TriggerRecipientsTypeEnum::Topic,
            ),
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;
    using Novu.Models.Components;

    var sdk = new NovuSDK(secretKey: "<YOUR_SECRET_KEY_HERE>");

    await sdk.TriggerAsync(triggerEventRequestDto: new TriggerEventRequestDto() {
        WorkflowId = "workflow_identifier",
        To = To.CreateTopicPayloadDto(new TopicPayloadDto() {
            Type = TriggerRecipientsTypeEnum.Topic,
            TopicKey = "topic_key",
        }),
    });
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;
    import co.novu.models.components.*;

    Novu novu = Novu.builder()
        .secretKey("<YOUR_SECRET_KEY_HERE>")
        .build();

    novu.trigger()
        .body(TriggerEventRequestDto.builder()
            .workflowId("workflow_identifier")
            .to(To2.of(TopicPayloadDto.builder()
                .type(TriggerRecipientsTypeEnum.TOPIC)
                .topicKey("topic_key")
                .build()))
            .build())
        .call();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -L -g -X POST 'https://api.novu.co/v1/events/trigger' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
    -d '{
        "name": "workflow_identifier",
        "to": {
            "type": "Topic",
            "topicKey": "topic_key"
        }
    }'
    ```
  </Tab>
</Tabs>

### To multiple topics

`to` field also accepts array of topics. This is useful when you want to trigger a workflow to multiple topics at once.

<Tabs>
  <Tab title="Node.js">
    ```ts theme={null}
    import { Novu } from "@novu/api"

    const novu = new Novu({ secretKey: "<YOUR_SECRET_KEY_HERE>", });

    await novu.trigger({
      to: [
        { type: "Topic", topicKey: "topic_key_1" }, 
        { type: "Topic", topicKey: "topic_key_2" }
      ],
      workflowId: "workflow_identifier",
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import novu_py
    from novu_py import Novu

    with Novu(secret_key=os.getenv("NOVU_SECRET_KEY", "")) as novu:
        novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
            workflow_id="workflow_identifier",
            to=[
                {"type": "Topic", "topic_key": "topic_key_1"},
                {"type": "Topic", "topic_key": "topic_key_2"},
            ],
        ))
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "os"

        novugo "github.com/novuhq/novu-go"
        "github.com/novuhq/novu-go/models/components"
    )

    s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

    res, err := s.Trigger(context.Background(), components.TriggerEventRequestDto{
        WorkflowID: "workflow_identifier",
        To: components.CreateToArrayOfTo1([]components.To1{
            components.CreateTo1TopicPayloadDto(components.TopicPayloadDto{
                Type:     components.TriggerRecipientsTypeEnumTopic,
                TopicKey: "topic_key_1",
            }),
            components.CreateTo1TopicPayloadDto(components.TopicPayloadDto{
                Type:     components.TriggerRecipientsTypeEnumTopic,
                TopicKey: "topic_key_2",
            }),
        }),
    }, nil)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    use novu;
    use novu\Models\Components;

    $sdk = novu\Novu::builder()
        ->setSecurity('<YOUR_SECRET_KEY_HERE>')
        ->build();

    $sdk->trigger(
        triggerEventRequestDto: new Components\TriggerEventRequestDto(
            workflowId: 'workflow_identifier',
            to: [
                new Components\TopicPayloadDto(
                    topicKey: 'topic_key_1',
                    type: Components\TriggerRecipientsTypeEnum::Topic,
                ),
                new Components\TopicPayloadDto(
                    topicKey: 'topic_key_2',
                    type: Components\TriggerRecipientsTypeEnum::Topic,
                ),
            ],
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;
    using Novu.Models.Components;
    using System.Collections.Generic;

    var sdk = new NovuSDK(secretKey: "<YOUR_SECRET_KEY_HERE>");

    await sdk.TriggerAsync(triggerEventRequestDto: new TriggerEventRequestDto() {
        WorkflowId = "workflow_identifier",
        To = To.CreateArrayOfTo1(new List<To1> {
            To1.CreateTopicPayloadDto(new TopicPayloadDto() {
                Type = TriggerRecipientsTypeEnum.Topic,
                TopicKey = "topic_key_1",
            }),
            To1.CreateTopicPayloadDto(new TopicPayloadDto() {
                Type = TriggerRecipientsTypeEnum.Topic,
                TopicKey = "topic_key_2",
            }),
        }),
    });
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;
    import co.novu.models.components.*;
    import java.util.List;

    Novu novu = Novu.builder()
        .secretKey("<YOUR_SECRET_KEY_HERE>")
        .build();

    novu.trigger()
        .body(TriggerEventRequestDto.builder()
            .workflowId("workflow_identifier")
            .to(To2.of(List.of(
                To1.of(TopicPayloadDto.builder()
                    .type(TriggerRecipientsTypeEnum.TOPIC)
                    .topicKey("topic_key_1")
                    .build()),
                To1.of(TopicPayloadDto.builder()
                    .type(TriggerRecipientsTypeEnum.TOPIC)
                    .topicKey("topic_key_2")
                    .build()))))
            .build())
        .call();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -L -g -X POST 'https://api.novu.co/v1/events/trigger' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
    -d '{
        "name": "workflow_identifier",
        "to": [
          {
              "type": "Topic",
              "topicKey": "topic_key_1"
          },
          {
              "type": "Topic",
              "topicKey": "topic_key_2"
          }
        ]
    }'
    ```
  </Tab>
</Tabs>

### Exclude actor

When a workflow is triggered to a topic, notification is sent to all subscribers present in the topic. However, you can exclude a specific subscriber from receiving the notification by using the `actor` field. Here actor is the subscriberId of the subscriber you want to exclude.

<Tabs>
  <Tab title="Node.js">
    ```ts theme={null}
    import { Novu } from "@novu/api"

    const novu = new Novu({ secretKey: "<YOUR_SECRET_KEY_HERE>", });

    await novu.trigger({
      to: [{ type: "Topic", topicKey: "topic_key_1" }],
      workflowId: "workflow_identifier",
      actor: "actor_subscriber_Id"
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import novu_py
    from novu_py import Novu

    with Novu(secret_key=os.getenv("NOVU_SECRET_KEY", "")) as novu:
        novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
            workflow_id="workflow_identifier",
            to=[{"type": "Topic", "topic_key": "topic_key_1"}],
            actor="actor_subscriber_Id",
        ))
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "os"

        novugo "github.com/novuhq/novu-go"
        "github.com/novuhq/novu-go/models/components"
    )

    s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

    res, err := s.Trigger(context.Background(), components.TriggerEventRequestDto{
        WorkflowID: "workflow_identifier",
        To: components.CreateToArrayOfTo1([]components.To1{
            components.CreateTo1TopicPayloadDto(components.TopicPayloadDto{
                Type:     components.TriggerRecipientsTypeEnumTopic,
                TopicKey: "topic_key_1",
            }),
        }),
        Actor: components.CreateActorStr("actor_subscriber_Id"),
    }, nil)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    use novu;
    use novu\Models\Components;

    $sdk = novu\Novu::builder()
        ->setSecurity('<YOUR_SECRET_KEY_HERE>')
        ->build();

    $sdk->trigger(
        triggerEventRequestDto: new Components\TriggerEventRequestDto(
            workflowId: 'workflow_identifier',
            to: [
                new Components\TopicPayloadDto(
                    topicKey: 'topic_key_1',
                    type: Components\TriggerRecipientsTypeEnum::Topic,
                ),
            ],
            actor: 'actor_subscriber_Id',
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;
    using Novu.Models.Components;
    using System.Collections.Generic;

    var sdk = new NovuSDK(secretKey: "<YOUR_SECRET_KEY_HERE>");

    await sdk.TriggerAsync(triggerEventRequestDto: new TriggerEventRequestDto() {
        WorkflowId = "workflow_identifier",
        To = To.CreateArrayOfTo1(new List<To1> {
            To1.CreateTopicPayloadDto(new TopicPayloadDto() {
                Type = TriggerRecipientsTypeEnum.Topic,
                TopicKey = "topic_key_1",
            }),
        }),
        Actor = Actor.CreateStr("actor_subscriber_Id"),
    });
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;
    import co.novu.models.components.*;
    import java.util.List;

    Novu novu = Novu.builder()
        .secretKey("<YOUR_SECRET_KEY_HERE>")
        .build();

    novu.trigger()
        .body(TriggerEventRequestDto.builder()
            .workflowId("workflow_identifier")
            .to(To2.of(List.of(
                To1.of(TopicPayloadDto.builder()
                    .type(TriggerRecipientsTypeEnum.TOPIC)
                    .topicKey("topic_key_1")
                    .build()))))
            .actor(TriggerEventRequestDtoActor.of("actor_subscriber_Id"))
            .build())
        .call();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -L -g -X POST 'https://api.novu.co/v1/events/trigger' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
    -d '{
        "name": "workflow_identifier",
        "to": [
          {
              "type": "Topic",
              "topicKey": "topic_key_1"
          }
        ],
        "actor": "actor_subscriber_Id"
    }'
    ```
  </Tab>
</Tabs>

## Explore the topics APIs

These are commonly used topics APIs. Explore all topics APIs on topics [api reference page](/api-reference/topics/topic-schema).

<Columns cols={2}>
  <Card title="Create a topic API" href="/api-reference/topics/create-a-topic" description="Create a new topic with name and topicKey" />

  <Card title="Update a topic API" href="/api-reference/topics/update-a-topic" description="Update existing topic using topicKey" />

  <Card title="Create topic subscription API" href="/api-reference/topics/create-topic-subscriptions" description="Add subscribers to a topic with optional JSON Logic conditions" />

  <Card title="List topic subscriptions API" href="/api-reference/topics/list-topic-subscriptions" description="List all subscribers present in a single topic" />

  <Card title="List all topics API" href="/api-reference/topics/list-all-topics" description="List all topics in an environment" />

  <Card title="Manage topic subscriptions" href="/platform/subscription/manage-topic-subscriptions" description="Conditions, Context scoping, @novu/js, and @novu/react" />

  <Card title="Check subscriber subscription API" href="/api-reference/topics/check-topic-subscriber" description="Check if a subscriber is present in a topic" />
</Columns>

## Frequently asked questions

Below are some of the most frequently asked questions about topics:

<AccordionGroup>
  <Accordion title="Do topics override subscriber preferences?">
    No. Topics only define delivery groups. Each subscriber's individual notification preferences remain intact.
  </Accordion>

  <Accordion title="Is a topic like a mailing list?">
    Conceptually, yes. Topics group users to receive shared messages. However, topics are more dynamic and integrated into your application logic.
  </Accordion>

  <Accordion title="Can a subscriber belong to multiple topics?">
    Yes. Subscribers can be members of any number of topics. This allows overlapping targeting strategies (for example, `task-updates`, `project-X`, and `admin-notifications`).
  </Accordion>

  <Accordion title="Can I reuse the same topic key across environments?">
    Topic keys must be unique within each [environment](/platform/developer/environments). You can reuse the same key (for example, `feature-release`) in different environments like staging and production.
  </Accordion>

  <Accordion title="What happens if I trigger a workflow to a topic with no subscribers?">
    The workflow is processed, but no notifications are delivered and, hence, this trigger is not counted for billing.
  </Accordion>

  <Accordion title="How do topic subscriptions differ from topic membership?">
    A topic subscription is how a subscriber joins a topic. It stores the subscriber's opt-in, optional JSON Logic conditions (evaluated against the trigger payload), and optional [Context](/platform/concepts/contexts) scope. When you trigger to a topic, Novu fan-out runs through matching subscriptions, not a flat list of subscriber IDs. See [Manage topic subscriptions](/platform/subscription/manage-topic-subscriptions).
  </Accordion>

  <Accordion title="Do Context on a trigger and Context on a subscription need to match?">
    Yes. Novu uses exact-match Context filtering. A subscription created with `{ tenant: "acme-corp" }` is included only when the workflow trigger passes the same Context. Triggers without Context do not reach Context-scoped subscriptions, and vice versa. See [Context-scoped subscriptions](/platform/subscription/manage-topic-subscriptions#context-scoped-subscriptions).
  </Accordion>

  <Accordion title="Are filtered topic subscriptions billed as workflow runs?">
    No. Subscribers filtered out by [Context](/platform/concepts/contexts) mismatch or JSON Logic conditions do not get a workflow run and are not counted toward usage. Only subscribers whose subscriptions pass create a billable workflow run. See [Billing and workflow runs](/platform/subscription/manage-topic-subscriptions#billing-and-workflow-runs).
  </Accordion>
</AccordionGroup>
