Leveling Up With Tinker: Configuration

/ Content

Last time we went through the commands and features that make Tinker feel like a real tool instead of a bare REPL. This time we're getting into the config file. PsySH has a configuration system that most people don't know about, and once you set it up, Tinker starts working the way you always wished it did.

The .psysh.php file

PsySH looks for a .psysh.php file in your project root when it boots. If it finds one, it executes it before the shell starts. That's it. Create the file, drop in your config, and Tinker picks it up automatically.

The file returns an array of configuration options, but here's the thing that makes it powerful: it's just PHP. Any code you put in there runs before Tinker's prompt shows up. That means you can register class aliases, set up event listeners, configure the shell's behavior, or anything else you'd want to happen every single time you open Tinker. Laravel already handles a lot of the PsySH defaults for you (like defaultIncludes and casters), so the project-level config is where you layer on your own preferences.

Here's the basic skeleton:

<?php

// Any PHP code here runs before Tinker starts

return [
    // PsySH config options go here
];

Everything above the return statement executes as regular PHP. Everything in the returned array configures PsySH. Both are optional. You can have a config file that only runs setup code with no return, or one that only returns config with no setup code.

Class aliases

This was the first thing I put in my config and it's still the one that saves me the most keystrokes. In Tinker, you can't just type Carbon::now(). You either need to run use Carbon\Carbon first, or type the fully qualified class name every time:

> Carbon::now();
  ERROR  Class "Carbon" not found.

> \Carbon\Carbon::now();
= Carbon\Carbon @1711234567 {#1234
    date: 2026-03-26 12:00:00.0 UTC (+00:00),
  }

Neither option is great when you're using Carbon 30 times in a session. Instead, put class_alias() calls in your config file and they're available the second Tinker opens:

<?php

class_alias(\Carbon\Carbon::class, 'Carbon');
class_alias(\GuzzleHttp\Client::class, 'Guzzle');

return [];

Now Carbon::now() just works. Same with new Guzzle() instead of new \GuzzleHttp\Client(). Tinker already aliases a lot of common classes for you (like Str, Arr, DB, and your app's models), so you really only need this for third-party classes or deep namespaces that Laravel doesn't cover. I add aliases for whatever classes I find myself typing out fully qualified more than a few times. Every project ends up with a slightly different list depending on what I'm working with.

Hooking into Laravel at boot

Since the config file runs PHP before Tinker starts, and Laravel is already booted at that point, you have access to the full framework. This is where things get fun.

The first thing I ever put in a config file was a query listener. I wanted to see every SQL query that Tinker was running, because I kept writing Eloquent chains that looked clean but were generating terrible queries behind the scenes. You can tap into DB::listen() right in the config:

<?php

use Illuminate\Support\Facades\DB;

class_alias(\Carbon\Carbon::class, 'Carbon');
class_alias(\GuzzleHttp\Client::class, 'Guzzle');

DB::listen(function ($query) {
    $sql = $query->sql;

    foreach ($query->bindings as $binding) {
        $value = is_numeric($binding) ? $binding : "'{$binding}'";
        $sql = preg_replace('/\?/', $value, $sql, 1);
    }

    $time = number_format($query->time, 2);

    echo "\n\033[36m{$sql}\033[0m";
    echo "\n\033[90m-- {$time}ms\033[0m\n";
});

return [];

Now every query shows up inline as you work:

> User::where('email', 'like', '%@example%')->first();

select * from `users` where `email` like '%@example%' limit 1
-- 1.24ms

= App\Models\User {#5678
    id: 42,
    email: "jane@example.com",
    ...
  }

The ANSI color codes give you cyan for the SQL and gray for the timing. Its nothing fancy, but it makes it dead obvious what's happening behind the scenes. I've caught N+1 queries, unnecessary joins, and missing indexes just by having this running passively while I work in Tinker.

You can do other things too. Register a macro, seed some test data, print a reminder about the environment you're connected to. Anything you can do in a Laravel service provider, you can do here.

Config options

The returned array is where you configure PsySH's behavior. Here are the options I actually use:

Option What it does Example
theme Sets the shell's color theme. Options are modern, compact, and classic. 'theme' => 'compact'
historySize Max number of history entries. 0 means unlimited. 'historySize' => 1000
prompt The input prompt string. 'prompt' => '🔥 > '
pager The output pager. Set to cat or false to disable paging, or less for the default. We covered pagers in the previous post. 'pager' => 'cat'
startupMessage A message displayed when the shell starts. Supports Symfony Console color tags. 'startupMessage' => '<info>Connected!</info>'
useBracketedPaste Enables bracketed paste, which prevents the shell from executing code as you paste it line by line. If you paste multi-line code and it runs prematurely, turn this on. Requires readline (not libedit). 'useBracketedPaste' => true
updateCheck Controls the "new version available" check. Set to false to kill the nag. 'updateCheck' => false
commands An array of custom command classes to register. We'll cover this in the next post. 'commands' => [...]

A full config file pulling a few of these together might look like:

<?php

class_alias(\Carbon\Carbon::class, 'Carbon');
class_alias(\GuzzleHttp\Client::class, 'Guzzle');

return [
    'theme' => 'compact',
    'pager' => 'cat',
    'historySize' => 1000,
    'useBracketedPaste' => true,
    'updateCheck' => false,
    'startupMessage' => '<fg=cyan>Tinker loaded. Happy debugging.</>',
];

For the full list of every config option, check the PsySH Config Options wiki. Most of them you'll never touch, but it's good to know what's available.

What's next

In the next post, we're getting into custom commands. PsySH lets you register your own commands that show up right alongside help, history, and wtf. Think project-specific shortcuts like resetting a user's password, toggling feature flags, or dumping the current queue state, all from a single command. That's where Tinker starts feeling less like a debugging tool and more like a control panel for your app.