Skip to content

Entry Reference

Every entry type, every option it adds, its default, and the exact shape it serializes. This is the page to keep open while writing an infolist; the shared API that all of them inherit is in Entries.

The eleven types

ClassEntryTypetypeSerialized valueTypeScript
TextEntryTexttextstring|nullTextEntryDefinition
BadgeEntryBadgebadge{label, color}|nullBadgeEntryDefinition
BooleanEntryBooleanbooleanboolBooleanEntryDefinition
DateTimeEntryDateTimedatetimestring|nullDateTimeEntryDefinition
KeyValueEntryKeyValuekey-valuelist<{key, value}>KeyValueEntryDefinition
IconEntryIconicon{icon, color, label}|nullIconEntryDefinition
ImageEntryImageimagestring|nullImageEntryDefinition
ColorEntryColorcolorstring|nullColorEntryDefinition
CodeEntryCodecodestring|nullCodeEntryDefinition
RepeatableEntryRepeatablerepeatablelist<{label, schema}>RepeatableEntryDefinition
CustomEntryCustomcustomunknownCustomEntryDefinition

All live in PandaPanel\Infolists\Components. The cases are PandaPanel\Infolists\Enums\EntryType; the TypeScript union is in resources/js/panel/types/infolist.ts.

One of each

php
use PandaPanel\Forms\Enums\CodeLanguage;
use PandaPanel\Infolists\Components\BadgeEntry;
use PandaPanel\Infolists\Components\BooleanEntry;
use PandaPanel\Infolists\Components\CodeEntry;
use PandaPanel\Infolists\Components\ColorEntry;
use PandaPanel\Infolists\Components\DateTimeEntry;
use PandaPanel\Infolists\Components\IconEntry;
use PandaPanel\Infolists\Components\ImageEntry;
use PandaPanel\Infolists\Components\KeyValueEntry;
use PandaPanel\Infolists\Components\TextEntry;
use PandaPanel\Tables\Enums\BadgeColor;

return $schema->columns(2)->schema([
    TextEntry::make('name'),
    BadgeEntry::make('status')->colors(['live' => BadgeColor::Success]),
    BooleanEntry::make('is_admin')->labels('Administrator', 'Member'),
    DateTimeEntry::make('created_at')->format('Y-m-d'),
    KeyValueEntry::make('meta'),
    IconEntry::make('status')->icons(['live' => 'check', 'draft' => 'pencil']),
    ImageEntry::make('avatar')->disk('public')->circular(),
    ColorEntry::make('brand')->copyable(),
    CodeEntry::make('payload')->language(CodeLanguage::Json)->copyable(),
]);

The base definition

Every entry serializes these keys before adding its own:

KeyTypeFrom
component'entry'Fixed — the node discriminant
namestringmake()
labelstringlabel(), or Str::headline() of the name
typeEntryType valueThe class
valuevariestoValue()
placeholderstring|nullplaceholder()
helperTextstring|nullhelperText()
columnSpanint|'full'columnSpan() / columnSpanFull()
actionActionDefinition|nullaction(), null when hidden, unauthorized, or the record does not exist

toArray() returns null entirely when visible() said no.


TextEntry

The default for anything that reads as words or a number.

MethodSignatureDefault
limit()limit(int $characters): selfnull — no truncation
prose()prose(bool $prose = true): selffalse
php
use PandaPanel\Infolists\Components\TextEntry;

TextEntry::make('reference');

TextEntry::make('name')->limit(5);
// 'Grace Hopper' → 'Grace...'

TextEntry::make('body')->prose()->columnSpanFull();

limit() truncates with Str::limit() on the server, so the payload stays small rather than sending the whole value and hiding it with CSS. The ellipsis is part of the returned string.

prose() swaps the renderer's truncate class for text-pretty, so a sentence wraps instead of clipping to one line. It is presentation only and does not change the value.

Value. Null for null or ''. A scalar is cast with (string); anything else is json_encode()d, and a value that will not encode answers null.

