PHPackages                             particle-academy/laravel-jobs - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. particle-academy/laravel-jobs

ActiveLibrary

particle-academy/laravel-jobs
=============================

Laravel package for job boards — employer job postings, public listings, and candidate applications.

v0.1.0(today)00MITPHPPHP ^8.3CI passing

Since Aug 2Pushed todayCompare

[ Source](https://github.com/Particle-Academy/laravel-jobs)[ Packagist](https://packagist.org/packages/particle-academy/laravel-jobs)[ Docs](https://github.com/Particle-Academy/laravel-jobs)[ RSS](/packages/particle-academy-laravel-jobs/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (8)Versions (2)Used By (0)

particle-academy/laravel-jobs
=============================

[](#particle-academylaravel-jobs)

Laravel package for job boards — employer job postings, a public listing, and candidate applications. The server half of the Fancy UI job board; the React half is [`@particle-academy/job-board`](https://github.com/Particle-Academy/job-board).

Install
-------

[](#install)

```
composer require particle-academy/laravel-jobs
php artisan migrate
```

The service provider is auto-discovered. Publish the config to change anything:

```
php artisan vendor:publish --tag=laravel-jobs-config
```

The one thing you must wire up
------------------------------

[](#the-one-thing-you-must-wire-up)

**This package does not own the employer.** Every host already has its own idea of the organisation doing the hiring — an agency, a company, a studio — so you point the package at yours:

```
// config/laravel-jobs.php
'employer_model' => App\Models\Agency::class,
'user_model'     => App\Models\User::class,
```

Because the employer is yours, the package cannot know who is allowed to act for one. You tell it, by binding `AuthorizesEmployers`:

```
use Illuminate\Http\Request;
use ParticleAcademy\LaravelJobs\Contracts\AuthorizesEmployers;

$this->app->bind(AuthorizesEmployers::class, fn () => new class implements AuthorizesEmployers {
    public function allows(Request $request, int|string $employerId): bool
    {
        return $request->user()?->agencies()->whereKey($employerId)->exists() ?? false;
    }
});
```

> **The default binding denies everything.** Installing this package grants nobody the ability to post as any employer — an unbound install is inert, not wide open. Every employer endpoint 403s until you bind your own rule.

Moderation gate
---------------

[](#moderation-gate)

Hosts usually approve employers before letting them advertise. Name the column and the value that means "cleared":

```
'employer_gate' => [
    'column'   => 'status',      // null disables gating entirely
    'approved' => 'approved',
],
```

Drafting is always allowed. **Publishing** is what the gate applies to, so a pending employer can set everything up while it waits.

### A richer rule

[](#a-richer-rule)

Approval is only the default. Bind `GatesPublishing` to charge per listing, enforce a plan quota, or anything else:

```
use ParticleAcademy\LaravelJobs\Contracts\GatesPublishing;
use ParticleAcademy\LaravelJobs\Support\PublishDecision;

class PaidListingGate implements GatesPublishing
{
    public function check(JobPosting $posting): PublishDecision
    {
        if ($this->alreadyPaid($posting)) {
            return PublishDecision::allow();
        }

        return PublishDecision::deny(
            reason: 'Publishing this listing costs $49.',
            code:   'payment_required',
            meta:   ['checkout_url' => $this->checkoutUrl($posting)],
        );
    }
}
```

A denial is deliberately richer than `false`: the `code` and `meta` travel out to the caller, so your UI can send the employer to checkout rather than just showing an error. `payment_required` answers **402**; anything else **403**.

Need to render "Publish — $49" without attempting the transition? `JobPostingService::publishDecision($posting)` asks the gate and changes nothing.

Creating a posting with `status: published` runs the same gate, inside a transaction — so it cannot be used as a way around `publish()`, and a refusal leaves no orphan draft.

API
---

[](#api)

Mounted at `api/jobs` by default (`routes.prefix`).

### Public — no auth

[](#public--no-auth)

`GET /postings`The board. Published, unexpired postings only. Supports `search`, `employment_type`, `location`, `is_remote`, `employer_id`, `per_page`.`GET /postings/{slug}`One posting. A draft, closed or expired posting is a 404 here for everyone.### Candidate — resolved from the authenticated user

[](#candidate--resolved-from-the-authenticated-user)

`POST /postings/{slug}/applications`Apply.`GET /my-applications`Your own applications.`POST /applications/{application}/withdraw`Withdraw yours.### Employer — gated by `AuthorizesEmployers`

[](#employer--gated-by-authorizesemployers)

`GET|POST /employers/{employer}/postings`List (every status) / create.`GET|PATCH|DELETE /employers/{employer}/postings/{posting}`Read / update / delete.`POST .../postings/{posting}/publish|unpublish|close`Status transitions.`GET /employers/{employer}/applications`Who applied, across your postings.`PATCH /employers/{employer}/applications/{application}`Advance an applicant.Design notes
------------

[](#design-notes)

- **Status transitions are not field updates.** `PATCH` silently drops `status`; publishing and closing go through their own endpoints so the gate and the events cannot be bypassed by smuggling a field.
- **Another employer's records 404, they do not 403.** Whether a posting exists is not something a competitor should be able to probe for.
- **`withdrawn` belongs to the candidate.** It is absent from the statuses an employer may assign.
- Domain refusals carry their own HTTP status (`403` not approved, `409`duplicate application, `422` applications closed) via a `render()` on the exception, so no host-side handler wiring is needed.

Events
------

[](#events)

`JobPostingPublished`, `JobPostingClosed`, `ApplicationSubmitted`, `ApplicationStatusChanged` — for notifications, search indexing, and the like.

Tests
-----

[](#tests)

```
composer install && vendor/bin/phpunit
```

The suite configures the package against its own stand-in host models, so the `employer_model` / `user_model` contract is exercised the way a real host uses it.

License
-------

[](#license)

MIT.

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/461446?v=4)[Wish Born](/maintainers/wishborn)[@wishborn](https://github.com/wishborn)

---

Top Contributors

[![wishborn](https://avatars.githubusercontent.com/u/461446?v=4)](https://github.com/wishborn "wishborn (8 commits)")

---

Tags

laraveljobsjob boardhiringApplicationsparticle-academy

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/particle-academy-laravel-jobs/health.svg)

```
[![Health](https://phpackages.com/badges/particle-academy-laravel-jobs/health.svg)](https://phpackages.com/packages/particle-academy-laravel-jobs)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.4M352](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

78727.1M205](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58174.6k18](/packages/api-platform-laravel)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9872.4M142](/packages/roots-acorn)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1239.7k25](/packages/fleetbase-core-api)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45844.8k1](/packages/pressbooks-pressbooks)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
