Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/directus/directus/llms.txt

Use this file to discover all available pages before exploring further.

Introduction

Directus supports GraphQL subscriptions for real-time updates when items are created, updated, or deleted in your collections. Subscriptions use WebSockets to maintain a persistent connection and push updates to clients as they happen.
GraphQL subscriptions are only available for item collections on the /graphql endpoint, not for system collections.

WebSocket Endpoint

Subscriptions connect via WebSocket:
ws://your-directus-instance.com/graphql
wss://your-directus-instance.com/graphql
Use wss:// for secure connections (HTTPS sites) and ws:// for local development.

Configuration

Enable GraphQL subscriptions via WebSockets with environment variables:

Enable GraphQL Subscriptions

WEBSOCKETS_ENABLED=true
WEBSOCKETS_GRAPHQL_ENABLED=true

Authentication Modes

Configure how clients authenticate:
# Options: strict, handshake, public
WEBSOCKETS_GRAPHQL_AUTH=handshake
Authentication Modes:
  • strict: Clients must authenticate before connecting (via URL or headers)
  • handshake: Clients authenticate during connection initialization
  • public: No authentication required (for public data only)

Authentication Timeout

Set timeout for handshake authentication:
WEBSOCKETS_GRAPHQL_AUTH_TIMEOUT=10000

Custom WebSocket Path

WEBSOCKETS_GRAPHQL_PATH=/graphql

Subscription Types

For each collection, Directus generates a subscription:
<collection>_mutated
This subscription fires when items are created, updated, or deleted.

Basic Subscription

Subscribe to all mutations on a collection:
subscription {
  articles_mutated {
    key
    event
    data {
      id
      title
      status
    }
  }
}
Response Fields:
  • key: The ID of the affected item
  • event: The mutation type (create, update, or delete)
  • data: The item data (null for delete events)

Filter by Event Type

Subscribe only to specific event types:
subscription {
  articles_mutated(event: "create") {
    key
    event
    data {
      id
      title
      created_at
    }
  }
}
Available Events:
  • create: Item was created
  • update: Item was updated
  • delete: Item was deleted

Subscribe to Specific Fields

Request only the fields you need:
subscription {
  articles_mutated {
    key
    event
    data {
      title
      status
      updated_at
    }
  }
}

Subscribe with Relationships

Include related data in subscription updates:
subscription {
  articles_mutated {
    key
    event
    data {
      id
      title
      author {
        id
        name
      }
      categories {
        categories_id {
          id
          name
        }
      }
    }
  }
}

Authentication

Handshake Authentication

Authenticate during connection initialization:
import { createClient } from 'graphql-ws';

const client = createClient({
  url: 'wss://your-directus-instance.com/graphql',
  connectionParams: {
    access_token: 'your-access-token',
  },
});

Strict Authentication

Authenticate via URL parameters:
const client = createClient({
  url: 'wss://your-directus-instance.com/graphql?access_token=your-token',
});

Client Implementation

Using graphql-ws

import { createClient } from 'graphql-ws';

const client = createClient({
  url: 'wss://your-directus-instance.com/graphql',
  connectionParams: {
    access_token: 'your-access-token',
  },
});

const subscription = client.iterate({
  query: `
    subscription {
      articles_mutated {
        key
        event
        data {
          id
          title
          status
        }
      }
    }
  `,
});

(async () => {
  for await (const result of subscription) {
    console.log('Update received:', result);
  }
})();

Using Apollo Client

import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import { createClient } from 'graphql-ws';

const httpLink = new HttpLink({
  uri: 'https://your-directus-instance.com/graphql',
  headers: {
    authorization: `Bearer ${token}`,
  },
});

const wsLink = new GraphQLWsLink(
  createClient({
    url: 'wss://your-directus-instance.com/graphql',
    connectionParams: {
      access_token: token,
    },
  })
);

const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

Using the Directus SDK

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

const client = createDirectus('https://your-directus-instance.com')
  .with(graphql())
  .with(realtime());

await client.connect();

const { subscription } = await client.subscribe(
  `
    subscription {
      articles_mutated {
        key
        event
        data {
          id
          title
        }
      }
    }
  `
);

for await (const message of subscription) {
  console.log('Received:', message);
}

Subscription Events

Create Event

Fired when an item is created:
{
  "data": {
    "articles_mutated": {
      "key": "abc-123",
      "event": "create",
      "data": {
        "id": "abc-123",
        "title": "New Article",
        "status": "draft"
      }
    }
  }
}

Update Event

Fired when an item is updated:
{
  "data": {
    "articles_mutated": {
      "key": "abc-123",
      "event": "update",
      "data": {
        "id": "abc-123",
        "title": "Updated Article",
        "status": "published"
      }
    }
  }
}

Delete Event