php
['type' => 'text', 'value' => 'Grace Hopper', 'prose' => false]

BadgeEntry

A short status word in a coloured pill.

MethodSignatureDefault
colors()colors(array $colors, BadgeColor $default = BadgeColor::Neutral): self[], default Neutral
php
use PandaPanel\Infolists\Components\BadgeEntry;
use PandaPanel\Tables\Enums\BadgeColor;

BadgeEntry::make('status')->colors([
    'open' => BadgeColor::Info,
    'done' => BadgeColor::Success,
    'void' => BadgeColor::Danger,
], default: BadgeColor::Warning);

The array is keyed by the value the badge stands for — after formatUsing(), if one is set. The colour is resolved on the server so the frontend maps a colour name to a literal class rather than deciding what a value means.

PandaPanel\Tables\Enums\BadgeColor is a closed set: Neutral, Success, Warning, Danger, Info. It is closed because each case maps to literal Tailwind classes in resources/js/panel/palette.ts, shared with tables and forms — a status is the same colour wherever it is shown.

Value. Null for null or ''. A non-scalar becomes an empty label, which then falls to the default colour.

php
['type' => 'badge', 'value' => ['label' => 'open', 'color' => 'info']]

BooleanEntry

A tick or a cross with a word beside it.

MethodSignatureDefault
labels()labels(string $true, string $false): self'Yes' / 'No'
php
use PandaPanel\Infolists\Components\BooleanEntry;

BooleanEntry::make('email_verified_at')->labels('Verified', 'Unverified');

Any value works, not only a boolean column: the value is cast with (bool), so a nullable timestamp reads as "has it happened".

Value. Always a real boolean, never null — which is why placeholder() is never reached on this type.

php
['type' => 'boolean', 'value' => true, 'trueLabel' => 'Verified', 'falseLabel' => 'Unverified']

DateTimeEntry

MethodSignatureDefault
format()format(string $format): self'M j, Y g:ia'
since()since(bool $since = true): selffalse
php
use PandaPanel\Infolists\Components\DateTimeEntry;

DateTimeEntry::make('created_at')->label('Joined');           // 'Aug 15, 2026 6:34pm'
DateTimeEntry::make('created_at')->format('Y-m-d');           // '2026-08-15'
DateTimeEntry::make('updated_at')->since();                   // '3 days ago'

format() is a PHP date format string, passed to Carbon::format().

since() renders diffForHumans(), computed on the server — so the string does not drift while a page sits open. A refresh is what updates it. When both are set, since() wins.

Value. Null only for a null value. A DateTimeInterface is used directly; anything else is cast to a string and parsed with Date::parse().

php
['type' => 'datetime', 'value' => '3 days ago']

KeyValueEntry

An array or JSON column rendered as pairs, in a three-column list.

It adds no methods. Everything it needs comes from Entry.

php
use PandaPanel\Infolists\Components\KeyValueEntry;

KeyValueEntry::make('meta')->placeholder('Nothing recorded.');

Value. [] when the value is not an array. Keys are cast with (string); a scalar item is cast, and anything nested is json_encode()d:

php
['plan' => 'pro', 'seats' => 4, 'flags' => ['beta']]

// becomes
[
    ['key' => 'plan', 'value' => 'pro'],
    ['key' => 'seats', 'value' => '4'],
    ['key' => 'flags', 'value' => '["beta"]'],
]

Flattened here rather than in Vue, so a nested value cannot arrive as an object the renderer has no branch for.

php
['type' => 'key-value', 'value' => [['key' => 'plan', 'value' => 'pro']]]

IconEntry

A value shown as an icon rather than as its text.

MethodSignatureDefault
icons()icons(array $icons): self[]
colors()colors(array $colors, BadgeColor $default = BadgeColor::Neutral): self[], default Neutral
iconUsing()iconUsing(Closure $callback): selfnull
php
use PandaPanel\Infolists\Components\IconEntry;
use PandaPanel\Tables\Enums\BadgeColor;

