Adds floating / concurrent software license pools to Snipe-IT: a license's seat count becomes a pool of concurrent-use slots that any number of users can share, with the license cost spread across the people actually using it — the Enscape-style case (17 seats, 35 users, everyone shares, cost split across all 35).
Notice: this plugin's code was AI-generated (with human review and a passing test suite of 105 tests). It is provided as-is, use at your own risk / use as needed. Test it on a staging copy of your Snipe-IT before deploying to production.
- Floating / concurrent pools attached 1:1 to existing licenses —
licenses.seatsis the pool size,licenses.purchase_costthe per-unit cost (total cost = per-unit × seats). - Master switch in Admin → Settings → General ("Floating Licenses (addon)") — a global gate only: when OFF the addon is fully inert (UI hidden, web/API routes 403).
- Strictly opt-in per license: a license behaves floating only when the Floating / Concurrent checkbox on its edit form is enabled (off by default), which persists a pool config with customizable cost mode / over-allocation. Licenses without a config stay plain fixed-seat licenses everywhere.
- Over-allocation support (on by default for floating-enabled licenses): more concurrent users than seats, with clear over-allocation indicators.
- Cost distribution modes:
pool_slot(fixed price per slot) andactive_user(total cost spread across currently active users). Total cost is the license's per-unit Purchase Cost × Seats (recalculated automatically). - Bulk add / bulk remove users on the license page — for floating licenses (allocations) and fixed licenses (real core seat checkout/checkin, mixed state handled).
- Per-license user export / import (CSV): export all assigned users (seat
checkouts + floating allocations, with an
assignment_typecolumn) and import users from a one-username-or-email-per-line CSV — floating licenses allocate pool slots, standard licenses claim seats. - Master export: one CSV of every license with full details plus one row per assigned user, from the Floating Licenses index page.
- Convert back to standard:
floating-licenses:convert-to-standardturns floating licenses into normal seat-based licenses, keeping every assigned user as a core seat checkout (with--dry-runpreview and--except=skips). - Single-checkout interception: core's normal checkout flow on a floating license allocates a pool slot instead of occupying a seat record; the checkout page shows floating-aware availability and an exhaustion callout.
- Availability-aware UI: licenses index
Avail/% Remaining, the license info panel "Remaining" row, and per-seat checkout buttons are all corrected for floating pools. - Audit logging into Snipe-IT's standard activity report, REST API
endpoints, optional lease durations/expiration (
floating-licenses:expire).
- Snipe-IT v8.x (developed and tested against v8.7.2 live and current
develop) - PHP >= 8.2
- No additional Composer packages beyond Snipe-IT's own
cd /path/to/snipe-it
# 1. Copy the package in
mkdir -p packages
cp -r /path/to/snipe-it-floating-license-plugin/package packages/floating-licenses
# 2. Wire composer.json (or run install.sh, which does 1+2+3 for you):
# - add a path repository { "type": "path", "url": "packages/floating-licenses", "options": { "symlink": true } }
# - add "snipe-it/floating-licenses": "@dev" to "require"
# - add "SnipeIt\\FloatingLicenses\\Tests\\": "packages/floating-licenses/tests/"
# to "autoload-dev" → "psr-4" (only needed to run the test suite)
# 3. Apply the core patches
git apply /path/to/snipe-it-floating-license-plugin/core-patches/01-licenses-controller.patch
# ... etc. If a patch fails, core-patches/README.md shows each small manual
# edit with its anchor. Apply 02 OR 02b depending on your checkout field names:
grep -n "assigned_to" app/Http/Controllers/Licenses/LicenseCheckoutController.php
# assigned_to present (v8.7.2) → 02b; assigned_user (develop) → 02
# 4. Register the package
composer update snipe-it/floating-licenses --no-interaction
# If composer fails on github.com authentication:
# composer config use-github-api false
# composer update snipe-it/floating-licenses --no-interaction
# 5. Migrate + clear caches
php artisan migrate --force
php artisan optimize:clearOr let the helper do steps 1–3:
/path/to/snipe-it-floating-license-plugin/install.sh /path/to/snipe-itThe package can also live anywhere on disk — point the composer path
repository's url at it instead of copying.
Copy package/ into packages/floating-licenses, edit composer.json as in
step 2 above, and make the twelve core edits by hand — core-patches/README.md
lists every hunk with its anchor text. Then steps 4–6 as above.
Mount or copy the package into the container and apply the core edits in your image build, then run the same steps inside the container, e.g.:
COPY snipe-it-floating-license-plugin/package /var/www/html/packages/floating-licenses
# apply core edits (COPY pre-patched files or RUN patch) — see core-patches/README.md
RUN composer update snipe-it/floating-licenses --no-interactionMigrations run at container start (php artisan migrate --force) as usual for
Snipe-IT Docker deployments.
- Admin → Settings → General → "Floating Licenses (addon)" — turn the master switch on.
- Grant the six
floating_licenses.*permissions to your groups (they appear under a "Floating Licenses" section on the group/user permission pages).
Floating is opt-in per license: edit the license and tick the Floating / Concurrent checkbox (off by default). The section also offers a cost-mode select and an over-allocation checkbox. Pool size and total cost always come from the license's own fields (total = per-unit Purchase Cost × Seats). Unchecking soft-deletes the config (only while no allocations are active).
- Bulk User Actions dropdown on the license page: Bulk Add Users / Bulk Remove Users. Floating licenses create/release allocations; fixed licenses use the exact core seat checkout/checkin mechanism (a license in a mixed state — floating allocations plus legacy seat checkouts — is cleaned up by one bulk-remove).
- Single checkout via core's checkout button allocates a pool slot (no seat record); a full pool without over-allocation refuses with an error.
- Checkin per floating user from the Assigned tab's floating table.
The Bulk User Actions dropdown on a license's view page offers Export
assigned users (CSV) — both seat checkouts and floating allocations, with an
assignment_type column — and Import users (CSV): upload a file with one
username or email per line; floating licenses allocate pool slots, standard
licenses claim seats, and unknown identifiers / seat exhaustion are reported
in the result flash.
The Export full (with users) button on the Floating Licenses index page
(/floating-licenses) downloads one master CSV containing every license with
its full information (company, product key, seats, cost, dates, manufacturer,
category, floating yes/no + pool size) plus one row per assigned user.
A $total-cost pool shared by N active users (total cost = the license's per-unit Purchase Cost × its Seats / pool size):
active_user— cost spread across active users. 17-seat pool, $X purchase cost, 35 users assigned → each active allocation carries $X/35. Recalculated on every allocate/release.pool_slot— fixed $X/pool_size per allocation regardless of usage.
When allowed, allocations may exceed the pool size. The UI flags this ("Over-allocated: 46 assigned / 35 seats (+11)") in the Assigned tab, the info panel, and the licenses index (Avail goes negative). It is purely informational — nothing is blocked.
Under /api/v1/, standard Snipe-IT token auth and response envelope; 403
while the master switch is off.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
POST |
/api/v1/floating-licenses/{license}/allocate |
floating_licenses.allocate |
Allocate a slot. Body: user_id (required), asset_id, notes. 422 when the pool is exhausted. |
GET |
/api/v1/floating-licenses/{license}/availability |
floating_licenses.view |
Pool stats incl. over_allocated / excess. |
POST |
/api/v1/floating-license-allocations/{allocation}/heartbeat |
own (allocate) or release |
Extend the lease (no-op for pools without a lease duration). |
POST |
/api/v1/floating-license-allocations/{allocation}/release |
own (allocate) or release |
Release the slot. |
| Command | Purpose |
|---|---|
php artisan floating-licenses:expire |
Expire stale allocations. Only pools that have lease/idle durations set are affected; schedule it (e.g. $schedule->command('floating-licenses:expire')->everyFiveMinutes(); in app/Console/Kernel.php) only if you use durations. Durations are null (never expire) by default. |
php artisan floating-licenses:convert-to-standard |
Convert every floating license back to a standard seat-based license while keeping all assigned users: each active allocation becomes a core seat checkout (seats are expanded automatically when more users are assigned than the seat count), allocations are marked released for history, and the pool config is removed. Licenses whose name contains rhino are skipped by default — override with --except=; preview with --dry-run. |
| Permission | Grants |
|---|---|
floating_licenses.view |
Pool list/detail pages; API availability. |
floating_licenses.manage |
Enable/edit/disable pool configurations. |
floating_licenses.allocate |
Allocate; bulk-add; release/heartbeat own allocations. |
floating_licenses.release |
Release/heartbeat any allocation; bulk-remove. |
floating_licenses.costs |
Cost visibility (totals, per-user costs). |
floating_licenses.history |
Allocation history panel. |
floating.allocate / floating.release / floating.revoke /
floating.expire / floating.heartbeat / floating.capacity_exceeded rows
in action_logs (visible in the standard activity report); bulk operations on
fixed licenses log normal core checkout/checkin entries.
Nine small, guarded edits (full details + anchors in core-patches/README.md):
| Core file | Change |
|---|---|
app/Http/Controllers/Licenses/LicensesController.php |
One sync call in store() and update(). |
app/Http/Controllers/Licenses/LicenseCheckoutController.php |
Floating interception hunk at the top of store() (02 = develop field names, 02b = v8.7.2 field names). |
app/Http/Controllers/SettingsController.php |
One line in postSettings(). |
app/Http/Transformers/LicensesTransformer.php |
Floating-aware avail/percent in the licenses index (2 grouped queries, no N+1). |
app/Http/Transformers/LicenseSeatsTransformer.php |
Suppress per-seat checkout for floating licenses. |
resources/views/licenses/edit.blade.php |
Floating section in the license form. |
resources/views/licenses/view.blade.php |
Info rows, floating-users table + checkin buttons, bulk dropdown, Available-tab callout, Remaining-row override. |
resources/views/licenses/checkout.blade.php |
Floating-aware availability + exhaustion callout. |
resources/views/settings/general.blade.php |
Master-switch checkbox. |
Upgrade/re-apply: after a Snipe-IT upgrade, take upstream versions of
conflicting files and re-apply the matching patch or manual hunk
(// [floating-licenses addon] BEGIN/END markers), then
composer update snipe-it/floating-licenses && php artisan migrate --force && php artisan optimize:clear.
php artisan migrate:rollback --path=packages/floating-licenses/database/migrations(drops the floating tables' data and the settings column).- Remove the twelve core edits (reverse the patches:
git apply -R). - Remove the composer.json entries (path repository,
require,autoload-devmapping) andpackages/floating-licenses. composer update snipe-it/floating-licenses --no-interaction && php artisan optimize:clear
Fixed-seat licenses, seats, and history are never touched.
The suite (105 tests, 347 assertions as of writing) runs against the host
Snipe-IT's test-suite conventions (LazilyRefreshDatabase, factories,
actingAsForApi()). With the autoload-dev mapping installed:
vendor/bin/phpunit --testsuite FloatingLicensesNote: the checkout-interception tests assume the v8.x request field name
assigned_to. When testing against develop (which uses assigned_user),
adjust with:
sed -i "s/'assigned_to' =>/'assigned_user' =>/" packages/floating-licenses/tests/Feature/FloatingCheckoutInterceptionTest.php(run the reverse when targeting v8.7.2 from a develop-based tree).
Same as Snipe-IT: AGPL-3.0.