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
- Prefix your settings - Use
your_module_prefix to avoid conflicts - Use descriptive names -
crm_default_currencynotcurrency - Provide defaults - Always handle missing settings gracefully
- Group related settings - Keep related options together in sections
- Use appropriate inputs - Match input type to setting type
- Add validation - Use
SETTINGS_UPDATE_VALIDATION_RULEShook - Clear caches - Listen to save hooks to clear related caches
- Document settings - List your module's settings in documentation
Troubleshooting
Tab Not Appearing
- Check hook is registered in
boot()method - Ensure
$this->app->booted()wrapper is used - Verify view path is correct (
module::path.to.view) - Check for PHP/Blade syntax errors in view
Settings Not Saving
- Verify input
nameattribute is set - Check field isn't in restricted fields list
- Ensure form has
@csrftoken - Look for validation errors
Section Not Displaying
- Confirm hook enum case is correct
- Verify return includes original
$htmlparameter - Check view rendering doesn't throw errors
Next Steps
- Settings API - Low-level settings CRUD operations
- Hooks Reference - Complete hooks documentation
- Module Development - Building modules