IconEntry::make('two_factor_confirmed_at')
    ->label('Two-factor')
    ->formatUsing(static fn (mixed $value): string => $value === null ? 'off' : 'on')
    ->icons(['on' => 'shield', 'off' => 'x'])
    ->colors(['on' => BadgeColor::Success, 'off' => BadgeColor::Neutral]);

icons() and colors() are both keyed by the value they stand for. iconUsing() is for an icon that depends on more than the value itself:

php
use Illuminate\Database\Eloquent\Model;

IconEntry::make('status')
    ->iconUsing(static fn (mixed $value, Model $record): ?string => $record->is_locked ? 'shield' : 'check');

When iconUsing() is set it replaces the icons() lookup entirely, but the colour is still looked up by the value.

The icon is a registry key resolved through resources/js/panel/icons/registry.ts, never markup: an unregistered name renders nothing rather than something arbitrary. Run php artisan panel:icons after adding one.

Value. Null when the resolved icon is not a non-empty string — so a value with no entry in icons() renders the placeholder. The label travels with the icon because an icon alone reads as empty to a screen reader; it is the value key, cast to a string.

php
['type' => 'icon', 'value' => ['icon' => 'shield', 'color' => 'success', 'label' => 'on']]

ImageEntry

A stored path shown as a picture.

MethodSignatureDefault
disk()disk(string $disk): selfnull — the value is used as-is
size()size(int $pixels): self96
circular()circular(bool $circular = true): selffalse
php
use PandaPanel\Infolists\Components\ImageEntry;

ImageEntry::make('avatar')->disk('public')->size(64)->circular();

The URL is built on the server, so the browser never turns a disk name into a link:

ValueResult
Not a string, or ''null
Starts with http:// or https://Used unchanged — an absolute URL is already an answer
A path, no disk() setUsed unchanged
A path, disk() setStorage::disk($disk)->url($value)
A path on a disk with no public URLnull — the driver throws RuntimeException and it is caught

A private disk answering null rather than a URL that 404s is the honest outcome; the renderer then shows the placeholder.

size() sets the rendered width and height in pixels. It does not resize the file.

php
['type' => 'image', 'value' => 'https://…/avatars/one.png', 'size' => 64, 'circular' => true]

ColorEntry

A stored colour shown as a swatch beside its value.

MethodSignatureDefault
copyable()copyable(bool $copyable = true): selffalse
php
use PandaPanel\Infolists\Components\ColorEntry;

ColorEntry::make('brand')->copyable();

Value. Validated against PandaPanel\Forms\Components\ColorPicker::isColor() — the same pattern the colour field accepts — and null when it fails. That value ends up inside a style attribute, so a stored string that is a stylesheet rather than a colour would otherwise be rendered as one:

php
ColorEntry::make('brand')->toValue(new Project(['brand' => '#ff0000']));               // '#ff0000'
ColorEntry::make('brand')->toValue(new Project(['brand' => 'red; background: url(x)'])); // null