Fired when an item is deleted:
{
  "data": {
    "articles_mutated": {
      "key": "abc-123",
      "event": "delete",
      "data": null
    }
  }
}
Delete events don’t include item data, only the key (ID) of the deleted item.

Permission Handling

Subscriptions respect user permissions:
  • Users only receive updates for items they have read access to
  • If permissions change and a user loses access, they won’t receive further updates
  • Permission errors are silently ignored (no notification sent)

Using Variables

Use variables to make subscriptions dynamic:
subscription ArticleUpdates($event: String) {
  articles_mutated(event: $event) {
    key
    event
    data {
      id
      title
      status
    }
  }
}
Variables:
{
  "event": "update"
}

Connection Lifecycle

Connection States

  1. Connecting: Establishing WebSocket connection
  2. Connected: Connection established, can subscribe
  3. Reconnecting: Lost connection, attempting to reconnect
  4. Disconnected: Connection closed

Handling Connection Errors

import { createClient } from 'graphql-ws';

const client = createClient({
  url: 'wss://your-directus-instance.com/graphql',
  connectionParams: {
    access_token: token,
  },
  on: {
    connected: () => console.log('Connected'),
    error: (error) => console.error('Connection error:', error),
    closed: () => console.log('Connection closed'),
  },
});

Auto-Reconnection

Most GraphQL clients handle reconnection automatically. Configure retry behavior:
const client = createClient({
  url: 'wss://your-directus-instance.com/graphql',
  retryAttempts: 10,
  retryWait: async (retries) => {
    await new Promise((resolve) => 
      setTimeout(resolve, Math.min(1000 * 2 ** retries, 10000))
    );
  },
});

Complete Example

Here’s a full example of using subscriptions in a React application:
import React, { useEffect, useState } from 'react';
import { createClient } from 'graphql-ws';

const client = createClient({
  url: 'wss://your-directus-instance.com/graphql',
  connectionParams: {
    access_token: 'your-token',
  },
});

function ArticleSubscription() {
  const [articles, setArticles] = useState([]);
  const [updates, setUpdates] = useState([]);

  useEffect(() => {
    const subscription = client.iterate({
      query: `
        subscription {
          articles_mutated {
            key
            event
            data {
              id
              title
              status
              updated_at
            }
          }
        }
      `,
    });

    (async () => {
      for await (const result of subscription) {
        const { key, event, data } = result.data.articles_mutated;

        setUpdates(prev => [
          { key, event, timestamp: new Date() },
          ...prev.slice(0, 9)
        ]);

        if (event === 'create') {
          setArticles(prev => [data, ...prev]);
        } else if (event === 'update') {
          setArticles(prev => 
            prev.map(article => 
              article.id === key ? data : article
            )
          );
        } else if (event === 'delete') {
          setArticles(prev => 
            prev.filter(article => article.id !== key)
          );
        }
      }
    })();

    return () => {
      subscription.return?.();
    };
  }, []);

  return (
    <div>
      <h2>Live Articles</h2>
      <ul>
        {articles.map(article => (
          <li key={article.id}>
            {article.title} - {article.status}
          </li>
        ))}
      </ul>

      <h3>Recent Updates</h3>
      <ul>
        {updates.map((update, i) => (
          <li key={i}>
            {update.event} - {update.key} at {update.timestamp.toLocaleTimeString()}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ArticleSubscription;

Best Practices

Request only the fields you need to reduce bandwidth:
# Good - minimal fields
subscription {
  articles_mutated {
    key
    event
    data {
      title
      status
    }
  }
}

# Avoid - unnecessary fields
subscription {
  articles_mutated {
    key
    event
    data {
      id
      title
      content
      author { id name email bio }
      created_at
      updated_at
    }
  }
}
If you only need specific events, filter them:
# Only listen for new items
subscription {
  articles_mutated(event: "create") {
    key
    data { title }
  }
}
Always implement reconnection logic for production:
const client = createClient({
  url: 'wss://your-instance.com/graphql',
  retryAttempts: Infinity,
  shouldRetry: () => true,
});
Always clean up subscriptions when components unmount:
useEffect(() => {
  const unsubscribe = subscribe({ query });
  return () => unsubscribe();
}, []);
Remember that delete events don’t include data:
if (event === 'delete') {
  // Only 'key' is available, 'data' is null
  removeItem(key);
}

Troubleshooting

Ensure WebSockets are enabled:
WEBSOCKETS_ENABLED=true
WEBSOCKETS_GRAPHQL_ENABLED=true
Check your authentication mode and token:
// For handshake mode
connectionParams: {
  access_token: 'your-valid-token'
}
Verify:
  • User has read permissions for the collection
  • Subscription query syntax is correct
  • WebSocket connection is established
Increase the authentication timeout:
WEBSOCKETS_GRAPHQL_AUTH_TIMEOUT=30000

Next Steps

GraphQL Queries

Learn how to query your data

GraphQL Mutations

Create, update, and delete data