PHPackages                             ivansostarko/laravel-api-errors - 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. [API Development](/categories/api)
4. /
5. ivansostarko/laravel-api-errors

ActiveLibrary[API Development](/categories/api)

ivansostarko/laravel-api-errors
===============================

Centralized, enum-based API error codes for Laravel — frontend-safe, RFC 7807 compatible, Swagger-ready, with TypeScript export, Sentry integration, and request tracing.

1.0.0(2mo ago)02MITPHPPHP ^8.2

Since Apr 8Pushed 2mo agoCompare

[ Source](https://github.com/ivansostarko/laravel-api-errors)[ Packagist](https://packagist.org/packages/ivansostarko/laravel-api-errors)[ Docs](https://github.com/ivansostarko/laravel-api-errors)[ RSS](/packages/ivansostarko-laravel-api-errors/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (1)Dependencies (4)Versions (2)Used By (0)

Laravel API Errors
==================

[](#laravel-api-errors)

[![Latest Version on Packagist](https://camo.githubusercontent.com/1c289131b06714ab47f49b609af700ed53c8e7a7b7a05aa80e59b7f1d4e4f16a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6976616e736f737461726b6f2f6c61726176656c2d6170692d6572726f72732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ivansostarko/laravel-api-errors)[![License: MIT](https://camo.githubusercontent.com/458425f8985b0b0c8a736cffe75e05a098e3d77906acddbcad2bfc54492a4e02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e7376673f7374796c653d666c61742d737175617265)](https://github.com/ivansostarko/laravel-api-errors/blob/main/LICENSE)[![PHP Version](https://camo.githubusercontent.com/2f0e5677e68d75d8cca63915c03c33836e384263e7d5bd95b1ddaf34f63b3827/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6976616e736f737461726b6f2f6c61726176656c2d6170692d6572726f72732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ivansostarko/laravel-api-errors)

**Centralized, enum-based API error codes for Laravel** — frontend-safe, RFC 7807 compatible, Swagger-ready, TypeScript-exportable, with Sentry integration, request tracing, and translation support.

Stop scattering magic strings and ad-hoc error responses across your codebase. Define every error code once as a PHP enum, and let the package handle JSON responses, logging, tracing, and cross-team contracts automatically.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Error Code Enums](#error-code-enums)
    - [The Contract](#the-contract)
    - [Creating Your Own Enum](#creating-your-own-enum)
    - [Registering Enums](#registering-enums)
    - [Domain Grouping](#domain-grouping)
- [Throwing &amp; Responding](#throwing--responding)
    - [Using the Enum Directly](#using-the-enum-directly)
    - [Using Helper Functions](#using-helper-functions)
    - [Validation Error Mapping](#validation-error-mapping)
- [Response Formats](#response-formats)
    - [Default JSON](#default-json)
    - [RFC 7807 Problem Details](#rfc-7807-problem-details)
- [Laravel Exception Handler Integration](#laravel-exception-handler-integration)
- [Request ID Tracing](#request-id-tracing)
- [Translations](#translations)
- [Logging Integration](#logging-integration)
- [Sentry Integration](#sentry-integration)
- [TypeScript Export](#typescript-export)
- [Swagger / OpenAPI Export](#swagger--openapi-export)
- [Artisan Commands](#artisan-commands)
- [Microservice Architecture](#microservice-architecture)
- [Monolith Architecture](#monolith-architecture)
- [Configuration Reference](#configuration-reference)
- [Testing](#testing)
- [License](#license)

---

Features
--------

[](#features)

FeatureDescription**Centralized error codes**Every code lives in a single enum — no duplicates, no magic strings.**Enum-based registry**PHP 8.1+ backed enums implement a shared contract.**ApiException**Throw a rich exception that auto-renders to JSON.**Helper functions**`api_error()`, `api_abort()`, `api_error_code()` for expressive code.**Auto JSON responses**Laravel's exception handler renders consistent JSON automatically.**Frontend-safe**Stable string codes that frontend teams can rely on as a contract.**RFC 7807 support**Toggle between flat JSON and `application/problem+json`.**TypeScript export**Generate a `.ts` file with all codes, types, and a type guard.**Swagger / OpenAPI**Export an OpenAPI 3.1 JSON schema of all error codes.**Translation ready**Every code resolves through Laravel's translator.**Validation mapping**Flattens Laravel validation errors into `[{field, message}]`.**Domain grouping**Group codes by logical domain (AUTH, BILLING, etc.).**Logging integration**Auto-logs errors with code, domain, request ID, and severity.**Sentry integration**Sets tags (`error_code`, `domain`, `request_id`) on Sentry events.**Request ID tracing**Middleware generates/propagates `X-Request-Id` across services.**Publishable config**Full control via `config/api-errors.php`.**Monolith friendly**Register multiple domain enums in one app.**Microservice friendly**Share the contract package; each service registers its own codes.**Backward compatible**Default format is a simple, flat JSON object.---

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11, 12, or 13

---

Installation
------------

[](#installation)

```
composer require ivansostarko/laravel-api-errors
```

Publish the config file:

```
php artisan vendor:publish --tag=api-errors-config
```

Optionally publish a starter enum stub into your app:

```
php artisan vendor:publish --tag=api-errors-stubs
```

---

Quick Start
-----------

[](#quick-start)

### 1. Define an error code enum

[](#1-define-an-error-code-enum)

```
// app/Enums/AppErrorCode.php

namespace App\Enums;

use LaravelApiErrors\Contracts\ApiErrorCode;
use LaravelApiErrors\Enums\InteractsWithApiError;

enum AppErrorCode: string implements ApiErrorCode
{
    use InteractsWithApiError;

    case ORDER_ALREADY_SHIPPED = 'ORDER_ALREADY_SHIPPED';

    public function code(): string       { return $this->value; }
    public function httpStatus(): int    { return 409; }
    public function domain(): string     { return 'ORDER'; }
    public function severity(): string   { return 'warning'; }

    public function message(): string
    {
        return match ($this) {
            self::ORDER_ALREADY_SHIPPED => 'This order has already been shipped.',
        };
    }
}
```

### 2. Register it

[](#2-register-it)

```
// config/api-errors.php
'extra_enums' => [
    \App\Enums\AppErrorCode::class,
],
```

### 3. Use it

[](#3-use-it)

```
// In a controller
use App\Enums\AppErrorCode;

public function cancel(Order $order)
{
    if ($order->shipped) {
        AppErrorCode::ORDER_ALREADY_SHIPPED->throw(['order_id' => $order->id]);
    }

    // ...
}
```

The response is automatically rendered as:

```
{
    "success": false,
    "error_code": "ORDER_ALREADY_SHIPPED",
    "message": "This order has already been shipped.",
    "domain": "ORDER",
    "status": 409,
    "request_id": "a1b2c3d4-...",
    "context": {
        "order_id": 42
    }
}
```

---

Error Code Enums
----------------

[](#error-code-enums)

### The Contract

[](#the-contract)

Every error code enum must implement `LaravelApiErrors\Contracts\ApiErrorCode`:

```
interface ApiErrorCode
{
    public function code(): string;      // Unique stable string, e.g. "AUTH_TOKEN_EXPIRED"
    public function message(): string;   // Default human-readable message
    public function httpStatus(): int;   // HTTP status code
    public function domain(): string;    // Logical group, e.g. "AUTH"
    public function severity(): string;  // Log level: debug, info, warning, error, fatal
}
```

### Creating Your Own Enum

[](#creating-your-own-enum)

Use the `InteractsWithApiError` trait to get helper methods (`throw()`, `respond()`, `exception()`, `translatedMessage()`):

```
enum BillingErrorCode: string implements ApiErrorCode
{
    use InteractsWithApiError;

    case CARD_DECLINED        = 'BILLING_CARD_DECLINED';
    case INSUFFICIENT_FUNDS   = 'BILLING_INSUFFICIENT_FUNDS';
    case SUBSCRIPTION_EXPIRED = 'BILLING_SUBSCRIPTION_EXPIRED';

    public function code(): string    { return $this->value; }
    public function domain(): string  { return 'BILLING'; }
    public function severity(): string { return 'warning'; }

    public function message(): string
    {
        return match ($this) {
            self::CARD_DECLINED        => 'The credit card was declined.',
            self::INSUFFICIENT_FUNDS   => 'Insufficient funds.',
            self::SUBSCRIPTION_EXPIRED => 'Your subscription has expired.',
        };
    }

    public function httpStatus(): int
    {
        return match ($this) {
            self::CARD_DECLINED        => 402,
            self::INSUFFICIENT_FUNDS   => 402,
            self::SUBSCRIPTION_EXPIRED => 403,
        };
    }
}
```

### Registering Enums

[](#registering-enums)

Add enum classes to the `extra_enums` array in `config/api-errors.php`. The registry validates that no two enums register the same code string — if they do, it throws a `LogicException` at boot time.

```
'extra_enums' => [
    \App\Enums\BillingErrorCode::class,
    \App\Enums\InventoryErrorCode::class,
    \Modules\Shipping\Enums\ShippingErrorCode::class,
],
```

### Domain Grouping

[](#domain-grouping)

Every code declares a `domain()`. You can list codes by domain:

```
php artisan api-errors:list --domain=BILLING
```

Or programmatically:

```
$registry = app(\LaravelApiErrors\Support\ErrorCodeRegistry::class);
$billingCodes = $registry->domain('BILLING');
$grouped      = $registry->groupedByDomain();
```

---

Throwing &amp; Responding
-------------------------

[](#throwing--responding)

### Using the Enum Directly

[](#using-the-enum-directly)

```
use App\Enums\AppErrorCode;

// Throw — renders automatically via Laravel's exception handler
AppErrorCode::ORDER_ALREADY_SHIPPED->throw(['order_id' => $id]);

// Return a response without throwing
return AppErrorCode::ORDER_ALREADY_SHIPPED->respond(['order_id' => $id]);

// Create the exception object for deferred throwing
$e = AppErrorCode::ORDER_ALREADY_SHIPPED->exception(['order_id' => $id]);
```

### Using Helper Functions

[](#using-helper-functions)

```
// Return a JSON response
return api_error('ORDER_ALREADY_SHIPPED', ['order_id' => $id]);

// Throw immediately
api_abort('ORDER_ALREADY_SHIPPED', ['order_id' => $id]);

// Resolve code string → enum case
$code = api_error_code('ORDER_ALREADY_SHIPPED');
```

Helpers accept either an `ApiErrorCode` enum instance or a code string.

### Validation Error Mapping

[](#validation-error-mapping)

When Laravel throws a `ValidationException` on an API route, the package automatically catches it and returns:

```
{
    "success": false,
    "error_code": "VALIDATION_ERROR",
    "message": "The given data was invalid.",
    "domain": "GENERAL",
    "status": 422,
    "context": {
        "errors": [
            { "field": "email", "message": "The email field is required." },
            { "field": "email", "message": "The email must be a valid email address." },
            { "field": "name",  "message": "The name field is required." }
        ]
    }
}
```

The nested validation errors are flattened into a consistent `[{field, message}]` array that frontends can iterate over directly.

---

Response Formats
----------------

[](#response-formats)

### Default JSON

[](#default-json)

Set `API_ERRORS_FORMAT=default` (this is the default):

```
{
    "success": false,
    "error_code": "AUTH_TOKEN_EXPIRED",
    "message": "Your authentication token has expired.",
    "domain": "AUTH",
    "status": 401,
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "context": {}
}
```

### RFC 7807 Problem Details

[](#rfc-7807-problem-details)

Set `API_ERRORS_FORMAT=rfc7807`:

```
{
    "type": "https://api-errors.dev/codes/auth_token_expired",
    "title": "AUTH_TOKEN_EXPIRED",
    "status": 401,
    "detail": "Your authentication token has expired.",
    "instance": "/api/v1/profile",
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

The `Content-Type` header is set to `application/problem+json` automatically.

---

Laravel Exception Handler Integration
-------------------------------------

[](#laravel-exception-handler-integration)

Register the package's renderable callbacks in your `bootstrap/app.php`:

```
use LaravelApiErrors\Support\ExceptionRenderer;

return Application::configure(basePath: dirname(__DIR__))
    ->withExceptions(function (Exceptions $exceptions) {
        ExceptionRenderer::register($exceptions);
    })
    ->create();
```

This automatically converts the following exceptions into consistent API error responses (only for requests that expect JSON or hit `api/*` routes):

Laravel ExceptionError Code`ApiException`Whatever code you set`ValidationException``VALIDATION_ERROR``AuthenticationException``AUTH_UNAUTHENTICATED``ModelNotFoundException``RESOURCE_NOT_FOUND``NotFoundHttpException``RESOURCE_NOT_FOUND``MethodNotAllowedHttpException``METHOD_NOT_ALLOWED``TooManyRequestsHttpException``TOO_MANY_REQUESTS`Any `HttpExceptionInterface``INTERNAL_SERVER_ERROR`---

Request ID Tracing
------------------

[](#request-id-tracing)

Add the middleware to your API stack:

```
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->api(prepend: [
        \LaravelApiErrors\Http\Middleware\AttachRequestId::class,
    ]);
})
```

Behavior:

- If the incoming request has an `X-Request-Id` header (e.g. from a gateway or upstream service), it is preserved.
- If not, a UUID v4 is generated automatically.
- The same ID is attached to the response header and included in every error JSON body.
- The ID is also sent to Sentry and the logger for end-to-end tracing.

Configure the header name in `config/api-errors.php`:

```
'request_id_header' => 'X-Request-Id',
```

---

Translations
------------

[](#translations)

Every error code's message is resolved through Laravel's translator. Generate a translation file:

```
php artisan api-errors:sync-translations --locale=en
php artisan api-errors:sync-translations --locale=es
```

This creates `lang/en/api-errors.php` with all registered codes. Edit the file to customize messages per locale:

```
// lang/es/api-errors.php
return [
    'AUTH_UNAUTHENTICATED' => 'Se requiere autenticación.',
    'VALIDATION_ERROR'     => 'Los datos proporcionados no son válidos.',
];
```

Translations are resolved automatically — no extra code needed.

---

Logging Integration
-------------------

[](#logging-integration)

Every `ApiException` is logged automatically with structured context:

```
[2026-04-08 12:00:00] local.ERROR: [API Error] AUTH_TOKEN_EXPIRED: Your authentication token has expired. {
    "error_code": "AUTH_TOKEN_EXPIRED",
    "domain": "AUTH",
    "http_status": 401,
    "request_id": "550e8400-...",
    "url": "https://example.com/api/v1/profile",
    "method": "GET"
}

```

Configure in `config/api-errors.php`:

```
'logging' => [
    'enabled' => true,
    'channel' => 'stack',        // null = default channel
    'exclude_status' => [404, 422], // Don't log these
],
```

---

Sentry Integration
------------------

[](#sentry-integration)

Enable in `.env`:

```
API_ERRORS_SENTRY=true

```

When an `ApiException` is captured, the package:

- Sets Sentry tags: `error_code`, `error_domain`, `http_status`, `request_id`
- Sets Sentry context with the full error payload
- Maps `severity()` to Sentry severity levels

This means you can filter Sentry issues by error code or domain directly in the Sentry dashboard.

---

TypeScript Export
-----------------

[](#typescript-export)

Generate a TypeScript file that your frontend can import:

```
php artisan api-errors:ts
```

Output (`resources/js/api-errors.ts`):

```
export const API_ERROR_CODES = {
  UNKNOWN_ERROR: { code: 'UNKNOWN_ERROR', status: 500, domain: 'GENERAL', message: 'An unexpected error occurred.' },
  VALIDATION_ERROR: { code: 'VALIDATION_ERROR', status: 422, domain: 'GENERAL', message: 'The given data was invalid.' },
  AUTH_UNAUTHENTICATED: { code: 'AUTH_UNAUTHENTICATED', status: 401, domain: 'AUTH', message: 'Authentication is required.' },
  // ... all registered codes
} as const;

export type ApiErrorCode = keyof typeof API_ERROR_CODES;

export type ApiErrorResponse = {
  success: false;
  error_code: ApiErrorCode;
  message: string;
  domain: string;
  status: number;
  request_id?: string;
  context?: Record;
};

export function isApiError(data: unknown): data is ApiErrorResponse {
  return typeof data === 'object' && data !== null && 'error_code' in data && 'success' in data;
}
```

Usage in frontend:

```
import { isApiError, API_ERROR_CODES } from './api-errors';

const res = await fetch('/api/orders/42', { method: 'DELETE' });
const data = await res.json();

if (isApiError(data)) {
  switch (data.error_code) {
    case 'ORDER_ALREADY_SHIPPED':
      toast.warn('Cannot cancel a shipped order.');
      break;
    case 'AUTH_TOKEN_EXPIRED':
      router.push('/login');
      break;
    default:
      toast.error(data.message);
  }
}
```

---

Swagger / OpenAPI Export
------------------------

[](#swagger--openapi-export)

```
php artisan api-errors:swagger
```

Generates an OpenAPI 3.1 JSON file at `storage/api-docs/error-codes.json` containing:

- An `ApiErrorCode` string enum schema with all registered codes
- `ApiErrorResponse` and `RFC7807ProblemDetail` object schemas
- `x-error-code-details` with HTTP status, domain, message, and severity for each code

Reference these schemas in your main OpenAPI spec:

```
responses:
  '409':
    description: Conflict
    content:
      application/json:
        schema:
          $ref: './error-codes.json#/components/schemas/ApiErrorResponse'
```

---

Artisan Commands
----------------

[](#artisan-commands)

CommandDescription`php artisan api-errors:list`List all registered error codes in a table.`php artisan api-errors:list --domain=AUTH`Filter by domain.`php artisan api-errors:ts`Export TypeScript file.`php artisan api-errors:swagger`Export OpenAPI schema.`php artisan api-errors:sync-translations`Generate translation file for a locale.---

Microservice Architecture
-------------------------

[](#microservice-architecture)

In a microservice setup, extract the contract into a shared Composer package:

```
shared-api-contracts/
├── src/
│   └── Contracts/
│       └── ApiErrorCode.php
│   └── Enums/
│       └── InteractsWithApiError.php
└── composer.json

```

Each microservice then:

1. Requires the shared contracts package.
2. Requires `laravel-api-errors/laravel-api-errors`.
3. Defines its own domain-specific enums implementing `ApiErrorCode`.
4. Registers them in `config/api-errors.php`.

The `X-Request-Id` header flows through service-to-service calls automatically.

---

Monolith Architecture
---------------------

[](#monolith-architecture)

In a monolith with multiple modules/domains, create one enum per domain and register them all:

```
app/
├── Enums/
│   ├── AuthErrorCode.php
│   ├── BillingErrorCode.php
│   ├── InventoryErrorCode.php
│   └── ShippingErrorCode.php

```

```
// config/api-errors.php
'extra_enums' => [
    \App\Enums\AuthErrorCode::class,
    \App\Enums\BillingErrorCode::class,
    \App\Enums\InventoryErrorCode::class,
    \App\Enums\ShippingErrorCode::class,
],
```

The registry ensures no two domains accidentally use the same code string.

---

Configuration Reference
-----------------------

[](#configuration-reference)

Publish with `php artisan vendor:publish --tag=api-errors-config`.

KeyDefaultDescription`format``default``default` or `rfc7807``debug``false`Include stack traces (only when `APP_DEBUG=true`)`request_id_header``X-Request-Id`Header name for request tracing`auto_request_id``true`Generate UUID if header is missing`request_id_in_response``true`Include request ID in JSON body`use_translations``true`Resolve messages through Laravel translator`translation_namespace``api-errors`Translation namespace`logging.enabled``true`Enable automatic logging`logging.channel``null`Log channel (null = default)`logging.exclude_status``[404, 422]`HTTP statuses to skip logging`sentry.enabled``false`Enable Sentry integration`sentry.set_tags``true`Set error\_code/domain as Sentry tags`validation_error_code``VALIDATION_ERROR`Code used for validation exceptions`extra_enums``[]`Additional enum classes to register`typescript_path``resources/js/api-errors.ts`TypeScript export output path`swagger_path``storage/api-docs/error-codes.json`Swagger export output path---

Testing
-------

[](#testing)

```
composer test
```

Or with PHPUnit directly:

```
./vendor/bin/phpunit
```

---

License
-------

[](#license)

The MIT License (MIT). Please see [LICENSE](https://github.com/ivansostarko/laravel-api-errors/blob/main/LICENSE) for more information.

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance85

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 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

80d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3688396?v=4)[Ivan Sostarko](/maintainers/ivansostarko)[@ivansostarko](https://github.com/ivansostarko)

---

Top Contributors

[![ivansostarko](https://avatars.githubusercontent.com/u/3688396?v=4)](https://github.com/ivansostarko "ivansostarko (3 commits)")

---

Tags

apierror-handlinglaravelapilaravelswaggerexceptionerrorstypescriptrfc7807error codes

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ivansostarko-laravel-api-errors/health.svg)

```
[![Health](https://phpackages.com/badges/ivansostarko-laravel-api-errors/health.svg)](https://phpackages.com/packages/ivansostarko-laravel-api-errors)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

815320.5k3](/packages/defstudio-telegraph)[resend/resend-laravel

Resend for Laravel

1212.2M8](/packages/resend-resend-laravel)[essa/api-tool-kit

set of tools to build an api with laravel

53386.5k](/packages/essa-api-tool-kit)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)[simplestats-io/laravel-client

Analytics for Laravel. Track visitors, registrations, and payments. Discover which channels actually drive revenue, not just traffic. Server-side, GDPR compliant, ad-blocker proof.

5019.3k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

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