Accepted syntaxes are hex (#rgb, #rgba, #rrggbb, #rrggbbaa), rgb()/rgba(), and hsl()/hsla(). A named colour like red is not accepted.

copyable() adds a Copy button that writes the value to the clipboard; a refused clipboard is silent, because the value is on screen and can be selected by hand.

php
['type' => 'color', 'value' => '#ff0000', 'copyable' => true]

CodeEntry

A value shown as preformatted text in a monospace block.

MethodSignatureDefault
language()language(CodeLanguage $language): selfCodeLanguage::Plain
copyable()copyable(bool $copyable = true): selffalse
php
use PandaPanel\Forms\Enums\CodeLanguage;
use PandaPanel\Infolists\Components\CodeEntry;

CodeEntry::make('payload')->language(CodeLanguage::Json)->copyable();

PandaPanel\Forms\Enums\CodeLanguage is closed: Plain, Json, Html, Css, JavaScript, Php, Sql, Yaml, Markdown. It is the same enum the code editor field uses, and it is closed because the frontend maps each case to a fixed label rather than accepting a free string. Nothing highlights the value: language crosses the wire and the renderer draws the block as plain monospace text, because syntax highlighting would mean a highlighter dependency in every panel bundle.

Value. Null for null or ''. A scalar is cast. Anything else is pretty-printed with JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES — here rather than in Vue, so what arrives is already the string that will be shown.

That makes formatUsing() returning an array a clean way to build a summary block:

php
use Illuminate\Database\Eloquent\Model;

CodeEntry::make('summary')
    ->copyable()
    ->formatUsing(static fn (mixed $value, Model $record): array => [
        'id' => $record->getKey(),
        'admin' => (bool) $record->getAttribute('is_admin'),
        'verified' => $record->getAttribute('email_verified_at') !== null,
    ]);
php
['type' => 'code', 'value' => "{\n    \"a\": 1\n}", 'language' => 'json', 'copyable' => true]

RepeatableEntry

One sub-schema rendered once per item.

MethodSignatureDefault
schema()schema(array $components): self[]
columns()columns(int $columns): self1, clamped to 1–4
itemLabel()itemLabel(string $label): selfnull
php
use PandaPanel\Infolists\Components\RepeatableEntry;
use PandaPanel\Infolists\Components\TextEntry;

RepeatableEntry::make('lines')->itemLabel('Line')->schema([TextEntry::make('product')]);

Full treatment in Repeatable entries.

php
['type' => 'repeatable', 'columns' => 3, 'value' => [['label' => 'Line 1', 'schema' => [...]]]]

CustomEntry

A value drawn by a Vue component of your own.

MethodSignatureDefault
component()component(string $component): self''
config()config(array $config): self[]
state()state(Closure $callback): selfnull
php
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Infolists\Components\CustomEntry;

CustomEntry::make('score')
    ->component('Panels/Admin/Entries/Gauge')
    ->config(['max' => 10])
    ->state(static fn (Model $record): int => 7);

Full treatment in Custom entries.

php
['type' => 'custom', 'value' => 7, 'componentName' => 'Panels/Admin/Entries/Gauge', 'config' => ['max' => 10]]

Which type for which value

The value isUse
A name, a reference, a numberTextEntry
A paragraphTextEntry with prose() and columnSpanFull()
One of a few statusesBadgeEntry, or IconEntry when the icon says it faster
A flag, or a nullable "has it happened" timestampBooleanEntry
A date or a timestampDateTimeEntry
A flat JSON objectKeyValueEntry
A stored file pathImageEntry
A colour stringColorEntry
A payload, a log line, a config blobCodeEntry
A list of records or rowsRepeatableEntry
None of the aboveCustomEntry

Gotchas

  • DateTimeEntry only null-checks. An empty string is not null, so it reaches Date::parse('') — which answers now. Cast the column, or guard it in formatUsing(), when the value can be ''.
  • BooleanEntry never shows a placeholder. (bool) null is false, so a missing value reads as the false label. Say so in the labels: labels('Verified', 'Unverified') rather than labels('Yes', 'No').
  • ImageEntry with no disk() passes the raw value through. That is intentional for a column already holding a URL, and confusing for one holding a path — the browser resolves it relative to the panel URL. Name the disk.
  • ColorEntry rejects named colours. red is not one of the three accepted syntaxes and serializes as null.
  • BadgeEntry and IconEntry key on the formatted value. If formatUsing() returns 'Administrator', the colors() key is 'Administrator', not the stored 1.
  • CustomEntry sends componentName, not component. The component key is the node discriminant and is 'entry' for every entry type.
  • A type with no extraArray() sends nothing extra. BadgeEntry, DateTimeEntry, KeyValueEntry and IconEntry put everything they resolved inside value, which is why their options do not appear as top-level keys.

See also

Released under the MIT License.