---
title: JavaScript
description: Notiflows JavaScript SDK for client-side notification integration
---

The JavaScript SDK (`@notiflows/client`) provides a client for interacting with the Notiflows User API. Use it to fetch notifications, manage notification state, and subscribe to real-time updates.

<Callout>
For React applications, we recommend using the [React SDK](/docs/sdks/client-side/react) which provides hooks and pre-built components on top of this client.
</Callout>

## Installation

```bash
npm install @notiflows/client
# or
pnpm add @notiflows/client
# or
yarn add @notiflows/client
```

## Quick Start

```typescript
import { Notiflows } from '@notiflows/client';

// Initialize the client
const client = new Notiflows({
  apiKey: 'pk_your_public_key',
  userId: 'user_123',
  userKey: 'jwt_token_from_backend',
});

// Get the notification feed
const feed = client.feed({ channelId: 'your-channel-id' });

// Fetch notifications
const collection = await feed.getEntries();
console.log(collection.items, collection.total_unread);

// Subscribe to real-time updates
feed.onDelivery = (entry) => {
  console.log('New notification:', entry);
};
feed.subscribeToRealtimeNotifications();

// Mark as read
await feed.markAsRead('entry_id');
```

## Configuration

```typescript
const client = new Notiflows({
  // Required
  apiKey: string,      // Your public API key (starts with pk_)
  userId: string,      // The user's external ID
  userKey: string,     // JWT token signed with your signing key

  // Optional
  apiUrl?: string,     // Custom API URL (default: https://api.notiflows.com/user/v1)
  wsUrl?: string,      // Custom WebSocket URL (default: wss://api.notiflows.com/ws/v1)
});
```

