xcore/inline-editing
xcore/inline-editing adds lightweight inline editing to Laravel apps, letting authorized users edit Eloquent model fields and page content directly in context without separate admin forms. Simple integration, configurable fields, and Blade-friendly helpers.
The package targets the XCore CMS ecosystem, so the first thing to confirm is that your project's Laravel version and Blade/Vite setup match what the repo's composer.json and package.json expect — with 2 stars and 0 dependents there is no ecosystem of blog posts to fall back on, so the README and src/ are your only documentation.
Minimum path to a working edit:
composer require xcore/inline-editing
php artisan vendor:publish --provider="Xcore\InlineEditing\InlineEditingServiceProvider"
Publish tags are the usual Laravel convention (--tag=config, --tag=assets, --tag=migrations), but check the provider's publishes() array rather than assuming — if the package ships a migration for an edit-audit or draft table you do not want to discover it after migrate in production.
Where to look first, in order:
config/inline-editing.php — this is almost always where the editable-field allowlist, the middleware/gate guarding edits, and the asset-loading toggle live. Read it top to bottom before writing a single Blade file.routes/ or src/Http/routes.php — find the persistence endpoint and note its URI, middleware stack, and verb. You need this for CSRF and auth debugging later.src/Http/Controllers — the save handler shows you the accepted payload shape (field name, model key, value) and the validation rules applied. Everything else in your integration must conform to it.resources/js — the front-end layer. Determine whether it swaps in an <input>/<textarea> or flips the element to contenteditable. This decision drives your sanitization strategy in the Gotchas section.First use case: make a single CMS page's title editable in place. Wrap the existing Blade output in whatever the package exposes — typically a directive such as @inlineEdit($page, 'title') or an <x-inline-edit> component — rather than reconstructing the markup yourself. Load a page, confirm the field is clickable, edit it, blur, and watch the network tab. If you get a 419, it is CSRF; a 403, it is the package's authorization gate; a 200 with no visible change, it is a cache or a fillable problem. Getting that one field working end-to-end before touching config teaches you more than reading the source.
Guard edits behind an explicit gate, not a session flag. The single most common integration is admin-only inline editing on a public-facing page. Wrap the directive in your own @can rather than relying on the package's default check, and make the config's authorization callback delegate to your existing policy:
// config/inline-editing.php
'authorize' => fn ($user) => $user?->can('update-content') ?? false,
This keeps the package out of your permission model and means a misconfigured deploy fails closed. Confirm by hitting the page logged out — the fields should render as plain text, with no editing affordance and, importantly, no save endpoint exposed.
Treat the field list as a contract. Most implementations of this pattern use an explicit allowlist of model => fields, which is your primary defense against a client posting is_admin or published_at. Prefer declaring it in config over per-call options spread across templates, because a single central list is greppable and reviewable:
'editable' => [
\App\Models\Page::class => ['title', 'excerpt', 'body'],
\App\Models\Block::class => ['heading', 'body'],
],
If the package instead infers editable attributes from $fillable, tightens that array — it is now a security boundary, not just a convenience.
Wire cache invalidation into the save path. The package will persist the attribute; it will not know about your view cache. In practice the cleanest hook is a model observer on saved that forgets the relevant fragment, so edits made through the package and edits made through your admin panel invalidate identically. Avoid putting invalidation in a package event listener unless you have confirmed those events actually fire — with a young package, an observer is the safer bet.
For structured content, compose rather than flatten. Long-form pages typically have many small editable regions. Rather than making a whole body column editable, expose block-level models and let the package edit each block's fields. Each save is then small, validation is per-block, and a botched edit does not destroy a 3000-word page. Pair this with a draft column or the package's versioning (if present) so editors can recover.
Keep the AJAX contract in one place. If the front-end layer is customisable, centralize the fetch wrapper — CSRF header, error toast, and optimistic UI in a single module — instead of letting each Blade partial hand-roll its own fetch. This pays off the moment you need to add a global "editing disabled" banner or retry logic.
Test the save endpoint with a Feature test. You will not get meaningful coverage from the package. One test asserting (a) an authorized user can update an allowlisted field, (b) a non-allowlisted field is rejected, and (c) a guest gets 401/403 protects you against the package's biggest failure modes and survives a version bump.
contenteditable is an XSS vector, and blur is not escape. If the package uses contenteditable rather than a form control, the DOM will contain whatever HTML the browser produced — including pasted markup from Word or a malicious onerror attribute. Sanitize server-side on save (HTMLPurifier, or strip_tags with an explicit allowlist for rich fields) and sanitize again on render. Never assume the package did it; verify in src/ and, if it did not, add it in a model mutator so every write path is covered.
Mass assignment surprises. If the save handler calls $model->update([$field => $value]), the field must be in $fillable, but if it calls ->save() after direct assignment, it will happily set anything the allowlist permits — including, potentially, a column you did not intend to expose. Read the controller line that writes. The symptom of the first case is a silent no-op 200 response, which is genuinely hard to diagnose without logging.
Cache will make you think it is broken. Page cache, view cache, and full-page CDN cache all sit between the save and your eyes. When an edit appears to succeed but the page is unchanged, check in this order: DB value (did it actually save?), Cache::forget coverage, then CDN. Hit the page with ?nocache=1 or in a private window before debugging the package.
Pin the version and vendor the risk. At 2 stars and 0 dependents, treat this as unmaintained until proven otherwise. Commit to an exact constraint ("xcore/inline-editing": "1.2.*"), and if the package becomes load-bearing, plan to fork. Budget for the possibility that a Laravel major upgrade breaks it with no fix forthcoming — keep your integration surface small (a couple of Blade wrappers and one config file) so replacing it with 50 lines of your own is a day's work, not a rewrite.
Asset pipeline mismatch. XCore-era packages often ship pre-built JS and a @inlineEditingScripts directive that assumes a script tag. If your app runs Vite with a strict CSP, or you have moved to @vite, the directive may emit a plain <script src> to a path that no longer exists after a build. Symptom: fields render but are inert, with a 404 in the console. Fix by importing the package's source into your own bundle and dropping the directive.
Debugging the payload. When a save fails opaquely, add a temporary Log::info(request()->all()) in a middleware that wraps the package's endpoint route, then php artisan route:list | grep -i inline to confirm the route name and middleware. Validation failures often come back as a redirect to / rather than a 422 because the request is being treated as non-AJAX — check that your fetch sends X-Requested-With: XMLHttpRequest and Accept: application/json.
Extension points worth knowing. The config's authorization callback and the editable-field map are the two stable seams; use those rather than subclassing the controller, which couples you to its internals. If you need a custom field type (a date picker, a relationship selector), implement it on the front-end and point it at the same save endpoint with the same payload shape — that keeps you on the supported path and means a package update is unlikely to break you.
How can I help you explore Laravel packages today?