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

Nova Belongs To Many Laravel Package

metasync-site/nova-belongs-to-many

A Laravel Nova field for working with belongs-to-many relationships. It offers a cleaner interface for attaching, detaching, and searching related records, making many-to-many management simpler inside Nova resources.

View on GitHub
Deep Wiki
Context7

Getting Started

Install into a Nova app, not a plain Laravel app — Nova is not on Packagist, so version resolution will silently fail if laravel/nova isn't already required.

composer require metasync-site/nova-belongs-to-many

Before running it, open the package's own composer.json and check the laravel/nova constraint. At 3 stars and 0 dependents this package is effectively unproven, and a stale Nova constraint (e.g. ^4.0 only) is the most common reason it "doesn't work" on a Nova 5 app.

Where to look first, in order:

  1. README.md — for the exact field class name. The namespace is not guaranteed to match the package handle, so confirm whether you import something like MetasyncSite\NovaBelongsToMany\BelongsToMany or a shorter alias. If the README is thin, go to step 2.
  2. src/ — find the field class. It will extend Nova's BelongsToMany or Field; the former means every Nova method still applies, the latter means you lose things like ->fields() for pivots and need to check.
  3. resources/js/components/ — the Vue component shows you which endpoint is called on keystroke. That endpoint tells you what "searchable" actually means here (a Nova associatable route vs. a package-published route) and what column it queries.
  4. config/ — if a config file exists, run php artisan vendor:publish --provider="..." and read it before wiring fields; it usually controls search columns and result limits.

First use case: a Resource whose BelongsToMany relation has more than a few hundred rows, where Nova's default multi-select loads every option on form render and the browser hangs. Swap the field:

use MetasyncSite\NovaBelongsToMany\BelongsToMany; // confirm from README

public function fields(NovaRequest $request)
{
    return [
        // ...
        BelongsToMany::make('Tags'),
    ];
}

If the page loads and typing in the select fires an XHR, the package is installed correctly. If the select still renders every Tag up front, the field class isn't the one you imported.

Implementation Patterns

Use it as a drop-in only where it earns its keep. Most Resources should keep Nova's stock BelongsToMany; the searchable variant is for large relations (tags, categories at scale, users across a big tenant). Mixing both in one app is fine and is the safest rollout — swap one field, verify, then expand.

Scope the searchable set in the Resource, not the field. The dominant lever for this field is Nova's relatable query. Constrain what users can find and pick:

public static function relatableTags(NovaRequest $request, $query)
{
    return $query->where('tenant_id', $request->user()->tenant_id)
                 ->where('active', true);
}

This is also the right place to eager-load or restrict columns. Whatever column the package searches on should be indexed in the database — a search-as-you-type field over an unindexed name column is a latency problem you will blame on the package.

Pivot attributes still work the Nova way. If the field extends Nova's BelongsToMany, keep using ->fields() with withPivot on the Eloquent relation:

BelongsToMany::make('Roles')
    ->fields(fn () => [
        Text::make('Assigned At', 'assigned_at'),
    ]),
// Model
public function roles()
{
    return $this->belongsToMany(Role::class)->withPivot('assigned_at')->withTimestamps();
}

If ->fields() is absent from the package's field, that's a hard signal it reimplements the select rather than extending Nova's, and pivot editing will need a separate solution.

Handle index/detail rendering explicitly. A searchable form field says nothing about how to display many values in the index. Set displayUsing for a compact summary and eager-load in indexQuery to avoid N+1:

BelongsToMany::make('Tags')
    ->displayUsing(fn ($tags) => $tags->pluck('name')->take(3)->join(', ')),
public static function indexQuery(NovaRequest $request, $query)
{
    return $query->with('tags:id,name');
}

Validate as an array of foreign keys, regardless of component:

'roles' => ['nullable', 'array'],
'roles.*' => ['integer', Rule::exists('roles', 'id')],

Nova's relationship controllers do most of this, but if you also accept the same payload through your own endpoints (importers, API resources), don't assume the field's client-side filtering is validation.

Use relatableQuery for self-referential and polymorphic relations. Many-to-many to the same table (related products, parent/child categories) is where users hit empty results or timeouts; restricting depth in the relatable callback is easier than patching the package.

Treat it as a template if you need behavior changes. At this maturity level, forking and owning the Vue component is a legitimate implementation pattern — cheaper than arguing with an unmaintained package's semantics.

Gotchas and Tips

  • "No results" is almost always authorization, not search. Nova's relatableQuery respects viewAny/view policies on the related resource. Before touching the package, verify a user can browse the related Resource in Nova at all.
  • The search column is usually hardcoded. Most packages in this category search a single attribute (name) or a config-defined column list. If your model uses title or a full name, you'll get zero hits with no error. Confirm in the Vue component or config, and if it's hardcoded, override the resource's relatable query to alias/search differently, or fork.
  • Nova major-version asset breakage. These packages ship pre-compiled Vue assets. Nova 5 changed the asset pipeline and frontend stack; a package built for Nova 4 can install cleanly and still render a dead component (no console error, no results). Check the package's last commit date and its nova constraint before blaming your field config. Rebuilding assets inside vendor/ is a temporary fix at best — Nova::script() / a fork is the durable one.
  • Each keystroke is a request. Check the component's debounce; if it's aggressive or absent, add it. Watch the Network tab on a form to see the actual request — the endpoint, query params, and payload reveal both the search column and the debounce.
  • Index N+1 is the silent killer. The field will resolve its values per row on the index. If a Resource with 50 rows takes seconds to load after adding the field, it's the relation, not the package — add with() in indexQuery.
  • Pivot validation lives outside the field. Adding/removing pivots goes through Nova's standard relationship controller. Anything requiring uniqueness on (parent_id, related_id) or pivot-level rules belongs in a Model observer or a custom Attach handler, not in fields().
  • Pin the version and vendor the risk. 3 stars, 0 dependents, no upstream pressure. Commit a specific version in composer.json, and if you rely on it in production, fork it into your own VCS. There is no community to catch regressions for you.
  • Debugging checklist when the field is blank or unresponsive: (1) confirm the class resolves and is the package's, not Nova's; (2) confirm the Resource's relatable query returns rows via php artisan tinker; (3) confirm the policy allows it; (4) check the Network tab for the search XHR; (5) check storage/logs/laravel.log for 403/500 on the search route. In that order — it isolates config, authorization, and asset problems from each other.
  • Extension points to prefer over monkey-patching: publish and edit the config (search columns, result limit), the Resource's relatableQuery (scoping), and displayUsing (presentation). Only reach into the Vue component if you need to change the request shape or result template itself.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib