The framework thatgets out of your way

With zero configuration and zero boilerplate, Tempest gives you the architectural freedom to focus entirely on your business logic.

Get started
Zero-configuration with code discovery

Tempest scans your code and instantly registers routes, view components, console commands, middleware and more. It doesn’t need hand-holding; it just works.

src/Books/BookController.php
final readonly class BookController
{
    #[Post('/books')]
    public function store(CreateBookRequest $request): Response
    {
        $book = map($request)->to(Book::class)->save();

        return new Redirect(uri([self::class, 'show'], book: $book->id));
    }
}
src/Books/x-book.view.php
<article>
  <h1>{{ $book->title }}</h1>
  {!! $book->body !!}
</article>
src/Books/BookObserver.php
final readonly class BookObserver
{
    #[EventHandler]
    public function onBookPublished(BookPublished $event): void
    {
        // …
    }
}
A refreshing new template engine

Tempest reimagines templating in PHP with a clean front-end engine, inspired by modern front-end frameworks.

Whether you love our modern syntax or prefer the battle-tested reliability of Blade and Twig, Tempest has you covered.

src/x-base.view.php
<!DOCTYPE html>
<html lang="en" class="h-dvh flex flex-col">
  <head>
    <!-- Conditional elements -->
    <title :if="isset($title)">{{ $title }} — Books</title>
    <title :else>Books</title>
    <!-- Built-in Vite integration -->
    <x-vite-tags />
  </head>
  <body class="antialiased flex flex-col grow">
    <x-slot /> <!-- Main slot -->
    <x-slot name="scripts" /> <!-- Named slot -->
  </body>
</html>
src/Books/index.view.php
<x-base :title="$this->seo->title">
  <ul>
    <li :foreach="$this->books as $book">
      <!-- Title -->
      <span>{{ $book->title }}</span>

      <!-- Metadata -->
      <span :if="$this->showDate($book)">
        <x-badge variant="outline">
          {{ $book->publishedAt }}
        </x-badge>
      </span>
    </li>
  </ul>
</x-base>
A truly decoupled ORM

Models in Tempest embrace modern PHP and are designed to be decoupled from the database; they don’t even have to persist to the database and can be mapped to any kind of data source.

src/Books/Book.php
final class Book
{
    #[Length(min: 1, max: 120)]
    public string $title;

    public ?Author $author = null;

    /** @var \App\Books\Chapter[] */
    public array $chapters = [];
}
$book = query(Book::class)
    ->select()
    ->where('title', 'Timeline Taxi')
    ->first();

// …

$json = map($book)->toJson();
Console applications reimagined

Console commands are automatically discovered and use PHP’s type system to define arguments and flags.

No need to search the documentation to remember the syntax, just write PHP.

src/Books/FetchBookCommand.php
final readonly class FetchBookCommand
{
    public function __construct(
        private BookRepository $repository,
        private Isbn $isbn,
        private Console $console,
    ) {}
    
    #[ConsoleCommand(description: 'Synchronize a book from ISBN by its title')]
    public function __invoke(string $title, bool $force = false): void 
    {
        $data = $this->isbn->findByTitle($title);

        if (! $data) {
            $this->console->error("No book found matching that title.");
            return;
        }

        $book = map($data)->to(Book::class);

        if ($this->repository->exists($book->isbn13) && ! $force) {
            $this->console->info("Book already exists.");
            return;
        }

        $this->repository->save($book);
        $this->console->success("Synchronized {$book->title}.");
    }
}
And much, much more.

Configuration objects for easy autocompletion and injection, data mapping, a powerful dependency container with autowiring. Tempest is designed to be frictionless.

src/sqlite.config.php
return new SQLiteConfig(
    path: env('DB_PATH', __DIR__ . '/../database.sqlite'),
);
src/Blog/MarkdownInitializer.php
final readonly class MarkdownInitializer implements Initializer
{
    #[Singleton]
    public function initialize(Container $container): MarkdownConverter
    {
        $highlighter = new Highlighter(new CssTheme())
            ->addLanguage(new TempestViewLanguage());
        
        $environment = new Environment()
            ->addRenderer(Code::class, new CodeBlockRenderer($highlighter));

        return new MarkdownConverter($environment);
    }
}
>_ ./tempest static:generate
/framework/01-getting-started .. /public/framework/01-getting-started/index.html
/framework/02-the-container ...... /public/framework/02-the-container/index.html
/framework/03-controllers .......... /public/framework/03-controllers/index.html
/framework/04-views ...................... /public/framework/04-views/index.html
/framework/05-models .................... /public/framework/05-models/index.html