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.

Laravel About 5 minutes Banimark 0.32.1 Composer package

What you are about to do

  1. Check the server has PHP and the ionCube loader
  2. composer require banimark/banimark
  3. php artisan banimark:install
  4. 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

NeedsWhy
PHP 8.2 or newerThe 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 loaderThe 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.
ComposerHow 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:

terminal
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.

check only - changes nothing
curl -fsSL https://banimark.com/setup.sh | bash -s -- --check

Happy with what it says? Run it for real:

Linux, macOS, cPanel / Plesk servers
curl -fsSL https://banimark.com/setup.sh | bash
Windows (PowerShell)
irm https://banimark.com/setup.ps1 | iex

What it does, in order:

  1. 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.
  2. Checks PHP 8.2+, the json, curl, pdo, mbstring and sodium extensions, a database driver, and zip (for one-click updates). Where it can, it offers to install or switch on what is missing.
  3. 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.
  4. 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

terminal
# 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:

terminal
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 with dnf 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

terminal
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'.

resources/views/layouts/app.blade.php
<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.

app/Http/Controllers/AnyController.php
$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:

resources/views/layouts/app.blade.php
<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

terminal
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:

.env
BANIMARK_ADMIN_PATH=support/control
BANIMARK_WIDGET_PATH=help
terminal
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

terminal
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:

terminal
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:

routes/console.php
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:

.env
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

  1. AI Providers — add your own key. Gemini, OpenAI, Anthropic, DeepSeek, Groq, Mistral, OpenRouter, xAI, or a local Ollama / LM Studio through a base URL.
  2. Rules — plain sentences in folders, not prompt engineering. “Never promise a refund date.” Switch a folder off without deleting it.
  3. 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.
  4. Staff — invite your team. Banimark has its own logins, separate from your application's users, with per-person permissions and optional 2FA.
  5. 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 utf8mb4 and 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?

Stuck on a step?

Send us what banimark:doctor prints — it tells us more in one paste than a paragraph ever does. Or hand the whole thing to us and we will finish it for you.