Configuration

Overview

Data Lens uses a two-tier configuration system:

1. Per-Panel Settings (Plugin Methods)

Configure these independently for each Filament panel:

DataLensPlugin::make()
    ->slug('reports')                    // Navigation (Filament 4.x only)
    ->navigationLabel('Reports')
    ->apiEnabled()                        // Features
    ->exportFormats(['csv', 'xlsx'])
    ->schedulingEnabled()
    ->modelDirectories(['app/Models'])   // Model discovery

Note: The ->slug() method is only available in Filament 4.x. For Filament 3.x, use the config file.

2. Global Settings (Config File)

Publish and configure system-wide settings:

php artisan vendor:publish --tag="data-lens-config"

This creates config/data-lens.php for:

  • Resource slug (Filament 3.x only - use 'slug' => 'custom-reports')
  • Security (IP whitelisting)
  • Performance (cache, queue settings)
  • Infrastructure (email, database)
  • Multi-tenancy

Multi-Tenancy Configuration

For comprehensive multi-tenant setup guidance, see the Multi-Tenant Setup Guide

Basic Tenant Settings

'tenant_aware' => false, // Enable for multi-tenant applications
'tenant_context' => [
    'key' => 'tenant_id', // Key used in execution context
],

Important: Enable tenant_aware before running migrations to ensure proper foreign key setup.

Tenant Foreign Key

'column_names' => [
    'tenant_foreign_key' => 'tenant_id', // Customize if needed
],

See the Multi-Tenant Setup Guide for step-by-step configuration, tenant resolution strategies, and troubleshooting.

API Configuration

Per-Panel API Settings

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

Global API Settings

// In config/data-lens.php
'api' => [
    'enabled' => false,  // Default if not set per-panel
    'path' => 'api/data-lens/reports',  // Default API path
    'middleware' => [DataLensApiAuth::class],  // Default middleware
    'hide_auth_ui' => false,  // Hide auth UI in admin panel
    'ip_whitelist' => null,  // Array of IPs or CIDR ranges, null/[] = allow all
],

IP Whitelist Behavior:

  • null or [] - Allow all IPs (no restrictions)
  • Array with IPs - Only listed IPs can access

Per-Panel API Routing

Customize API paths and authentication per panel for enterprise deployments:

// Custom API path
DataLensPlugin::make()
    ->apiPath('system/api/reports')

// Custom middleware stack
DataLensPlugin::make()
    ->apiMiddleware([
        'enterprise.auth',
        'throttle:api',
    ])

// Hide authentication UI (when managed externally)
DataLensPlugin::make()
    ->hideAuthenticationUI()

// Disable API routes entirely
DataLensPlugin::make()
    ->disableApiRoutes()

Enterprise Example:

// Admin panel with custom enterprise auth
Panel::make()
    ->id('admin')
    ->plugin(
        DataLensPlugin::make()
            ->apiPath('api/internal/reports')
            ->apiMiddleware(['enterprise.oauth', 'enterprise.audit'])
            ->hideAuthenticationUI()
    );

// Public panel with standard auth
Panel::make()
    ->id('public')
    ->plugin(
        DataLensPlugin::make()
            ->apiPath('api/public/reports')
            ->apiMiddleware(['throttle:10,1'])
    );

Resource Slug Configuration

The URL slug for custom reports is configured differently between Filament versions:

Filament 4.x (Per-Panel)

// Each panel can have its own slug
DataLensPlugin::make()
    ->slug('admin-reports')  // Admin panel uses /admin-reports
    
// Another panel
DataLensPlugin::make()
    ->slug('my-reports')     // User panel uses /my-reports

Filament 3.x (Global Only)

// In config/data-lens.php
'slug' => 'custom-reports',  // All panels use /custom-reports

Important: In Filament 3.x, all panels share the same slug because the resource uses static properties. This limitation is resolved in Filament 4.x.

User Model Configuration

'user_model' => App\Models\User::class,

Report Sharing Configuration

Share reports with other users in your application. When a report is shared, users receive both in-app and email notifications.

Basic Sharing

// In config/data-lens.php
'features' => [
    'sharing' => [
        'user_sharing_enabled' => true,

        'user_selection' => [
            'filter_query' => null,
            'display_formatter' => null,
            'searchable_columns' => ['name', 'email'],
            'skip_auto_tenant_scope' => false,
        ],

        'notifications' => [
            'database' => true,
            'email' => true,
        ],
    ],
],

Custom User Filtering

Filter which users appear in the sharing dropdown. Use a class for config:cache compatibility:

// app/DataLens/ActiveUserFilter.php
namespace App\DataLens;

