PHPackages                             oulaa/settings - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Admin Panels](/categories/admin)
4. /
5. oulaa/settings

ActiveLibrary[Admin Panels](/categories/admin)

oulaa/settings
==============

A composable settings toolkit for Laravel with Vue 3 components

v1.0.0(3mo ago)01↓90%MITPHPPHP &gt;=8.4

Since Mar 25Pushed 3mo agoCompare

[ Source](https://github.com/ovp87/settings)[ Packagist](https://packagist.org/packages/oulaa/settings)[ RSS](/packages/oulaa-settings/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (1)Dependencies (7)Versions (2)Used By (0)

oulaa/settings
==============

[](#oulaasettings)

Define settings in PHP, store them in the database, and render them as Vue components — automatically.

```
$service->make(
    new Boolean(key: 'notifications', title: 'Email notifications', defaultValue: true, ...),
    new Text(key: 'site_name', title: 'Site name', defaultValue: 'My App', ...),
    new Color(key: 'brand_color', title: 'Brand color', defaultValue: '#4f46e5', ...),
);
```

Each setting handles its own validation, persistence, and frontend UI. You define it once in PHP and get a working settings page with zero frontend code.

---

Quick Start
-----------

[](#quick-start)

### 1. Install

[](#1-install)

> Requires PHP 8.4+ and Laravel 12+.

```
composer require oulaa/settings
php artisan migrate
php artisan vendor:publish --tag=oulaa-settings-vue-components
```

Migrations are loaded automatically. To customize them, publish first:

```
php artisan vendor:publish --tag=oulaa-settings-migrations
```

**Using the Image type?** It requires [cropperjs](https://www.npmjs.com/package/cropperjs) for client-side cropping and must be registered separately — see [Image](#image) below.

### 2. Define your settings

[](#2-define-your-settings)

Create a helper that returns your settings. You'll reference this from your routes:

```
use Oulaa\Settings\Boolean;
use Oulaa\Settings\Text;
use Oulaa\Settings\Enum;
use Oulaa\Settings\EnumOption;

function mySettings(): array
{
    return [
        new Boolean(
            key: 'maintenance_mode',
            title: 'Maintenance mode',
            description: 'Prevent all non-admin access.',
            defaultValue: false,
            editable: true,
            isSensitive: false,
        ),
        new Text(
            key: 'site_name',
            title: 'Site name',
            description: null,
            defaultValue: 'My App',
            editable: true,
            isSensitive: false,
            placeholder: 'Enter your site name',
        ),
        new Enum(
            key: 'theme',
            title: 'Theme',
            description: 'Choose a colour scheme',
            defaultValue: null,
            editable: true,
            isSensitive: false,
            options: [
                new EnumOption('light', 'Light', null),
                new EnumOption('dark', 'Dark', null),
            ],
        ),
    ];
}
```

### 3. Create the routes

[](#3-create-the-routes)

```
use Oulaa\Settings\SettingsService;

Route::get('/settings', function (SettingsService $service) {
    return inertia('Settings')->with(
        $service->make(...mySettings())->toUnsafeArray()
    );
});

Route::post('/settings', function (Request $request, SettingsService $service) {
    $collection = $service->make(...mySettings())
        ->parseRequest($request);

    if (! $collection->hasErrors()) {
        $collection->save();
    }

    return response()->json(
        $collection->toUnsafeArray(),
        $collection->hasErrors() ? 422 : 200
    );
});
```

### 4. Render the page

[](#4-render-the-page)

```

import { usePage } from '@inertiajs/vue3';
import { SettingsComponent } from './vendor/oulaa/settings/components.js';

const page = usePage();

```

That's it. You have a working settings page with validation, persistence, and interactive controls.

---

Reading Settings
----------------

[](#reading-settings)

Use the `setting()` helper to read values anywhere in your app — middleware, controllers, blade views, queued jobs:

```
setting('site_name');              // stored value or null
setting('site_name', 'My App');    // stored value or 'My App'
setting('maintenance_mode', false);
```

Or inject the repository directly:

```
use Oulaa\Settings\Repositories\SettingRepository;

$repo = app(SettingRepository::class);
$repo->get('site_name', 'My App');
```

**A note on defaults:** The `setting()` helper reads raw values from the database. It does not know about the `defaultValue` you set on your Setting objects — those defaults only apply on the settings page. If a setting has never been saved, `setting('key')` returns `null`. Always pass the default explicitly at the call site:

```
// Good — default is visible where it's used
$limit = setting('max_upload', 10);

// Risky — returns null if never saved, even if your IntRange has defaultValue: 10
$limit = setting('max_upload');
```

---

Organising with Groups
----------------------

[](#organising-with-groups)

Wrap related settings in groups to give the page visual structure:

```
use Oulaa\Settings\Group;

$service->make(
    Group::make('general', 'General', 'Basic site configuration')
        ->collapsible()
        ->settings(
            new Boolean(key: 'maintenance_mode', ...),
            new Text(key: 'site_name', ...),
        ),

    Group::make('appearance', 'Appearance')
        ->settings(
            new Color(key: 'brand_color', ...),
            new Enum(key: 'theme', ...),
        ),

    // Ungrouped settings work alongside groups
    new Boolean(key: 'analytics_enabled', ...),
);
```

Groups render as bordered `` sections. Use `->collapsible()` to let users collapse them, or `->collapsible(collapsed: true)` to start collapsed. Groups can be nested.

---

Showing and Hiding Settings
---------------------------

[](#showing-and-hiding-settings)

Make settings appear or disappear based on other settings:

```
new Boolean(key: 'enable_smtp', title: 'Use custom SMTP', ...),

(new Text(key: 'smtp_host', title: 'SMTP Host', ...))
    ->visibleWhen('enable_smtp'),

(new Text(key: 'smtp_port', title: 'SMTP Port', ...))
    ->visibleWhen('enable_smtp'),
```

Toggle "Use custom SMTP" and the host/port fields appear instantly with a smooth animation — no page reload. On the backend, hidden settings skip validation and save, so a required rule on a hidden field won't block the form.

More examples:

```
// Show when an enum matches a specific value
(new Text(key: 'api_key', ...))->visibleWhen('provider', 'sendgrid'),

// Show when an enum matches any of several values
(new Text(key: 'webhook_url', ...))->visibleWhen('provider', ['sendgrid', 'mailgun']),

// Hide when something is true (inverse)
(new Text(key: 'basic_view', ...))->hiddenWhen('advanced_mode'),

// Show when an array setting (MultiSelect, Tags) contains a specific item
use Oulaa\Settings\Condition;
(new Text(key: 'slack_webhook', ...))->when(Condition::contains('channels', 'slack')),
```

If all settings in a group are hidden, the group itself disappears too.

---

Available Setting Types
-----------------------

[](#available-setting-types)

Every setting type shares these parameters:

ParameterTypeDescription`key``string`Unique identifier, used as database key and field name.`title``string`Human-readable label.`description``?string`Helper text below the field.`defaultValue`variesFallback when no stored value exists.`editable``bool``false` makes it read-only.`isSensitive``bool``true` redacts the value in `toArray()` responses.`rules``array`Laravel validation rules.### Boolean

[](#boolean)

Toggle switch. Stored as `true`/`false`.

```
new Boolean(key: 'notifications', title: 'Email notifications', defaultValue: false, ...)
```

If the key is absent from a form submission (as happens with HTML checkboxes when unchecked), Boolean automatically sets the value to `false`.

### Text

[](#text)

Single-line text input.

```
new Text(key: 'site_name', title: 'Site name', defaultValue: 'My App', placeholder: 'Enter name', maxLength: 100, ...)
```

Extra: `placeholder`, `maxLength`. Default rules: `['nullable', 'string', 'max:255']`.

### TextArea

[](#textarea)

Multi-line text input.

```
new TextArea(key: 'bio', title: 'About', defaultValue: null, placeholder: '...', rows: 6, maxLength: 500, ...)
```

Extra: `placeholder`, `rows`, `maxLength`. Default rules: `['nullable', 'string']`.

### Secret

[](#secret)

Masked input for API keys and tokens. The frontend renders a password field with a reveal toggle.

```
new Secret(key: 'api_key', title: 'API Key', defaultValue: null, isSensitive: true, placeholder: 'sk-...', ...)
```

Extra: `placeholder`. Default rules: `['nullable', 'string', 'max:255']`.

When `isSensitive: true` and the frontend sends `null` (because the value was redacted), `Secret` ignores the update and keeps the existing stored value. This prevents accidental overwrites — the frontend never sees the real value, so it can't send it back.

To clear a secret, the user must type an empty string into the field and save. An empty string is treated as an intentional update, not a redaction artifact.

### Number

[](#number)

Plain numeric input (not a slider).

```
new Number(key: 'max_upload', title: 'Max upload size', defaultValue: 10, min: 1, max: 100, step: 0.1, suffix: 'MB', ...)
```

Extra: `min`, `max`, `step`, `placeholder`, `prefix` (e.g. `"$"`), `suffix` (e.g. `"MB"`).

### IntRange

[](#intrange)

Integer slider with min/max bounds.

```
new IntRange(key: 'team_size', title: 'Max team size', min: 1, max: 50, defaultValue: 10, minLabel: '1', maxLabel: '50', ...)
```

Extra: `min` (required), `max` (required), `minLabel`, `maxLabel`. Validation rules are added automatically.

### FloatRange

[](#floatrange)

Decimal slider with step control.

```
new FloatRange(key: 'tax', title: 'Tax rate', min: 0.0, max: 1.0, defaultValue: 0.2, step: 0.05, ...)
```

Extra: `min` (required), `max` (required), `step` (default `0.01`), `minLabel`, `maxLabel`.

### Color

[](#color)

Hex color picker with optional palette restriction.

```
new Color(key: 'brand', title: 'Brand color', defaultValue: '#4f46e5', allowAlpha: true, ...)

// Or restrict to a palette:
new Color(key: 'accent', title: 'Accent', defaultValue: '#ef4444', palette: ['#ef4444', '#3b82f6', '#22c55e'], ...)
```

Extra: `palette` (array of hex strings, or `null` for freeform), `allowAlpha` (allow `#rrggbbaa`).

### Date

[](#date)

Date or datetime picker. Stored as ISO strings.

```
new Date(key: 'launch', title: 'Launch date', defaultValue: null, min: '2026-01-01', ...)

// With time:
new Date(key: 'maintenance', title: 'Maintenance window', defaultValue: null, includeTime: true, ...)
```

Extra: `includeTime`, `min`, `max`, `placeholder`.

### Enum

[](#enum)

Single-select dropdown.

```
new Enum(key: 'theme', title: 'Theme', defaultValue: null, options: [
    new EnumOption('light', 'Light', 'A bright look'),
    new EnumOption('dark', 'Dark', 'Easy on the eyes'),
], ...)
```

`EnumOption(string $value, string $title, ?string $description)`. The `$value` is stored; `$title` and `$description` are display-only.

### MultiSelect

[](#multiselect)

Checkbox group for multiple selections. Stored as an array of strings.

```
new MultiSelect(key: 'channels', title: 'Notifications', defaultValue: ['email'], options: [
    new EnumOption('email', 'Email', null),
    new EnumOption('sms', 'SMS', null),
    new EnumOption('slack', 'Slack', null),
], minSelections: 1, maxSelections: 3, ...)
```

Extra: `minSelections`, `maxSelections`.

### Combobox

[](#combobox)

Searchable single-select. Works with static options, server-side search, or both.

```
// Static:
new Combobox(key: 'country', title: 'Country', options: [...], placeholder: 'Search...', ...)

// Server search:
new Combobox(key: 'timezone', title: 'Timezone', placeholder: 'Search...', ...)
    ->onSearch(function (string $query, string $key) {
        // Return EnumOption[] matching the query
    }, url: '/settings/search/timezone')
```

The `url` tells the frontend where to send search requests. Register the route:

```
Route::get('settings/search/{key}', function (string $key, Request $request, SettingsService $service) {
    $collection = $service->make(...mySettings());
    $results = $collection->search($key, $request->query('query', ''));
    return response()->json(array_map(fn(EnumOption $o) => $o->toArray(), $results));
});
```

### Tags

[](#tags)

Free-form tag input. Tags are added with Enter, comma, or blur. Stored as an array of unique strings.

```
// Free-form:
new Tags(key: 'domains', title: 'Allowed domains', defaultValue: ['example.com'], maxTags: 10, ...)

// With suggestions (autocomplete dropdown):
new Tags(key: 'labels', title: 'Labels', defaultValue: ['bug'], suggestions: [
    new EnumOption('bug', 'Bug', null),
    new EnumOption('feature', 'Feature', null),
], allowCustom: true, maxTags: 5, ...)
```

Extra: `placeholder`, `minTags`, `maxTags`, `suggestions` (EnumOption\[\]), `allowCustom` (default `true` — set to `false` to restrict to suggestions only).

### File

[](#file)

File upload with MIME filtering, file count, and size limits. You provide callbacks for storage:

```
use Oulaa\Settings\StoredFile;

new File(key: 'avatar', title: 'Profile photo', defaultValue: null, accept: ['image/jpeg', 'image/png'], maxFiles: 1, maxSize: 5 * 1024 * 1024, ...)
    ->onUpload(function (UploadedFile $file, string $key): StoredFile {
        $path = $file->store('avatars', 'public');
        return new StoredFile(
            path: $path,
            originalName: $file->getClientOriginalName(),
            mime: $file->getMimeType(),
            size: $file->getSize(),
        );
    })
    ->onDelete(function (StoredFile $file, string $key): void {
        Storage::disk('public')->delete($file->path);
    })
    ->onUrl(function (StoredFile $file, string $key): string {
        return Storage::disk('public')->url($file->path);
    })
```

Extra: `accept` (MIME array), `maxFiles`, `maxSize` (bytes).

Use the `MimeType` enum for cleaner accept lists:

```
use Oulaa\Settings\MimeType;
accept: [MimeType::PNG, MimeType::JPEG]
// or:
accept: MimeType::images()
accept: MimeType::documents()
```

**Callbacks:**

CallbackWhenReturns`onUpload`A new file is submitted`StoredFile``onDelete`A file is removed by the user`void` (runs after DB commit)`onUrl`Serializing for the frontend`string` (single URL) or `array` (named URL map)`onUpload` is required when users submit files. `onDelete` and `onUrl` are optional.

`onDelete` runs after the database transaction commits, not during `save()`. This prevents orphaned files — if the save fails and rolls back, your storage stays untouched.

**Spatie Media Library example:**

```
use Spatie\MediaLibrary\MediaCollections\Models\Media;

new File(key: 'avatar', ...)
    ->onUpload(function (UploadedFile $file, string $key) use ($user): StoredFile {
        $media = $user->addMedia($file)->toMediaCollection('avatar');
        return new StoredFile(
            path: $media->getPath(),
            originalName: $media->file_name,
            mime: $media->mime_type,
            size: $media->size,
            meta: ['uuid' => $media->uuid],
        );
    })
    ->onDelete(fn(StoredFile $file) => Media::where('uuid', $file->meta['uuid'])->first()?->delete())
    ->onUrl(function (StoredFile $file): array {
        $media = Media::where('uuid', $file->meta['uuid'])->first();
        return [
            'original' => $media?->getUrl() ?? '',
            'mobile'   => $media?->getUrl('mobile') ?? '',
        ];
    })
```

When `onUrl` returns an array, the serialized file includes a `urls` map instead of a single `url`.

### Image

[](#image)

Specialised `File` for single-image uploads with dimension constraints and client-side cropping. Extends `File` with `maxFiles` fixed to 1.

```
new Image(key: 'logo', title: 'Logo', defaultValue: null, minWidth: 200, maxWidth: 1920, aspectRatio: 2/1, maxSize: 2 * 1024 * 1024, ...)
    ->onUpload(...)
    ->onDelete(...)
    ->onUrl(...)
```

Extra: `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `aspectRatio` (width/height, e.g. `16/9`). Accept defaults to common image types. The aspect ratio is enforced client-side by a crop tool and validated server-side with a 1% tolerance for rounding.

**Required setup** — Image has two extra steps compared to other types:

1. Install cropperjs (used for client-side cropping):

```
npm install cropperjs
```

2. Register the Image component manually. It's excluded from `registerComponents()` so projects that don't use it don't need cropperjs as a dependency:

```
import { registerComponents, registerComponent } from './vendor/oulaa/settings/components.js';
import ImageComponent from './vendor/oulaa/settings/components/Image.vue';

const app = createApp(App);
registerComponents(app);
registerComponent('image', ImageComponent);
app.mount('#app');
```

Cropperjs is loaded dynamically (only when the crop modal opens), so it won't affect bundle size for pages that don't trigger cropping.

### Info

[](#info)

A non-interactive informational block. No value, no input, no persistence — just a heading and optional description displayed inline with settings.

```
new Info(key: 'profile_info', title: 'Profile Settings', description: 'These settings are visible to other users.')
```

Useful for adding section headings or contextual notices without using a full group.

---

Conditional Visibility Reference
--------------------------------

[](#conditional-visibility-reference)

### visibleWhen / hiddenWhen

[](#visiblewhen--hiddenwhen)

CallVisible when...`->visibleWhen('key')`Referenced setting is truthy`->visibleWhen('key', 'value')`Equals the value`->visibleWhen('key', ['a', 'b'])`Matches any in the array`->hiddenWhen('key')`Referenced setting is truthy (hides)`->hiddenWhen('key', 'value')`Equals the value (hides)`->hiddenWhen('key', ['a', 'b'])`Matches any in the array (hides)### Condition class (for -&gt;when())

[](#condition-class-for--when)

For advanced operators, pass a `Condition` directly:

```
use Oulaa\Settings\Condition;

(new Text(...))->when(Condition::contains('channels', 'slack'));
```

FactoryTrue when...`Condition::equals($key, $value)`Setting equals the value`Condition::notEquals($key, $value)`Does not equal`Condition::in($key, $values)`Is one of the values`Condition::notIn($key, $values)`Is not one of the values`Condition::truthy($key)`Non-null, non-empty, non-zero, non-empty-array`Condition::falsy($key)`Null, empty, zero, or empty array`Condition::contains($key, $item)`Array setting contains the item`Condition::notContains($key, $item)`Array setting does not contain the item**How it works:** Conditions are serialized to JSON and evaluated client-side for instant reactivity. On the backend, settings with unmet conditions are skipped during validation and save. Empty arrays are treated as falsy on both sides (consistent with PHP's `empty([])`).

---

Frontend
--------

[](#frontend)

### Settings component

[](#settings-component)

The `` renders a complete settings form:

```

            {{ isLoading ? 'Saving...' : 'Save' }}

```

PropTypeDefaultDescription`initialSettings``Object`requiredThe `settings` object from `toUnsafeArray()`.`layout``Array``null`The `layout` array from `toUnsafeArray()`. Required when using groups.`endpoint``string``'/settings'`POST endpoint.`saveButtonText``string``'Save'`Save button label.`showSaveButton``boolean``true`Set `false` to use your own save button via the `#actions` slot.**Events:** `saved`, `error`, `update`, `beforeSave`.

**Slots:**

- `#actions` — Replace the save button. Receives `{ save, isLoading, isSaved }`.
- `#group-header-{id}` — Customise a group's title and description. Receives `{ group }`.
- `#group-{id}` — Override a group's entire rendering (header + body). Receives `{ group, settings, isLoading }`.
- `#unknown-type` — Fallback for unrecognised types. Receives `{ setting }`.

#### Custom save button

[](#custom-save-button)

```

            {{ isLoading ? 'Saving...' : 'Save' }}

```

#### Custom group header

[](#custom-group-header)

Add an icon, badge, or any markup to a specific group's header without replacing the group body:

```

            {{ group.title }}

                Requires API access

            {{ group.description }}

```

### useSettings composable

[](#usesettings-composable)

For full control over the UI:

```
import { useSettings } from './vendor/oulaa/settings/useSettings.js';

const { settings, errors, isLoading, isSaved, layout, save, updateSetting, resetSetting, resetAll } = useSettings(
    pageProps.settings,
    '/settings',
);
```

Call `updateSetting(key, value)` on change, then `await save()` to POST. The composable handles `multipart/form-data` automatically when file settings are present.

### Accent color

[](#accent-color)

All components use CSS custom properties for their accent color, defaulting to Tailwind's zinc (neutral). Override them to match your brand:

```
.settings-root {
    --settings-accent-50: #eef2ff;
    --settings-accent-100: #e0e7ff;
    --settings-accent-200: #c7d2fe;
    --settings-accent-400: #818cf8;
    --settings-accent-500: #6366f1;
    --settings-accent-600: #4f46e5;
    --settings-accent-700: #4338ca;
}
```

Or scope it to a wrapper element if you only want to retheme a specific instance:

```
.my-settings-page {
    --settings-accent-600: #0891b2; /* cyan-600 */
    /* ... */
}
```

### Global registration

[](#global-registration)

```
import { registerComponents } from './vendor/oulaa/settings/components.js';

const app = createApp(App);
registerComponents(app);  // registers oulaa-boolean, oulaa-text, oulaa-settings, etc.
app.mount('#app');
```

### Inertia integration

[](#inertia-integration)

```
// Controller
return inertia('Settings')->with(
    $service->make(...mySettings())->toUnsafeArray()
);
```

This merges `settings`, `errors`, `saved`, and `layout` (when groups are used) into the page props.

---

Sensitive Settings
------------------

[](#sensitive-settings)

`isSensitive: true` controls serialization:

- **`toArray()`** — setting is present but `value` and `default_value` are `null`. Safe for customer-facing responses.
- **`toUnsafeArray()`** — real values included. Use for admin responses.

**Important:** Use `toUnsafeArray()` for the POST response so the frontend receives real values back after save. Using `toArray()` would send `null` for sensitive settings, causing the frontend to re-submit `null` on the next save (which triggers validation errors for required fields or accidentally wipes Secret values).

---

Validation
----------

[](#validation)

Each setting validates on every `setValue()` call. Errors are collected per key:

```
$collection = $service->make(...mySettings())->parseRequest($request);

if ($collection->hasErrors()) {
    return response()->json($collection->toUnsafeArray(), 422);
}

$collection->save();
return response()->json($collection->toUnsafeArray());
```

Response shape:

```
{
    "settings": { ... },
    "errors": { "key": ["error message"] },
    "saved": true,
    "layout": [...]
}
```

---

Custom Model
------------

[](#custom-model)

Override the Eloquent model used for persistence:

```
// config/oulaa.php (publish with: php artisan vendor:publish --tag=oulaa-settings-config)
return [
    'setting_model' => App\Models\Setting::class,
];
```

Your model must be fillable for `key`, `value`, `type` and the table needs those columns (with `key` unique and `value` as JSON/text).

---

Long-Running Processes (Octane / Queues)
----------------------------------------

[](#long-running-processes-octane--queues)

The package caches all settings in memory per request. The service provider automatically flushes the cache on Octane's `RequestTerminated` event. For queue workers or other contexts, flush manually:

```
app(SettingRepository::class)->flush();
```

`save()` flushes automatically, so reads after a save always return fresh values.

---

Exceptions
----------

[](#exceptions)

ExceptionWhen`InvalidSettingState`Validation failure, non-editable modification, or saving with errors.`SettingConfigurationException`Stored type doesn't match the PHP class (code/data mismatch between deploys).`RuntimeException`File submitted without an `onUpload` handler.`InvalidArgumentException`Duplicate keys, duplicate option values, or `min > max`.

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance82

Actively maintained with recent releases

Popularity1

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

91d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/dd7ea9e9246a2a616e8bb668e66ed90ef44d3a57557d51bf611568fed432e09e?d=identicon)[oulaa](/maintainers/oulaa)

---

Top Contributors

[![ovp87](https://avatars.githubusercontent.com/u/5088174?v=4)](https://github.com/ovp87 "ovp87 (7 commits)")

---

Tags

laravelconfigurationSettingsadminvue

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/oulaa-settings/health.svg)

```
[![Health](https://phpackages.com/badges/oulaa-settings/health.svg)](https://phpackages.com/packages/oulaa-settings)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.6k](/packages/larastan-larastan)[laravel/ai

The official AI SDK for Laravel.

9782.1M161](/packages/laravel-ai)[spatie/laravel-health

Monitor the health of a Laravel application

87411.3M152](/packages/spatie-laravel-health)[moonshine/moonshine

Laravel administration panel

1.3k239.9k75](/packages/moonshine-moonshine)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
