Skip to content

Anti patterns

30. Anti-Patterns & Common Mistakes

When to Use

What NOT to do and WHY.

Configuration Anti-Patterns

Using Views UI for One-Off Queries

Anti-pattern: Creating a view for a single-use admin report that only you will ever run.

Why bad: Views add overhead (config storage, UI complexity, cache management) for queries that could be 10 lines of EntityQuery code.

Do instead: Write custom EntityQuery in controller or drush command. Simpler, faster, clearer intent.

Not Exporting Views to Config

Anti-pattern: Building views in production UI, never exporting to YAML.

Why bad: Views live only in database. Can't version control, can't reproduce on other environments, lost on DB restore, no audit trail.

Do instead: Always export production views. Config sync is the Drupal way.

Overly Complex Single Views

Anti-pattern: One view with 8 displays, 15 relationships, 20 filters, trying to be everything.

Why bad: Unmaintainable nightmare. Hard to debug, slow to load in UI, fragile (one change breaks multiple displays).

Do instead: Split into multiple focused views. Each view does one thing well.

Performance Anti-Patterns

DISTINCT Without Relationships

Anti-pattern: query.options.distinct: true on view with no relationships.

Why bad: DISTINCT is expensive (sorts all rows to find duplicates). Without relationships, there are no duplicates.

Do instead: Only use DISTINCT when multiple relationships cause row multiplication. Test query without DISTINCT first.

No Pager on Unbounded Queries

Anti-pattern: pager.type: none on a view querying all nodes (10,000+ rows).

Why bad: Loads entire dataset into memory, times out, crashes page, kills database.

Do instead: Always limit results. Use some pager for fixed limit, full/mini for pagination.

Tag Cache on Rarely-Changing High-Traffic Views

Anti-pattern: Tag-based cache on homepage view of archived content that changes monthly.

Why bad: Tag tracking overhead for content that doesn't change. Time-based cache would be more efficient.

Do instead: Use time-based cache with long lifespan (24 hours or more) for semi-static content.

Security Anti-Patterns

disable_sql_rewrite on User-Facing Views

Anti-pattern: query.options.disable_sql_rewrite: true on public article listing.

Why bad: CRITICAL SECURITY ISSUE. Bypasses ALL node access grants. Unpublished content visible, private content exposed, access control modules ignored.

Do instead: Never use on user-facing views. Only for admin-only views with strict permission checks.

REST Export Without Authentication

Anti-pattern: REST export display with access: none or no auth setting.

Why bad: Public API endpoint exposing potentially sensitive data. No rate limiting, no accountability.

Do instead: Always require authentication (auth: ['basic_auth']) and set appropriate access permissions.

No Contextual Filter Validation

Anti-pattern: Contextual filter with specify_validation: false accepting arbitrary input.

Why bad: Malicious input can cause query errors, expose data, or bypass access checks.

Do instead: Always validate contextual filters. Use entity validator with access check for entity reference arguments.

UX Anti-Patterns

Exposed Filters Without Labels

Anti-pattern: expose.label: '' on exposed filter.

Why bad: Form shows field machine name or blank label. Users don't know what the filter does.

Do instead: Always set clear, user-friendly labels on exposed filters.

Required Exposed Filters

Anti-pattern: expose.required: true on all exposed filters.

Why bad: Users can't view all results. Must always pick a filter value.

Do instead: Use required: false and set reasonable defaults. Let users refine, not restrict from start.

Block Display Without block_description

Anti-pattern: Block display with block_description: ''.

Why bad: Block shows as "View: {view_id} - {display_id}" in block UI. Confusing for site builders.

Do instead: Set descriptive block_description. "Recent Articles" not "Block 1".

Maintainability Anti-Patterns

Generic Machine Names

Anti-pattern: Views named view_1, block_2, page.

Why bad: Impossible to understand purpose without opening config. Debugging logs are cryptic.

Do instead: Descriptive names: articles_by_category, user_dashboard_recent_activity.

No Admin Labels on Relationships

Anti-pattern: Relationship with admin_label: ''.

Why bad: Views UI shows field name (uid) instead of purpose. Can't distinguish multiple relationships to same entity type.

Do instead: Always set admin_label: "author", "editor", "reviewer".

Duplicated Config Across Displays

Anti-pattern: Repeating same filter config in every display instead of using default display.

Why bad: Changing filter requires updating 5 displays. Easy to miss one, creates inconsistency.

Do instead: Put shared config in default display. Override only what differs in child displays.

See Also

  • Section 29: Best Practices & Patterns — what to do instead
  • Section 31: Security & Performance — security details