Advanced Features

Complex Aggregate Filtering

Build sophisticated aggregate conditions using the visual filter builder:

Filter Groups

Create multiple filter groups with AND/OR logic:

  • Group filters logically
  • Nest conditions within groups
  • Visual organization with operator display

Relationship Filters in Aggregates

Filter aggregates based on related model conditions:

COUNT(orders) WHERE orders.status = 'completed' 
  AND orders.customer.country = 'US'

Through Relationships

Data Lens provides optimized support for HasOneThrough and HasManyThrough relationships.

Performance Optimization

// config/data-lens.php
'through_relationships' => [
    'max_depth' => 3, // Maximum chain depth
    'performance_threshold_ms' => 1000, // Log slow queries
    'auto_index_suggestion' => true, // Suggest indexes
    'optimize_queries' => true, // Enable optimizations
],

Automatic Index Suggestions

When enabled, Data Lens logs index recommendations:

[Data Lens] Suggested index: orders (customer_id, created_at)

Cache Management

Intelligent Caching

Data Lens caches expensive operations:

  • Model introspection results
  • Relationship metadata
  • Filter type detection
  • Through relationship chains

Multi-Tenant Cache Isolation

šŸ“– For complete multi-tenant setup, see the Multi-Tenant Setup Guide

Automatic cache isolation per tenant prevents data leakage:

// config/data-lens.php
'cache' => [
    'tenant_resolver' => function () {
        return auth()->user()?->company_id;
    },
],

Cache Clearing Strategies

# Clear all cache
php artisan data-lens:clear-cache

# Clear specific type
php artisan data-lens:clear-cache --type=model_fields

# In deployment scripts
php artisan data-lens:clear-cache --force

Column Type Detection

Data Lens automatically detects appropriate column types based on patterns:

Money Fields

Automatically formatted when field names contain:

  • price, cost, amount, balance
  • fee, payment, salary, total
  • budget, revenue, income, expense, tax

Boolean Fields

Automatically rendered as badges when field names:

  • Start with: is_, has_, can_, should_
  • Contain: active, enabled, approved

Custom Patterns

// config/data-lens.php
'column_type_detection' => [
    'money_field_patterns' => ['custom_price_field'],
    'boolean_field_patterns' => ['custom_flag'],
],

Model Labels

Data Lens reads display labels directly from your Filament resources. If a CustomerResource overrides getModelLabel() to return a translated string, that label appears in the model picker in the report builder, the "Data Model" column in the report list, and the "Data Model" entry on the report view page.

No configuration is required — this is automatic for any Filament resource registered in the active panel.

How resolution works

  1. The active Filament panel's registered resources are scanned.
  2. The first resource whose $model matches the report's data model wins.
  3. That resource's getModelLabel() (or getPluralModelLabel() for plurals) is called.
  4. If no resource matches, Data Lens falls back to humanizing the class basename — App\Models\OrderItem becomes Order Item.

Caching

Resolved labels are cached using the existing model_fields cache. The default TTL is six hours and is configurable:

// config/data-lens.php
'cache' => [
    'ttl' => [
        'model_fields' => 21600, // 6 hours
    ],
],

When no panel is resolvable (e.g. inside a queued job that runs before any panel context is established), labels are not cached, so a later request inside a panel can still resolve a resource override.

Example: translated singular and plural

namespace App\Filament\Resources;

use Filament\Resources\Resource;

class CustomerResource extends Resource
{
    protected static ?string $model = \App\Models\Customer::class;

    public static function getModelLabel(): string
    {
        return __('models.customer.singular'); // "Cliente"
    }

    public static function getPluralModelLabel(): string
    {
        return __('models.customer.plural'); // "Clientes"
    }
}

The report-builder model picker will list "Cliente"; the report list and view page will mirror it.

Verifying it works

In Tinker:

\Padmission\DataLens\Support\ModelLabelResolver::singular(\App\Models\Customer::class);
// => "Cliente"

To clear the cache during development:

php artisan data-lens:clear-cache

Polymorphic (MorphTo) Relationships

Data Lens supports polymorphic relationships (MorphTo) on host models, letting users pick fields from any concrete target type the relation can point to. The feature is opt-in per model.

Declaring morph targets

Add the HasDataLensMorphTargets trait to your host model and declare the concrete target classes for each polymorphic relation:

use Padmission\DataLens\Concerns\HasDataLensMorphTargets;

class Transaction extends Model
{
    use HasDataLensMorphTargets;

    public function payeeable(): \Illuminate\Database\Eloquent\Relations\MorphTo
    {
        return $this->morphTo('payeeable');
    }

    public static function dataLensMorphTargets(): array
    {
        return [
            'payeeable' => [
                \App\Models\Agency::class,
                \App\Models\Vendor::class,
                \App\Models\PropertyOwner::class,
            ],
        ];
    }
}

The trait declaration is the security boundary: a MorphTo without it stays hidden from the report builder.

How users see it

When a model declares MorphTo targets, the relationship dropdown in the report builder shows one entry per declared branch — for example payeeable → Agency, payeeable → Vendor, payeeable → PropertyOwner. Users pick the branch they want; the field list then populates from that branch's columns.

Reports can select fields from multiple branches in the same view. Each row populates exactly one branch column (the one matching that row's stored *_type value); the others are NULL.

Filtering by morph type

Data Lens auto-exposes a synthetic {relation}_type field per declared MorphTo (e.g. payeeable_type for the example above). Use it as a filter to scope a report to one or more concrete types, or as an output column to display the human label of each row's type.

Filter semantics

When a filter targets a branch field (e.g. payeeable → Agency → name IS NULL), Data Lens routes the query through whereHasMorph so the type predicate is implicit. IS NULL correctly means "the payeeable is an Agency with a null name" — not "the payeeable is anything other than an Agency".

Scope notes

  • The MorphTo branch picker uses Eloquent's eager-loading + per-row traversal; no extra joins are added to the base query.
  • Aggregating over a morph branch (e.g. "count agencies-paid") is not supported — the feature is for column selection and filtering only.
  • Deeper traversal beyond a morph branch (e.g. payeeable → Agency → users → country) is supported by the path walker but not yet surfaced in the deep-relationship configuration modal.

Calculated Columns

Calculated columns are user-definable through the report builder UI — no code changes required. The form has three kinds:

  • Simple math between two operands (column or constant): +, -, *, /, %
  • Aggregate functions over a relationship: COUNT, SUM, AVG, MIN, MAX, COUNT DISTINCT
  • Date arithmetic on date or datetime columns: add a duration, subtract a duration, or compute the difference between two dates

Operands can mix database columns, constants, and other calculated columns; expressions are evaluated by the database during the report query.

Date arithmetic operations

| Operation | Operands | Result | |---|---|---| | DATE_ADD | date_column + N unit | New date shifted forward | | DATE_SUBTRACT | date_column āˆ’ N unit | New date shifted backward | | DATE_DIFF | date_column ↔ date_column (unit) | Integer count of unit between the two dates |

Supported units: days, weeks, months, quarters, years.

Example: an order's expected delivery date is seven days after it shipped. Add a calculated column with:

  • Left operand: shipped_at (column)
  • Operation: DATE_ADD
  • Right operand: 7 (constant)
  • Unit: days

The report query selects the computed timestamp alongside your other columns.

Database support

Date arithmetic is compiled per connection at query time:

  • MySQL / MariaDB — native DATE_ADD / DATE_SUB / TIMESTAMPDIFF
  • PostgreSQL — interval arithmetic (col + (N || ' unit')::interval) plus AGE() / EXTRACT() for differences
  • SQLite — datetime(col, '+N units') plus julianday() and strftime() for differences

The dialect is detected from the active database connection — no configuration is required, and reports created against one engine work unchanged when the connection is switched. Postgres has no QUARTER interval, so quarter arithmetic is internally rewritten to 3 months.

NULL handling

When any operand is NULL (for example, an order without a shipped_at), the calculated value is also NULL and the column renders empty. This holds across all three database engines.

Limitations

  • Filtering and sorting on a calculated date column use the column alias; indexes on the source date columns won't be used for that filter. For large datasets, prefer pre-filtering with a regular date filter on the source column.
  • Quarters on Postgres behave as 3 months. For workloads where calendar-quarter semantics matter (e.g. fiscal-year boundaries), prefer expressing the report in months.

Advanced Formatting

Badge Columns

Use the visual formatting interface to create badge columns:

  • Select badge style in the format configuration modal
  • Choose colors based on field values (success, warning, danger, etc.)
  • Configure color mappings for different states

Custom Icons

Add Heroicons to enhance visual communication:

  • Position before or after text
  • Independent color configuration
  • Full Heroicon library support

HTML Content

Safely render HTML content:

  • Automatic sanitization
  • Preserves safe formatting
  • Prevents XSS attacks

API Advanced Usage

IP Whitelisting

Restrict API access to specific IPs (global configuration):

// Enable API per panel
DataLensPlugin::make()
    ->apiEnabled()

// Configure IP whitelist globally in config/data-lens.php
'api' => [
    'ip_whitelist' => [
        '192.168.1.0/24',  // Subnet
        '10.0.0.50',       // Single IP
    ],
],

Scheduling Advanced Features

Complex Recurrence Rules

Data Lens uses RFC 5545 compliant RRULE for complex schedules:

  • Every weekday at 9 AM
  • First and third Monday of each month
  • Last Friday of each quarter
  • Custom RRULE strings

Timezone Handling

Configure timezone resolution for your application:

// In AppServiceProvider boot method
use Padmission\DataLens\DataLens;

DataLens::setTimezoneResolver(function () {
    return auth()->user()?->timezone ?? config('app.timezone');
});

Email Customization

Create custom mail classes for advanced features:

class TenantAwareReportEmail extends ReportEmail
{
    public function build()
    {
        // Add tenant-specific headers
        $this->withSymfonyMessage(function ($message) {
            $message->getHeaders()->addTextHeader(
                'X-Tenant-ID', 
                $this->schedule->customReport->tenant_id
            );
        });
        
        return parent::build();
    }
}

Export Optimizations

Streaming CSV Driver

For CSV exports, Data Lens uses a high-performance streaming driver by default that bypasses PhpSpreadsheet:

// In config/data-lens.php
'exports' => [
    'csv_driver' => 'streaming', // or 'laravel-excel'
],

Streaming driver benefits:

  • O(n) performance vs O(n²) with PhpSpreadsheet
  • Uses native fputcsv() with cursor-based iteration
  • Minimal memory footprint regardless of export size
  • Memory-efficient storage: uses rename() for local disks (zero memory copy), streams for S3/remote disks

When to use laravel-excel:

  • You need advanced cell formatting
  • You require formula support
  • You're using custom Laravel Excel features

Chunked Processing

Configure chunk size globally for large exports:

// In config/data-lens.php
'exports' => [
    'chunk_size' => 5000, // Process 5000 rows at a time
],

Memory Management

Exports use streaming to minimize memory usage:

  • Processes data in chunks via cursor iteration
  • Streams directly to file without loading into memory
  • Automatic cleanup of temporary files
  • Detects local vs remote storage for optimal file handling

Security Features

Column Security

Automatically exclude sensitive columns:

php artisan data-lens:suggest-excluded-columns --format=config

Output categorized by risk level:

  • HIGH: Passwords, tokens, keys
  • MEDIUM: Personal info, financial data
  • LOW: Tracking, configuration

Model Exclusions

Prevent entire models from appearing in reports:

'excluded_models' => [
    \App\Models\AdminUser::class,
    \App\Models\SystemConfig::class,
],

Performance Monitoring

Query Performance Tracking

Log slow queries for optimization:

'through_relationships' => [
    'performance_threshold_ms' => 500, // Log queries over 500ms
],

Export Performance

Monitor export processing times:

  • Tracked in schedule history
  • Available via API metadata
  • Logged for analysis

Integration Features

Data Lens integrates with popular Filament packages:

'integrations' => [
    'custom_fields' => true,    // Relaticle Custom Fields
    'advanced_tables' => true,  // Advanced Tables
],

Custom Fields

  • Rich field types (text, select, date, file upload, etc.)
  • Multi-tenancy field configuration
  • Built-in validation framework

Advanced Tables

  • User-customizable saved views
  • Advanced filter builder
  • Column reordering and persistence

Debugging Tools

Debug Mode

Enable detailed logging:

php artisan data-lens:check-scheduled-reports --debug

Cache Inspection

View cache contents for debugging:

// In tinker or debug code
app(\Padmission\DataLens\Services\CacheManager::class)
    ->getCacheKey('model_fields', 'App\Models\User');