MoonShine is an open-source admin panel for Laravel, and on December 10, 2024, the release of MoonShine v3 took place. I have already published an article about the history of the project and discussed the new features.
In this material, I will explain what exactly has changed in MoonShine itself. We will explore improvements in installation and configuration, support for different configuration approaches, enhancements in field management, new integration opportunities with APIs, and better work with components and menus. We will also focus on new front-end capabilities through Alpine.js and the introduction of new systems such as JSON responses and OpenAPI specifications.
Let's take a closer look!
Installation
Let's start with the installation. What has changed? Firstly, during the installation, there will be a lot more questions, allowing you to configure the admin panel according to your needs right away. Now, during the installation, you specify whether migrations and notifications are needed and which driver they will work on:
If you have installed MoonShine before and need the default configuration, you can run the installation command with the -Q
option to skip the questions during installation:
php artisan moonshine:install -Q
Notifications
Previously, MoonShine used a hardcoded database driver for notifications, but now there is the ability to switch it to any other driver.
If you don't want to use database notifications and prefer, for example, broadcast or another driver, you can replace the default implementation with your own (details are in the documentation).
Let's take the Rush package as an example. In Rush, notification handling is based on WebSockets, and Rush is not tied to MoonShine. It is simply a service container that injects its notification implementation, using the broadcast driver in this case.
Configuration: Array/Object
In MoonShine v3, two configuration approaches have been introduced to provide flexibility for different preferences: using an array and using an object.
- Array-based Configuration
This is the familiar Laravel-style approach, where settings are stored in the
moonshine.php file
.
return [
'title' => env('MOONSHINE_TITLE', 'MoonShine'),
'logo' => '/assets/logo.png',
'domain' => env('MOONSHINE_DOMAIN'),
'prefix' => 'admin',
'auth' => [
'enabled' => true,
'guard' => 'moonshine',
],
// …
];
- Object-based Configuration
This approach is implemented via the
MoonShineServiceProvider
.
$config
->title('My Application')
->logo('/assets/logo.png')
->guard('moonshine')
->authEnable();
The object-based approach was introduced because array-based configuration has certain drawbacks. For example, if there's a typo in the configuration (e.g., writing enable
instead of enabled
in the auth section), and the project has insufficient test coverage, such an error could make it to production, leaving the project running without authentication.
With the object-based approach, such errors are significantly reduced, as the code can leverage IDE autocompletion, making configuration management more convenient. For instance, you can use the authDisable
method to disable authentication, making it easier to control.
Note: Object-based configuration takes higher precedence over settings in the configuration file.
In MoonShine v3, a new parameter called resource_prefix
has been introduced, which affects the URL structure. By default, the URL will look like this:
/admin/resource/{resourceUri}/{pageUri}
If resource_prefix
is set to an empty value, the URL will look like this:
/admin/{resourceUri}/{pageUri}
Documentation Updates
The documentation for MoonShine v3 has been completely rewritten and is now in Markdown format, hosted on its own platform. A new feature has been added to the documentation: tabs. When multiple approaches are available (e.g., for configuration), you can easily compare how to achieve the same task using the Config approach versus the Provider approach.
Documentation in MoonShine v3 Has Improved Significantly. Here’s What I’d Like to Highlight:
More Examples
Every section of the documentation now includes detailed code examples. Whether it's configuring the menu, working with tables, or adding assets, you can quickly implement these features by copying and adapting the provided examples.Recipes for Common Tasks
The documentation now features recipes — ready-made solutions for common tasks. This saves time and helps users better understand the system.
For example:
- How to implement asynchronous deletion of an item on button click in the background.
- Saving site settings via the Dashboard.
English Out of the Box
If you need another localization, you can install the required package from the marketplace. Currently, Russian, Ukrainian, German, and recently added Chinese localizations are available. There are also other localization packages, but some of them have not yet been updated for version 3.
Layout
Documentation
In MoonShine v3, the layout system has been completely revamped. It has become more flexible and developer-friendly, allowing for easy customization of the appearance and behavior of the admin panel. Let's break down the key changes and benefits.
The layout system from MoonShine v2 has been entirely removed. Instead, a new, more flexible and modular system has been introduced, which allows you to easily override the layout on any page.
Default Layout
By default, the layout is set in the MoonShine configuration. However, you can now override it for any page, making the system more flexible:
namespace App\MoonShine\Layouts;
use App\MoonShine\Resources\PackageCategoryResource;
use App\MoonShine\Resources\PackageResource;
use App\MoonShine\Resources\UserResource;
use MoonShine\ColorManager\ColorManager;
use MoonShine\Contracts\ColorManager\ColorManagerContract;
use MoonShine\Laravel\Components\Layout\{Locales, Notifications, Profile, Search};
use MoonShine\Laravel\Layouts\CompactLayout;
use MoonShine\MenuManager\MenuGroup;
use MoonShine\MenuManager\MenuItem;
use MoonShine\UI\Components\{Breadcrumbs,
Components,
Layout\Assets,
Layout\Div,
Layout\Body,
Layout\Burger,
Layout\Content,
Layout\Favicon,
Layout\Flash,
Layout\Footer,
Layout\Head,
Layout\Header,
Layout\Html,
Layout\Layout,
Layout\Logo,
Layout\Menu,
Layout\Meta,
Layout\Sidebar,
Layout\ThemeSwitcher,
Layout\Wrapper,
When};
final class MoonShineLayout extends CompactLayout
{
public function build(): Layout
{
return Layout::make([
Html::make([
Head::make([
Meta::make()->customAttributes([
'name' => 'csrf-token',
'content' => csrf_token(),
]),
Favicon::make()->bodyColor($this->getColorManager()->get('body')),
Assets::make(),
])
->bodyColor($this->getColorManager()->get('body'))
->title($this->getPage()->getTitle()),
Body::make([
Wrapper::make([
Sidebar::make([
Div::make([
Div::make([
Logo::make(
$this->getHomeUrl(),
$this->getLogo(),
$this->getLogo(small: true),
)->minimized(),
])->class('menu-heading-logo'),
Div::make([
Div::make([
ThemeSwitcher::make(),
])->class('menu-heading-mode'),
Div::make([
Burger::make(),
])->class('menu-heading-burger'),
])->class('menu-heading-actions'),
])->class('menu-heading'),
Div::make([
Menu::make(),
When::make(
fn(): bool => $this->isAuthEnabled(),
static fn(): array => [Profile::make(withBorder: true)],
),
])->customAttributes([
'class' => 'menu',
':class' => "asideMenuOpen && '_is-opened'",
]),
])->collapsed(),
Div::make([
Flash::make(),
Header::make([
Breadcrumbs::make($this->getPage()->getBreadcrumbs())->prepend(
$this->getHomeUrl(),
icon: 'home',
),
Search::make(),
When::make(
fn(): bool => $this->isUseNotifications(),
static fn(): array => [Notifications::make()],
),
Locales::make(),
]),
Content::make([
Components::make(
$this->getPage()->getComponents(),
),
]),
Footer::make()
->copyright(static fn(): string
=> sprintf(
<<<'HTML'
© 2021-%d Made with ❤️ by
CutCode
HTML,
now()->year,
))
->menu([
'https: ]),
])->class('layout-page'),
]),
])->class('theme-minimalistic'),
])
->customAttributes([
'lang' => $this->getHeadLang(),
])
->withAlpineJs()
->withThemes(),
]);
}
}
MoonShine v3 is a full-fledged UI kit, and you can use its components anywhere. Here’s an example of a Blade template:
<x-moonshine::layout>
<x-moonshine::layout.html :with-alpine-js="true" :with-themes="true">
<x-moonshine::layout.head>
<x-moonshine::layout.meta name="csrf-token" :content="csrf_token()"/>
<x-moonshine::layout.favicon />
<x-moonshine::layout.assets>
@vite([
'resources/css/main.css',
'resources/js/app.js',
], 'vendor/moonshine')
</x-moonshine::layout.assets>
</x-moonshine::layout.head>
<x-moonshine::layout.body>
<x-moonshine::layout.wrapper>
<x-moonshine::layout.sidebar :collapsed="true">
<x-moonshine::layout.div class="menu-heading">
<x-moonshine::layout.div class="menu-heading-logo">
<x-moonshine::layout.logo href="/" logo="/logo.png" :minimized="true"/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-heading-actions">
<x-moonshine::layout.div class="menu-heading-mode">
<x-moonshine::layout.theme-switcher/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-heading-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu" ::class="asideMenuOpen && '_is-opened'">
<x-moonshine::layout.menu :elements="[['label' => 'Dashboard', 'url' => '/'], ['label' => 'Section', 'url' => '/section']]"/>
</x-moonshine::layout.div>
</x-moonshine::layout.sidebar>
<x-moonshine::layout.div class="layout-page">
<x-moonshine::layout.header>
<x-moonshine::breadcrumbs :items="['#' => 'Home']"/>
<x-moonshine::layout.search placeholder="Search" />
<x-moonshine::layout.locales :locales="collect()"/>
</x-moonshine::layout.header>
<x-moonshine::layout.content>
<article class="article">
Ваш контент
</article>
</x-moonshine::layout.content>
</x-moonshine::layout.div>
</x-moonshine::layout.wrapper>
</x-moonshine::layout.body>
</x-moonshine::layout.html>
</x-moonshine::layout>
Themes
Instead of manually specifying colors and classes for theme switching (as was the case in version 2), now it’s enough to specify which Layout to use. For example, you can choose CompactLayout or AppLayout. In the future, adding new themes (e.g., BatmanLayout) will be just as simple — you only need to specify its name.
final class MyLayout extends AppLayout
or
final class MyLayout extends CompactLayout
Integration of Menu, Assets, and Colors
With the new Layout system, not only visual elements are tied to it, but also the menu, assets, colors, and a set of components. This allows for centralized management of all aspects of the admin panel.
Unlike version 2, where menus and other elements had to be overridden via callback functions inside the Service Provider, now everything is in one place. This simplifies development and makes the code more readable.
Provider as Configuration
In the new system, the Provider is used solely for configuration. You declare the settings, and that’s it — no extra callback functions or complex overrides.
This is especially convenient when you need to create a unique Layout for a specific page or section of the admin panel.
Imagine This: every page of your admin panel can, if desired, have its own template, complete with its own menu, set of assets, and a custom order and list of components. This level of flexibility allows for highly tailored and dynamic admin interfaces.
class CustomPage extends Page
{
protected ?string $layout = AppLayout::class;
// ...
}
Components
With the release of MoonShine v3, the number of components has significantly increased, thanks to the new Layout system. Now, each Layout is not just a configuration of the sidebar or topbar, but a full-fledged page customization. You can change any element, starting from the tag, to setting a Favicon or connecting assets for each page individually. All of this is done through classes, without the need to move files into Blade, which simplifies the process and makes it more flexible.
While this approach might seem a bit unusual at first, it opens up endless possibilities for interface customization. Each Layout can have its own design, eliminating the need to use Blade templates for customizations and making the process convenient and intuitive.
Creating a new Layout is simple: you create a template, and during the setup, you’ll be prompted to make it the default. If you agree, this template will automatically be set as the main one in the configuration. Each Layout can have its own assets, and the sidebar and topbar panels can be quickly configured: they can be enabled or disabled as needed, or both can be used simultaneously.
public function build(): Layout
{
return Layout::make([
Html::make([
$this->getHeadComponent(),
Body::make([
Wrapper::make([
// $this->getTopBarComponent(),
$this->getSidebarComponent(),
Menu
First, all elements that were previously "hardcoded" are now flexible. For example, the ActionButton
is now a separate entity that you can customize. You can change its attributes, such as the background
, or completely replace it with another button.
Key Innovations
Unlimited Menu Nesting
The menu now supports unlimited nesting levels. No matter how many levels you create, the system will handle and display them correctly.Full Customization of Menu Elements
If you need to replace standard elements, such as theActionButton
, with something completely different, it’s now possible. You can create your own custom view and use it instead of the default elements.Integration with Packages and Service Providers
If you develop Laravel packages, you can use service providers and theboot
method, which supports Dependency Injection (DI). This allows you to inject core components from MoonShine, such asAssetManager
orMenuManager
, and configure them globally. For example, you can add menu items for all users directly from your package.Unique Menu for Each Page
Each page can now have its own Layout, and therefore, a unique menu. This allows you to create specialized navigation for different sections, making the interface more intuitive and adaptive.
Icons
-
Basic Icon Setup
Icons can now be specified in a simplified format. For example, to add an icon to a button, you only need to specify its name, such as
"users"
. This works the same as before, but now without the need to use theheroicons
prefix:
->icon('cog')
However, the real breakthrough in working with icons in MoonShine comes with the MetaStorm plugin for PHPStorm, which provides autocomplete for all system icons:
This allows you to quickly find and insert the desired icons, saving time and simplifying the development process. Special thanks to Dmitry Derepko, the author of this amazing plugin, which has already been highly appreciated by all MoonShine users. Working with icons is just the tip of the iceberg.
-
Custom Icon Mode
You can now use custom icons, including SVGs, via the
custom
mode. This is especially useful if you want to use icons from third-party packages, such as Blade Icons, or load your own. You can specify the path to the directory with icons so that they are loaded from your project, not from the MoonShine vendor.
->icon(
svg('path-to-icon-pack')->toHtml(),
custom: true
)
- Using Icon Packages You can use packages like Blade Icons, which provide convenient helpers for working with SVGs. This makes it easy to connect icons from such packages and display them in the interface. There is also a package by JeRabix for integrating the Iconify service (200,000 Open Source icons).
Asset Manager
The Asset Manager has been significantly redesigned, becoming a more flexible and powerful tool for managing assets (CSS, JS, and other resources).
Now, the Asset Manager allows you to easily connect and use CSS and JS files. The link
and script
tags support attributes like defer
, as well as "sugar" for simplifying their addition. This makes the process of connecting assets faster and more convenient, minimizing the amount of code.
use MoonShine\AssetManager\Js;
Js::make('/js/app.js');
Js::make('/js/app.js')->defer();
Js::make('/js/app.js')->customAttributes([
'data-module' => 'main'
]);
Added the ability to embed JS and CSS directly into the code (inline), which is useful for small code snippets that do not require a separate file. You can also now add arbitrary HTML code, such as meta tags or custom links, expanding customization possibilities.
InlineJs::make(<<<'JS'
document.addEventListener("DOMContentLoaded", function() {
console.log("Loaded");
});
JS);
use MoonShine\AssetManager\Raw;
Raw::make('<link rel="preconnect" href="https://fonts.googleapis.com">');
The Asset Manager has become more flexible: assets can be added, modified, and their priorities managed through the add, prepend, and the new modifyAssets
methods. The latter allows, for example, adding or removing GET parameters, which is useful for fine-tuning in complex projects.
$assetManager->modifyAssets(function($assets) {
return array_filter($assets, function($asset) {
return !str_contains($asset->getLink(), 'remove-this');
});
});
Integration with DI (Dependency Injection) allows the Asset Manager to be used anywhere in the application, including service providers and packages. This makes it universal and convenient for modular development.
For local asset management, the assets
method and the onLoad
hook have been added, which load assets only for the active resource or page. This optimizes performance, as resources are loaded only where they are actually needed. You can also set conditions for loading, for example, only for form or edit pages, making the Asset Manager more adaptive.
use MoonShine\AssetManager\Css;
use MoonShine\AssetManager\Js;
protected function onLoad(): void
{
parent::onLoad();
$this->getAssetManager()
->add(Css::make('/css/app.css'))
->append(Js::make('/js/app.js'));
}
Added support for asset versioning, which simplifies cache management and updates. This is especially important for projects where resources change frequently, as it helps avoid issues with outdated file versions.
Color Manager
The Color Manager allows you to easily manage the color scheme of your application. It can customize the interface to suit your needs.
The Color Manager is integrated into the system in such a way that it can be used both through DI and directly in the Layout. This makes it a universal tool that can be applied anywhere in your application. For example, if you need to change the color of a button or another interface element, you can easily do so through the Color Manager.
You can set custom colors for various elements, such as buttons, text, or backgrounds, and they will be applied globally. For example, if you want to change the primary color, just set a new value, and all elements using this color will automatically update. This is especially convenient if you want to quickly adapt the interface to a new design or brand.
$colorManager->primary('120, 67, 233');
$colorManager->secondary('236, 65, 118');
$colorManager->successBg('0, 170, 0');
$colorManager->successText('255, 255, 255');
$colorManager->warningBg('255, 220, 42');
$colorManager->warningText('139, 116, 0');
$colorManager->errorBg('224, 45, 45');
$colorManager->errorText('255, 255, 255');
$colorManager->infoBg('0, 121, 255');
$colorManager->infoText('255, 255, 255');
The Color Manager also supports default color settings. This means you can set a base color scheme that will be used throughout the application, but easily override it for individual elements if needed. For example, you can change the button color on just one page without affecting the rest of the application.
Model Resource
The Model Resource has become more flexible and user-friendly.
Simplified Base Structure
The basics of working with resources remain the same: fields, tables, and basic methods. However, some features, such as import-export, have been moved to separate packages to avoid overloading the core system. This has made MoonShine more lightweight and modular.
TinyMCE and Markdown Fields
Fields like TinyMCE
and Markdown have also been moved to separate packages. This allows developers to connect only the features they actually need in their project. The documentation provides detailed instructions on how to install and use these fields, making the process quick and convenient.
Changes in Button and Action Confirmation Handling
In version 3, the mechanism for handling buttons requiring action confirmation has been redesigned. Previously, clicking a button would immediately send a request, and then a confirmation window would open. This was inconvenient and illogical. Now, the request is sent only after confirmation, making the process more predictable and user-friendly.
updateOnPreview Method
Allows editing a specific field directly in the table (quick field editing). However, now if the connection to the server is lost, the system throws an error, preventing data loss. Also added is the ability to lock editing (using a "lock" icon) to prevent accidental changes.
updateInPopover Method
Another option for editing a specific field directly in the table — in a modal window. This is especially useful if the data in the table takes up a lot of space or requires more complex editing. After changes, the data is automatically updated in the table. This is convenient.
Validation When Updating Data
Previously, the updateOnPreview
and updateInPopover
methods did not take into account the validation rules set in the resource. Now, validation is performed automatically, preventing the saving of incorrect data.
Flexibility in Working with Tables
In version 3, the ability to customize tables at the resource level has been added. Now you can easily add or modify elements in thead
, tbody
, and tfoot
. For example, you can add additional rows or cells.
Removal of the TD Component
The TD component has been removed, as its functionality is now fully covered by the customWrapperAttributes
method, which can add attributes for a cell directly from the field.
Text::make('Title')
->customWrapperAttributes(['style' => 'width: 200px;'])
stackFields Field
Allows grouping fields in one table cell, taking into account the data context. For example, depending on the data value, different fields or components can be displayed.
StackFields::make('Stack')->fields(fn(StackFields $ctx) => $ctx->getData()?->getOriginal()->id === 3 ? [
Date::make(__('moonshine::ui.resource.created_at'), 'created_at')
->format("d.m.Y")
->sortable(),
] : [
Date::make(__('moonshine::ui.resource.created_at'), 'created_at')
->format("d.m.Y")
->sortable(),
LineBreak::make(),
Email::make(__('moonshine::ui.resource.email'), 'email')
->sortable(),
])
Fields
MoonShine 3 has significantly reworked the field system while retaining the core concepts. In particular, the dynamic display of fields using showWhen
has been completely rewritten.
Previously, showWhen
simply hid fields, but the data continued to be sent in the request. Now, by default, hidden fields are not sent with the request, avoiding redundant data. If you need the old behavior and want to keep hidden fields in the request, you can specify this setting in the config.
Additionally, a new field has been added — RelationRepeater
. Previously, the logic was built into JSON, making it cumbersome and cluttered. Now, the logic has been moved to a separate RelationRepeater
field, simplifying the structure and improving code readability. Now, if you need to display HasMany
relationships in the main form, you can use RelationRepeater
. This field simplifies working with relationships, allowing them to be displayed in tabs or other parts of the form.
In MoonShine 3, you can now easily display relationships like HasMany
and HasOne
in various formats, including tabs. This is especially useful when you need to compactly and conveniently display related data. Here’s one of the recipes for demonstration.
There is also the ability to display HasMany
or HasOne
in one tab and use TableBuilder
in another to beautifully present the data in a table. The interface has become more user-friendly — working with large volumes of related data has become easier.
CRUD Resource
MoonShine 3 now supports CRUD resources, opening new possibilities for working with data via API. In particular, you can use an external API to fetch and display data and perform CRUD operations on them.
When the page loads, data is fetched via API, and even if the API is slow, the page itself loads instantly thanks to the use of lazy load mode. This allows the page to be displayed immediately, with data being loaded as needed, significantly improving the user experience.
You can configure this behavior using the lazy
property, which is enabled in the config. After that, pages will load quickly, and API requests will be executed in the background. This is perfect for working with large volumes of data and external services.
As for configuration, MoonShine provides many default pages (e.g., dashboard, profile, error pages, and login). These pages can be easily overridden to suit your needs. The moonshine:publish
command allows you to quickly publish all necessary pages and forms. Everything is designed to simplify setup and reduce routine work: you don’t need to start from scratch, just adapt the ready-made components to your tasks.
With this approach, working with CRUD resources becomes not only convenient but also fast, while giving you full control over the appearance and functionality of the pages.
JS
In MoonShine 3, the documentation section for working with JavaScript has become much more convenient and functional. Now, if you work with the frontend, you don’t need to figure out how to write class components from scratch — all the details are explained directly in the documentation.
In particular, you can use Alpine.js to create components, simplifying frontend integration. Components are initialized via the x-data
directive, and you only need to specify the component name. This allows you to easily manage the state and behavior of components, with all assets being connected through the Asset Manager.
The documentation provides a complete description of all events that can be interacted with via JavaScript, so even a developer with little JS experience can correctly call events and work with them.
SD UI
MoonShine 3 introduces an interesting ability to switch the system to JSON response mode, opening new horizons for integration with other platforms. In this mode, MoonShine will send data in JSON format, where each component is represented as an object with its state, type, and other parameters. This is perfect for integrating MoonShine with mobile applications or other frontend solutions.
For example, using this approach, you can create a Flutter app that receives MoonShine components in JSON format. This allows you to modify components on the fly without needing to rebuild the app. This approach also simplifies the creation of custom frontends, where you don’t need to compile each page, but can work with components received via API.
To use this functionality, you need to send a specific header and receive a structure that includes all the components of the page, such as the Dashboard. You will be able to see which components are used, their internal state, and disable some of them or change the display of only the layout if necessary.
This is a great opportunity for developers who want to integrate MoonShine with other systems or build complex frontend solutions on top of it. If anyone is interested in implementing such solutions, I would be happy to help bring this functionality out of beta and further refine it.
API
Allows you to switch the admin panel to JSON response mode, enabling integration with other systems via API. In this mode, all data and interactions occur in JSON format, allowing you to work with MoonShine as an API platform.
Also added is a package for generating OpenAPI specifications. With it, you can easily generate documentation for your API. The process is the same as when creating resources in MoonShine: you create the necessary resources, generate the specification, and get documentation that will be available via Swagger UI. All of this is simple to set up and use.
Authentication
One of the interesting features is the support for auth pipelines. This mechanism is used for additional data processing, for example, after a user fills out and submits a form. During authentication or data validation, additional modifications or checks can be performed, depending on the settings.
In Conclusion
This is only a part of the innovations introduced in MoonShine v3. As you can see, many improvements have been made to the admin panel, along with new functionality. The article turned out to be quite long, and I’ve only covered the main changes. To describe all the innovations, it would probably take more than one article. MoonShine is evolving almost every day, and new features may have been added while you were reading this article.
Also, I invite you to check out the documentation, which has become much better and more user-friendly.
If you have any questions or suggestions, you can always discuss them in the chat, where active work is underway to fix bugs and improve functionality: MoonShine Telegram Chat
I’m currently working on a comprehensive video guide for MoonShine v3, which will help you dive deeper into the system and use its capabilities to the fullest. Videos are being added to this playlist, which will be regularly updated, so don’t forget to subscribe to the YouTube channel.
Link to the GitHub repository where you can leave stars:
MoonShine GitHub
That’s all for now! Wishing you all the best! Use MoonShine in your projects and be happy!
Top comments (0)