PHPackages                             ikromjon/laravel-clipboard-core - 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. ikromjon/laravel-clipboard-core

ActiveLibrary

ikromjon/laravel-clipboard-core
===============================

The clipboard-watching and history engine behind laravel-clipboard: a framework-agnostic, UI-free Laravel package for capturing, deduplicating, and pruning clipboard history

00PHPCI passing

Since Aug 29Pushed todayCompare

[ Source](https://github.com/Ikromjon1998/laravel-clipboard-core)[ Packagist](https://packagist.org/packages/ikromjon/laravel-clipboard-core)[ RSS](/packages/ikromjon-laravel-clipboard-core/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

laravel-clipboard-core
======================

[](#laravel-clipboard-core)

[![Latest version on Packagist](https://camo.githubusercontent.com/288eefa4dede977dac4e983f7ba79250c3ea3a3243a7ecb756ad19c7e4fb188f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f696b726f6d6a6f6e2f6c61726176656c2d636c6970626f6172642d636f72652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ikromjon/laravel-clipboard-core)[![Tests](https://camo.githubusercontent.com/993a06e11140b2a1757196aad382901f02c16fccb704d31a95d4b4ef1c886179/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f496b726f6d6a6f6e313939382f6c61726176656c2d636c6970626f6172642d636f72652f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/Ikromjon1998/laravel-clipboard-core/actions/workflows/tests.yml)[![PHPStan](https://camo.githubusercontent.com/fff00cebb924e124a7335e6bd8ca8f8cf38869463c1654eff45d0939f1f21c57/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230382d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](https://phpstan.org/)[![Total downloads](https://camo.githubusercontent.com/2f955eae5f249e4639ddb78907a82eaf1b2539bd3f2148c50e13ca221d3ee87d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f696b726f6d6a6f6e2f6c61726176656c2d636c6970626f6172642d636f72652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ikromjon/laravel-clipboard-core)[![License](https://camo.githubusercontent.com/c88229564bfdf30ed4abbf0719497aa58d36161d18b9bc6894702189691d1082/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f696b726f6d6a6f6e2f6c61726176656c2d636c6970626f6172642d636f72652e7376673f7374796c653d666c61742d737175617265)](LICENSE)

Clipboard history for Laravel: capture what gets copied, deduplicate it, prune it, and react to it.

This is the engine behind [laravel-clipboard](https://github.com/Ikromjon1998/laravel-clipboard), a macOS menu bar clipboard manager, extracted so you can build your own clipboard tooling on the same foundation. It ships **no UI** — no windows, no tray, no hotkeys, no Blade. Bring your own interface, or none at all.

```
use Ikromjon\ClipboardCore\Facades\ClipboardHistory;

ClipboardHistory::recent(20);            // pinned first, then most recent
ClipboardHistory::search('composer');    // instant over a bounded history
ClipboardHistory::use($clip);            // hand it back to the system clipboard
```

Why this exists
---------------

[](#why-this-exists)

macOS provides no clipboard-change notification, so every clipboard manager on the platform polls. Doing that well is fiddlier than it looks. You need change detection that stays cheap when nothing changed, a cadence that does not drain the battery while you sleep, deduplication that resurfaces rather than duplicates, a hard bound on storage growth, and a way to *not* record the password your password manager just put on the pasteboard.

This package is that engine, with those parts solved and tested.

Installation
------------

[](#installation)

```
composer require ikromjon/laravel-clipboard-core
php artisan migrate
```

> In a NativePHP desktop app use `php artisan native:migrate` instead — NativePHP runs on its own database connection, and plain `migrate` will quietly migrate the wrong one.

Then start the watcher. In a [NativePHP](https://nativephp.com) app, run it as a supervised child process so a crash cannot take your UI down with it:

```
// app/Providers/NativeAppServiceProvider.php
use Native\Desktop\Facades\ChildProcess;

ChildProcess::artisan('clipboard:watch', alias: 'watcher', persistent: true);
```

In a plain Laravel app, `php artisan clipboard:watch` is an ordinary long-running command — run it under Supervisor, systemd, or any process manager.

That is the whole integration. Everything below is optional.

Usage
-----

[](#usage)

### Reading history

[](#reading-history)

```
use Ikromjon\ClipboardCore\Facades\ClipboardHistory;

ClipboardHistory::recent();          // pinned clips first, then by recency
ClipboardHistory::recent(10);        // cap the number returned
ClipboardHistory::search('SELECT');  // case-insensitive; % and _ match literally
ClipboardHistory::find($id);
ClipboardHistory::count();
```

Each result is a `Clip` model:

```
$clip->content;         // the full text
$clip->preview(80);     // single-line, length-capped, for list rows
$clip->kind;            // ClipKind::Text or ClipKind::Url
$clip->pinned;          // exempt from pruning
$clip->times_copied;    // incremented each time it is copied again
$clip->last_copied_at;
```

### Managing clips

[](#managing-clips)

```
ClipboardHistory::use($clip);            // put it back on the clipboard
ClipboardHistory::pin($id);              // exempt from pruning, sorts first
ClipboardHistory::pin($id, false);
ClipboardHistory::forget($id);
ClipboardHistory::clear();               // unpinned only
ClipboardHistory::clear(includePinned: true);
```

### Pausing capture

[](#pausing-capture)

```
ClipboardHistory::pause();
ClipboardHistory::isPaused();
ClipboardHistory::resume();
ClipboardHistory::toggle();
```

Pausing crosses a process boundary, so it works even though the watcher runs separately. On resume, whatever was copied during the pause is treated as already-seen and is never recorded.

### Reacting to events

[](#reacting-to-events)

EventFired when`ClipCaptured`A clip was recorded — `$clip`, and `$isNew` to distinguish new from resurfaced`ClipsPruned`Old clips were dropped — `$removed`, `$limit``ClipRejected`A guard refused a snapshot — `$bytes` only, never content`WatcherPaused` / `WatcherResumed`Capture was paused or resumedThese are plain Laravel events, deliberately. Broadcasting one to a desktop window is a host concern, so hosts listen and re-emit over whatever transport they use:

```
use Ikromjon\ClipboardCore\Events\ClipCaptured;

Event::listen(ClipCaptured::class, function (ClipCaptured $event) {
    ClipboardUpdated::dispatch($event->clip);   // your own broadcast event
});
```

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

[](#configuration)

Publish the config if you want to change the defaults:

```
php artisan vendor:publish --tag=clipboard-config
```

```
'limit'      => 100,          // unpinned clips to keep; pinned are exempt
'max_bytes'  => 2_000_000,    // clips larger than this are ignored
'cadence'    => [
    'hot'  => 250,            // ms, within 30s of the last change
    'warm' => 500,            // ms, default
    'cold' => 1_000,          // ms, after 5 minutes of silence
],
```

How the watcher behaves
-----------------------

[](#how-the-watcher-behaves)

Polling adapts, because people copy in bursts and then not at all:

TierIntervalWhenHot250 msWithin 30 s of the last changeWarm500 msDefaultCold1 sAfter 5 minutes of silenceA poll that finds nothing new costs one read and one hash of a bounded prefix, which is what keeps idle CPU near zero.

The interval is also a **miss window**: anything polling-based loses a clip replaced before the next poll. That is inherent to the platform rather than to this package — macOS exposes no change notification, so every clipboard manager on it has some version of this window. The tiers above keep it at a second or less. Closing it properly means a native helper watching `NSPasteboard.changeCount`, which is cheap enough to poll several times a second and can feed this engine through the same `ClipboardSource` contract.

Storage is a ring buffer. Pruning runs immediately after each capture, keeps the newest `limit` clips, and never touches pinned ones. Re-copying something already in history bumps its recency and copy count instead of inserting a duplicate.

### Self-writes

[](#self-writes)

When you hand a clip back with `use()`, the watcher would otherwise see it as a fresh copy and reorder the history under the user's cursor. `SuppressionLog` prevents that, and it is file-backed rather than in-memory for a specific reason: **the paste and the poll usually happen in different OS processes.** An in-memory note would be written in the UI process and read in the watcher process, which is to say never read at all. Entries expire after ten seconds, so an unconsumed note cannot silently swallow a genuine copy of the same text later.

Replacing behaviour
-------------------

[](#replacing-behaviour)

Every default is bound by interface, so swapping one is a single `bind()` in your own service provider.

ContractDefaultSwap it when`ClipboardSource``NativePhpClipboardSource`, or `ProcessClipboardSource` when a probe is configuredYou are testing, or reading from somewhere else entirely`ClipRepository``DatabaseClipRepository`You want encryption, sync, or memory-only storage`PrivacyGuard`blank + concealed + size guardsYou need per-app exclusions or secret detection`PasteStrategy``CopyOnlyStrategy`You can synthesise a paste keystroke`PauseSwitch``FilePauseSwitch`Your processes share a cache or socket instead`SuppressionLog``FileSuppressionLog`Same — it exists for the same reason```
$this->app->bind(PrivacyGuard::class, fn () => new CompositeGuard(
    new NotBlankGuard,
    new MaxSizeGuard(500_000),
    new NeverFromPasswordManagers,   // your own
));
```

Seeing what the runtime cannot
------------------------------

[](#seeing-what-the-runtime-cannot)

Some pasteboard state is invisible to a cross-platform runtime. On macOS, applications mark secret contents with custom pasteboard types that Electron does not expose — which is exactly the information a clipboard manager needs in order to *not* record your passwords.

`ProcessClipboardSource` reads from a long-running helper that can see them, over a deliberately small protocol: one JSON object per line on stdout.

```
{"change":42,"concealed":false,"app":"Safari","text":"hello"}
{"change":43,"concealed":true,"app":"1Password"}
{"change":44,"concealed":false,"app":"Xcode","oversize":true}

```

FieldMeaning`change`Monotonic counter; emitted only when something changed`concealed`The owning application marked this secret — **omit `text`**`app`Frontmost application, for exclusion rules (optional)`text`The contents; absent when concealed, oversize, or not text`oversize`Text existed but exceeded the helper's byte ceilingPoint the package at one and it takes over reading; writes continue through the runtime, because a probe only observes:

```
// config/clipboard.php
'probe' => ['command' => ['/path/to/clipboard-probe', '--interval-ms', '150']],
```

The protocol is intentionally implementable in any language. A reference macOS helper in ~140 lines of Swift lives in [laravel-clipboard](https://github.com/Ikromjon1998/laravel-clipboard/blob/main/native/ClipboardProbe.swift); it polls `NSPasteboard.changeCount`, which is a plain integer, so idle cost is one comparison and it can poll several times a second without measurable expense.

Testing without a desktop
-------------------------

[](#testing-without-a-desktop)

The `ClipboardSource` contract exists so the engine can be tested anywhere. `ArrayClipboardSource` is an in-memory pasteboard you queue values into:

```
use Ikromjon\ClipboardCore\Contracts\ClipboardSource;
use Ikromjon\ClipboardCore\Sources\ArrayClipboardSource;
use Ikromjon\ClipboardCore\Watcher\ClipboardWatcher;

$clipboard = new ArrayClipboardSource;
$this->app->instance(ClipboardSource::class, $clipboard);

$clipboard->queue('hello', 'hello', 'world');

app(ClipboardWatcher::class)->tick();   // captures "hello"
app(ClipboardWatcher::class)->tick();   // null — nothing changed
app(ClipboardWatcher::class)->tick();   // captures "world"
```

The watcher exposes a single `tick()` rather than owning a loop, so the caller controls the sleeping and tests never wait. This package's own suite runs in about two seconds on Linux, with no Electron and no desktop.

Things you could build with it
------------------------------

[](#things-you-could-build-with-it)

- **Team clipboard** — broadcast `ClipCaptured` over WebSockets to a shared history
- **Snippet sync** — mirror pinned clips into a gist, a repo, or a notes app
- **Compliance auditing** — bind a `PrivacyGuard` that flags secrets leaving a machine
- **Form filler** — a repository of pinned templates, pasted by hotkey
- **Analytics** — `times_copied` is already tracked on every clip

Privacy, honestly
-----------------

[](#privacy-honestly)

Guards run before anything reaches the database, so a rejected clip leaves no trace on disk. `NotConcealedGuard` honours the [nspasteboard.org](https://nspasteboard.org) convention that password managers use to mark contents concealed or transient.

**It can only honour what the source reports**, and that depends on which source you use.

`NativePhpClipboardSource` reads through Electron, which cannot see custom pasteboard types. It therefore always reports `concealed: false`, the guard passes everything through, and copies from a password manager *are* recorded. If that is your setup, treat "never logs passwords" as unenforced and give users a visible pause control.

`ProcessClipboardSource` fixes this by reading from a native helper that can see those types. The guarantee is structural rather than a check: a conforming helper never emits the text of a concealed item, so secret content does not enter PHP at all and no bug on this side can write it to disk. As a second line of defence, text arriving alongside `concealed: true` is discarded on arrival anyway.

Clip content is stored as plaintext in your application's database, protected by file permissions. Encryption at rest is not provided — bind your own `ClipRepository` if you need it.

Requirements
------------

[](#requirements)

PHP 8.3+, Laravel 12 or 13.

`nativephp/desktop` ^2.0 is optional. Without it the package falls back to an in-memory source, so it installs and its tests run in a plain Laravel app with no desktop runtime.

Testing
-------

[](#testing)

```
composer test       # Pest
composer analyse    # PHPStan, level 8, no baseline
composer lint       # Pint
```

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md) for what has changed recently.

Contributing
------------

[](#contributing)

Pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). You do not need macOS or a desktop runtime to work on this package.

Security
--------

[](#security)

If you find a vulnerability that could expose clipboard contents, please email  rather than opening a public issue. See [SECURITY.md](SECURITY.md).

Credits
-------

[](#credits)

- [Ikromjon Ochilov](https://github.com/Ikromjon1998)
- [All contributors](https://github.com/Ikromjon1998/laravel-clipboard-core/contributors)

Built on [NativePHP](https://nativephp.com), which makes desktop applications in PHP possible in the first place.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

20

—

LowBetter than 12% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 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.

### Community

Maintainers

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

---

Top Contributors

[![Ikromjon1998](https://avatars.githubusercontent.com/u/57859849?v=4)](https://github.com/Ikromjon1998 "Ikromjon1998 (6 commits)")

---

Tags

clipboardclipboard-managerlaravellaravel-packagenativephpphp

### Embed Badge

![Health badge](/badges/ikromjon-laravel-clipboard-core/health.svg)

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

PHPackages © 2026

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