# Directus SDK

> A JavaScript and TypeScript library that simplifies working with Directus.

The Directus SDK allows to work with Directus directly in your JavaScript and TypeScript projects. The SDK is split into separate modules, giving granular control over which features to include and which can be pruned at build-time. It is lightweight and dependency-free.

```bash
npm install @directus/sdk
```

## Create a Client

The Directus SDK is a "Composable Client" that allows you to customize and build a client with the specific features you need. The client starts as an empty wrapper without any functionality. To add features, import and use the following composables:

<table>
<thead>
  <tr>
    <th>
      Composable
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        rest()
      </code>
    </td>
    
    <td>
      Adds <code>
        .request()
      </code>
      
       method for making REST requests.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        graphql()
      </code>
    </td>
    
    <td>
      Adds <code>
        .query()
      </code>
      
       method for making GraphQL requests.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        authentication()
      </code>
    </td>
    
    <td>
      Adds <code>
        .login()
      </code>
      
      , <code>
        .logout()
      </code>
      
      , and <code>
        .refresh()
      </code>
      
       methods. Also adds token handling.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        realtime()
      </code>
    </td>
    
    <td>
      Adds <code>
        .connect()
      </code>
      
      , <code>
        .subscribe()
      </code>
      
      , <code>
        .sendMessage()
      </code>
      
      , and <code>
        .onWebSocket()
      </code>
      
       methods. Also adds reconnect logic.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        staticToken()
      </code>
    </td>
    
    <td>
      Adds <code>
        .setToken()
      </code>
      
       and <code>
        .getToken()
      </code>
      
       methods for manually managing tokens.
    </td>
  </tr>
</tbody>
</table>

<tabs>
<div className="pr-6" label="JavaScript">

```js
import { createDirectus, rest } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com').with(rest());
```

</div>

<div className="pr-6" label="TypeScript">

You must provide a `Schema` when creating a Directus client to make use of type hinting and completion. This schema contains definitions for each collection and provides you with type hints (on input) and completion (on output).

```ts
import { createDirectus, rest } from '@directus/sdk';

interface Post {
  id: number;
  title: string;
  content: string;
}

interface Schema {
  posts: Post[];
}

const directus = createDirectus<Schema>('http://directus.example.com').with(rest());
```

<callout icon="i-lucide-graduation-cap" color="secondary" to="/tutorials/tips-and-tricks/advanced-types-with-the-directus-sdk">

Learn how to leverage TypeScript with the SDK for auto-completion and generated output types.

</callout>
</div>
</tabs>

## Making Requests

To make a request, you must create the client with the `rest()` or `graphql()` composable. If using `rest()`, you must also import and use one of the query functions.

```js
import { createDirectus, rest, readItems } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com').with(rest());

const allPosts = await directus.request(readItems('posts'));

const somePosts = await directus.request(
  readItems('posts', {
    filter: { status: { _eq: 'published' } },
    sort: ['-date_created'],
    fields: ['id', 'title', 'date_created'],
    limit: 3
  })
);
```

<callout icon="i-lucide-info">

**Breakdown of snippet**

- Imports

  - `createDirectus` is required to create a client.
  - `rest` is required to make REST requests, and adds the `.request()` method.
  - `readItems` is a query function which fetches
- Creating the client

  - A new client is created and held in the `directus` variable.
  - `createDirectus` requires the valid URL of a Directus project.
  - The client is created with the `rest()` composable.
- Requests

  - `allPosts` makes a request to `readItems` in the `posts` collection.
  - `somePosts` does the same, but only the specified fields from the latest 3 published items.

</callout>

<callout icon="i-lucide-square-code" color="green" to="/api/items">

The API Reference contains SDK examples for endpoints, showing the required function usage.

</callout>

<callout icon="i-lucide-book-open" color="primary" to="/guides/connect/query-parameters">

See all query parameters with SDK examples.

</callout>

### Custom Endpoints

To call custom endpoints using the SDK, you can manually provide a path and method. If using TypeScript, you can type the output.

<tabs>
<div className="pr-6" label="JavaScript">

```js
import { createDirectus, rest } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com').with(rest());

const result = await directus.request(() => ({
  path: '/custom/endpoint',
  method: 'GET',
}));
```

</div>

<div className="pr-6" label="TypeScript">

```ts
import { createDirectus, rest, customEndpoint } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com').with(rest());

const result = await directus.request(customEndpoint<OutputType>({
  path: '/custom/endpoint',
  method: 'GET',
}));
```

</div>
</tabs>

### GraphQL Usage

