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 Laravel Package

netgen/content-browser

Netgen Content Browser is a Symfony bundle that provides a reusable content-picker interface for Ibexa/eZ Platform. Browse, search, and filter repository content by content type, then select items for layouts, blocks, and other integrations.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate the stack. netgen/content-browser is a Symfony bundle, not a Laravel package. There is no service provider, no config publish, no facade. In a Laravel codebase you are not installing it — you are harvesting the frontend picker (the JS modal/browse UI) and reimplementing the few REST endpoints as Laravel controllers. Budget the "getting started" phase around the contract, not Composer. The repo you'll actually be reading most is the JS bundle plus Backend/BackendInterface, which documents the item shape the frontend expects.

If you're on Symfony, the minimal path is:

composer require netgen/content-browser
// config/bundles.php
Netgen\ContentBrowser\NetgenContentBrowserBundle::class => ['all' => true],
# config/routes.yaml — the search endpoint is what the JS calls; a missing
# route import is the #1 cause of "the modal opens but nothing happens"
netgen_content_browser:
    resource: "@NetgenContentBrowserBundle/Resources/config/routing.yaml"

Then provide at least one backend service implementing BackendInterface, tagged netgen_content_browser.backend, and a matching item type tagged netgen_content_browser.item_type. Without both, the browser has no "kind" to render and no data to list.

If you're on Laravel, your "getting started" is a one-week spike:

  1. Vendor the JS frontend into resources/js/vendor/content-browser/.
  2. Open the compiled bundle in the browser's Network tab while clicking through Browse → expand a node → Search → Page 2. Record every request path, query param, and JSON response shape. That list is your port spec.
  3. Implement those 3–5 endpoints as Laravel routes against one real Eloquent model.
  4. Return a JSON array shaped like the browser's item object — { id, name, parentId, ... } — and confirm the picker round-trips a selection back to the page.

Look here first, in this order: Backend/BackendInterface (the contract), Item/ItemInterface + Item/ItemTypeInterface (the payload shape and its rendering metadata), then the JS entrypoint's selection callback (how IDs get back to your form).

First real use case: a single "pick articles for this content block" modal on one admin form. Store the returned IDs in a JSON column. Resist the urge to wire up multi-source adapters on day one.

Implementation Patterns

Adapter-per-source, normalized at the edge. Every backend — the picker doesn't care whether it's Eloquent, Elasticsearch, a PIM API, or a static catalog — gets its own adapter that returns the same item shape. In Laravel this means one small class per source:

interface PickerSource
{
    public function browse(?string $parentId, int $page): LengthAwarePaginator;
    public function search(string $term, int $page): LengthAwarePaginator;
    public function find(string $id): ?array; // { id, name, type, ... }
}

Register them in a PickerSourceRegistry keyed by a source name, and let the request specify which source it wants. This mirrors the bundle's BackendInterface/tagged-service pattern without dragging Symfony's container along.

Wrap the picker behind your own interface. Even if you vendor the JS verbatim, put a thin <x-content-picker source="articles"> Blade component (or a Livewire component) in front of it. When you inevitably fork, replace, or debug the third-party JS, only that seam changes. This is the single most valuable pattern given the project's bus factor.

Two endpoints, not one. The browse-tree and the search are distinct request shapes in practice — tree navigation is paginated by parent, search is paginated by query. Keep them separate; trying to overload one endpoint with an "is this a search or a browse?" branch is where the port gets ugly.

IDs in, IDs out — and re-validate on save. The whole architectural value of this package is that it returns identifiers, not hydrated objects. Honor that: store ["42", "108", "9"] in a block_items JSON column, hydrate them with a repository query when rendering the frontend. On save, re-run exists checks against the source — the client is untrusted, and items disappear from catalogs.

Form integration in Laravel. Use a hidden input plus the picker modal, then a FormRequest rule that validates the array:

'items'   => ['array'],
'items.*' => ['string', Rule::exists('articles', 'id')],

The Symfony side gives you a ready-made ContentBrowserType form field; in Laravel you're writing the equivalent binding yourself, and the validation rule is where you add the per-source authorization check ("can this editor pick from this source?").

Order and duplicates. The picker hands back an unordered-ish list. Preserve insertion order in a separate sort index if the block cares about position, and de-dupe server-side — the UI usually prevents duplicates, but don't rely on it.

Multi-surface configuration. Configure the picker per surface (which source, whether search/filter/pagination are enabled, which actions show). The bundle expresses this as item-type options; in your port, a config/picker.php keyed by surface name keeps the per-page variance in one place instead of scattered in Blade.

Preview/actions as links out. The "configurable item previews and actions" feature is just: given an item, render a URL to your existing edit screen or a small preview endpoint. Don't build a preview framework — route to the pages you already have.

Gotchas and Tips

  • Don't try to run the Symfony PHP half in Laravel. No Laravel equivalent of config/bundles.php, and the bundle leans on Symfony's DI, routing, and Twig. Port the endpoints; don't bridge the container.
  • Silent-nothing failures are almost always wiring. On Symfony: missing route import, untagged backend service, or no item_type registered. On a Laravel port: the picker called an endpoint path you didn't implement, or the JSON key names don't match (id vs ID, nested vs flat). Check the Network tab first, always.
  • The item-source endpoints are an authenticated API surface. The modal itself may sit behind an admin guard, but the AJAX endpoints need their own authorization — an editor with access to page A shouldn't be able to enumerate items they can't otherwise see. Put this in middleware/policy, not in the controller body.
  • Search relevance is yours. This package is the selection surface; it does not index, rank, or cache. If search feels slow or bad, that's your query, your index, your problem.
  • Pin a commit and vendor. ~14 stars, 0 dependents, single-vendor upstream. Treat it as a fork from day one: commit the JS into your repo, note the upstream SHA, and track patches locally. Do not depend on Composer/npm resolving to ^x and hoping.
  • Build tooling rot. The JS was compiled with whatever webpack/node version was current when Netgen cut it. Expect to re-bundle. Price that in before the spike concludes — it's often the deciding cost between "extract" and "write our own."
  • Version drift. If you go the sidecar-Symfony route, you inherit a PHP/Symfony app whose supported versions may already lag current Laravel. That's permanent operational surface for one admin feature — usually not worth it.
  • Extension points worth knowing: custom item types (icon/label/kind rendering), custom action handlers (what clicking an item does), and the backend contract itself (the true integration point). Everything else is chrome you can replace.
  • Debugging tip: stand the frontend up against mocked JSON before writing any server code. If the picker renders and round-trips against mocks, your only remaining risk is the adapters. If it doesn't, you've learned that cheaply.
  • Scope discipline: if the requirement is a single <select> or a checkbox list, this is over-engineering. Reach for it when there's a hierarchy to browse or multiple sources to unify — that's where the shell earns its keep.
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.
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
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings