Install Banimark in a Laravel app
Add a self-hosted AI support desk to a Laravel app in about five minutes: composer require, php artisan banimark:install, embed the widget. Your data never leaves your server.
What you are about to do
- Check the server has PHP and the ionCube loader
- composer require banimark/banimark
- php artisan banimark:install
- Paste one line into your layout
You do this once. From then on, new versions install themselves from a button in your own Banimark admin — no terminal, no developer, and a way back if you need one. How updating works.
Laravel is Banimark's first-class home: a service provider wires the engine, migrations create
its tables, an artisan command does the setup and the admin panel mounts itself on
/banimark/admin. Nothing to scaffold, nothing to compile.
Before you start
| Needs | Why |
|---|---|
| PHP 8.2 or newer | The engine ships encoded for PHP 8.2, and an 8.2 build runs on 8.2 and later — it will not run on 8.1 at all. We test on 8.2 and 8.3. |
| ext-curl, ext-json, PDO plus a driver: pdo_mysql, pdo_pgsql or pdo_sqlite | Calling your AI provider, and talking to your database. |
| The ionCube loader | The engine ships encoded, so the free loader has to be active. Without it, Banimark shows a page saying exactly what to install — the rest of your site keeps working. How to install it. |
| A database MySQL 5.7+/8, MariaDB 10.4+, PostgreSQL 12+, or SQLite | Banimark keeps its own banimark_* tables. It never writes to yours. |
| Composer | How the package is installed. Banimark is not on Packagist; each guide shows the one line that tells Composer where it lives. |
| HTTPS (strongly recommended) | Chat transcripts and the signed visitor token travel over it. |
No queue worker, no Redis, no Node build step, no cron job required.
Install the ionCube loader
Your licensed build of Banimark ships encoded, so the ionCube PHP loader has to be active before anything will run. Most commercial hosts already have it — check first:
php -v
# ...with the ionCube PHP Loader <- you are done, skip this section
If that line is there, skip the rest of this section entirely. If it is missing, the setup script below does the whole job for you.
The quick way: run the setup script
One command checks everything Banimark needs — PHP version, extensions, the ionCube loader, Composer, whether this server can reach us — and installs the loader if it is missing. It works out your operating system, processor, PHP version and control panel by itself, and asks before it changes anything.
Look before you leap. Run it with --check first: it reports exactly what it
finds and what it would do, and changes nothing at all. Safe on a live server.
curl -fsSL https://banimark.com/setup.sh | bash -s -- --check
Happy with what it says? Run it for real:
curl -fsSL https://banimark.com/setup.sh | bash
irm https://banimark.com/setup.ps1 | iex
What it does, in order:
- Finds every PHP on the server and asks which one your website uses — on cPanel, Plesk, XAMPP or a Mac with MAMP there is usually more than one.
- Checks PHP 8.2+, the
json,curl,pdo,mbstringandsodiumextensions, a database driver, andzip(for one-click updates). Where it can, it offers to install or switch on what is missing. - Downloads the official loader from ionCube for your exact PHP, copies it in, and adds it to PHP's configuration so it loads first — backing up any file it changes. It then starts PHP to prove it works, and puts everything back if it does not.
- Offers to restart PHP-FPM / Apache / LiteSpeed, and to check the PHP your website actually runs (a tiny, randomly named file in your public folder, read once and deleted).
The script is plain text — read it (or the Windows one) before you run it. On shared hosting, where nobody can change PHP from a terminal, it tells you where the switch is in your panel instead.
Prefer to do it by hand, or need to send it to whoever runs your server? The manual steps:
How to install the ionCube loader
The loader is a single .so that must match your exact PHP minor version
— the current archive carries builds for PHP 8.1 through 8.5.
On your own Linux server
# 1. which PHP, and where do its extensions live?
php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION."\n";' # e.g. 8.3
php -r 'echo ini_get("extension_dir")."\n";' # e.g. /usr/lib/php/20230831
# 2. fetch the loaders (use ioncube_loaders_lin_aarch64.tar.gz on ARM servers)
cd /tmp
curl -fsSLO https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64.tar.gz
tar xzf ioncube_loaders_lin_x86-64.tar.gz
# 3. copy the ONE that matches your PHP
PHPMM=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
sudo cp "ioncube/ioncube_loader_lin_${PHPMM}.so" "$(php -r 'echo ini_get("extension_dir");')/"
Now switch it on. On Debian and Ubuntu, PHP reads a directory of .ini files, and the
loader has to be first — the 00- prefix is what guarantees it loads before OPcache:
PHPMM=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
echo "zend_extension=ioncube_loader_lin_${PHPMM}.so" | sudo tee /etc/php/${PHPMM}/mods-available/00-ioncube.ini
sudo ln -sf /etc/php/${PHPMM}/mods-available/00-ioncube.ini /etc/php/${PHPMM}/fpm/conf.d/00-ioncube.ini
sudo ln -sf /etc/php/${PHPMM}/mods-available/00-ioncube.ini /etc/php/${PHPMM}/cli/conf.d/00-ioncube.ini
sudo systemctl restart php${PHPMM}-fpm
php -v # must now say "with the ionCube PHP Loader"
On RHEL, Rocky and AlmaLinux the equivalent file is /etc/php.d/00-ioncube.ini,
then sudo systemctl restart php-fpm.
On cPanel, Plesk or shared hosting
- cPanel — Select PHP Version → Extensions, tick
ioncube_loader. From a WHM shell, find EasyApache's package for your PHP withdnf list available 'ea-php83-php-ioncube*'and install the newest one — Banimark needs loader 15 or newer. The setup script does this for you. - Plesk — the loader ships with Plesk's own PHP builds; enable it under Websites & Domains → PHP Settings.
- Anything else — ask your host to “enable the ionCube PHP loader”. It is a standard request and almost every commercial host supports it.
How you will know
You do not have to guess. If the loader is missing, or PHP has not been restarted since you added
it, every Banimark admin page shows “Banimark needs the ionCube Loader” with these
steps, the chat widget politely says support is temporarily unavailable, and the rest of your
site carries on as normal. On Laravel, php artisan banimark:doctor says the same thing
in the terminal. Once it is fixed, reload — nothing needs reinstalling.
Install
composer require "banimark/banimark:^0.32"
php artisan banimark:install
banimark:install publishes config/banimark.php, creates the
banimark_* tables, writes an identity secret into your .env and asks
for your first admin account. It is idempotent — re-run it whenever you like.
Then open /banimark/admin, enter your licence key and add an AI
provider.
Embed the widget
One line in your layout, just before </body>. It is a same-origin script, so a
strict Content-Security-Policy needs nothing more than script-src 'self'.
<script src="{{ url('banimark/widget.js') }}" defer></script>
Colour, position, greeting, theme and reply polling are all set in the panel under Widget — no redeploy to change them.
Somewhere a script cannot go
For email signatures, QR codes and SMS, Widget → Share as a link gives the same chat as
a full page at /banimark/chat-page.
Let the AI answer about the logged-in customer
Anonymous visitors get general answers. To let the assistant look up this customer's orders, tickets or invoices, your Laravel app mints a short-lived signed token and the widget carries it. The token is made on your server with a shared secret; the AI never sees it, and it cannot choose whose records to read — the scoping comes from the claims you signed.
An invalid or expired token quietly means “anonymous”. It is never an error a visitor can probe.
$token = \Banimark\Identity\VisitorToken::mint(
['user_id' => auth()->id(), 'name' => auth()->user()->name],
config('banimark.identity_secret')
);
Hand it to the widget and every tool the assistant runs is scoped to that user, server-side:
<script src="{{ url('banimark/widget.js') }}"
data-token="{{ $token }}" defer></script>
A tool declares :_user_id as an identity placeholder; the value comes
from the claims above, never from the model. A tool with no identity context refuses to run
rather than running unscoped.
Confirm the install
php artisan banimark:doctor
It checks PHP and its extensions, the database and schema version, the ionCube loader, the licence, your provider and the file store — and prints what to do about anything red. Send its output with any support request.
Your own addresses (optional)
By default the panel is at /banimark/admin and the visitor side (the widget, the
chat and the shareable chat link) at /banimark/.... On Laravel you do not need
.htaccess for this — Laravel owns its routes, so you just tell Banimark where
to live. Add to your .env:
BANIMARK_ADMIN_PATH=support/control
BANIMARK_WIDGET_PATH=help
php artisan config:clear
php artisan route:clear # or route:cache if you cache routes
Now the panel is at /support/control, the widget at /help/widget.js and
the chat link at /help/chat-page. The widget and the chat link pick up the new
addresses by themselves; update the script tag in your layout (or use
route('banimark.widget'), which always gives the right address). The old
/banimark/... addresses stop answering.
Already published config/banimark.php before this option existed? Set
'prefix' under 'admin' and 'widget' in that file instead,
or add env('BANIMARK_ADMIN_PATH', 'banimark/admin') there. Use plain words and
slashes only. For the Flutter app:
BanimarkConfig.laravel('https://yourapp.com', path: 'help').
Updating
From the panel, with no terminal
Open /banimark/admin → Changelog. When a release is
available you get an Update to … button, and then an Update the
database button. Banimark downloads the release, checks it is byte-for-byte the file we
signed, keeps a copy of your current version — just the one, so updates never pile up on
your disk — and swaps it over, with a Restore this one button if you want it back.
Check for updates now and Reinstall are on the same page. That is there for
whoever runs the business, not the person who deploys it.
From the command line
composer require "banimark/banimark:^0.32"
php artisan migrate
Not composer update: on 0.x, Composer treats each minor release as a
breaking one, so ^0.25 never installs 0.26. composer update picks up fixes
within your line; composer require with the new line moves you up. The newest line
right now is ^0.32.
This is the one part of Laravel Banimark leans on. Every release that needs new columns ships a
migration, and the package loads its own migrations, so php artisan migrate finds
them with nothing published and nothing to copy.
The migration is a single call to Schema::ensureCurrent(), which compares the
schema_version row in banimark_settings with the version now on disk
and brings the database up to it. Additive only: CREATE TABLE IF NOT EXISTS,
ALTER TABLE … ADD COLUMN, any missing index, and a conversion to
utf8mb4 if your tables predate it. Nothing is dropped and nothing outside
Banimark's own banimark_ prefix is touched, so it is safe to re-run.
Forgot to migrate? The doctor says so in as many words:
php artisan banimark:doctor
# schema up to date for 0.32.1 ... run: php artisan migrate
Not using Laravel anywhere else? The standalone guide shows how the same function is called without a migration runner.
Optional: retention on a schedule
If you set a retention policy under Data & protection, give it a scheduler entry so old conversations and their files really do get deleted:
Schedule::command('banimark:prune')->daily();
Your licence
A fresh install asks us for a free trial on its own — 7 days, one per site . When it ends, the admin panel locks until you enter a purchased key. Your chat widget is never affected: visitors carry on being served whatever your licence says.
Enter the key under License in the panel, or set it before first boot:
BANIMARK_LICENSE_KEY=BM-XXXX-XXXX-XXXX-XXXX
Activation needs one call to our servers. After that the licence is good offline for 14 days at a time, so a network problem on your side never interrupts anything.
First five minutes in the panel
- AI Providers — add your own key. Gemini, OpenAI, Anthropic, DeepSeek, Groq, Mistral, OpenRouter, xAI, or a local Ollama / LM Studio through a base URL.
- Rules — plain sentences in folders, not prompt engineering. “Never promise a refund date.” Switch a folder off without deleting it.
- Tools — the read-only lookups the assistant may run against your database. Pick a table, tick the columns it may show, and scope every row to the signed-in visitor. SELECT only, always bound, never widened by the AI.
- Staff — invite your team. Banimark has its own logins, separate from your application's users, with per-person permissions and optional 2FA.
- Notifications — SMTP, so a handover reaches someone.
Check it over
Before you call it done:
- Open the panel and confirm the licence page is green.
- Send a message through the widget on a real page — not just curl. The widget is JavaScript; a curl check proves nothing about it.
- Type an emoji. If it comes back, your database is
utf8mb4and you are fine. - Ask something that needs a tool, while signed in, and confirm it returns your rows.
- Trigger a handover and confirm the email lands.
Not your stack?
Custom PHP
One file in your web root and a browser installer. No framework, no build step - and Composer optional.
Read the guide →CodeIgniter
The standalone runtime inside CodeIgniter 4's public folder, with the token minted from your session.
Read the guide →Node.js
Banimark is PHP. Run it beside your Node app and mount it on the same origin with nginx.
Read the guide →Python
Same shape as Node: a small PHP service behind your Django, Flask or FastAPI app.
Read the guide →Docker
No PHP on the server? Bring your own: PHP-FPM, nginx and MySQL in three containers.
Read the guide →