PHPackages                             joepages/laravel-phone-numbers - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. joepages/laravel-phone-numbers

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

joepages/laravel-phone-numbers
==============================

Polymorphic phone numbers package for Laravel - Attach N phone numbers to any model

v1.1.0(5mo ago)0347↓88.9%MITPHPPHP ^8.2CI passing

Since Feb 2Pushed 3w agoCompare

[ Source](https://github.com/joepages/laravel-phone-numbers)[ Packagist](https://packagist.org/packages/joepages/laravel-phone-numbers)[ Docs](https://github.com/joepages/laravel-phone-numbers)[ RSS](/packages/joepages-laravel-phone-numbers/feed)WikiDiscussions main Synced 3d ago

READMEChangelogDependencies (6)Versions (3)Used By (0)

Laravel Phone Numbers
=====================

[](#laravel-phone-numbers)

[![Tests](https://github.com/joepages/laravel-phone-numbers/actions/workflows/tests.yml/badge.svg)](https://github.com/joepages/laravel-phone-numbers/actions/workflows/tests.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/a0132d6cca84d056e9fefe3b3775e7c0c0120ef1e5cd59a678ed74f221f4eee3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a6f6570616765732f6c61726176656c2d70686f6e652d6e756d626572732e737667)](https://packagist.org/packages/joepages/laravel-phone-numbers)[![License](https://camo.githubusercontent.com/242e3b5bfb84c46f92165eaae0fc4515f502229e9def60b5dd35829b6e887b2c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6a6f6570616765732f6c61726176656c2d70686f6e652d6e756d626572732e737667)](https://packagist.org/packages/joepages/laravel-phone-numbers)

Polymorphic phone numbers for Laravel. Attach multiple phone numbers to any Eloquent model with full CRUD, bulk sync, primary management, E.164 formatting, and multi-tenancy awareness.

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

[](#requirements)

- PHP 8.2+
- Laravel 11 or 12

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

[](#installation)

```
composer require joepages/laravel-phone-numbers
```

Run the install command to publish the config and migrations:

```
php artisan phone-numbers:install
php artisan migrate
```

The installer auto-detects [stancl/tenancy](https://tenancyforlaravel.com/) and publishes migrations to `database/migrations/tenant/` when present.

### Install options

[](#install-options)

```
php artisan phone-numbers:install --force            # Overwrite existing files
php artisan phone-numbers:install --skip-migrations  # Only publish config
```

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

[](#quick-start)

### 1. Add the trait to your model

[](#1-add-the-trait-to-your-model)

```
use PhoneNumbers\Concerns\HasPhoneNumbers;

class Facility extends Model
{
    use HasPhoneNumbers;
}
```

### 2. Add the controller trait

[](#2-add-the-controller-trait)

```
use PhoneNumbers\Concerns\ManagesPhoneNumbers;

class FacilityController extends BaseApiController
{
    use ManagesPhoneNumbers;
}
```

### 3. Register routes

[](#3-register-routes)

```
Route::phoneNumberRoutes('facilities', FacilityController::class);
```

This registers the following routes:

MethodURIActionGET`/facilities/{facility}/phone-numbers``listPhoneNumbers`POST`/facilities/{facility}/phone-numbers``storePhoneNumber`PUT`/facilities/{facility}/phone-numbers/{phoneNumber}``updatePhoneNumber`DELETE`/facilities/{facility}/phone-numbers/{phoneNumber}``deletePhoneNumber`Model Trait API
---------------

[](#model-trait-api)

The `HasPhoneNumbers` trait provides three relationships on your model:

```
$facility->phoneNumbers;                 // All phone numbers (MorphMany)
$facility->primaryPhoneNumber;           // Primary phone number (MorphOne)
$facility->phoneNumbersOfType('mobile'); // Filtered by type (MorphMany)
```

PhoneNumber Model
-----------------

[](#phonenumber-model)

### Fields

[](#fields)

FieldTypeDescription`type`stringPhone type (`mobile`, `home`, `work`, `fax`, `other`)`is_primary`booleanWhether this is the primary phone number`country_code`stringCompound dial code + ISO country (e.g. `+1:US`, `+44:GB`) or plain dial code (`+1`)`number`stringPhone number digits`extension`string|nullExtension number`formatted`string|nullDisplay-formatted number (e.g. `(555) 123-4567`)`is_verified`booleanWhether the number has been verified`metadata`array|nullCustom JSON data### Scopes

[](#scopes)

```
PhoneNumber::primary()->get();           // Only primary numbers
PhoneNumber::ofType('mobile')->get();    // Filter by type
PhoneNumber::forModel($facility)->get(); // All numbers for a specific model
PhoneNumber::verified()->get();          // Only verified numbers
```

### Helpers

[](#helpers)

```
$phone->markAsPrimary();   // Sets as primary, unsets all others for the same parent
$phone->e164;              // "+15551234567" (E.164 format)
$phone->full_number;       // "(555) 123-4567 ext. 200" (formatted + extension)
$phone->dial_code;         // "+1" (dial code portion of country_code)
$phone->iso_country_code;  // "US" (ISO portion of compound country_code, or null)
```

Country Code Format
-------------------

[](#country-code-format)

The `country_code` field supports a compound format `+{dialCode}:{isoCode}` that pairs the dial code with the ISO 3166-1 alpha-2 country code. This disambiguates countries that share the same dial code (e.g. US and Canada both use `+1`).

FormatExampleDial CodeISO CodeCompound`+1:US``+1``US`Compound`+1:CA``+1``CA`Compound`+44:GB``+44``GB`Plain (legacy)`+1``+1``null`The compound format is recommended. Plain dial codes are still supported for backwards compatibility but `iso_country_code` will return `null`.

Controller Trait
----------------

[](#controller-trait)

The `ManagesPhoneNumbers` trait provides two integration modes:

### Standalone CRUD

[](#standalone-crud)

Use the `storePhoneNumber`, `updatePhoneNumber`, `deletePhoneNumber`, and `listPhoneNumbers` methods directly via the route macro.

### Bulk Sync via BaseApiController

[](#bulk-sync-via-baseapicontroller)

When your controller extends `BaseApiController`, the `attachPhoneNumber()` method is called automatically during `store()` and `update()`. Send a `phone_numbers` array in the request body:

```
{
  "name": "Main Facility",
  "phone_numbers": [
    {
      "id": 1,
      "country_code": "+1:US",
      "number": "5559999999",
      "formatted": "(555) 999-9999"
    },
    {
      "country_code": "+44:GB",
      "number": "2071234567",
      "type": "work",
      "is_primary": true
    }
  ]
}
```

- Records **with an `id`** are updated
- Records **without an `id`** are created
- Existing records **not included** in the array are deleted

API Resource
------------

[](#api-resource)

Add phone numbers to your JSON responses:

```
use PhoneNumbers\Concerns\WithPhoneNumbersResource;

class FacilityResource extends JsonResource
{
    use WithPhoneNumbersResource;

    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            ...$this->phoneNumbersResource(),
        ];
    }
}
```

Validation
----------

[](#validation)

The `PhoneNumberRequest` form request validates:

FieldRules`country_code`required, string, max:10`number`required, string, max:20`extension`nullable, string, max:10`formatted`nullable, string, max:30`type`sometimes, string (validated against config when `allow_custom_types` is false)`is_primary`sometimes, boolean`is_verified`sometimes, boolean`metadata`nullable, arrayConfiguration
-------------

[](#configuration)

```
// config/phone-numbers.php

return [
    // 'auto' detects stancl/tenancy, 'single' or 'multi' to force
    'tenancy_mode' => 'auto',

    // Allowed phone number types
    'types' => ['mobile', 'home', 'work', 'fax', 'other'],

    // Default type when none specified
    'default_type' => 'mobile',

    // When false, only types in the 'types' array are accepted
    'allow_custom_types' => true,
];
```

Database Schema
---------------

[](#database-schema)

```
CREATE TABLE phone_numbers (
    id             BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    phoneable_type VARCHAR(255) NOT NULL,
    phoneable_id   BIGINT UNSIGNED NOT NULL,
    type           VARCHAR(50) DEFAULT 'mobile',
    is_primary     BOOLEAN DEFAULT FALSE,
    country_code   VARCHAR(10) NOT NULL,
    number         VARCHAR(255) NOT NULL,
    extension      VARCHAR(255) NULL,
    formatted      VARCHAR(255) NULL,
    is_verified    BOOLEAN DEFAULT FALSE,
    metadata       JSON NULL,
    created_at     TIMESTAMP NULL,
    updated_at     TIMESTAMP NULL,

    INDEX (phoneable_type, phoneable_id),
    INDEX (type),
    INDEX (is_primary),
    INDEX (number)
);
```

Testing
-------

[](#testing)

```
composer test
```

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance85

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity48

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

Every ~0 days

Total

2

Last Release

153d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8759bf04f295d7d6dcaf23c6defdaee79de5fb85d7a4fbd3fe4e38a885bc7134?d=identicon)[joepages](/maintainers/joepages)

---

Top Contributors

[![joepages](https://avatars.githubusercontent.com/u/583979?v=4)](https://github.com/joepages "joepages (2 commits)")

---

Tags

laravelmulti-tenancypolymorphicphone-numbers

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/joepages-laravel-phone-numbers/health.svg)

```
[![Health](https://phpackages.com/badges/joepages-laravel-phone-numbers/health.svg)](https://phpackages.com/packages/joepages-laravel-phone-numbers)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[laravel/ai

The official AI SDK for Laravel.

1.0k3.2M200](/packages/laravel-ai)[api-platform/laravel

API Platform support for Laravel

58171.6k14](/packages/api-platform-laravel)[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.

45444.2k1](/packages/pressbooks-pressbooks)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.0k](/packages/simplestats-io-laravel-client)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1235.9k20](/packages/fleetbase-core-api)

PHPackages © 2026

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