PHPackages                             opscale-co/nova-dynamic-resources - 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. opscale-co/nova-dynamic-resources

ActiveLibrary[Admin Panels](/categories/admin)

opscale-co/nova-dynamic-resources
=================================

Config-based rendering for Nova resources

1.11.0(1mo ago)0695—7.7%13MITPHPPHP ^8.3CI passing

Since Sep 30Pushed 1mo agoCompare

[ Source](https://github.com/opscale-co/nova-dynamic-resources)[ Packagist](https://packagist.org/packages/opscale-co/nova-dynamic-resources)[ Docs](https://github.com/opscale-co/nova-dynamic-resources)[ RSS](/packages/opscale-co-nova-dynamic-resources/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (31)Versions (23)Used By (3)

Support us
----------

[](#support-us)

At Opscale, we’re passionate about contributing to the open-source community by providing solutions that help businesses scale efficiently. If you’ve found our tools helpful, here are a few ways you can show your support:

⭐ **Star this repository** to help others discover our work and be part of our growing community. Every star makes a difference!

💬 **Share your experience** by leaving a review on [Trustpilot](https://www.trustpilot.com/review/opscale.co) or sharing your thoughts on social media. Your feedback helps us improve and grow!

📧 **Send us feedback** on what we can improve at . We value your input to make our tools even better for everyone.

🙏 **Get involved** by actively contributing to our open-source repositories. Your participation benefits the entire community and helps push the boundaries of what’s possible.

💼 **Hire us** if you need custom dashboards, admin panels, internal tools or MVPs tailored to your business. With our expertise, we can help you systematize operations or enhance your existing product. Contact us at  to discuss your project needs.

Thanks for helping Opscale continue to scale! 🚀

Description
-----------

[](#description)

Create Nova resources UI (tables and forms) based on dynamic configuration stored in the database. Perfect for quick prototyping when you need to rapidly create and iterate on data structures without writing code.

Important

This package is experimental and it's not inteded to be used in production (yet)

[![Demo](https://raw.githubusercontent.com/opscale-co/nova-dynamic-resources/refs/heads/main/screenshots/nova-dynamic-resources.gif)](https://raw.githubusercontent.com/opscale-co/nova-dynamic-resources/refs/heads/main/screenshots/nova-dynamic-resources.gif)

Installation
------------

[](#installation)

[![Latest Version on Packagist](https://camo.githubusercontent.com/a058457b85d4ae251d1b62d75f77bec08fae4cc7af8c77a2cee0b4b063a4911c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f707363616c652d636f2f6e6f76612d64796e616d69632d7265736f75726365732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/opscale-co/nova-dynamic-resources)

You can install the package in to a Laravel app that uses [Nova](https://nova.laravel.com) via composer:

```
composer require opscale-co/nova-dynamic-resources
```

Next up, you must register the tool with Nova. This is typically done in the `tools` method of the `NovaServiceProvider`.

```
// in app/Providers/NovaServiceProvider.php
// ...
public function tools()
{
    return [
        // ...
        new \Opscale\NovaDynamicResources\Package(),
    ];
}
```

Usage
-----

[](#usage)

This package provides three different approaches to create dynamic resources, each suited for different use cases:

TypeUse CaseModelNova ResourceFields**Dynamic**Fully dynamic resourcesUses `Record` modelAuto-generatedAll from template**Inherited**Own model/table with `template_id`Custom with `UsesTemplate`Custom with `UsesTemplate`Static + template**Composited**Add dynamic fields to existing modelsExisting model with `UsesTemplate`Existing resource with `UsesTemplate`Static + template---

### 1. Dynamic Type

[](#1-dynamic-type)

**Best for:** Rapid prototyping, admin-configurable resources, or when you don't need custom business logic.

Dynamic templates create fully dynamic resources where both the model and Nova resource are generated automatically. All data is stored in the `dynamic_resources_records` table.

#### Setup

[](#setup)

Simply create a template in Nova or via seeder:

```
use Opscale\NovaDynamicResources\Models\Template;
use Opscale\NovaDynamicResources\Models\Enums\TemplateType;

Template::create([
    'label' => 'Events',
    'singular_label' => 'Event',
    'uri_key' => 'events',
    'title' => 'name',
    'type' => TemplateType::Dynamic,
    'related_class' => null,
]);
```

Then add fields to the template:

```
$template->fields()->createMany([
    ['type' => 'name', 'label' => 'Name', 'name' => 'name', 'required' => true],
    ['type' => 'description', 'label' => 'Description', 'name' => 'description'],
    ['type' => 'address', 'label' => 'Address', 'name' => 'address'],
    ['type' => 'date', 'label' => 'Date', 'name' => 'date', 'required' => true],
]);
```

The resource will appear automatically in Nova's sidebar.

---

### 2. Inherited Type

[](#2-inherited-type)

**Best for:** When you have a base model that needs different dynamic fields depending on conditions. Each record can reference a different template, allowing varied field configurations per record.

Inherited templates use your own Eloquent model with a `template_id` foreign key. This allows you to have a dedicated table for your records while still leveraging dynamic fields from templates.

#### 1. Database Migration

[](#1-database-migration)

Create a migration for your model's table with a `template_id` foreign key, your static columns, and a `data` JSON column for dynamic template fields:

```
Schema::create('products', function (Blueprint $table) {
    $table->id();
    $table->foreignId('template_id')->constrained('dynamic_resources_templates')->cascadeOnDelete();
    // Static columns
    $table->string('name');
    $table->text('description')->nullable();
    $table->decimal('price', 10, 2);
    $table->unsignedInteger('stock')->default(0);
    // Dynamic template fields stored here
    $table->json('data')->nullable();
    $table->timestamps();
});
```

#### 2. Model Setup

[](#2-model-setup)

Create your model with the `UsesTemplate` trait:

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Opscale\NovaDynamicResources\Models\Concerns\UsesTemplate;

class Product extends Model
{
    use UsesTemplate;

    protected $fillable = [
        'template_id',
        'name',
        'description',
        'price',
        'stock',
        'data',
    ];

    protected $casts = [
        'price' => 'decimal:2',
        'stock' => 'integer',
        'data' => 'array',
    ];
}
```

The `UsesTemplate` trait automatically detects the `template_id` field and uses a `belongsTo` relationship to the template.

#### 3. Create the Template

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

```
use Opscale\NovaDynamicResources\Models\Template;
use Opscale\NovaDynamicResources\Models\Enums\TemplateType;

Template::create([
    'label' => 'Products',
    'singular_label' => 'Product',
    'uri_key' => 'products',
    'title' => 'name',
    'type' => TemplateType::Inherited,
    'related_class' => \App\Nova\Product::class,
]);
```

#### 4. Add Template Fields

[](#4-add-template-fields)

Add dynamic fields that will be stored in the `data` JSON column:

```
$template->fields()->createMany([
    ['type' => 'quantity', 'label' => 'Weight', 'name' => 'weight'],
    ['type' => 'quantity', 'label' => 'Height', 'name' => 'height'],
    ['type' => 'quantity', 'label' => 'Width', 'name' => 'width'],
]);
```

#### 5. Nova Resource Integration

[](#5-nova-resource-integration)

Create a Nova resource with the `UsesTemplate` trait. You can define static fields before the dynamic template fields:

```
namespace App\Nova;

use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Currency;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Number;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Resource;
use Opscale\NovaDynamicResources\Nova\Concerns\UsesTemplate;

class Product extends Resource
{
    use UsesTemplate;

    public static $model = \App\Models\Product::class;

    public static $title = 'name';

    public function fields(NovaRequest $request): array
    {
        return [
            ID::make()->sortable(),

            BelongsTo::make(__('Template'), 'template', \Opscale\NovaDynamicResources\Nova\Template::class)
                ->searchable()
                ->withoutTrashed(),

            // Static fields defined in the Nova resource
            Text::make(__('Name'), 'name')
                ->sortable()
                ->rules('required', 'string', 'max:255'),

            Textarea::make(__('Description'), 'description')
                ->rules('nullable', 'string')
                ->hideFromIndex(),

            Currency::make(__('Price'), 'price')
                ->sortable()
                ->rules('required', 'numeric', 'min:0'),

            Number::make(__('Stock'), 'stock')
                ->sortable()
                ->rules('required', 'integer', 'min:0')
                ->default(0),

            // Dynamic fields from the associated template
            ...$this->renderTemplateFields(),
        ];
    }
}
```

The Product resource will display the static database fields (name, description, price, stock) followed by any dynamic fields defined in the template (weight, height, width) which are stored in the `data` JSON column.

#### Predefined Template Data

[](#predefined-template-data)

Inherited templates support predefined values via the template's `data` JSON column. When a template has a value stored in its `data` for a given field name, that field will be rendered as a hidden field with the predefined value instead of a visible input.

This is useful when you want certain field values to be fixed by the template rather than entered by the user:

```
$template = Template::create([
    'label' => 'Electronics',
    'singular_label' => 'Electronic',
    'uri_key' => 'electronics',
    'title' => 'name',
    'type' => TemplateType::Inherited,
    'related_class' => \App\Nova\Product::class,
]);

// Set predefined data on the template
$template->setData('category', 'electronics');
$template->save();

// Add fields — 'category' will be auto-filled and hidden
$template->fields()->createMany([
    ['type' => 'name', 'label' => 'Category', 'name' => 'category', 'required' => true],
    ['type' => 'quantity', 'label' => 'Weight', 'name' => 'weight'],
]);
```

In this example, the "Category" field will be automatically set to `"electronics"` as a hidden field, while "Weight" remains a regular visible input.

---

### 3. Composited Type

[](#3-composited-type)

**Best for:** When you need the same extra dynamic fields for all records of an existing model. All records share the same template, linked via the Nova resource class.

Composited templates attach dynamic fields to existing models (like User, Order, etc.) and Nova resources. The dynamic field values are stored in a `data` JSON column on the existing table.

#### 1. Database Migration

[](#1-database-migration-1)

Add a `data` column to your existing table:

```
Schema::table('users', function (Blueprint $table) {
    $table->json('data')->nullable();
});
```

#### 2. Model Setup

[](#2-model-setup-1)

Add the `UsesTemplate` trait to your model:

```
use Opscale\NovaDynamicResources\Models\Concerns\UsesTemplate;

class User extends Authenticatable
{
    use UsesTemplate;

    protected $fillable = [
        'name',
        'email',
        'password',
        'data', // Add this
    ];
}
```

The `UsesTemplate` trait provides:

- A `template()` relationship to access the model's template configuration
- A `fields()` relationship to access the template's fields
- Dynamic data storage and retrieval via the `data` JSON column
- Automatic eager loading of template and fields

#### 3. Create the Template

[](#3-create-the-template-1)

```
use Opscale\NovaDynamicResources\Models\Template;
use Opscale\NovaDynamicResources\Models\Enums\TemplateType;

Template::create([
    'label' => 'Users',
    'singular_label' => 'User',
    'uri_key' => 'users',
    'title' => 'name',
    'type' => TemplateType::Composited,
    'related_class' => \App\Nova\User::class,
]);
```

#### 4. Add Template Fields

[](#4-add-template-fields-1)

```
$template->fields()->createMany([
    ['type' => 'phone', 'label' => 'Phone', 'name' => 'phone'],
]);
```

#### 5. Nova Resource Integration

[](#5-nova-resource-integration-1)

Add the `UsesTemplate` trait to your Nova resource and use `renderTemplateFields()` to render the dynamic fields:

```
use Opscale\NovaDynamicResources\Nova\Concerns\UsesTemplate;

class User extends Resource
{
    use UsesTemplate;

    public function fields(NovaRequest $request): array
    {
        return [
            // Your existing static fields...
            ID::make()->sortable(),
            Text::make('Name'),
            Text::make('Email'),
            Password::make('Password')->onlyOnForms(),

            // Render dynamic fields from template
            ...$this->renderTemplateFields(),
        ];
    }
}
```

This approach allows you to extend existing models with configurable fields without modifying the database schema beyond the `data` column.

### Field Configuration

[](#field-configuration)

Each field in your dynamic resource consists of:

- **Label**: The display name for the field
- **Name**: The database column name
- **Type**: The business field type (see available types below)
- **Required**: Whether the field is mandatory
- **Validation Rules**: Additional Laravel validation rules
- **Config**: Field-specific configuration options
- **Hooks**: Action classes that process config values before applying them to fields

### Available Field Types

[](#available-field-types)

Field types are based on business logic and can be found in the `config/nova-dynamic-resources.php` configuration file. Each type maps to a specific Nova field class with predefined behavior.

You can add more field types by extending the configuration file. The available types include common business field patterns like text, email, phone, address, etc. Business types are used instead of technical field types because they carry implicit validation rules and configuration - for example, an "email" type automatically includes email format validation, while a "phone" type includes phone number pattern validation.

#### Bundled Opscale field packages

[](#bundled-opscale-field-packages)

The default config also exposes types backed by sibling Opscale Nova field packages, all pulled in automatically as dependencies:

Type(s)Package`bpmn`[`opscale-co/nova-bpmn-field`](https://github.com/opscale-co/nova-bpmn-field) — BPMN 2.0 process diagrams`dbml`[`opscale-co/nova-dbml-field`](https://github.com/opscale-co/nova-dbml-field) — DBML data-model diagrams`location`, `place`, `geofence`, `area`, `route`[`opscale-co/nova-geospatial-fields`](https://github.com/opscale-co/nova-geospatial-fields) — Leaflet-backed geospatial fields### Field Configuration Options

[](#field-configuration-options)

The **Config** section allows you to call specific methods on the Nova field instance. For example:

- `required => true` is equivalent to calling `->required(true)` on the field
- `placeholder => "Enter your name"` calls `->placeholder("Enter your name")`
- `help => "This field is important"` calls `->help("This field is important")`

This approach provides flexibility to configure Nova fields dynamically while maintaining type safety and IDE support.

### Field Hooks Options

[](#field-hooks-options)

The **Hooks** section allows you to intercept and transform config values before they are applied to the field. Hooks are action classes that extend `Opscale\Actions\Action` and process the config value dynamically.

For example, when a field type has:

```
'config' => [
    'options' => 'gender',
],
'hooks' => [
    'options' => \Opscale\NovaDynamicResources\Services\Actions\SelectOptions::class,
]
```

The `SelectOptions` hook will be called with the value `'gender'` and will fetch the actual options from a catalog, returning them to be passed to the `->options()` method on the field.

This is useful for:

- Fetching dynamic data from the database (e.g., select options from catalogs)
- Transforming values before applying them to fields
- Running custom logic based on field configuration

Relationships
-------------

[](#relationships)

In addition to fields, templates can define **relationships** to other templates. This enables hierarchical structures (parent/children), ownership references, and links between dynamic resources without writing model code. Relationships are stored in the `dynamic_resources_relationships` table and resolved at runtime.

### Relationship Cardinalities

[](#relationship-cardinalities)

Three cardinalities are supported, each backed by a Nova relation field configured in `config/nova-dynamic-resources.php` under the `relationships` key:

CardinalityNova FieldDescription`BelongsTo``BelongsTo`Owns a single related record (the foreign key lives on this side)`HasOne``HasOne`Inverse single-record relationship; the related record holds the foreign key`HasMany``HasMany`Collection of related records; each related record holds the foreign key### Defining Relationships

[](#defining-relationships)

Use the `relationships()` method on a template and provide the cardinality, related template, and foreign key. The example below creates a self-referential parent/children relationship:

```
use Opscale\NovaDynamicResources\Models\Enums\RelationshipCardinality;

$template->relationships()->create([
    'name' => 'parent',
    'label' => 'Parent',
    'cardinality' => RelationshipCardinality::BelongsTo,
    'related_template_id' => $template->id, // self-referential
    'foreign_key' => 'parent_id',
    'inverse_name' => 'children',
    'required' => false,
]);
```

Available attributes:

- **name**: Relationship name on the source template (also the Nova field name)
- **label**: Display label in Nova
- **cardinality**: `RelationshipCardinality::BelongsTo`, `HasOne`, or `HasMany`
- **related\_template\_id**: ID of the target template
- **foreign\_key**: Column that stores the relationship key
- **inverse\_name**: Optional name used to expose the inverse side on the related template
- **required**: Whether the relationship is mandatory
- **rules** / **config**: Extra Laravel validation rules and Nova field config (merged with the defaults from `config/nova-dynamic-resources.php`)

Relationships whose related Nova resource is not yet registered are skipped silently when rendering.

### Layout

[](#layout)

On the dynamic `Record` resource, relationships are laid out around the template fields based on their cardinality:

- **`BelongsTo`** relationships render **before** the template fields, so the parent reference is the first thing the user sees.
- **`HasOne`** and **`HasMany`** relationships render **after** the template fields. When at least one is present, the form is split into Nova tabs: a `Fields` tab containing the `BelongsTo` relationships and the template fields, plus one tab per `HasOne` / `HasMany` relationship using its `label`.

For example, an `Author` template with a `HasMany` relationship to `Books` will render as a `Fields` tab plus a `Books` tab. A `Book` template with a `BelongsTo` relationship to `Author` renders the author selector before the book's own fields, with no extra tabs.

Templated Repeater
------------------

[](#templated-repeater)

For HasMany scenarios where each child row should be rendered as one of several **Composited** templates targeting the same Nova resource, the package ships a `TemplatedRepeater` field. It wraps Nova's `Repeater` and auto-populates its repeatables from the Composited templates whose `related_class` matches the child resource — Nova's "+ Add" menu then lists one block type per template.

```
use Opscale\NovaDynamicResources\Nova\Fields\TemplatedRepeater;

TemplatedRepeater::make(__('Items'), 'items')
    ->forResource(\App\Nova\LineItem::class)
    ->asHasMany(\App\Nova\LineItem::class)
    ->uniqueField('uuid');
```

Requirements on the child model:

- A foreign key back to the parent (e.g. `bundle_id`).
- A `uuid` column (or whichever column you pass to `uniqueField()`) so Nova's HasMany preset can diff added/removed/updated rows across submissions.
- The `UsesTemplate` trait, plus a `template_id` column and a `data` JSON column — exactly as for any Composited resource.

Each row persists its own `template_id` plus the inline values of that template's dynamic fields. Detail/edit of existing rows happens on the child's own Nova resource.

For advanced wiring (custom Composited filter, hand-built repeatables list) the underlying class is `Opscale\NovaDynamicResources\Nova\Repeatables\Record` — `TemplatedRepeater::forResource()` is just a thin sugar around `Record::repeatablesFor()`.

Testing
-------

[](#testing)

```
npm run test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](https://github.com/opscale-co/.github/blob/main/CONTRIBUTING.md) for details.

Security
--------

[](#security)

If you discover any security related issues, please email  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Opscale](https://github.com/opscale-co)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

49

—

FairBetter than 94% of packages

Maintenance91

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community17

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 74.4% 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

Every ~12 days

Recently: every ~22 days

Total

20

Last Release

44d ago

PHP version history (2 changes)1.0.0PHP ^8.2

1.9.0PHP ^8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3722594?v=4)[opscale](/maintainers/opscale)[@opscale](https://github.com/opscale)

---

Top Contributors

[![opscale-development](https://avatars.githubusercontent.com/u/181295122?v=4)](https://github.com/opscale-development "opscale-development (61 commits)")[![semantic-release-bot](https://avatars.githubusercontent.com/u/32174276?v=4)](https://github.com/semantic-release-bot "semantic-release-bot (20 commits)")[![kevin-Oz](https://avatars.githubusercontent.com/u/31225283?v=4)](https://github.com/kevin-Oz "kevin-Oz (1 commits)")

---

Tags

laravelpackagetoolresourcesdynamicnovaopscale

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

### Embed Badge

![Health badge](/badges/opscale-co-nova-dynamic-resources/health.svg)

```
[![Health](https://phpackages.com/badges/opscale-co-nova-dynamic-resources/health.svg)](https://phpackages.com/packages/opscale-co-nova-dynamic-resources)
```

###  Alternatives

[runlinenl/nova-profile-tool

A tool for Laravel Nova to allow users to edit their profile data

37220.0k](/packages/runlinenl-nova-profile-tool)[khalin/nova-link-field

A Laravel Nova Link field.

31587.5k2](/packages/khalin-nova-link-field)[inani/nova-resource-maker

A Laravel Nova package to help you generate resources fields

2413.7k](/packages/inani-nova-resource-maker)

PHPackages © 2026

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