Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Content Browser Ui Laravel Package

netgen/content-browser-ui

A reusable front-end for Netgen Content Browser that gives PHP applications a configurable content-browsing and selection interface. Browse, search, and pick content items through a clean UI designed to plug into existing Netgen Content Browser backends. Ideal for admin tools and content pickers.

View on GitHub
Deep Wiki
Context7

Getting Started

Calibrate expectations before you write any code: netgen/content-browser-ui is not a Composer/Laravel package. It is the JavaScript presentation layer for Netgen Content Browser, which sits in the Netgen Layouts / Symfony / Ibexa (eZ Platform) ecosystem. From a Laravel codebase you will consume it as a JS dependency wired to your own HTTP endpoints — or as a proxy to a running Content Browser service. Treat it as a frontend component, not a composer require.

Where to look first, in order:

  1. package.json — peer dependencies (React? Vue? plain JS?), build tooling, and whether it is published to npm. With 14 stars and no dependents, the README is likely thin; the manifest tells you more.
  2. src/ — find the exported entry point and the props/callbacks it expects. This is your integration contract.
  3. example/ or demo/ (if present) — a working harness is worth more than any doc.
  4. netgen/content-browser (the sibling backend package) — the JSON shape the UI consumes lives there. Read its controllers/responses before writing any Laravel code.

Minimal steps to first render:

# Pin a commit SHA — there is no semver safety net
npm install github:netgen-layouts/content-browser-ui#<commit-sha>

Then decide your backend strategy up front:

  • Path A (isolated service): deploy netgen/content-browser in a small Symfony container, expose its browse/select endpoints, proxy to them from Laravel.
  • Path B (reimplement contract): write a handful of Laravel endpoints returning the same JSON shape the UI expects.

First use case to target: a single-item picker. A "Browse" button opens the UI, the user selects a node, and you store the item's id (not the full payload) on a json or string column. Get that loop working end-to-end — open, filter, select, persist, re-hydrate — before touching multi-select, facets, or custom viewers.

Implementation Patterns

Mount as an island, not a framework migration. Compile the browser into its own bundle and attach it to a DOM node rendered by Blade. Do not try to make this coexist in the same entrypoint as Alpine/Vue/Livewire — a bundler will fight you and the picker does not need your app state.

{{-- resources/views/admin/editor.blade.php --}}
<div id="content-picker"></div>
<input type="hidden" name="hero_item_id" id="hero_item_id" value="{{ $page->hero_item_id }}">
@vite('resources/js/content-browser.js')
// resources/js/content-browser.js
import { mountContentBrowser } from 'content-browser-ui';

mountContentBrowser(document.getElementById('content-picker'), {
  searchUrl: '/admin/content-browser/search',
  itemUrl:   '/admin/content-browser/item',
  onSelect: (item) => {
    document.getElementById('hero_item_id').value = item.id;
    document.dispatchEvent(new CustomEvent('content-browser:selected', { detail: item }));
  },
});

The CustomEvent decouples the picker from whatever state layer owns the rest of the form. Alpine can x-on:content-browser:selected.window="...", Livewire can listen via @script, and plain JS can just add an event listener. The picker never needs to know your app exists.

Build a thin adapter, not a fork. Keep the mapping from your domain models to the Content Browser JSON shape in one place:

// app/Services/ContentBrowserAdapter.php
class ContentBrowserAdapter
{
    public function toBrowserItem(Page $page): array
    {
        return [
            'id'       => "page:{$page->id}",
            'name'     => $page->title,
            'type'     => 'page',
            'children' => $page->children->map(fn ($c) => $this->toBrowserItem($c))->all(),
        ];
    }
}

Two routes, one service:

Route::middleware(['auth', 'can:browse-content'])->group(function () {
    Route::get('/admin/content-browser/search', [ContentBrowserController::class, 'search']);
    Route::get('/admin/content-browser/item/{id}',  [ContentBrowserController::class, 'show']);
});

When the upstream schema shifts (assume it will — the UI is tightly coupled to netgen/content-browser), you change the adapter, not the Blade views or the JS.

Persist identifiers, re-hydrate on render. Store {"id": "page:42", "type": "page"} — never the full tree node. On edit, resolve the id through the adapter so the picker can show the currently selected label without a client-side round trip.

$selected = $page->hero_item_id
    ? $this->contentBrowser->toBrowserItem(Page::find($id))
    : null;

Use a proxy controller if you go Path A. Hide the Symfony service behind a Laravel route so cookies, auth, and CSRF stay consistent. Forward the JSON verbatim — do not reshape it twice.

Gotchas and Tips

  • It is not a Laravel package — this is the number-one trap. There is no ContentBrowserUiServiceProvider, no config file, no php artisan vendor:publish. If a junior engineer reaches for composer require netgen/content-browser-ui, stop them. Only the JS side is in scope.
  • Version skew is the most likely breakage. content-browser-ui and content-browser are maintained in lockstep. If you proxy to a service, pin both to compatible versions and upgrade them together. Symptom of drift: the UI renders but the tree is empty, or a search call returns 200 with a shape the UI silently ignores.
  • No semver safety net. Install by commit SHA (github:...#abc1234), not #main or #v1. Record the SHA in your package.json and in a comment in the adapter so future-you knows what contract you were targeting.
  • Peer dependency collisions. If the package expects React and your Laravel app uses Vue/Alpine, ship it in a separate Vite entrypoint (vite.config.js → multiple input keys) rather than bundling it into your main app asset. Duplicate framework copies will double the bundle and can produce two React instances — a classic "hooks do nothing" debugging session.
  • Asset path assumptions. In the original Symfony host, assets are served by assets:install / Encore under /bundles/netgencontentbrowserui/.... If you lift the assets into Laravel, that base path disappears. Check the source for hardcoded baseURL or asset import paths; override via the mount options if the package exposes them, otherwise patch the bundle.
  • Iframe integration has a security tax. If you embed a remote Content Browser instead of proxying, budget for Content-Security-Policy: frame-src https://browser.internal and postMessage origin validation. Do not trust event.origin === '*'. Build a small typed event helper on both sides and treat the payload schema as a versioned contract.
  • Auth across the proxy. When proxying, forward the Laravel session cookie and translate your Auth::user() into whatever X-User / session header the Symfony service expects. Do not expose the upstream service directly to the browser — it probably has its own (different) auth model.
  • Debugging checklist when nothing renders, in order: (1) network tab — is searchUrl being called and returning the expected shape? (2) console — peer dependency / duplicate framework warnings; (3) element — is the mount target non-empty and visible (mounting into a display:none container yields zero-height pickers); (4) props — confirm option names match the version you pinned (they have changed historically). Start at the network tab; ~80% of "the picker is broken" reports are a shape mismatch or 401.
  • Extension points are limited. Custom viewers/renderers, if exposed at all, are typically passed as renderItem/renderNode props. That is your only real hook — reach for it before forking. A fork means owning a second frontend forever; a single renderNode override is a one-file change.
  • Keep the whole thing behind an interface. Wrap the mount in your own mountPicker(el, opts) helper (as in the snippet above). If the package goes stale — 14 stars, 0 dependents, single-vendor maintenance — you replace one file, not every Blade view. The MIT license means you can fork; it does not mean you should.

If you are not already running Ibexa or Netgen Layouts, the honest assessment is that a native Blade + Livewire picker against your own pages/media tables will be faster to build, cheaper to operate, and require none of the Symfony/Twig/Ibexa ramp-up this package implicitly assumes. Reserve this package for teams already inside that ecosystem.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console