Skip to main content

Creating custom props

This guide explains how to create custom prop types for the Livewire Page Builder.

Structure

To create a custom prop, you need to:

  1. Create a new prop class that extends the base Prop class
  2. Create a corresponding Blade view for the prop
  3. Implement the required methods

Required Properties

  • type - The namespace of your custom prop class
  • label - The label of the prop
  • default - The default value of the prop

Creating a Custom Prop Class

Here's the basic structure for a custom prop class:

<?php

namespace App\Props;

use LivewirePageBuilder\Props\Prop;

class CustomProp extends Prop
{
public function render()
{
return view('props.custom-prop', [
'key' => $this->key,
'label' => $this->config['label'] ?? '',
'default' => $this->config['default'] ?? '',
]);
}

public function handleSubmit($value)
{
return $value;
}
}

Methods

  1. render()

    • Returns the Blade view for the prop
    • Must pass the prop's key, label, and default value to the view
  2. handleSubmit($value) (optional)

    • Processes the submitted value
    • Can be used to transform or validate the input
    • Returns the processed value
  3. hasInternalValidation() (optional)

    • Returns a boolean indicating whether the prop handles its own validation
    • When returning true, the prop's rules will be skipped and validation should be handled in handleSubmit()
    • Example of internal validation in handleSubmit():
    public function handleSubmit($value)
    {
    $validator = Validator::make(['file' => $value], ['file' => $this->config['rules']]);
    if ($validator->fails()) {
    throw new \Exception($validator->errors()->first());
    }
    return $value;
    }
    • Any exceptions thrown during validation will be automatically added as errors to the page builder input field.

Creating the Blade View

Create a Blade view for your prop in resources/views/props/custom-prop.blade.php:

<div class="flex flex-col space-y-1">
<x-lpb::inputs.label :for="$key">{{ $label }}</x-lpb::inputs.label>
<!-- Your custom input here -->
</div>

Important: Wire Model Binding

All custom prop inputs must use the wire model binding editingComponentProps.{{ $key }} to properly update the component's properties. This is required for the page builder to track and save changes to your prop.

Example:

<input 
type="text"
wire:model="editingComponentProps.{{ $key }}"
class="w-full rounded border border-gray-300"
value="{{ $default }}"
/>

Available Input Components

You can use these built-in input components in your Blade views:

  1. Text Input
<x-lpb::inputs.text wire:model="editingComponentProps.{{ $key }}" />
  1. Textarea
<x-lpb::inputs.textarea wire:model="editingComponentProps.{{ $key }}" />
  1. Number Input
<x-lpb::inputs.number 
wire:model="editingComponentProps.{{ $key }}"
:min="$min"
:max="$max"
:step="$step"
/>
  1. Select
<x-lpb::inputs.select 
:options="[
'value1' => 'Label 1',
'value2' => 'Label 2',
'value3' => 'Label 3'
]"
wire:model="editingComponentProps.{{ $key }}"
/>
  1. File Upload
<x-lpb::inputs.file 
wire:model="editingComponentProps.{{ $key }}"
:accepts="$accepts"
:multiple="$multiple"
/>
  1. Label
<x-lpb::inputs.label :for="$key">{{ $label }}</x-lpb::inputs.label>
  1. Color
<x-lpb::inputs.color wire:model="editingComponentProps.{{ $key }}" />

Example: Custom Date Picker Prop

Here's a complete example of a custom date picker prop:

<?php

namespace App\Props;

use LivewirePageBuilder\Props\Prop;

class DatePicker extends Prop
{
public function render()
{
return view('props.date-picker', [
'key' => $this->key,
'label' => $this->config['label'] ?? '',
'default' => $this->config['default'] ?? now()->format('Y-m-d'),
'min' => $this->config['min'] ?? null,
'max' => $this->config['max'] ?? null,
]);
}

public function handleSubmit($value)
{
if (empty($value)) {
return null;
}

try {
return \Carbon\Carbon::parse($value)->format('Y-m-d');
} catch (\Exception $e) {
return $this->config['default'] ?? now()->format('Y-m-d');
}
}
}

Blade view (resources/views/props/date-picker.blade.php):

<div class="flex flex-col space-y-1">
<x-lpb::inputs.label>{{ $label }}</x-lpb::inputs.label>
<input
type="date"
wire:model="editingComponentProps.{{ $key }}"
class="w-full rounded border border-gray-300"
value="{{ $default }}"
@if($min) min="{{ $min }}" @endif
@if($max) max="{{ $max }}" @endif
/>
</div>

Usage in component:

public static function getConfig(): array
{
return [
'name' => __('Custom Component'),
'description' => __('A custom component with a date picker.'),
'icon' => '/path/to/icon.svg',
'allow_children' => false,
'props' => [
'publishDate' => [
'type' => \App\Props\DatePicker::class,
'label' => __('Publish Date'),
'default' => now()->format('Y-m-d'),
'min' => now()->format('Y-m-d'),
'max' => now()->addDays(30)->format('Y-m-d'),
'rules' => ['nullable', 'date'],
],
]
];
}

Example: Custom File Upload Prop with Internal Validation

Here's an example of a custom file upload prop that uses internal validation:

<?php

namespace App\Props;

use LivewirePageBuilder\Props\Prop;
use Illuminate\Support\Facades\Validator;

class FileUpload extends Prop
{
public static function hasInternalValidation(): bool
{
return true;
}

public function render()
{
return view('props.file-upload', [
'key' => $this->key,
'label' => $this->config['label'] ?? '',
'default' => $this->config['default'] ?? '',
'accepts' => $this->config['accepts'] ?? '*',
'multiple' => $this->config['multiple'] ?? false,
]);
}

public function handleSubmit($value)
{
if (empty($value)) {
return null;
}

// Validate the file using internal validation
$validator = Validator::make(
['file' => $value],
['file' => $this->config['rules'] ?? ['required', 'file', 'max:10240']]
);

if ($validator->fails()) {
throw new \Exception($validator->errors()->first());
}

return $value;
}
}

Blade view (resources/views/props/file-upload.blade.php):

<div class="flex flex-col space-y-1">
<x-lpb::inputs.label>{{ $label }}</x-lpb::inputs.label>
<x-lpb::inputs.file
wire:model="editingComponentProps.{{ $key }}"
:accepts="$accepts"
:multiple="$multiple"
/>
@error('editingComponentProps.' . $key)
<x-lpb::inputs.error :message="$message" />
@enderror
</div>

Usage in component:

public static function getConfig(): array
{
return [
'name' => __('File Upload Component'),
'description' => __('A component with custom file upload validation.'),
'icon' => '/path/to/icon.svg',
'allow_children' => false,
'props' => [
'document' => [
'type' => \App\Props\FileUpload::class,
'label' => __('Upload Document'),
'accepts' => '.pdf,.doc,.docx',
'multiple' => false,
'rules' => ['required', 'file', 'max:10240', 'mimes:pdf,doc,docx'],
],
]
];
}

In this example:

  1. The hasInternalValidation() method returns true, indicating that this prop handles its own validation
  2. The handleSubmit() method performs custom validation using Laravel's Validator
  3. If validation fails, it throws an exception with the first error message
  4. The error will be automatically displayed in the page builder's input field
  5. The validation rules are still defined in the component config but are used internally by the prop