laraish/options
laraish/options is a lightweight Laravel package for defining, storing, and retrieving application options and settings through a simple, consistent API, helping keep configuration values organized and accessible across your project.
laraish/options is not a Laravel package in the conventional sense — it is a WordPress admin options-page builder from the Laraish theme framework ecosystem, which means it leans on Illuminate components (container, config, Blade) but runs inside wp-admin, not inside an artisan-driven app. Read it as "Laravel-shaped ergonomics for the WP Settings API," and you'll get value out of it quickly.
Install and prerequisites
composer require laraish/options
It assumes a Laraish-style theme bootstrap: an Illuminate container, a service-provider registration path, and Blade available for rendering. If you are dropping this into a vanilla WordPress theme with no Illuminate container, expect to wire that up yourself — that is the single biggest upfront cost and the main reason to check the source before committing.
Where to look first, in this order
src/ — identify the options-page class, the field/control classes, and any service provider. These three files tell you the entire public surface of a 10-star package.examples/ or test fixtures — for a package this small, the README is often thinner than the code, so treat the source as the documentation.laraish/laraish framework repo — the options package is designed to be consumed alongside it, and reading the framework clarifies the expected bootstrap.composer.json — check the Illuminate version constraint and the minimum PHP/WordPress versions. This is where most integration pain originates.First use case
Start with one page, two fields, one render site. Conceptually:
// In a service provider registered on admin_init
OptionsPage::make('acme-settings')
->pageTitle('Acme Settings')
->menuTitle('Acme')
->capability('manage_options')
->fields([
'company_name' => ['type' => 'text', 'label' => 'Company name'],
'support_email'=> ['type' => 'email', 'label' => 'Support email'],
]);
Then read it back in a Blade view:
<h1>{{ options('company_name') }}</h1>
<a href="mailto:{{ options('support_email') }}">Contact</a>
Field type names, the exact builder method names, and the accessor helper (options() vs. a facade vs. an injected repository) all need to be confirmed against src/ — they are stable in spirit but not guaranteed verbatim. Get this two-field loop working end-to-end (register → render in admin → save → read in front end) before adding tabs, repeaters, or conditional logic.
Config-array as the source of truth. The idiomatic pattern in this ecosystem is to declare pages and fields as plain PHP arrays in a dedicated config file, then feed that array to the builder in a service provider. Laravel developers will feel at home immediately: the array is your schema, the provider is your registration, and Blade is your view layer. Keep the field definitions in one file per page so the schema is greppable.
Class-per-page for anything non-trivial. Once a page exceeds roughly a dozen fields, move from the fluent builder to a dedicated class with fields(), sections(), and sanitize() methods. This gives you a single place to add validation and a natural target for unit tests (you can instantiate the class without booting WordPress if you keep WordPress calls out of the constructor).
Sections and tabs as grouping primitives. Structure pages as sections → fields rather than a flat list. It matches how the Settings API renders, keeps the admin UI legible, and makes partial rendering in Blade straightforward when you want custom markup around specific groups.
Reading values. Two patterns are worth standardizing on:
// Convenience accessor in views
{{ options('company_name', 'Default Ltd') }}
// Typed access in PHP (controller/service layer)
$settings = app(OptionsRepository::class)->get('acme-settings');
Pick one access path and enforce it. Mixing a global helper in views with a repository in services is fine; mixing three different access styles is how option-key typos become production bugs.
Sanitize at write, escape at read. Sanitization belongs where the option is saved (WordPress will hand you raw $_POST), and escaping belongs in the Blade template ({{ }} for text, {!! !!} never for user-editable option values). Do not rely on the package to do either for you — verify in src/ whether it registers a sanitize_callback per field type, and add your own callback for anything custom.
Blade for the admin UI when the builder is too rigid. The biggest practical win in this package class is that the admin page is a Blade view you can override. When a client asks for a layout the field builder can't express, don't fight the builder — publish/override the view and keep the field registration intact.
Integration with the rest of the theme. Options are read-only data for the front end. Treat the options page as the admin-facing schema and expose the values through a small service or view composer so templates never touch option keys directly:
View::composer('partials.footer', fn ($view) =>
$view->with('contact', app(OptionsRepository::class)->get('acme-settings'))
);
Read this as a source dependency, not a library dependency. Ten stars and zero dependents means no Stack Overflow answers, no blog posts, and no guarantee the README matches the code. Pin the exact version in composer.json, and seriously consider vendoring or forking it into your project if the options page is load-bearing. Budget half a day to read the source on first adoption — it will be faster than debugging blind.
It is not a Laravel package. There is no artisan command, no migration, no route file, no HTTP kernel. It hooks the WordPress Settings API and renders admin pages. Do not expect php artisan vendor:publish; configuration and view overrides are typically done by convention or by filters, so confirm the mechanism in the source.
Option autoloading will bite you. WordPress autoloads options on every request by default. If the package stores each page as a single serialized array (common), a bloated options page silently slows every front-end page load. Keep options pages focused; for large datasets, store IDs or references and fetch the heavy content on demand. Verify with:
wp option get acme-settings --format=json
wp db query "SELECT option_name, LENGTH(option_value), autoload FROM wp_options WHERE autoload='yes' ORDER BY LENGTH(option_value) DESC LIMIT 20;"
Security is your responsibility until proven otherwise. Check that the package registers a nonce and enforces current_user_can() on save. If either is missing or uses the page capability rather than a field-level capability, wrap the save path yourself. Also confirm which capability gates the menu — defaulting to manage_options is fine, but editors who need a settings tab will need it lowered deliberately.
Type coercion surprises. Values round-trip through the options table as strings unless the field type casts them. Booleans in particular are a classic trap: an unchecked checkbox may persist as '0', '', or be absent entirely. Normalize on read:
$enabled = filter_var(options('feature_enabled', false), FILTER_VALIDATE_BOOLEAN);
Debugging without a stack trace. Errors in the admin save path often surface as "options not saving" rather than exceptions. Enable WP_DEBUG and WP_DEBUG_LOG, then check wp-content/debug.log; also inspect the network response of the admin-post.php / options.php POST to see whether the redirect carries settings-updated=true. The absence of that flag tells you the save handler never ran — usually a capability or nonce issue, not a field-mapping issue.
Illuminate version collisions. If the host theme or another plugin also ships Illuminate components, Composer may resolve a shared version that satisfies neither. If you see Class not found on an Illuminate symbol that clearly exists in vendor/, check composer why illuminate/support before touching the package.
Extension points to look for. In order of usefulness: per-field sanitize callbacks, a filter/hook for the page capability, the Blade view path for admin rendering, and any registry where you can register a custom field type. Custom field types are usually the cleanest extension point — add yours there rather than branching templates per type.
Know when to walk away. If the package's field set does not cover your needs and you find yourself writing three custom field types in the first week, compare against Carbon Fields or ACF's options page. Those are heavier but have an ecosystem that answers questions; this package does not. The honest trade-off: laraish/options gives you Blade + config-array ergonomics for the cost of reading a small codebase, and only pays off if you're already inside the Laraish/Illuminate-on-WordPress world.
How can I help you explore Laravel packages today?