<Callout type="warn">
The `userKey` must be generated on your backend using your Application Signing Key. Never expose your signing key in client-side code. See [Generating User Tokens](/docs/api/user/authentication#generating-user-tokens) for instructions.
</Callout>

## Feed

The Feed resource manages notification entries for a specific channel.

### Creating a Feed Instance

```typescript
const feed = client.feed({
  channelId: 'your-in-app-channel-id',
});
```

### Fetching Entries

```typescript
import { FeedEntryStatus } from '@notiflows/client';

// Get all notifications
const collection = await feed.getEntries();

// Access the data
const { items, total_unread, total_unseen } = collection;

// With filters
const unreadOnly = await feed.getEntries({
  status: FeedEntryStatus.Unread,
  limit: 20,
});

// With pagination
const collection = await feed.getEntries({ limit: 10 });

if (collection.has_more_after) {
  const nextPage = await feed.getEntries({
    limit: 10,
    after: collection.after,
  });
}

// Filter by notiflow handle
const orderNotifs = await feed.getEntries({
  notiflow: 'order-updates',
});

// Filter by topic
const topicNotifs = await feed.getEntries({
  topic: 'order:123',
});

// Include archived entries
const withArchived = await feed.getEntries({
  archived: true,
});
```

### Feed Settings

Fetch feed settings (e.g. whether branding is required):

```typescript
const settings = await feed.getSettings();
console.log(settings.branding_required); // true on free plan
```

### Real-time Updates

Subscribe to receive notifications as they're delivered:

```typescript
// Set up handlers before subscribing
feed.onDelivery = (entry) => {
  console.log('New notification:', entry);
  // Add to your UI, play a sound, etc.
};

// Start listening for real-time updates
feed.subscribeToRealtimeNotifications();

// Stop listening when done
feed.stop();
```

### Updating Entry State

Mark notifications as seen, read, clicked, or archived:

```typescript
// Single entry updates
await feed.markAsSeen(entryId);
await feed.markAsRead(entryId);
await feed.markAsClicked(entryId);
await feed.markAsArchived(entryId);
await feed.markAsUnarchived(entryId);

// Batch updates
await feed.batchMarkAsSeen([entryId1, entryId2]);
await feed.batchMarkAsRead([entryId1, entryId2]);
await feed.batchMarkAsClicked([entryId1, entryId2]);
await feed.batchMarkAsArchived([entryId1, entryId2]);
await feed.batchMarkAsUnarchived([entryId1, entryId2]);

// Generic update with multiple fields
await feed.updateEntry(entryId, {
  read: true,
  seen: true,
});

await feed.batchUpdateEntries([entryId1, entryId2], {
  archived: true,
});
```

## User Preferences

Manage user notification preferences:

```typescript
const preferences = client.userPreferences();

// Get current preferences
const prefs = await preferences.get();
console.log(prefs.notiflows); // Per-notiflow preferences

// Update preferences
await preferences.update({
  notiflows: {
    'marketing-emails': {
      enabled: false,
    },
  },
});
```

## Types

### FeedEntry

```typescript
interface FeedEntry {
  id: string;
  notiflow_handle: string;
  data: {
    body: string;
    action_type: 'default' | 'single' | 'multi';
    action_url?: string;
    primary_action?: { label: string; url: string };
    secondary_action?: { label: string; url: string };
  };
  actor?: {
    id: string;
    external_id: string;
    first_name?: string;
    last_name?: string;
    avatar?: string;
    email?: string;
  };
  topic?: string;
  status: FeedEntryStatus;
  sent_at?: string;
  seen_at?: string;
  read_at?: string;
  clicked_at?: string;
  archived_at?: string;
  created_at: string;
}
```

### FeedEntryCollection

```typescript
interface FeedEntryCollection {
  items: FeedEntry[];
  total_unread: number;
  total_unseen: number;
  has_more_after: boolean;
  after: string | null;
}
```

### FeedSettings

```typescript
interface FeedSettings {
  branding_required: boolean;
}
```

### FeedEntryStatus

```typescript
enum FeedEntryStatus {
  Unseen = 'unseen',
  Seen = 'seen',
  Unread = 'unread',
  Read = 'read',
  Archived = 'archived',
}
```

### Preferences

```typescript
interface Preferences {
  notiflows: {
    [notiflowHandle: string]: {  // keyed by notiflow handle (e.g. "order-updates")
      name: string;
      enabled: boolean;
    };
  };
}
```

## Error Handling

The SDK provides typed errors for different API responses:

```typescript
import {
  isApiError,
  BadRequestError,
  UnauthenticatedError,
  ForbiddenError,
  NotFoundError,
  RateLimitedError,
  ValidationFailedError,
  ConflictError,
  InternalError,
  ServiceUnavailableError,
} from '@notiflows/client';

try {
  await feed.getEntries();
} catch (error) {
  if (isApiError(error)) {
    console.log(error.code);    // Error code
    console.log(error.message); // Error message
    console.log(error.details); // Additional details

    if (error instanceof UnauthenticatedError) {
      // Redirect to login or refresh token
    } else if (error instanceof RateLimitedError) {
      // Back off and retry
    } else if (error instanceof ValidationFailedError) {
      // Check error.details for field-level errors
    }
  }
}
```

### Error Types

| Error | HTTP Status | Description |
|-------|-------------|-------------|
| `BadRequestError` | 400 | Invalid request parameters |
| `UnauthenticatedError` | 401 | Invalid or missing authentication |
| `ForbiddenError` | 403 | Insufficient permissions |
| `NotFoundError` | 404 | Resource not found |
| `ConflictError` | 409 | Resource state conflict |
| `ValidationFailedError` | 422 | Validation errors on request body |
| `RateLimitedError` | 429 | Too many requests |
| `InternalError` | 500 | Server error |
| `ServiceUnavailableError` | 503 | Service temporarily unavailable |

## TypeScript

The SDK is written in TypeScript and exports all types:

```typescript
import type {
  FeedEntry,
  FeedEntryCollection,
  FeedEntryStatus,
  FeedSettings,
  GetEntriesParams,
  EntryStateUpdate,
  Preferences,
  NotiflowsOptions,
} from '@notiflows/client';
```

## Allowed Origins

Before using the SDK in a browser, you must add your domain to the project's allowed origins list. Without this, the browser will block all API requests due to CORS.

See [Allowed Origins](/docs/learn/security/allowed-origins) for setup instructions.

## Browser Support

The SDK supports all modern browsers (ES2022+) and Node.js 18+.

For older browsers, you may need to polyfill:
- `Promise`
- `fetch` (if using Node.js < 18)

## Next Steps

- [React SDK](/docs/sdks/client-side/react) - Pre-built components and hooks for React
- [Authentication](/docs/api/user/authentication) - Learn about user token generation
- [API Reference](/docs/api/user) - Full User API documentation
