Settings System

Complete guide to extending the LaraDashboard settings page with custom tabs, sections, and integrating with the settings API.

Settings System

LaraDashboard provides a powerful, extensible settings system with a tab-based UI, hook-driven customization, and a simple key-value API for storing configuration. Modules can add their own tabs and sections without modifying core code.

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    Settings Page                             │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Tabs (filter: SETTINGS_TABS)                        │   │
│  │  ┌─────────┬────────────┬─────────┬─────────┬─────┐ │   │
│  │  │ General │ Appearance │ Content │ Integr. │ Sec │ │   │
│  │  └─────────┴────────────┴─────────┴─────────┴─────┘ │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Tab Content                                         │   │
│  │  ┌─────────────────────────────────────────────┐    │   │
│  │  │  Section 1 (BEFORE_SECTION_START hook)      │    │   │
│  │  ├─────────────────────────────────────────────┤    │   │
│  │  │  Default Section Content                     │    │   │
│  │  ├─────────────────────────────────────────────┤    │   │
│  │  │  Section 2 (BEFORE_SECTION_END hook)        │    │   │
│  │  └─────────────────────────────────────────────┘    │   │
│  │  ┌─────────────────────────────────────────────┐    │   │
│  │  │  Custom Section (AFTER_SECTION_END hook)    │    │   │
│  │  └─────────────────────────────────────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Default Settings Tabs

LaraDashboard includes 5 default tabs:

Tab Key Title Icon Purpose
general General Settings lucide:settings Site name, logos, favicon
appearance Site Appearance lucide:palette Theme colors, dark mode
content Content Settings lucide:file-text Pagination, content options
integrations Integrations lucide:plug Google Analytics, third-party
performance-security Security lucide:shield Auth settings, reCAPTCHA

Adding Custom Tabs

Use the SETTINGS_TABS filter hook to add new tabs from your module.

Step 1: Create a Settings Hook Service

<?php

declare(strict_types=1);

namespace Modules\YourModule\Services;

use App\Enums\Hooks\SettingFilterHook;
use App\Support\Facades\Hook;

class SettingsHookService
{
    /**
     * Register all settings hooks.
     */
    public function register(): void
    {
        // Add custom tab
        Hook::addFilter(
            SettingFilterHook::SETTINGS_TABS,
            [$this, 'addSettingsTab']
        );
    }

    /**
     * Add a new settings tab.
     *
     * @param array $tabs Existing tabs
     * @return array Modified tabs
     */
    public function addSettingsTab(array $tabs): array
    {
        $tabs['your-module'] = [
            'title' => __('Your Module'),
            'icon' => 'lucide:box',
            'view' => 'your-module::settings.tab',
        ];

        return $tabs;
    }
}

Step 2: Create the Tab View

Create modules/YourModule/resources/views/settings/tab.blade.php:

<x-card>
    <x-slot name="header">
        {{ __('Your Module Settings') }}
    </x-slot>

    <div class="space-y-6">
        <x-inputs.input
            label="{{ __('API Key') }}"
            name="your_module_api_key"
            :value="config('settings.your_module_api_key')"
            placeholder="{{ __('Enter your API key') }}"
        />

        <x-inputs.checkbox
            label="{{ __('Enable Feature X') }}"
            name="your_module_feature_x"
            :checked="config('settings.your_module_feature_x')"
        />

        <x-inputs.select
            label="{{ __('Default Mode') }}"
            name="your_module_mode"
            :options="[
                'basic' => __('Basic'),
                'advanced' => __('Advanced'),
            ]"
            :value="config('settings.your_module_mode')"
        />
    </div>
</x-card>

Step 3: Register in Service Provider

<?php

namespace Modules\YourModule\Providers;

use Illuminate\Support\ServiceProvider;
use Modules\YourModule\Services\SettingsHookService;

class YourModuleServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->app->booted(function () {
            app(SettingsHookService::class)->register();
        });
    }

    public function register(): void
    {
        $this->app->singleton(SettingsHookService::class);
    }
}

Tab Configuration Options

Key Type Required Description
title string Yes Tab display name
icon string No Iconify icon identifier
view string Yes* Blade view path
content string No* Raw HTML content
data array No Data passed to view

*Either view or content is required.


Adding Sections to Existing Tabs

You can add sections to existing tabs without creating new ones using section hooks.

Available Section Hooks

Each tab has hooks for inserting content at specific positions:

Hook Enum Position
SETTINGS_*_TAB_BEFORE_SECTION_START Before first section
SETTINGS_*_TAB_BEFORE_SECTION_END Before section closes
SETTINGS_*_TAB_AFTER_SECTION_END After section ends

Replace * with: GENERAL, APPEARANCE, CONTENT, INTEGRATIONS, PERFORMANCE_SECURITY, AI_INTEGRATIONS, RECAPTCHA_INTEGRATIONS

Example: Add Section to General Tab

<?php

declare(strict_types=1);

namespace Modules\YourModule\Services;

use App\Enums\Hooks\SettingFilterHook;
use App\Support\Facades\Hook;

class SettingsHookService
{
    public function register(): void
    {
        // Add section after General tab content
        Hook::addFilter(
            SettingFilterHook::SETTINGS_GENERAL_TAB_BEFORE_SECTION_END,
            [$this, 'addGeneralSection']
        );
    }

    public function addGeneralSection(string $html): string
    {
        return $html . view('your-module::settings.general-section')->render();
    }
}

Section View Example

{{-- modules/YourModule/resources/views/settings/general-section.blade.php --}}

<div class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
    <h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
        {{ __('Your Module Options') }}
    </h4>

    <div class="space-y-4">
        <x-inputs.input
            label="{{ __('Custom Field') }}"
            name="your_module_custom_field"
            :value="config('settings.your_module_custom_field')"
        />
    </div>
</div>

Adding Multiple Sections

public function register(): void
{
    // Before section starts
    Hook::addFilter(
        SettingFilterHook::SETTINGS_APPEARANCE_TAB_BEFORE_SECTION_START,
        [$this, 'addAppearanceNotice']
    );

    // At section end
    Hook::addFilter(
        SettingFilterHook::SETTINGS_APPEARANCE_TAB_BEFORE_SECTION_END,
        [$this, 'addAppearanceOptions']
    );

    // After section ends (new card)
    Hook::addFilter(
        SettingFilterHook::SETTINGS_APPEARANCE_TAB_AFTER_SECTION_END,
        [$this, 'addAppearanceAdvanced']
    );
}

public function addAppearanceNotice(string $html): string
{
    return $html . '<x-alerts.info message="' . __('Custom theme options below.') . '" />';
}

public function addAppearanceOptions(string $html): string
{
    return $html . view('your-module::settings.appearance-options')->render();
}

public function addAppearanceAdvanced(string $html): string
{
    return $html . view('your-module::settings.appearance-advanced')->render();
}

Tab Menu Hooks

Insert content before/after tab menu items:

use App\Enums\Hooks\SettingFilterHook;

// Before specific tab menu item
Hook::addFilter(
    SettingFilterHook::SETTINGS_TAB_MENU_BEFORE->value . 'general',
    function (string $html) {
        return $html . '<li class="text-xs text-gray-400 px-4">Core</li>';
    }
);

// After specific tab menu item
Hook::addFilter(
    SettingFilterHook::SETTINGS_TAB_MENU_AFTER->value . 'integrations',
    function (string $html) {
        return $html . '<li class="border-l border-gray-300 dark:border-gray-600 mx-2"></li>';
    }
);

Tab Content Hooks

Insert content before/after tab content panels:

// Before tab content
Hook::addFilter(
    SettingFilterHook::SETTINGS_TAB_CONTENT_BEFORE->value . 'general',
    function (string $html) {
        return $html . '<x-alerts.warning message="' . __('Demo mode active.') . '" />';
    }
);

// After tab content
Hook::addFilter(
    SettingFilterHook::SETTINGS_TAB_CONTENT_AFTER->value . 'general',
    function (string $html) {
        return $html . '<div class="mt-4 text-sm text-gray-500">' . __('Last saved: ') . now() . '</div>';
    }
);

Complete Hook Reference

Filter Hooks (SettingFilterHook)

Hook Purpose
SETTINGS_TABS Add/modify settings tabs
SETTINGS_UPDATE_VALIDATION_RULES Modify validation rules
SETTINGS_RESTRICTED_FIELDS Add restricted field names
SETTINGS_AFTER_BREADCRUMBS Content after page breadcrumbs
SETTINGS_TAB_MENU_BEFORE_{key} Before tab menu item
SETTINGS_TAB_MENU_AFTER_{key} After tab menu item
SETTINGS_TAB_CONTENT_BEFORE_{key} Before tab content
SETTINGS_TAB_CONTENT_AFTER_{key} After tab content
SETTINGS_*_TAB_BEFORE_SECTION_START Before tab's first section
SETTINGS_*_TAB_BEFORE_SECTION_END Before tab's section ends
SETTINGS_*_TAB_AFTER_SECTION_END After tab's section ends

