> ## Documentation Index
> Fetch the complete documentation index at: https://novu-c5de82d9-docs-homepage-redesign.mintlify.site/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.

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 all subscribers assigned to the topic.
* A separate workflow event is created for each subscriber.

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>

## 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 multiple subscribers into a topic" />

  <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="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>
</AccordionGroup>