Add the `graphql()` composable to the client, and use the `.query()` method.

```ts
import { createDirectus, graphql } from '@directus/sdk';

interface Post {
  id: number;
  title: string;
  content: string;
}

interface Schema {
  posts: Post[];
}

const directus = createDirectus<Schema>('http://directus.example.com').with(graphql());

const result = await directus.query<Post[]>(`
  query {
    posts {
      id
      title
      content
    }
  }
`);
```

## Authentication

The `authentication()` composable provides the Directus client with new `login()`, `logout()`, and `refresh()` methods. It also manages token storage and refreshing on your behalf.

```js
import { createDirectus, authentication } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com').with(authentication());

await directus.login({ email, password }, login_options);
await directus.refresh();
await directus.logout();
```

This approach will handle refreshing of the token automatically. The current token is stored inside the initialized client.

### Login Options

The login options object contains three optional parameters to control the behavior of the request.

```ts
type LoginOptions = {
  otp?: string;
  mode?: AuthenticationMode;
  provider?: string;
};
```

- `otp` contains the user's one-time-password if two-factor authentication is enabled.
- `mode` defines how the refresh token is returned. One of `json`, `cookie` or `session`. Defaults to `cookie`.
- `provider` specifies a non-redirect authentication provider. LDAP providers use `identifier` (typically a username) rather than `email`:

```js
await client.login({ identifier: 'username', password: 'd1r3ctu5' }, { provider: 'ldap' });
```

