PHPackages                             loupekit/laravel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. loupekit/laravel

ActiveLibrary

loupekit/laravel
================

Loupe for Laravel — embeddable visual feedback that stores comments in your own database, gates access per user, serves the Loupe dashboard, and exposes the backlog to Claude Code over MCP.

v0.7.0(1mo ago)0108↑50%MITPHPPHP ^8.4

Since Jul 11Pushed 1mo agoCompare

[ Source](https://github.com/mohamed-ashraf-elsaed/loupe-laravel)[ Packagist](https://packagist.org/packages/loupekit/laravel)[ Docs](https://mohamed-ashraf-elsaed.github.io/loupe/)[ RSS](/packages/loupekit-laravel/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (20)Versions (15)Used By (0)

[ ![Loupe — Pin feedback to the live UI. Hand it to Claude.](https://raw.githubusercontent.com/mohamed-ashraf-elsaed/loupe/main/docs/store/promo-marquee-1400x560.jpg)](https://mohamed-ashraf-elsaed.github.io/loupe/)loupekit/laravel
================

[](#loupekitlaravel)

**Loupe for Laravel — visual feedback, in your own app.**
Your users pin a comment to any element on your live product and capture a screenshot.
Comments are stored in **your** database, gated to **your** users, triaged on **your** dashboard,
and handed to **Claude Code** over MCP — no separate backend to run.

 [![Packagist version](https://camo.githubusercontent.com/fc44d32f486f1c6bf6f38f3bb10ba741313d06a9f481685d7e1b3f1d12e1ec8d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6c6f7570656b69742f6c61726176656c3f636f6c6f723d346135356436266c6162656c3d7061636b6167697374)](https://packagist.org/packages/loupekit/laravel) [![Packagist downloads](https://camo.githubusercontent.com/4d2edf3cb161c053f4ca62370e589540aba612d7b192604aa2e331a30ae495a5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6c6f7570656b69742f6c61726176656c3f636f6c6f723d346135356436)](https://packagist.org/packages/loupekit/laravel) [![PHP version](https://camo.githubusercontent.com/44d55b0ab9fd86d19a701b3fa10eae0f6c0ca1b827e55f0b81ed42cecd033fbd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6c6f7570656b69742f6c61726176656c3f636f6c6f723d346135356436266c6162656c3d706870)](https://camo.githubusercontent.com/44d55b0ab9fd86d19a701b3fa10eae0f6c0ca1b827e55f0b81ed42cecd033fbd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6c6f7570656b69742f6c61726176656c3f636f6c6f723d346135356436266c6162656c3d706870) [![Laravel 11, 12, 13](https://camo.githubusercontent.com/2d42d4450289f0d5c9b3334dd14a1382abe96415be9f778019cd9c6886ee38f5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31312532307c25323031322532307c25323031332d346135356436)](https://camo.githubusercontent.com/2d42d4450289f0d5c9b3334dd14a1382abe96415be9f778019cd9c6886ee38f5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31312532307c25323031322532307c25323031332d346135356436) [![100% test coverage](https://camo.githubusercontent.com/c5df64baafaec1d0f4257e7b5644c7f57849334bc3045d5a4bcff77c3e508a81/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d3130302532352d346135356436)](https://camo.githubusercontent.com/c5df64baafaec1d0f4257e7b5644c7f57849334bc3045d5a4bcff77c3e508a81/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d3130302532352d346135356436) [![MIT license](https://camo.githubusercontent.com/113091a99ddb774bbe106ab12de0474ecc3a73690089dfa01049098f7b8af116/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6c6f7570656b69742f6c61726176656c3f636f6c6f723d346135356436)](https://camo.githubusercontent.com/113091a99ddb774bbe106ab12de0474ecc3a73690089dfa01049098f7b8af116/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6c6f7570656b69742f6c61726176656c3f636f6c6f723d346135356436)

 [**Website**](https://mohamed-ashraf-elsaed.github.io/loupe/) · [**Docs**](https://mohamed-ashraf-elsaed.github.io/loupe/guide/#laravel) · [**GitHub**](https://github.com/mohamed-ashraf-elsaed/loupe) · [**Full guide**](https://github.com/mohamed-ashraf-elsaed/loupe/blob/main/docs/LARAVEL.md) · [**Changelog**](https://github.com/mohamed-ashraf-elsaed/loupe/blob/main/CHANGELOG.md) · [**SDK**](https://www.npmjs.com/package/@loupekit/sdk)

---

Overview
--------

[](#overview)

Traditional feedback — *"the revenue card looks off on the dashboard"* — loses the one thing an engineer needs: **which element, in what state, on which page.** Loupe captures all of it at the moment of the comment. This package brings that loop into any Laravel app: the widget is a single Blade directive, comments are Eloquent rows in **your** database, and the triage board is a route **you** own behind **your** auth.

 [![Pin a comment to any element](https://raw.githubusercontent.com/mohamed-ashraf-elsaed/loupe/main/docs/store/screenshot-1-inspect.jpg)](https://raw.githubusercontent.com/mohamed-ashraf-elsaed/loupe/main/docs/store/screenshot-1-inspect.jpg)

Table of contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Requirements](#requirements)
- [Install](#install)
- [Quick start](#quick-start)
- [How it works](#how-it-works)
- [Authorization — who can use it](#authorization--who-can-use-it)
- [The dashboard](#the-dashboard)
- [Claude Code over MCP](#claude-code-over-mcp)
- [Configuration](#configuration)
- [Data model](#data-model)
- [Try it locally](#try-it-locally)
- [Publishing](#publishing)
- [Testing](#testing)
- [Related packages](#related-packages)

Features
--------

[](#features)

🎯 **Click-to-comment inspector**Hover-highlight any element, click to pin a comment — dropped in with one `@loupeWidget` directive.💬 **Free comments**Drop a page-level note anywhere with the **Note** mode — no element, no screenshot.▭ **Free-region screenshots**Drag a free-size box, screenshot exactly that area, comment on it. Anchors to the element under its center so it tracks reflow and scrolling.🧲 **Dockable control**A DevTools-style panel — dock it left / right / bottom (pushes your page over so nothing is covered) or float it; light/dark theme, collapses to a small `◎` launcher, and a bottom sheet on mobile.🔁 **Redeploy-surviving re-anchoring**A multi-signal fingerprint re-locates the element after the UI changes; if it can't, the pin **detaches** instead of pointing at the wrong thing.🗄️ **Your database**Comments are an Eloquent `Comment` model in a `loupe_comments` table. Swap in your own subclass to add relations/scopes.🔐 **Per-user gating**`loupe:use` / `loupe:admin` Gate abilities **and** config closures decide who sees the widget and who opens the dashboard.📋 **Dashboard on your routes**The full Kanban triage board at `/loupe/dashboard`, behind your session auth.🤖 **Claude Code over MCP**`php artisan mcp:start loupe` hands Claude the fully-contextual backlog.🔑 **No secrets to manage**Authenticates with your existing session + CSRF token. No HMAC keys.✅ **100% tested**A Testbench suite with a hard 100% line-coverage gate, across Laravel 11/12/13.Requirements
------------

[](#requirements)

Supported**PHP****8.4** and higher**Laravel****11, 12, 13****Database**anything Eloquent supports (MySQL, PostgreSQL, SQLite, SQL Server)**MCP** (optional)`laravel/mcp` **^0.8** — Laravel 11, 12 &amp; 13Install
-------

[](#install)

```
composer require loupekit/laravel
php artisan loupe:install
php artisan migrate
```

`loupe:install` publishes the config, migration and browser assets (to `public/vendor/loupe`), then publishes and registers an `App\Providers\LoupeServiceProvider` where you control access.

Quick start
-----------

[](#quick-start)

**1.** Add the widget to your Blade layout, just before ``:

```
@loupeWidget

```

**2.** Decide who sees it. By default the widget and dashboard are visible **only in `local`**. Open `app/Providers/LoupeServiceProvider.php` and grant access:

```
Gate::define('loupe:use', fn ($user) => $user->is_staff);   // who sees the widget
Gate::define('loupe:admin', fn ($user) => $user->is_admin);  // who opens the dashboard
```

**3.** Open the board at **`/loupe/dashboard`**. That's the whole setup.

How it works
------------

[](#how-it-works)

 ```
flowchart LR
  subgraph App["Your Laravel app"]
    W["@loupeWidget(Loupe SDK)"]
    API["/loupe/v1/*Comment + Blob controllers"]
    DASH["/loupe/dashboardKanban board"]
    DB[("loupe_commentsyour database")]
    FS[["screenshotsyour disk"]]
    MCP["php artisanmcp:start loupe"]
  end
  CLAUDE["Claude Code"]

  W -->|"session cookie + CSRF"| API --> DB
  API --> FS
  DASH --> API
  MCP --> DB
  CLAUDE |MCP| MCP
```

      Loading Identity is always the authenticated session user (`auth()->user()`); the store endpoint rejects a comment whose `author.id` is not the current user, so nobody can post as someone else.

Authorization — who can use it
------------------------------

[](#authorization--who-can-use-it)

Two abilities, checked in this order — **config closure**, then **Gate ability**:

```
// Option A — Gate abilities (in the published App\Providers\LoupeServiceProvider)
Gate::define('loupe:use',   fn ($user) => $user->hasRole('staff'));
Gate::define('loupe:admin', fn ($user) => $user->hasRole('admin'));

// Option B — config closures (config/loupe.php); take precedence over the Gates
'authorize' => [
    'use'       => fn ($user) => $user->can_give_feedback,
    'dashboard' => fn ($user) => $user->is_admin,
],
```

Denied users never receive the widget markup, and the API/dashboard return `403`.

The dashboard
-------------

[](#the-dashboard)

The full Kanban board — open / in progress / done, page filter, screenshot thumbnails, status moves, delete, and **Copy for Claude** — served at `/loupe/dashboard` behind your `web`+`auth`middleware and the `loupe:admin` ability. Configuration is injected server-side, so no secret ever reaches the browser.

 [![Triage board](https://raw.githubusercontent.com/mohamed-ashraf-elsaed/loupe/main/docs/store/screenshot-2-board.jpg)](https://raw.githubusercontent.com/mohamed-ashraf-elsaed/loupe/main/docs/store/screenshot-2-board.jpg)

Claude Code over MCP
--------------------

[](#claude-code-over-mcp)

With `laravel/mcp` installed, a local MCP server named **`loupe`** is registered automatically:

```
php artisan mcp:start loupe
```

It reads your database directly (no HTTP hop, no admin key) and exposes three tools:

ToolArgumentsReturns`list_comments``status?`, `url?`the backlog, newest first`get_comment``id`Claude-ready package: request + element HTML + computed styles + the screenshot as an image + any recording URL`propose_change``id`, `html`, `css?`, `notes?`stores Claude's modified HTML/CSS on the comment; the dashboard shows code + a live before/after preview`update_status``id`, `status`marks a comment open / in\_progress / done [![Hand the backlog to Claude](https://raw.githubusercontent.com/mohamed-ashraf-elsaed/loupe/main/docs/store/screenshot-3-claude.jpg)](https://raw.githubusercontent.com/mohamed-ashraf-elsaed/loupe/main/docs/store/screenshot-3-claude.jpg)

Configuration
-------------

[](#configuration)

`config/loupe.php` (published by `loupe:install`):

KeyDefaultPurpose`enabled``true`Master switch.`path``loupe`Route prefix for the API + dashboard.`project_key``app`Scopes comments (one app = one project).`middleware.api``['web','loupe.auth']`Guards the JSON API.`middleware.dashboard``['web','loupe.auth']`Guards the dashboard.`guards``env('LOUPE_GUARDS')`Auth guards to resolve the user through, in order (e.g. `web,admin`). Empty = default guard.`authorize.use` / `authorize.dashboard``null`Closures `fn($user): bool` (take precedence over Gates).`user_resolver``null`Customize the `{id,name,email}` payload sent to the SDK.`comment_model``Loupekit\Loupe\Models\Comment`Swap for your own subclass.`disk``public`Filesystem disk for screenshots.`asset_url``env('LOUPE_ASSET_URL')`Origin Loupe's own JS is served from. Defaults to the app URL — see the CDN note below.### Multiple auth guards

[](#multiple-auth-guards)

If your app uses separate guards for users and admins (e.g. `web` and `admin`), tell Loupe which guards to resolve the current user through, in order:

```
LOUPE_GUARDS=web,admin
```

Loupe then resolves identity the **same way** when rendering the widget and when handling the API request (first authenticated guard wins), so a user logged into a non-default guard no longer hits `403 "cannot post as another user"`. Leave it unset for single-guard apps (identical to `auth()->user()`).

### Heads-up: CDN / `ASSET_URL`

[](#heads-up-cdn--asset_url)

Loupe's browser files live on your app's own filesystem at `public/vendor/loupe/**`, and Loupe loads them from your **app URL** — it deliberately does **not** use Laravel's `asset()` helper. That matters if you set `ASSET_URL` to a CDN/S3 bucket and upload only your Vite build (`public/build`) there: `asset()` would point Loupe's files at the CDN, which doesn't host them, and the widget would silently fail to load. Loupe sidesteps this automatically. If you *do* serve `public/vendor/loupe` from another origin, set `LOUPE_ASSET_URL` to that origin.

See the [full guide](https://github.com/mohamed-ashraf-elsaed/loupe/blob/main/docs/LARAVEL.md) for Sanctum/SPA setups, private screenshot disks, and the complete reference.

Data model
----------

[](#data-model)

Migration `create_loupe_comments_table` → `loupe_comments`:

ColumnTypeNotes`id`string (PK)client-generated UUID`project_key`string, indexedscopes to this app`url`textnormalized (utm / click ids stripped)`status`string, indexed`open` · `in_progress` · `done``body`textthe comment`kind`string`element` · `region` · `free` (page-level note)`author` / `author_id`json / string`{id,name,email?}` + denormalized id`anchor` / `context` / `offset`jsonfingerprint, element HTML + styles, pin position`region`json, nullablerectangle for region comments`screenshot_url`text, nullableURL of the stored screenshot`created_at` / `updated_at`timestampsTry it locally
--------------

[](#try-it-locally)

Point a scratch Laravel app at this package with a [path repository](https://getcomposer.org/doc/05-repositories.md#path):

```
// composer.json of your test app
"repositories": [
  { "type": "path", "url": "../loupe/packages/laravel" }
]
```

```
composer require loupekit/laravel:@dev
php artisan loupe:install && php artisan migrate
# add @loupeWidget to resources/views/…​, log in, and open /loupe/dashboard
```

Publishing
----------

[](#publishing)

This package lives in the Loupe monorepo under `packages/laravel`. Packagist reads a repo's **root** `composer.json`, so it's mirrored to a dedicated repo automatically by [`.github/workflows/laravel-split.yml`](https://github.com/mohamed-ashraf-elsaed/loupe/blob/main/.github/workflows/laravel-split.yml):

- every push to `main` syncs the split repo's `main`;
- every **`vX.Y.Z` tag is forwarded** to the split repo → Packagist auto-updates.

The same tag also drives the npm release (`@loupekit/*`), so **one `vX.Y.Z` tag ships the npm packages and the Packagist package together**.

**One-time setup:**

1. Create the target repo (default `loupekit/laravel`; override via the `LARAVEL_SPLIT_ORG` / `LARAVEL_SPLIT_REPO` repository variables).
2. Add a Personal Access Token with `repo` scope as the **`ACCESS_TOKEN`** secret.
3. Submit the split repo once at [packagist.org/packages/submit](https://packagist.org/packages/submit) and enable **Auto-update** (the Packagist GitHub webhook).

After that, releasing is just: `git tag -a vX.Y.Z && git push --tags`.

Testing
-------

[](#testing)

```
composer install
composer test               # run the suite
composer test:coverage-100  # run with the hard 100% coverage gate
```

Every push runs the suite across Laravel 11/12/13 in CI ([`.github/workflows/laravel.yml`](https://github.com/mohamed-ashraf-elsaed/loupe/blob/main/.github/workflows/laravel.yml)). The browser bundles in `resources/dist` are vendored from `@loupekit/sdk` and `@loupekit/dashboard`; refresh them with `bin/sync-assets.sh` after changing either.

Related packages
----------------

[](#related-packages)

- [**@loupekit/sdk**](https://www.npmjs.com/package/@loupekit/sdk) — the embeddable widget (bundled here).
- [**@loupekit/mcp**](https://www.npmjs.com/package/@loupekit/mcp) — the standalone MCP server.
- [**@loupekit/shared**](https://www.npmjs.com/package/@loupekit/shared) — canonical types + `normalizeUrl`.

Author
------

[](#author)

Created and maintained by **[Mohamed Ashraf Elsaed](https://www.linkedin.com/in/mohamedashrafelsaed/)** — [LinkedIn](https://www.linkedin.com/in/mohamedashrafelsaed/) · [GitHub](https://github.com/mohamed-ashraf-elsaed) ·

License
-------

[](#license)

[MIT](LICENSE) © [Mohamed Ashraf Elsaed](https://www.linkedin.com/in/mohamedashrafelsaed/)

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance91

Actively maintained with recent releases

Popularity13

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

14

Last Release

41d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/cc554f2d0e27d196758e968c9aca637d84850c4bad1b4f490f9edfe38877862e?d=identicon)[MohamedAshrafElsaed](/maintainers/MohamedAshrafElsaed)

---

Top Contributors

[![mohamed-ashraf-elsaed](https://avatars.githubusercontent.com/u/110828041?v=4)](https://github.com/mohamed-ashraf-elsaed "mohamed-ashraf-elsaed (12 commits)")

---

Tags

laravelmcpproductfeedbackloupevisual-feedback

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/loupekit-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/loupekit-laravel/health.svg)](https://phpackages.com/packages/loupekit-laravel)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3365.5M359](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

80732.6M270](/packages/laravel-mcp)[laravel/ai

The official AI SDK for Laravel.

1.1k6.4M360](/packages/laravel-ai)[illuminate/queue

The Illuminate Queue package.

20433.5M1.9k](/packages/illuminate-queue)[api-platform/laravel

API Platform support for Laravel

58190.1k22](/packages/api-platform-laravel)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45945.2k1](/packages/pressbooks-pressbooks)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
