karkowg headjar
Articles » Tinkertown
March 6, 2025

Tinkertown

I've admired Val Town from the jump. The idea is simple: write a JavaScript function, hit save, and you've got a live HTTP endpoint. No servers to configure, no deployment pipelines. Just code and a URL.

Steve Krouse and the Val Town team built something that feels like magic for the JS ecosystem. It made me wonder: why doesn't something like this exist for PHP? Laravel developers love building things quickly. What if we could write a snippet and have it deployed instantly?

So I built Tinkertown. Tinker big, reach for the clouds.

What It Does

At its core, Tinkertown lets you write "vals" - lightweight PHP functions that become HTTP endpoints. Think of them as Single File Snippets, inspired by Vue's Single File Components. Everything you need lives in one place.

You write something like this:

return function (Request $request) {
    $name = $request->get('name', 'world');

    return response()->json([
        'message' => "Hello, {$name}!",
    ]);
};

Save it, give it a name, and it's instantly available at /v/your-slug. No controller files to create, no routes to register. The platform handles all of that behind the scenes.

There's also support for Fusion vals - hybrid Vue + PHP components - for when you need a bit of frontend sprinkled in. But that's a story for another time.

The Editor

The editing experience borrows from the SFC philosophy. You open a val, you see your code, you edit it. CodeMirror handles syntax highlighting. There are views for the editor and the preview. Nothing fancy.

Tech Stack

Under the Hood

The interesting part is how it compiles your code. When you save a val, Tinkertown parses your PHP using nikic/php-parser to build an Abstract Syntax Tree. We then walk that tree, extracting the pieces we need:

  • Use statements - your imports
  • Top-level variables - configuration or constants
  • Helper functions - closures assigned to variables like $format = fn($x) => ...
  • The main handler - the returned closure that handles the request

Here's a more complete example:

use Illuminate\Support\Str;

$greeting = 'Hello';

$format = fn(string $name) => Str::title($name);

return function (Request $request) use ($greeting, $format) {
    $name = $request->get('name', 'world');

    return response()->json([
        'message' => "{$greeting}, {$format($name)}!",
    ]);
};

That snippet becomes a full Laravel controller. Helper functions are converted to protected methods, and references like $format() are rewritten to $this->format():

class GreeterValController extends Controller
{
    protected function format(string $name)
    {
        return Str::title($name);
    }

    public function __invoke(Request $request)
    {
        $greeting = 'Hello';

        $name = $request->get('name', 'world');

        return response()->json([
            'message' => "{$greeting}, {$this->format($name)}!",
        ]);
    }
}

In the proof of concept, the compiler naïvely writes this controller to app/Http/Controllers/Vals/ and registers a route in routes/vals.php - all within the same Laravel installation.

Where It Was Headed

The naïve local file approach was a placeholder. The vision was integrating it with Laravel Cloud. Instead of writing controllers to disk, the compiler would deploy vals via their API. Write a function, deploy to the cloud, get a URL.

There was no API available when I built this, but that's where the tagline comes from: reach for the clouds.

Maybe one day. For now, it's a fun experiment in what sharing executable PHP snippets could feel like.