Action Hooks (SettingActionHook)

Hook Triggered Parameters
SETTINGS_SAVING_BEFORE Before batch save $settings array
SETTINGS_SAVED_AFTER After batch save $settings array
SETTING_CREATED_BEFORE Before single create $key, $value
SETTING_CREATED_AFTER After single create $setting model
SETTING_UPDATED_BEFORE Before single update $setting, $newValue
SETTING_UPDATED_AFTER After single update $setting, $oldValue
SETTING_DELETED_BEFORE Before delete $setting model
SETTING_DELETED_AFTER After delete $key string
SETTINGS_IMPORTING_BEFORE Before import $settings array
SETTINGS_IMPORTED_AFTER After import $settings array
SETTINGS_EXPORTING_BEFORE Before export -
SETTINGS_EXPORTED_AFTER After export $settings array
SETTINGS_CACHE_CLEARED Cache cleared -
SETTINGS_CACHE_REFRESHED Cache refreshed -

Using Action Hooks

use App\Enums\Hooks\SettingActionHook;
use App\Support\Facades\Hook;

// Clear cache when settings are saved
Hook::addAction(
    SettingActionHook::SETTINGS_SAVED_AFTER,
    function (array $settings) {
        Cache::tags(['your-module'])->flush();
    }
);

// Log setting changes
Hook::addAction(
    SettingActionHook::SETTING_UPDATED_AFTER,
    function ($setting, $oldValue) {
        Log::info("Setting {$setting->option_name} changed", [
            'old' => $oldValue,
            'new' => $setting->option_value,
        ]);
    }
);

Real-World Example: CRM Settings

Here's how the CRM module adds its settings:

CrmSettingsServiceProvider.php

<?php

declare(strict_types=1);

namespace Modules\Crm\Providers;

use App\Enums\Hooks\SettingFilterHook;
use App\Support\Facades\Hook;
use Illuminate\Support\ServiceProvider;

class CrmSettingsServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->registerSettingsTab();
    }

    protected function registerSettingsTab(): void
    {
        Hook::addFilter(
            SettingFilterHook::SETTINGS_TABS,
            function (array $tabs) {
                // Insert CRM tab after integrations
                $newTabs = [];
                foreach ($tabs as $key => $tab) {
                    $newTabs[$key] = $tab;
                    if ($key === 'integrations') {
                        $newTabs['crm'] = [
                            'title' => __('CRM'),
                            'icon' => 'lucide:headphones',
                            'view' => 'crm::settings.crm-tab',
                        ];
                    }
                }
                return $newTabs;
            }
        );
    }
}

Form Field Naming Convention

Settings are stored using the name attribute as the key:

{{-- These become settings with keys: --}}
<input name="site_name" />           {{-- site_name --}}
<input name="your_module_api_key" /> {{-- your_module_api_key --}}
<input name="crm_default_currency" /> {{-- crm_default_currency --}}

Accessing Settings

// In PHP
$siteName = config('settings.site_name');
$apiKey = config('settings.your_module_api_key');

// Using helper
$siteName = get_setting('site_name');
$apiKey = get_setting('your_module_api_key', 'default-value');
{{-- In Blade --}}
{{ config('settings.site_name') }}
{{ get_setting('your_module_api_key') }}

Best Practices

  1. Prefix your settings - Use your_module_ prefix to avoid conflicts
  2. Use descriptive names - crm_default_currency not currency
  3. Provide defaults - Always handle missing settings gracefully
  4. Group related settings - Keep related options together in sections
  5. Use appropriate inputs - Match input type to setting type
  6. Add validation - Use SETTINGS_UPDATE_VALIDATION_RULES hook
  7. Clear caches - Listen to save hooks to clear related caches
  8. Document settings - List your module's settings in documentation

Troubleshooting

Tab Not Appearing

  1. Check hook is registered in boot() method
  2. Ensure $this->app->booted() wrapper is used
  3. Verify view path is correct (module::path.to.view)
  4. Check for PHP/Blade syntax errors in view

Settings Not Saving

  1. Verify input name attribute is set
  2. Check field isn't in restricted fields list
  3. Ensure form has @csrf token
  4. Look for validation errors

Section Not Displaying

  1. Confirm hook enum case is correct
  2. Verify return includes original $html parameter
  3. Check view rendering doesn't throw errors

Next Steps

/