For SSO providers that use browser redirects (Google, GitHub, Okta, etc.), see [SSO Login](#sso-login) below.

### SSO Login

SSO providers like Google, GitHub, and Okta rely on browser redirects. The `.login()` method does not apply to these providers. Instead, redirect the user to Directus and call `.refresh()` after they return.

1. Create a link that sends the user to your Directus `/auth/login/<provider>` endpoint with a `redirect` query parameter pointing back to your app. Replace `<provider>` with the provider name configured in `AUTH_PROVIDERS`.
2. The user authenticates with the external provider.
3. Directus redirects the user back to the URL in the `redirect` parameter.
4. On your callback page, call `client.refresh()` to establish the session.

Use `session` mode with `credentials: 'include'` so the session cookie set by Directus during the redirect is sent with subsequent requests.

```html
<a href="https://directus.example.com/auth/login/google?redirect=https://your-app.com/callback">
  Login with Google
</a>
```

```js
import { createDirectus, authentication, rest, readMe } from '@directus/sdk';

const client = createDirectus('https://directus.example.com')
  .with(authentication('session', { credentials: 'include' }))
  .with(rest({ credentials: 'include' }));

await client.refresh();

const me = await client.request(readMe());
```

<callout icon="i-lucide-triangle-alert" color="warning">

The redirect URL must be included in `AUTH_<PROVIDER>_REDIRECT_ALLOW_LIST` or the redirect will be blocked. See [Authentication & SSO configuration](/configuration/auth-sso) for all provider settings.

</callout>

<callout icon="i-lucide-book-open" color="primary" to="/guides/auth/sso/seamless">

Read the Seamless SSO guide for session cookie configuration and local development setup.

</callout>

### Token Management

#### Set Token

<tabs>
<div className="pr-6" label="Create client with token">

Import `staticToken` and use it when creating a client.

```js
import { createDirectus, staticToken, rest } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com')
  .with(staticToken('TOKEN'))
  .with(rest());
```

</div>

<div className="pr-6" label="Token for single requests">

Import `withToken` and use it as a request function with your token as the first parameter, and your original request as the second.

```js
import { createDirectus, rest, withToken, readItems } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com').with(rest());

const request = await directus.request(
  withToken('TOKEN', readItems('posts'))
);
```

</div>

<div className="pr-6" label="Set client token manually">

Import `authentication` or `staticToken` and use it when creating a client. You now have access to the `setToken` method.

```js
import { createDirectus, authentication } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com').with(authentication());

await directus.setToken('TOKEN');
```

</div>
</tabs>

#### Get a Token

Import `authentication` or `staticToken` and use it when creating a client. You now have access to the `getToken` method.

```js
import { createDirectus, authentication } from '@directus/sdk';
const directus = createDirectus('http://directus.example.com').with(authentication());

const token = await directus.getToken();
```

### Configure Custom Storage

Internally, `getToken()` and `setToken()` make use of the configurable storage, which can be customized for your environment's needs. There must be a `get()` and `set()` method exposed, and the `AuthData` type returned.

<callout icon="i-lucide-info">

**Example**
Instead of storing `AuthData` in an object in the browser, this custom storage implementation stores and retrieves data in `localStorage`:

```js
import { createDirectus, authentication } from '@directus/sdk';

class LocalStorage {
  get() {
    return JSON.parse(localStorage.getItem("directus-data"));
  }
  set(data) {
    localStorage.setItem("directus-data", JSON.stringify(data));
  }
}

const storage = new LocalStorage();
const directus = createDirectus('http://directus.example.com')
    .with(authentication('json', { storage }));

// Set a long term or static token without expiry information.
directus.setToken('TOKEN');

// Set custom credentials to the storage.
storage.set({
  access_token: 'token',
  refresh_token: 'token',
  expires_at: 123456789
});
```

</callout>

### Cross-Domain Cookies

A common situation is for the Directus backend and frontend to be hosted on different domains, requiring extra configuration to make sure cookies are passed correctly. Usually this is only required for authentication with cookies but this can be set globally for each composable that does requests. This will then apply to all requests made using that composable:

```js
const directus = createDirectus('http://directus.example.com')
  .with(authentication('cookie', { credentials: 'include' }))
  .with(graphql({ credentials: 'include' }))
  .with(rest({ credentials: 'include' }));
```

Or you can enable this only for specific REST requests using the `withOptions`:

```js
const result = await directus.request(
  withOptions(refresh(), { credentials: 'include' })
);
```

## Realtime

The Directus SDK makes it easier to work with <product-link product="realtime">



</product-link>

 by adding `.connect()`, `.subscribe()`, `.sendMessage()`, and `.onWebSocket()` methods. It also handles reconnect logic.

<callout icon="i-lucide-book-open" color="primary" to="/getting-started/connect-to-realtime">

Read the Directus Realtime quickstart.

</callout>

## Global APIs

To keep the SDK dependency-free, it does rely on the APIs mentioned below, which originally came from the browser ecosystem and may not be available in all environments.

### The `fetch` API

This API is shipped with almost every modern runtime. Nevertheless, there might be reasons to overwrite or set the implementation, for example, if an alternative implementation is preferred or if you actually work with a special runtime where `fetch` is not available.

- [`node-fetch`](https://www.npmjs.com/package/node-fetch)
- [`ofetch`](https://www.npmjs.com/package/ofetch)
- [`whatwg-fetch`](https://www.npmjs.com/package/whatwg-fetch)

### The `URL` API

This API is shipped with almost every modern runtime. However, there are exceptions, like `react-native`, that require a polyfill for the SDK to work.

- [`url-polyfill`](https://www.npmjs.com/package/url-polyfill)
- [`react-native-url-polyfill`](https://www.npmjs.com/package/react-native-url-polyfill)

### The `WebSocket` API

This API is optional if you're not making use of the `realtime()` features in the SDK. Backend JavaScript environments often do not ship with an implementation of WebSockets.

- [`ws`](https://www.npmjs.com/package/ws)
- [`isomorphic-ws`](https://www.npmjs.com/package/isomorphic-ws)

### The `logger` API

This API is optional and currently only used for debugging `realtime()` features. This will default to the `Console` however in environments where this isn't shipped you can overwrite this with any logger.

### Polyfilling

There are two polyfilling approaches, with the first taking precedence.

#### Options Parameter of `createDirectus`

```js
import { createDirectus } from '@directus/sdk';
import { ofetch } from 'ofetch';
import WebSocket from 'ws';

const directus = createDirectus('http://directus.example.com', {
  globals: {
    WebSocket: WebSocket,
    fetch: ofetch,
  }
});
```

#### `globalThis` object

```js
import { createDirectus } from '@directus/sdk';
import { ofetch } from 'ofetch';
import WebSocket from 'ws';

globalThis.WebSocket = WebSocket;
globalThis.fetch = ofetch;

import 'react-native-url-polyfill/auto';

const directus = createDirectus('http://directus.example.com');
```

Polyfill libraries will often register itself to the `globalThis` object. For example, the `react-native-url-polyfill` package.

## Error Handling

### `isDirectusError` type guard

The SDK exports an `isDirectusError` type guard utility function to determine if the error thrown was from the API

```js
import { createDirectus, rest, isDirectusError, readItems } from '@directus/sdk';

const directus = createDirectus('http://directus.example.com').with(rest());

try {
  const request = await directus.request(readItems('posts')));
} catch(error){
  if(isDirectusError(error)){
    // some error has been returned from the API
  } else {
    // some unknown non API error has been thrown (e.g. unable to parse the JSON response)
  }
}
```
