# Announcement

The Announcement module allows your app to show a message to everyone in the organization, in the header alert or on a glossary page. Crowdin requests the announcements each time it renders one of these placements and passes the reader's context, so your app decides what to show per request.

## Sample

```js ins="announcement" title="index.js"
    import crowdinModule from '@crowdin/app-project-module';

    const configuration = {
      baseUrl: 'https://123.ngrok.io',
      clientId: 'clientId',
      clientSecret: 'clientSecret',
      name: 'Sample App',
      identifier: 'sample-app',
      description: 'Sample App description',
      port: 8080,
      dbFolder: import.meta.dirname,
      imagePath: import.meta.dirname + '/logo.png',
      announcement: [
        {
          placement: 'header-alert',
          getAnnouncements: async () => [
            {
              id: 'release-freeze-2024-05',
              text: 'Merges are frozen until Monday.',
              dismissible: false,
              expiresAt: '2024-05-27T08:00:00Z',
              actionLabel: 'Release schedule',
              actionUrl: 'https://wiki.example.com/release-schedule',
            }
          ]
        },
        {
          placement: 'glossary',
          getAnnouncements: async ({ glossaryId }) => [
            {
              id: 'glossary-guidelines-v3',
              text: `Follow the naming rules before adding terms to glossary ${glossaryId}.`,
              dismissible: true,
            }
          ]
        }
      ]
    };

    crowdinModule.createApp(configuration);
    ```
  ```ts ins="announcement" title="index.ts"
    import crowdinModule from '@crowdin/app-project-module';
    import type { Config } from '@crowdin/app-project-module';

    const configuration: Config = {
      baseUrl: 'https://123.ngrok.io',
      clientId: 'clientId',
      clientSecret: 'clientSecret',
      name: 'Sample App',
      identifier: 'sample-app',
      description: 'Sample App description',
      port: 8080,
      dbFolder: import.meta.dirname,
      imagePath: import.meta.dirname + '/logo.png',
      announcement: [
        {
          placement: 'header-alert',
          getAnnouncements: async () => [
            {
              id: 'release-freeze-2024-05',
              text: 'Merges are frozen until Monday.',
              dismissible: false,
              expiresAt: '2024-05-27T08:00:00Z',
              actionLabel: 'Release schedule',
              actionUrl: 'https://wiki.example.com/release-schedule',
            }
          ]
        },
        {
          placement: 'glossary',
          getAnnouncements: async ({ glossaryId }) => [
            {
              id: 'glossary-guidelines-v3',
              text: `Follow the naming rules before adding terms to glossary ${glossaryId}.`,
              dismissible: true,
            }
          ]
        }
      ]
    };

    crowdinModule.createApp(configuration);
    ```
  ## Configuration

| Parameter | Description | Allowed values | Default value |
|---|---|---|---|
| `key` | Raw module key, sent as `jwt.module`. Required when several modules share one `placement`. The derived key comes from the placement alone, so reordering the list never changes it. | String | `<identifier>-announcement-<placement>` |
| `name` | Module name shown in the installation dialog. | String | derived from `placement`, e.g. `Header announcements` |
| `placement` | Where Crowdin shows this module's items. Declare one module per placement, because an admin grants the audience per module on install. | `'header-alert' \| 'glossary'` | – |
| `getAnnouncements` | Async callback that returns what to show for the given context. | Function | – |
| `environments` | Restrict the module to `crowdin` and/or `crowdin-enterprise`. | `'crowdin' \| 'crowdin-enterprise'` or array | both |

### `getAnnouncements` Function

This function is called each time Crowdin renders the declared placement, with the context of the user looking at the page.

#### Parameters

- `placement` - The placement being rendered: `'header-alert'` or `'glossary'`.
- `organizationId` - Numeric Crowdin organization ID, read from the verified JWT. Safe to key your own storage by.
- `userId` - Numeric ID of the user looking at the page. The JWT is cached per organization and module, so it identifies the installing user rather than the viewer — which is why this one arrives in the request body instead. Use it to choose what to show, not to decide who may see it: Crowdin has already authorized the reader before calling.
- `glossaryId` - Numeric glossary ID. Only sent for the `glossary` placement.
- `client` - Crowdin API client.
- `context` - [Context object](/app-project-module/reference/context/).

#### Return Value

The function should return an array of announcements, most important first. An empty array means nothing to show, which is the normal case rather than an error.

| Field | Description | Required |
|---|---|---|
| `id` | Keys the reader's dismiss state. Changed text under a new `id` shows again to everyone who closed the old one. Up to 255 characters. | yes |
| `text` | Plain text. Crowdin renders no markup; the browser links bare `http`/`https` addresses. | yes |
| `dismissible` | Whether the reader can close it. A non-dismissible announcement stays until it expires. | yes |
| `expiresAt` | ISO 8601 timestamp after which Crowdin stops showing it. | no |
| `actionLabel` | Label of the call-to-action link. Up to 255 characters. | only with `actionUrl` |
| `actionUrl` | Target of the call-to-action link. `https://` only, up to 2048 characters. | only with `actionLabel` |

Omit an optional field you have no value for: `null`, `''` and `[]` are rejected rather than read as "no value", and the announcement carrying one is skipped.

Crowdin validates each announcement separately and takes at most 10 per module. An invalid announcement is skipped and the rest still show. If the app fails to answer, Crowdin skips the request silently and the page renders unchanged.

### Multiple Announcement Modules

You can register multiple announcement modules in a single app by providing an array. One keyless module per placement is enough, as in the sample above. Two modules on the same placement need an explicit `key` each, because the derived key comes from the placement alone:

```js title="index.js"
const configuration = {
  // ... other configuration
  announcement: [
    {
      key: 'release-notes',
      name: 'Release notes',
      placement: 'header-alert',
      getAnnouncements: async () => []
    },
    {
      key: 'incidents',
      name: 'Incidents',
      placement: 'header-alert',
      getAnnouncements: async () => []
    }
  ]
};
```