Back to Insights

Laravel Packages: Creating My First - Part 1

I've been using PHP packages for years but never created one. Here's how I went from scratching an itch to scoping, structuring, and building a Laravel package from the ground up.

I've been using PHP packages for years...and before this I was adding the equivalent functionality into my repos as standalone classes (often massively bloated!), or before that a collection of procedural files. include_once() vs require_once() anyone?

Packages have been part of the PHP ecosystem for a long time! However, I hadn't ever created one myself, despite knowing that there were a number of problems I could solve in my own (and others) processes by doing so. So, finding my excuse to code, and eager to get back to developing in/for Laravel, I jumped in.

Scratching an itch

I knew that creating anything has to be rooted in either a great idea, or to address a problem. I opted for the latter. A package to help address the "dead time" developers sometimes have at the end of the day/week. This package would hop onto GitHub, show the repositories that were available (set by the dev via token perms) and allow the dev to quickly view, triage, start work. One key aspect which I knew I'd need to set out was speed. I wanted this to be a quick process. No additional work, just a quick flow to action.

This necessitated scoping the issues. There would be no point in the package showing issues which required days of work. The focus would be much narrower: smaller issues which addressed easy problems or incremental improvements on the platform. Tasks which could be completed in half an hour or less.

But how would the package integrate into the workflow, be easy and quick to use (there's only a short amount of time available!), and provide a useful addition to a developer's toolkit?

How would I use this package?

Enter the CLI. Laravel developers already run artisan commands, and likely also interact with docker either to tail logs or hop into containers. For me the CLI is home, where I do almost all my work. I use Neovim with LazyVim, tmux, ghostty as my dev environment, so remaining here was a good fit for me.

But what exactly should the package do? What did the devs need? What's the smallest amount of functionality that gets them there? I needed answers, so I started scoping things out.

What would it actually do?

Trying to figure out an MVP is hard work (as I've written about before), and even though this project was just a package, a lot of the process remains the same: identifying core functionality, plotting the quickest course to achieving this, logging the shortcomings and tradeoffs, creating a roadmap to pick these up or discard at a later date. My aim was to start high level, purposefully avoiding the detail (but logging thoughts about the detail) to avoid losing myself in the "what ifs" that would inevitably crop up.

The scope landed on four artisan commands:

  • housekeeping:list would be the main entry point - an interactive browser that walks you through selecting a repository, picking a label, and drilling into an issue.
  • housekeeping:show would display a single issue with its full body, metadata and comments, for when you already know the issue number.
  • housekeeping:start would handle the git workflow: check out the base branch, pull latest, create a feature branch named after the issue, and self-assign on GitHub.
  • housekeeping:export would dump an issue and its comments as structured JSON, designed to be piped into AI tools or saved for later context.

Everything else including label management, bulk exports, statistics went onto the roadmap.

Housekeeping package in use
A nice, gentle, relaxed interface. Perfect for late afternoons.

The goal was a tight loop: find a task, read it, branch off, start working. Anything that did not directly serve that loop was deferred.

Groundwork

Having never written a package before, I wanted to ensure that what I built would be in line with expectations in the Laravel ecosystem, so to the docs I went. While there wasn't anything here that I hadn't encountered before, it was useful to have a mild refresher on some key elements in Laravel (namely Service providers).

To summarise: Service providers are the backbone of any Laravel package. When you install a package via Composer, Laravel's package auto-discovery reads the provider from your composer.json and registers it automatically - the consumer never has to touch config/app.php.

Inside the provider, you bind your classes into the service container (in this case, registering the Housekeeping class as a singleton so every command shares the same instance), merge your package config so it works out of the box without publishing, and register your artisan commands. It's effectively the map for your package: what gets loaded, when, and how.

It was important to get this right, as it would ensure that the package would feel native to any Laravel project - the dev would run composer require, add a token to .env, and they'd be ready to go.

In the interest of clarity, I didn't code absolutely everything myself: why reinvent when there are already solid community packages? I knew if I didn't use these, I could triple my dev time, and likely fall foul of some already-tackled technical issues.

So, I opted to use two packages to do the heavy lifting. One to interface with GitHub, and the other to provide a nice to use CLI interface. graham-campbell/github provides a clean Laravel integration with the GitHub API - it handles authentication, wraps the underlying API client, and plays nicely with the service container, so I did not have to write any HTTP or OAuth plumbing.

laravel/prompts gave me the interactive terminal UI: searchable dropdowns, multi-select lists, spinners for API calls, text inputs with validation. Lots of options for making this functional and pretty.

Between them, I could focus on the workflow logic rather than the mechanics of talking to GitHub or rendering a usable interface.

Development

Now I had a plan.

As I'm using an AI development setup, I created a Laravel-specific file to instruct the agent in generic Laravel package development, setting the tone and expectations for the project. I also created a specification file that would help to guardrail the development.

Instructions

The instruction file was essentially a contract between me and the AI agent, covering language and style (British English throughout, no emojis, no em dashes), code standards (PSR-12, strict types on every file, type hints and return types on all methods), and testing expectations (self-contained tests, mock external services, never hit live endpoints). I also made sure that rules were set for git: commits should be atomic with brief imperative messages, no conventional commit prefixes unless asked.

Specifications

The specification file described each command in detail - what arguments it would accept, what it should display, how it should handle edge cases like empty results or API failures. Having both these together, allowed me to hand off discrete tasks and get back code that matched my expectations without constant correction, which can be a huge timesink when coding in an AI setup.

The agent was not making architectural decisions; it was executing against a clear brief. When it drifted, the spec was the reference point to pull it back. This setup took perhaps an hour to write, but it saved a lot more than that over the course of development.

Conclusion

I enjoyed this project (and still am! Version 0.8 just landed), and learned a fair few things about package development, and the importance of having clear priorities and goals to avoid scope creep. I also realised how much work had gone into the packages I use in every Laravel project, and how I should be showing more love to the developers behind them!

But all is not finished! This is the first in a short series. Next time, I'll explore the packages that power the CLI experience and the GitHub integration, and the problems I ran into along the way while building.