Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All documentation

Documentation

Matte

Connecting your apps

Updated

Matte is a provider: your other applications send it images. A Laravel app connects through the artisan-build/matte-client package, and Scalpels can do the wiring for any Laravel app you host on Laravel Cloud or Laravel Forge. Anything that is not a Laravel app calls the API directly; the client is a convenience, never a requirement.

Attach an app through Scalpels

Attaching is described in full in Attach to an existing app, including the preparation the Matte deployment needs and what each refusal means. For Matte specifically, attaching:

  • mints a credential for that app on your Matte instance;
  • writes MATTE_URL (the Matte environment's base URL) and MATTE_TOKEN (the credential) into the app's Cloud environment or Forge .env, along with BFC_CLIENT_IDENTITY, the id of the connection Scalpels recorded;
  • opens a draft pull request on the app's repository, on a branch named scalpels/connect-matte, that adds artisan-build/matte-client to composer.json and the two variable names to .env.example, with the token left blank.

MATTE_URL is the base URL only. The client appends /v1/remove and /v1/jobs/... itself.

Scalpels writes no other Matte settings. Every other client setting has a working default. The pull request touches no .gitignore, because the client writes nothing into your repository.

Scalpels cannot regenerate composer.lock. Check out the branch, run the composer update command shown in the pull request, commit the lock file, then review, merge and deploy. Scalpels never merges it for you.

You do not need to run php artisan matte:install. That command prompts for a URL and a token and writes them to .env, which is exactly what attaching has already done.

Use the client

Once the package is installed and the two values are set, call Matte through the facade:

use ArtisanBuild\MatteClient\Facades\Matte;
use ArtisanBuild\MatteClient\Jobs\AwaitRemovalJob;

// Submit and return straight away with a handle.
$handle = Matte::remove($request->file('photo'), ['mode' => 'grabcut', 'preset' => 'quality']);

// Watch the job on your own queue. Fires MatteRemovalCompleted when it finishes.
AwaitRemovalJob::dispatch($handle->id());

// Or block until it is done and take the PNG bytes.
$png = $handle->result();

// Or, for a small image, convert inside this request.
$png = Matte::removeSync($smallImage);

The image can be a file path, raw image bytes, an SplFileInfo or an UploadedFile. Options are the same as the API's: mode, preset, model, edge_mode, iterations and margin.

The handle

Matte::remove() returns a JobHandle:

  • id() returns the job id.
  • status() asks the server for the current status.
  • wait($timeout) polls until the job is done, and throws if it fails or the timeout passes. Without an argument it uses MATTE_POLL_TIMEOUT.
  • result() waits, then returns the PNG bytes.

Matte::status($jobId) and Matte::result($jobId) do the same by job id.

wait() sleeps between polls, so calling it inside a web request or a queue job holds that process for as long as the conversion takes.

The completion event

AwaitRemovalJob runs on your app's own queue. It polls the job and then dispatches ArtisanBuild\MatteClient\Events\MatteRemovalCompleted, whose public fields are jobId, status, path and error. Listen for that event to carry on with your own work.

  • If MATTE_STORE_DISK is set, the job saves the PNG to matte/<job-id>.png on that disk and puts that path in path.
  • If polling or the download throws, the job does not rethrow. It dispatches the event with a failed status and the error message instead.
  • When the event reports done, you can always fetch the bytes again with Matte::result($event->jobId).

Errors

The client throws ArtisanBuild\MatteClient\Exceptions\MatteException when MATTE_URL or MATTE_TOKEN is missing, when the image is not a supported type, when a job fails or times out, and when the server returns an unexpected status. The client does not retry on its own; decide retry behaviour in your app.

Verify the connection

Run one synchronous conversion in the connected app with a real image:

php artisan tinker --execute='$png = \ArtisanBuild\MatteClient\Facades\Matte::removeSync("/absolute/path/to/input.jpg"); file_put_contents(storage_path("app/matte-verify.png"), $png); dump(str_starts_with($png, "\x89PNG\r\n\x1a\n"), strlen($png));'

A working connection prints true followed by a positive byte count and writes storage/app/matte-verify.png. It uses the configured URL and credential without printing either.

A 401 here means the app is not holding a credential the Matte instance recognises. See Troubleshooting.

Apps Scalpels cannot attach

For a Laravel app hosted somewhere other than Laravel Cloud or Forge, mint a credential on the Matte instance as described in Using the API, then in the app run:

composer require artisan-build/matte-client
php artisan matte:install

matte:install prompts for the server URL and the credential, writes them to .env as MATTE_URL and MATTE_TOKEN, and publishes the client config. Run it in a terminal you control, because it asks for a secret.