use Illuminate\Database\Eloquent\Builder;
use Padmission\DataLens\Contracts\UserFilterContract;
use Padmission\DataLens\Models\CustomReport;

class ActiveUserFilter implements UserFilterContract
{
    public function __invoke(Builder $query, ?CustomReport $report): Builder
    {
        return $query->where('is_active', true);
    }
}

// In config/data-lens.php
'filter_query' => \App\DataLens\ActiveUserFilter::class,

Custom User Display

Customize how users appear in the dropdown:

// app/DataLens/UserDisplayFormatter.php
namespace App\DataLens;

use Illuminate\Database\Eloquent\Model;
use Padmission\DataLens\Contracts\UserDisplayFormatterContract;

class UserDisplayFormatter implements UserDisplayFormatterContract
{
    public function __invoke(Model $user): string
    {
        return "{$user->name} ({$user->department}) - {$user->email}";
    }
}

// In config/data-lens.php
'display_formatter' => \App\DataLens\UserDisplayFormatter::class,

Pivot Table Tenant Relationships

When users relate to tenants via a pivot table (not a direct foreign key), set skip_auto_tenant_scope to true and handle tenant filtering in your filter_query:

// app/DataLens/TeamUserFilter.php
namespace App\DataLens;

use Filament\Facades\Filament;
use Illuminate\Database\Eloquent\Builder;
use Padmission\DataLens\Contracts\UserFilterContract;
use Padmission\DataLens\Models\CustomReport;

class TeamUserFilter implements UserFilterContract
{
    public function __invoke(Builder $query, ?CustomReport $report): Builder
    {
        $tenantId = Filament::getTenant()?->id;

        if ($tenantId) {
            $query->whereHas('teams', fn ($q) => $q->where('teams.id', $tenantId));
        }

        return $query;
    }
}

// In config/data-lens.php
'user_selection' => [
    'filter_query' => \App\DataLens\TeamUserFilter::class,
    'skip_auto_tenant_scope' => true,
],

Notification Channels

Control how users are notified when reports are shared:

'notifications' => [
    'database' => true,  // In-app notifications
    'email' => true,     // Email notifications
],

Model Directories Configuration

Type: Per-Panel Setting

Configure multiple model directories independently for each panel:

// AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            DataLensPlugin::make()
                ->modelDirectories([
                    'app/Models',
                    'app/Admin/Models',
                ]),
        ]);
}

// UserPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            DataLensPlugin::make()
                ->modelDirectories([
                    'app/Models',
                    'app/User/Models',
                ]),
        ]);
}

Third-Party Integrations

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

Relationship Configuration

Eligible Relationships

'eligible_relationships' => [
    BelongsTo::class,
    HasOne::class,
    HasOneThrough::class,
    MorphOne::class,
    HasMany::class,
    HasManyThrough::class,
    BelongsToMany::class,
],

Exclusions

'excluded_relationships' => [
    // 'tenant', // Add relationship names to exclude
],

'excluded_models' => [
    // \App\Models\Tenant::class,
],

Export Configuration

Per-Panel Export Settings

// Configure via plugin methods
DataLensPlugin::make()
    ->exportsEnabled()              // Enable/disable exports
    ->exportFormats(['csv', 'xlsx']) // Available formats
    ->defaultExportFormat('xlsx')    // Default format
    ->useTimestampsInFilename(false) // Timestamps in filenames

Global Export Settings

// In config/data-lens.php
'exports' => [
    'enabled' => true,  // Default if not set per-panel
    'formats' => ['csv', 'xlsx'],  // Default if not set per-panel
    'default_format' => 'csv',  // Default if not set per-panel
    'chunk_size' => env('DATA_LENS_EXPORT_CHUNK_SIZE', 2000),  // Global only
    'timeout' => env('DATA_LENS_EXPORT_TIMEOUT', 600),  // Global only
    'use_timestamps_in_filename' => true,  // Default if not set per-panel
    'template' => 'report_{report_name}_{date}',  // Global only
    'queue' => env('DATA_LENS_QUEUE'),  // Global only
    'should_queue' => env('DATA_LENS_EXPORT_QUEUE', true),  // Global only
],

Filename Templates

'filename_templates' => [
    // Manual exports: {report_name}, {report_id}, {date}, {time}
    'exports' => 'report_{report_name}_{date}',
    
    // Scheduled: {report_name}, {report_id}, {date}, {time}, {schedule_name}, {schedule_id}
    'scheduling' => 'report_{report_name}_{date}',
],

Scheduling Configuration

Per-Panel Scheduling

// Enable/disable scheduling per panel
DataLensPlugin::make()
    ->schedulingEnabled()  // Enable for this panel

Global Scheduling Settings

