Directus Troubleshooting: Common Issues and Fixes | OpsBlu Docs

Directus Troubleshooting: Common Issues and Fixes

Common issues and solutions for Directus-powered websites including API performance, real-time subscriptions, and tracking problems.

This guide covers common issues specific to Directus-powered websites. Issues often relate to API configuration, real-time subscriptions, and database performance.

Common Issues Overview

Directus is an open-source headless CMS that wraps your database with a dynamic API. Common troubleshooting scenarios involve API configuration, permissions, relationships, real-time subscriptions, and frontend integration.

Common Issue Categories

Performance Issues

  • API query optimization
  • Relationship depth impact
  • Real-time subscription overhead
  • Image transformation performance

Tracking Issues

Installation Problems

Directus Instance Setup

Database Connection Failures

Symptoms:

  • Directus won't start
  • "Connection refused" errors
  • "Access denied" for database user

Check database configuration:

# .env file
DB_CLIENT="mysql"  # or "postgres", "sqlite3", "mssql", "cockroachdb"
DB_HOST="localhost"
DB_PORT="3306"
DB_DATABASE="directus"
DB_USER="directus_user"
DB_PASSWORD="secure_password"

Test connection:

# MySQL test
mysql -h localhost -u directus_user -p directus

# PostgreSQL test
psql -h localhost -U directus_user -d directus

# Check Directus logs
docker-compose logs directus
# or
pm2 logs directus

Common fixes:

-- Grant proper permissions (MySQL)
GRANT ALL PRIVILEGES ON directus.* TO 'directus_user'@'localhost';
FLUSH PRIVILEGES;

-- PostgreSQL
GRANT ALL PRIVILEGES ON DATABASE directus TO directus_user;

SDK Installation Issues

Install Directus SDK:

# For JavaScript/TypeScript projects
npm install @directus/sdk

# Verify installation
npm list @directus/sdk

TypeScript types:

# Generate types from your schema
npx directus-extension-types export-schema --host http://localhost:8055 --email admin@example.com --password password

Analytics Integration

Tracking Code Not Loading

Implementing analytics in Directus frontend:

// React/Next.js example
import { createDirectus, rest } from '@directus/sdk';
import Script from 'next/script';

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Script
        strategy="afterInteractive"
        src={`https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX`}
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'G-XXXXXXXXXX');
        `}
      </Script>
      <Component {...pageProps} />
    </>
  );
}

Vue/Nuxt example:

// nuxt.config.js
export default {
  head: {
    script: [
      {
        src: 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX',
        async: true
      }
    ]
  },
  mounted() {
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXXXXX');
  }
}

Configuration Issues

Issue Symptoms Common Causes Solutions
Permissions Errors 403 Forbidden on API calls Role permissions not configured Update role permissions in Directus admin
Deep Relationship Issues Nested data not loading Deep query parameter not set Add ?deep[field]=* to API query
Image Not Transforming Original images served Transform parameters incorrect Use proper asset URL format
CORS Errors API blocked in browser CORS_ORIGIN not configured Set CORS_ORIGIN in .env
Rate Limiting 429 Too Many Requests Excessive API calls Implement caching, increase RATE_LIMITER_POINTS
WebSocket Connection Failed Real-time not working WebSocket config missing Configure WEBSOCKETS_ENABLED=true
Flow Not Triggering Automation not running Flow conditions not met Check Flow logs in admin
File Upload Failing Upload returns error Storage adapter misconfigured Verify STORAGE_LOCATIONS config
Auth Token Expired 401 Unauthorized Token TTL exceeded Refresh token or increase AUTH_ACCESS_TOKEN_TTL
Collection Not Found API returns 404 Collection name mismatch Verify collection name matches API call

Debugging with Developer Tools

Directus Admin Debugging

Activity Log Inspection

View API activity:

  1. Log in to Directus admin
  2. Navigate to Settings → Activity Log
  3. Filter by:
    • User
    • Action (create, update, delete, login)
    • Collection
    • Time range

Check for:

  • Failed authentication attempts
  • Permission denials
  • Unexpected data changes

Flow Execution Logs

Debug automated workflows:

  1. Settings → Flows
  2. Click on specific flow
  3. View "Logs" tab
  4. Check execution history:
    • Trigger events
    • Operation results
    • Error messages
// Add logging to Flow operations
{
  "operation": "log",
  "options": {
    "message": "Flow triggered with data: {{$trigger}}"
  }
}

API Debugging

SDK Request Logging

Enable request logging:

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

// Custom fetch with logging
const customFetch = (url, options) => {
  console.log('API Request:', url);
  console.log('Options:', options);
  const start = performance.now();

  return fetch(url, options).then(response => {
    console.log(`Response time: ${performance.now() - start}ms`);
    console.log('Status:', response.status);
    return response;
  });
};

const client = createDirectus('http://localhost:8055', {
  globals: {
    fetch: customFetch
  }
}).with(rest()).with(authentication());

Network Tab Inspection

Monitor Directus API calls:

  1. Open DevTools → Network tab
  2. Filter for your Directus domain
  3. Look for:
    • /items/collection_name - Collection queries
    • /assets/ - File requests
    • /auth/login - Authentication
    • /graphql - GraphQL queries (if enabled)

Check request details:

GET https://your-directus.com/items/articles?fields=*,author.first_name&filter[status][_eq]=published

Headers:
  Authorization: Bearer your-token-here
  Content-Type: application/json

Response:
  Status: 200 OK
  {
    "data": [...]
  }

Browser Console Debugging

Test SDK in console:

// Make client available globally for debugging
window.directusClient = client;

// Test queries in console
const articles = await window.directusClient.request(
  readItems('articles', {
    fields: ['*'],
    limit: 5
  })
);
console.log(articles);

Platform-Specific Challenges

Permissions and Role Management

Tracking by User Role

Problem: Need different analytics for different user roles

Solution - Conditional tracking:

import { readMe } from '@directus/sdk';

async function initAnalytics() {
  try {
    // Get current user
    const me = await client.request(readMe({
      fields: ['role.name']
    }));

    const userRole = me.role?.name;

    // Don't track admins
    if (userRole === 'Administrator') {
      console.log('Admin user - analytics disabled');
      return;
    }

    // Initialize analytics
    if (typeof gtag !== 'undefined') {
      gtag('config', 'G-XXXXXXXXXX', {
        'custom_map': {
          'dimension1': 'user_role'
        }
      });

      gtag('event', 'page_view', {
        'user_role': userRole
      });
    }
  } catch (error) {
    console.error('Failed to get user info:', error);
  }
}

Public vs Authenticated Content

Track content access level:

async function trackContentView(collectionName, itemId) {
  // Check if user is authenticated
  const token = await client.getToken();
  const isAuthenticated = !!token;

  if (typeof gtag !== 'undefined') {
    gtag('event', 'content_view', {
      'content_type': collectionName,
      'content_id': itemId,
      'access_level': isAuthenticated ? 'authenticated' : 'public'
    });
  }
}

Real-Time Subscriptions

Tracking Real-Time Events

Problem: Track when users receive real-time updates

Solution - WebSocket event tracking:

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

const client = createDirectus('http://localhost:8055')
  .with(realtime());

// Subscribe to collection
const { subscription, unsubscribe } = await client.subscribe('articles', {
  query: {
    fields: ['*'],
    filter: { status: { _eq: 'published' } }
  }
});

// Track subscription events
subscription.on('init', () => {
  console.log('Subscription initialized');
  if (typeof gtag !== 'undefined') {
    gtag('event', 'realtime_connected', {
      'collection': 'articles'
    });
  }
});

subscription.on('message', (data) => {
  console.log('Real-time update:', data);
  if (typeof gtag !== 'undefined') {
    gtag('event', 'realtime_update', {
      'collection': 'articles',
      'event_type': data.event, // create, update, delete
      'item_id': data.data?.id
    });
  }
});

subscription.on('error', (error) => {
  console.error('Subscription error:', error);
  if (typeof gtag !== 'undefined') {
    gtag('event', 'realtime_error', {
      'collection': 'articles',
      'error_message': error.message
    });
  }
});

Collection-Based Tracking

Tracking Different Collections

Problem: Need to track views across multiple collections

Generic tracking function:

async function trackCollectionView(collection, itemId, additionalData = {}) {
  try {
    // Fetch item details
    const item = await client.request(
      readItem(collection, itemId, {
        fields: ['id', 'title', 'status', 'date_created']
      })
    );

    if (typeof gtag !== 'undefined') {
      gtag('event', 'collection_view', {
        'collection_name': collection,
        'item_id': itemId,
        'item_title': item.title,
        'item_status': item.status,
        ...additionalData
      });
    }
  } catch (error) {
    console.error('Tracking error:', error);
  }
}

// Usage
trackCollectionView('articles', '123');
trackCollectionView('products', '456', { category: 'electronics' });

Flow-Based Analytics

Webhook to Analytics

Problem: Send Directus events to analytics via Flows

Create Flow:

  1. Trigger: Items Create (articles collection)
  2. Operation: Webhook
  3. Method: POST
  4. URL: https://your-api.com/track

Webhook payload:

{
  "event": "article_created",
  "data": {
    "id": "{{$trigger.key}}",
    "title": "{{$trigger.payload.title}}",
    "author": "{{$trigger.accountability.user}}",
    "timestamp": "{{$now}}"
  }
}

Process in your API:

// pages/api/track.js
export default async function handler(req, res) {
  const { event, data } = req.body;

  // Send to Google Analytics via Measurement Protocol
  await fetch(`https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_API_SECRET`, {
    method: 'POST',
    body: JSON.stringify({
      client_id: 'directus-flow',
      events: [{
        name: event,
        params: data
      }]
    })
  });

  res.status(200).json({ success: true });
}

Error Messages and Solutions

Common Directus Errors

"Invalid user credentials"

Error:

401 Unauthorized: Invalid user credentials

Causes:

  1. Wrong email/password
  2. User account disabled
  3. 2FA required

Solutions:

// Check authentication
try {
  await client.login(email, password);
  console.log('Login successful');
} catch (error) {
  console.error('Login failed:', error.message);

  // Check specific error
  if (error.errors?.[0]?.extensions?.code === 'INVALID_CREDENTIALS') {
    console.log('Wrong credentials');
  } else if (error.errors?.[0]?.extensions?.code === 'USER_SUSPENDED') {
    console.log('Account suspended');
  }
}

"You don't have permission to access this"

Error:

403 Forbidden: You don't have permission to access this

Check permissions:

  1. Go to Settings → Roles & Permissions
  2. Select user's role
  3. Check collection permissions
  4. Ensure CRUDS permissions are set correctly

Debug permission:

// Check what user can access
const me = await client.request(
  readMe({
    fields: ['*', 'role.admin_access', 'role.app_access']
  })
);
console.log('User role:', me.role);

"Field doesn't exist"

Error:

Invalid query - Field "field_name" doesn't exist in collection "collection_name"

Solutions:

// List all available fields
const fields = await client.request(
  readFields('collection_name')
);
console.log('Available fields:', fields.map(f => f.field));

// Use correct field names
const items = await client.request(
  readItems('articles', {
    fields: ['id', 'title', 'body'] // Must match exact field names
  })
);

API Rate Limiting

Error:

429 Too Many Requests: Rate limit exceeded

Adjust rate limits:

# .env
RATE_LIMITER_ENABLED=true
RATE_LIMITER_POINTS=50  # Requests allowed
RATE_LIMITER_DURATION=1  # Per second

Implement client-side throttling:

import pThrottle from 'p-throttle';

const throttle = pThrottle({
  limit: 10,  // 10 requests
  interval: 1000  // per second
});

const throttledRequest = throttle(async (collection) => {
  return await client.request(readItems(collection));
});

// Use throttled function
const articles = await throttledRequest('articles');

Performance Problems

Slow API Queries

Problem: Queries taking too long

Diagnosis:

const start = performance.now();
const data = await client.request(
  readItems('articles', {
    fields: ['*', 'author.*', 'categories.*']
  })
);
console.log(`Query took ${performance.now() - start}ms`);

Optimization strategies:

  1. Limit fields:
// Bad - fetches everything
const articles = await client.request(readItems('articles'));

// Good - only needed fields
const articles = await client.request(
  readItems('articles', {
    fields: ['id', 'title', 'slug']
  })
);
  1. Control relationship depth:
// Fetch author but limit fields
const articles = await client.request(
  readItems('articles', {
    fields: ['*', 'author.first_name', 'author.last_name'],
    deep: {
      author: {
        _filter: {
          status: { _eq: 'active' }
        }
      }
    }
  })
);
  1. Add pagination:
const articles = await client.request(
  readItems('articles', {
    limit: 20,
    offset: 0
  })
);
  1. Use search instead of filter for text:
// Better for full-text search
const articles = await client.request(
  readItems('articles', {
    search: 'keyword',
    limit: 10
  })
);

Image Transform Performance

Problem: Image transformations slow

Optimize image requests:

// Efficient image URL
const imageUrl = `${directusUrl}/assets/${file.id}?width=800&height=600&fit=cover&quality=80`;

// With Next.js Image component
import Image from 'next/image';

<Image
  src={imageUrl}
  alt={file.title}
  width={800}
  height={600}
  loading="lazy"
/>

Configure image cache:

# .env
ASSETS_CACHE_TTL="30d"
ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION="6000"

When to Contact Support

Directus Community Support

Contact when:

  • Bug in Directus core
  • Feature requests
  • General usage questions
  • Extension development help

Support channels:

Cloud/Enterprise Support

Directus Cloud:

  • Support tickets in cloud dashboard
  • SLA-backed response times
  • Direct support team access

Self-Hosted Issues:

  • Check server logs first
  • Database performance tuning
  • Infrastructure optimization

When to Hire a Developer

Complex scenarios:

  1. Custom extension development
  2. Complex Flow automation
  3. Performance optimization at scale
  4. Advanced permission systems
  5. Multi-tenant implementations
  6. Custom authentication providers
  7. Migration from other systems
  8. Real-time application architecture

Advanced Troubleshooting

Environment Debugging

Complete .env check:

# Core
PORT=8055
PUBLIC_URL="http://localhost:8055"

# Database
DB_CLIENT="mysql"
DB_HOST="localhost"
DB_PORT="3306"
DB_DATABASE="directus"
DB_USER="directus"
DB_PASSWORD="password"

# Auth
KEY="random-key-here"
SECRET="random-secret-here"

# CORS
CORS_ENABLED=true
CORS_ORIGIN=true

# Rate Limiting
RATE_LIMITER_ENABLED=true
RATE_LIMITER_POINTS=50
RATE_LIMITER_DURATION=1

# Caching
CACHE_ENABLED=true
CACHE_STORE="memory"

# File Storage
STORAGE_LOCATIONS="local"
STORAGE_LOCAL_ROOT="./uploads"

# Security
ACCESS_TOKEN_TTL="15m"
REFRESH_TOKEN_TTL="7d"

# WebSockets (for real-time)
WEBSOCKETS_ENABLED=true

Database Query Analysis

Enable query logging:

# .env
DB_LOGGING=true

Check slow queries:

-- MySQL
SELECT * FROM information_schema.processlist
WHERE time > 1
ORDER BY time DESC;

-- PostgreSQL
SELECT pid, now() - query_start as duration, query
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY duration DESC;

Log File Analysis

Check Directus logs:

# Docker
docker logs directus-container

# PM2
pm2 logs directus

# Check log files
tail -f /var/log/directus/error.log
tail -f /var/log/directus/access.log

Enable debug logging:

LOG_LEVEL="debug"
LOG_STYLE="pretty"

For platform-agnostic troubleshooting, see: