Skip to content

Security performance

31. Security & Performance

When to Use

Critical security and performance considerations for production views.

Security Best Practices

SQL Injection

Risk: Views is generally safe from SQL injection (uses parameterized queries), but custom filter plugins or query alterations can introduce vulnerabilities.

Mitigation: - Never concatenate user input into SQL strings - Use placeholders: $query->where('field = :value', [':value' => $input]) - Validate and sanitize all exposed filter input - Contextual filters use placeholders by default — keep it that way

Access Bypass via disable_sql_rewrite

Risk: Setting query.options.disable_sql_rewrite: true completely bypasses node access grants.

Mitigation: - NEVER use on user-facing views - Only use on admin views with strict permission checks (perm: 'administer nodes') - Document WHY it's disabled in view description - Audit all views with disable_sql_rewrite: true during security reviews

REST Export Exposure

Risk: Unauthenticated REST exports expose data to anyone, including scrapers and attackers.

Mitigation: - Always set auth: ['basic_auth'] or stronger authentication - Set appropriate access permissions (not none) - Rate limit REST endpoints (use contrib module or reverse proxy) - Only expose necessary fields - Consider CORS headers for API endpoints

Reference: Drupal Caching Best Practices

Exposed Filter Injection

Risk: Exposed filters accept user input. Malicious input could expose data or cause errors.

Mitigation: - Use grouped filters instead of free-text when possible - Set operator_limit_selection: true to restrict available operators - Validate filter values in custom filter plugins - Use entity validators on entity reference exposed filters

Contextual Filter Validation Bypass

Risk: Unvalidated contextual filters accept arbitrary input, potentially exposing hidden content.

Mitigation: - Always set specify_validation: true - Use entity:node validator with access: true for node ID arguments - Set fail: 'not found' to return 404 on invalid input - Never trust URL arguments without validation

Performance Best Practices

Query Optimization

Index filtered and sorted fields:

CREATE INDEX idx_node_status_changed
ON node_field_data (status, changed);

Views queries benefit from indexes on: - Fields used in filters - Fields used in sorts - Fields used in contextual filters

Avoid SELECT *: - Use fields-based row style instead of entity rendering when possible - Only add fields you actually display - Entity row loads ALL entity fields — expensive for large entities

Use EXPLAIN to analyze slow queries:

// Enable query logging
$view->query->addTag('debug');

Then check query log for slow views queries.

Relationship Performance

  • Each relationship adds a JOIN — limit to 2-3 relationships per view
  • Use required: true (INNER JOIN) when relationship always exists — faster than LEFT JOIN
  • Consider denormalizing data instead of deep relationships

Pager Performance

  • Full pager requires count query — expensive on large datasets
  • Mini pager skips count query — faster for "Previous/Next" use cases
  • Some pager with LIMIT is fastest — no pagination overhead

Caching Strategy

  • Tag-based: Best for content-driven views, auto-invalidates on entity changes
  • Time-based: Best for high-traffic semi-static content, avoids tag tracking overhead
  • None: Only for development/debugging, never in production

Cache contexts matter:

cache_metadata:
  contexts:
    - 'user.permissions'  # Separate cache per permission set
    - 'url.query_args'    # Separate cache per exposed filter combo

More contexts = more cache bins = more cache misses. Balance freshness vs performance.

Reference: Optimizing Drupal Views Caching

Avoid N+1 Queries

Problem: Views query returns 50 nodes. Entity row renders each node, loading 50 sets of field data (51 queries total).

Solution: - Use fields row instead of entity row when possible - Preload entities in hook_views_pre_render() with entity type manager - Enable render caching on entity view modes

Performance Monitoring

Enable Views query debugging:

// settings.php or settings.local.php
$config['views.settings']['ui']['show']['sql_query']['enabled'] = TRUE;

Shows executed SQL in Views preview.

Measure view execution time:

$start = microtime(TRUE);
$view->execute();
$time = microtime(TRUE) - $start;
\Drupal::logger('performance')->info('View @view took @time seconds', [
  '@view' => $view->id(),
  '@time' => round($time, 3),
]);

Use database query logging:

// settings.local.php
$databases['default']['default']['log_queries'] = TRUE;

Logs all queries — check for duplicate or inefficient queries.

Common Performance Mistakes

  • Not using cache at all → Every page request re-runs query
  • Using tag cache on high-traffic views with frequent content changes → Cache invalidates constantly, no performance benefit
  • Deep relationship chains (3+ levels) → Exponential JOIN complexity
  • Exposed filters on unindexed fields → Full table scans on every filter change
  • Entity row on large lists → Loads full entities when only need title + link

See Also

  • Section 13: Caching Configuration — cache strategies
  • Section 14: Access Control — access security
  • Section 16: Query Settings — query tuning
  • Reference: OWASP Top 10 for web security