// In config/data-lens.php - all settings are global
'scheduling' => [
    'enabled' => false,  // Default if not set per-panel
    'from_email' => env('MAIL_FROM_ADDRESS'),  // Global only
    'from_name' => env('MAIL_FROM_NAME'),  // Global only
    'max_attachment_size' => 1024, // KB - Global only
    'check_interval' => 1, // minutes - Global only
    'max_runtime' => 300, // seconds - Global only
    'retry_attempts' => 3,  // Global only
    'retry_delay' => 5, // minutes - Global only
    'history_retention_days' => 30,  // Global only
    'max_recipients_per_schedule' => 50,  // Global only
    'download_link_expiry_days' => 7,  // Global only
    'queue' => env('DATA_LENS_QUEUE'),  // Global only
],

Column Security

Excluded Columns

'excluded_columns' => [
    'global' => [
        'password',
        'remember_token',
        'deleted_at',
        // 'api_token',
        // 'two_factor_secret',
    ],
    'models' => [
        // \App\Models\User::class => ['social_security_number'],
    ],
],

Auto-Detection Command

# Scan all models for sensitive columns
php artisan data-lens:suggest-excluded-columns

# Get copy-pastable config
php artisan data-lens:suggest-excluded-columns --format=config

# Check specific model
php artisan data-lens:suggest-excluded-columns --model="App\Models\User"

Column Type Detection

'column_type_detection' => [
    'money_field_patterns' => [
        'price', 'cost', 'amount', 'balance', 'fee', 'payment',
        'salary', 'total', 'budget', 'revenue', 'income', 'expense', 'tax',
    ],
    'boolean_field_patterns' => [
        'is_', 'has_', 'can_', 'should_', 'active', 'enabled', 'approved',
    ],
],

Cache Configuration

'cache' => [
    'enabled' => env('DATA_LENS_CACHE_ENABLED', true),
    'force_in_local' => env('DATA_LENS_CACHE_FORCE_IN_LOCAL', false),
    'ttl' => [
        'relationship_class' => env('DATA_LENS_CACHE_RELATIONSHIP_CLASS_TTL', 21600), // 6 hours
        'relationship_type' => env('DATA_LENS_CACHE_RELATIONSHIP_TYPE_TTL', 21600),
        'model_relationships' => env('DATA_LENS_CACHE_MODEL_RELATIONSHIPS_TTL', 21600),
        'model_fields' => env('DATA_LENS_CACHE_MODEL_FIELDS_TTL', 21600),
        'filter_type' => env('DATA_LENS_CACHE_FILTER_TYPE_TTL', 21600),
    ],
    'prefix' => env('DATA_LENS_CACHE_PREFIX', 'data_lens'),
    'tenant_resolver' => null, // Custom tenant resolver callback
],

Cache Management

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

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

# Skip confirmation
php artisan data-lens:clear-cache --force

Through Relationships

'through_relationships' => [
    'max_depth' => env('DATA_LENS_THROUGH_MAX_DEPTH', 3),
    'performance_threshold_ms' => env('DATA_LENS_THROUGH_PERFORMANCE_THRESHOLD', 1000),
    'auto_index_suggestion' => env('DATA_LENS_THROUGH_AUTO_INDEX', true),
    'cache_ttl' => env('DATA_LENS_THROUGH_CACHE_TTL', 43200), // 12 hours
    'optimize_queries' => env('DATA_LENS_THROUGH_OPTIMIZE_QUERIES', true),
],

Database Tables

'table_names' => [
    'custom_reports' => 'custom_reports',
    'custom_report_user' => 'custom_report_user',
    'custom_report_schedules' => 'custom_report_schedules',
    'custom_report_schedule_history' => 'custom_report_schedule_history',
    'custom_report_schedule_recipients' => 'custom_report_schedule_recipients',
],

Storage Configuration

'storage_disk' => env('FILAMENT_FILESYSTEM_DISK', 'local'),

Timezone Configuration

'timezone' => [
    'default' => env('DATA_LENS_TIMEZONE', 'UTC'),
],

Custom Timezone Resolver

use Padmission\DataLens\DataLens;

// In AppServiceProvider boot method
DataLens::setTimezoneResolver(function () {
    return tenant()?->timezone ?? config('app.timezone');
});

Custom Mailable Classes

'mailable_classes' => [
    'report_email' => Padmission\DataLens\Mail\ReportEmail::class,
],

Programmatic Configuration

Custom Models

use Padmission\DataLens\DataLens;

// In AppServiceProvider boot method
DataLens::useCustomReportModel(CustomReport::class);
DataLens::useTenantModel(Tenant::class);

Table Configuration

DataLens::configureTableUsing(function (Table $table) {
    return $table
        ->paginated([10, 25, 50, 100])
        ->striped();
});