PHPackages                             bensondevs/indonesian-ktp - 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. [Validation &amp; Sanitization](/categories/validation)
4. /
5. bensondevs/indonesian-ktp

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

bensondevs/indonesian-ktp
=========================

Validate Indonesian NIK (Nomor Induk Kependudukan) using compiled wilayah data (cahyadsn/wilayah, MIT).

v0.1.2(3w ago)3142—0%13MITPHPPHP ^8.3

Since May 14Pushed 3w agoCompare

[ Source](https://github.com/bensondevs/indonesian-ktp)[ Packagist](https://packagist.org/packages/bensondevs/indonesian-ktp)[ RSS](/packages/bensondevs-indonesian-ktp/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (9)Versions (4)Used By (0)

🇮🇩 Indonesian KTP (NIK) validation
==================================

[](#-indonesian-ktp-nik-validation)

Validate Indonesian NIK (*Nomor Induk Kependudukan*) with structural checks and bundled wilayah data (no MySQL, no Nusantara). Public API: [`KTP`](src/KTP.php).

Table of contents
-----------------

[](#table-of-contents)

- [What gets validated](#what-gets-validated)
- [Requirements](#requirements)
- [Install](#install)
- [Quick start](#quick-start)
- [Usage](#usage)
    - [Basic validation](#basic-validation)
    - [Laravel Validator (rule object and ktp-nik)](#laravel-validator-rule-object-and-ktp-nik)
    - [Quick checks](#quick-checks)
    - [Expectations and aliases](#expectations-and-aliases)
    - [validate() and ValidationResult](#validate-and-validationresult)
    - [Parsed values](#parsed-values)
    - [Region inputs](#region-inputs)
    - [Two-digit birth years: ambiguity and asOf()](#two-digit-birth-years-ambiguity-and-asof)
    - [Region hierarchy lookup](#region-hierarchy-lookup)
    - [Eloquent HasIndonesianKtp](#eloquent-hasindonesianktp)
        - [Explicit matchers](#explicit-matchers)
        - [Trait methods](#trait-methods)
    - [NIK column and accessors](#nik-column-and-accessors)
- [Develop and test](#develop-and-test)
- [Support](#support)
- [Data source](#data-source)
- [Security](#security)
- [Versioning and support](#versioning-and-support)

What gets validated
-------------------

[](#what-gets-validated)

- **Structure** — length, digits, birth date / gender encoding.
- **Region hierarchy** — district code in the NIK must exist in [`data/wilayah.php`](data/wilayah.php) (province → regency → subdistrict).

Optional checks (birth, age, gender, wilayah names/codes): [Usage](#usage). Dataset: [Data source](#data-source).

Invalid length or unknown district:

```
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('123')->isValid(); // false — wrong length
KTP::nik('9999991501900001')->isValid(); // false — unknown district (no expectations)
```

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

[](#requirements)

RequirementNotesPHP8.3+Laravel10–13; `illuminate/contracts`, `illuminate/database`, `illuminate/support`, `illuminate/validation` match your appCarbon`nesbot/carbon` ^2.67 or ^3.0Install 📦
---------

[](#install-)

```
composer require bensondevs/indonesian-ktp
```

Quick start ✅
-------------

[](#quick-start-)

Minimal check (structure + wilayah hierarchy):

```
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('3315131501901235')->isValid();
```

Structure + region only until you add expectations (see [Usage](#usage)).

Usage
-----

[](#usage)

- `KTP::nik($raw)` returns a fluent, **immutable** `Query`: each chained call is a new instance.
- `isValid()` → one `bool`. `validate()` → [`ValidationResult`](src/NIK/ValidationResult.php) with per-flag detail.
- Laravel’s [`Validator`](https://laravel.com/docs/validation) is supported via [`KtpNik`](src/Rules/KtpNik.php) and string rules registered in [`IndonesianKtpServiceProvider`](src/IndonesianKtpServiceProvider.php) — see [Laravel Validator (rule object and ktp-nik)](#laravel-validator-rule-object-and-ktp-nik).

```
use Bensondevs\IndonesianKtp\Gender;
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('3315131501901235')
    ->expectGender(Gender::Male)
    ->isValid();
```

### Basic validation

[](#basic-validation)

```
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('3315131501901235')->isValid();
```

### Laravel Validator (rule object and ktp-nik)

[](#laravel-validator-rule-object-and-ktp-nik)

With [package discovery](https://laravel.com/docs/packages#package-discovery), [`IndonesianKtpServiceProvider`](src/IndonesianKtpServiceProvider.php) registers translation lines and validation extensions automatically. You can validate request input either with a [rule object](https://laravel.com/docs/validation#using-rule-objects) or with a string rule.

**Rule object vs string rule**

```
use Bensondevs\IndonesianKtp\Rules\KtpNik;

$request->validate([
    'nik' => ['required', 'string', new KtpNik],
]);

// Equivalent string rule (underscore alias: ktp_nik)
$request->validate([
    'nik' => ['required', 'string', 'ktp-nik'],
]);
```

**What this checks**

Same asPlain `KTP::nik($value)->isValid()` — **structure** (length, digits, birth/gender segment rules) plus **complete wilayah hierarchy** for the district code.Does **not** includeChained expectations such as `expectBirthDate`, `expectGender`, `expectProvince`, age rules, etc. For those, use the fluent `Query` API — [Expectations and aliases](#expectations-and-aliases).**Composing with `required` / `nullable`**

Use Laravel’s built-in rules for presence: `required|string|…` when the field must be present, or `nullable|string|…` when it is optional. The `KtpNik` rule **does not fail** on `null` or `''`, so optional fields stay easy to express without fighting the custom rule.

**Input types**

Integer or numeric string values are cast to string before validation. Arrays, objects, and booleans fail. In practice, pair the rule with Laravel’s `string` rule as in the examples above.

**Messages and localization**

The default English message lives under the `indonesian-ktp` namespace (`validation.ktp_nik`). Override or translate it like any vendor lang line (for example files under `lang/vendor/indonesian-ktp`). See [Laravel localization](https://laravel.com/docs/localization).

**Custom wilayah data**

If you rebind [`RegionHierarchyLookup`](src/Regions/Lookup/RegionHierarchyLookup.php), `KTP::nik()` uses it when the container is available — so these validator rules pick up the same lookup. See [Region hierarchy lookup](#region-hierarchy-lookup).

### Quick checks

[](#quick-checks)

`match*` helpers compare the NIK to a value; they do **not** add `expect*` rules to the query.

**Gender and birth date**

```
use Bensondevs\IndonesianKtp\Gender;
use Bensondevs\IndonesianKtp\KTP;

$query = KTP::nik('3315131501901235');

$query->matchBirthDate('1990-01-15');
$query->matchGender(Gender::Male);
$query->matchGender('male');
```

**Age / minimum age:** `matchAge()` / `matchAtLeastYears()` need a resolved birth year — use `asOf()` as in [Two-digit birth years](#two-digit-birth-years-ambiguity-and-asof).

### Expectations and aliases

[](#expectations-and-aliases)

Chain then call `isValid()` or `validate()`. Each chained call returns a new `Query`.

Area`expect*`AliasBirth date`expectBirthDate``birthDate`Integer age`expectAge``age`Minimum age`expectAtLeastYears`, `expectSeventeenOrOlder`, `expectTwentyOneOrOlder`—Gender`expectGender``gender`Province`expectProvince``province`Regency`expectRegency``regency`Subdistrict`expectSubdistrict``subdistrict`**Full names**

```
use Bensondevs\IndonesianKtp\Gender;
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('3315131501901235')
    ->expectBirthDate('1990-01-15')
    ->expectGender(Gender::Male)
    ->expectProvince('jawa tengah')
    ->isValid();
```

**Aliases**

```
use Bensondevs\IndonesianKtp\Gender;
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('3315131501901235')
    ->birthDate('1990-01-15')
    ->gender(Gender::Male)
    ->province('jawa tengah')
    ->isValid();
```

`isValid()` is the same as `validate()->isFullyValid()` (structure + hierarchy + every set expectation). Chaining `expectAge` / `age` needs `asOf()` — see [Two-digit birth years](#two-digit-birth-years-ambiguity-and-asof).

### validate() and ValidationResult

[](#validate-and-validationresult)

[`ValidationResult`](src/NIK/ValidationResult.php) exposes **methods** (not public properties).

MethodReturnNotes`hasValidStructure()``bool``hasValidRegionHierarchy()``bool``hasValidBirthDate()``bool` or `null``null` = expectation not set`hasValidGender()``bool` or `null``hasValidProvince()``bool` or `null``hasValidRegency()``bool` or `null``hasValidSubdistrict()``bool` or `null``hasValidAge()``bool` or `null``hasValidMinimumAge()``bool` or `null``isFullyValid()``bool`Same as `isValid()` on the `Query`AliasEquivalent`hasValidKabupaten()`, `hasValidCity()``hasValidRegency()``hasValidKecamatan()``hasValidSubdistrict()````
use Bensondevs\IndonesianKtp\KTP;

$validationResult = KTP::nik('3315131501901235')->validate();

$validationResult->hasValidStructure();
$validationResult->hasValidRegionHierarchy();
$validationResult->hasValidGender(); // null — no expectation

$validationResult = KTP::nik('3315131501901235')
    ->birthDate('1990-01-01')
    ->validate();

$validationResult->hasValidBirthDate(); // false — mismatch
```

```
use Bensondevs\IndonesianKtp\KTP;

$validationResult = KTP::nik('3315131501901235')->validate();

$validationResult->isFullyValid(); // same as isValid() on the query
```

### Parsed values

[](#parsed-values)

`parsed()` returns a [`Parsed`](src/NIK/Parsed.php) snapshot (read-only fields from the NIK). `KTP::nik(...)->parsed()` attaches wilayah **names** from the app’s region lookup when the district code is known; use `provinceCode()` / `regencyCode()` / `districtCode()` for keys from the NIK alone.

MethodRole`raw()`Normalized 16-digit string`structureValid()`Structural segment checks`provinceCode()`, `regencyCode()`, `districtCode()`Wilayah **codes** from the NIK (e.g. `33`, `33.15`, `33.15.13`)`province()`, `provinsi()`Province **display name** when the bound lookup resolves the district (e.g. `Jawa Tengah`); `null` if unknown or parser-only `Parsed` without `withRegionHierarchy()``regency()`, `kabupaten()`, `kota()`, `city()`Regency / city **display name** when resolved (same value for all four; NIK does not distinguish kabupaten vs kota); `null` otherwise`district()`, `kecamatan()`Kecamatan **display name** when resolved; `null` otherwise`birthDate()`Single date, or `null` if two-digit year is ambiguous ([Two-digit birth years](#two-digit-birth-years-ambiguity-and-asof))`possibleBirthDates()`All plausible dates when ambiguous`gender()`, `serial()`Parsed gender / serial`age($asOf?)`, `isAtLeastYears($min, $asOf)`, `isSeventeenOrOlder($asOf)`, `isTwentyOneOrOlder($asOf)`Age helpers (conservative when ambiguous). `age()` with no argument uses the current instant (`Carbon::now()`).On the `Query`, `resolvedAge()` uses the pivot instant when you chained `asOf()` ([Two-digit birth years](#two-digit-birth-years-ambiguity-and-asof)). For real validation, prefer `validate()` / `isValid()`.

```
use Bensondevs\IndonesianKtp\KTP;
use Carbon\Carbon;

$parsed = KTP::nik('3315131501901235')->parsed();

$parsed->raw();
$parsed->structureValid();
$parsed->districtCode();              // NIK wilayah key, e.g. "33.15.13"
$parsed->provinceCode();              // "33"
$parsed->regencyCode();               // "33.15"
$parsed->province();                 // e.g. "Jawa Tengah" — null if lookup has no row
$parsed->regency();                  // e.g. "Kabupaten Grobogan"; alias: city(), kabupaten(), kota()
$parsed->district();                 // e.g. "Purwodadi"; alias: kecamatan()
$parsed->birthDate();                // null if ambiguous (no asOf on query)
$parsed->possibleBirthDates();
$parsed->gender();
$parsed->serial();
$parsed->age();                      // optional asOf; defaults to now()
$parsed->age(Carbon::parse('2026-01-01'));
$parsed->isSeventeenOrOlder(Carbon::parse('2026-01-01'));
```

### Region inputs

[](#region-inputs)

[`NikRegionMatcher`](src/Regions/Matching/NikRegionMatcher.php): province, regency, and subdistrict each accept **codes** (int / string shapes) or **names**. More cases: [`tests/Feature/Ktp/KtpRegionInputsTest.php`](tests/Feature/Ktp/KtpRegionInputsTest.php).

```
use Bensondevs\IndonesianKtp\KTP;

$sampleNik = '3315131501901235';

KTP::nik($sampleNik)->expectProvince(33)->isValid();
KTP::nik($sampleNik)->expectProvince('JAWA TENGAH')->isValid();
```

```
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('3315131501901235')
    ->expectRegency(15) // province taken from NIK (33…)
    ->isValid();
```

```
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('3315131501901235')
    ->expectRegency(15)
    ->expectSubdistrict(13)
    ->isValid();
```

Unknown district:

```
use Bensondevs\IndonesianKtp\KTP;

KTP::nik('9999991501900001')->isValid(); // false
```

### Two-digit birth years: ambiguity and asOf()

[](#two-digit-birth-years-ambiguity-and-asof)

```
use Bensondevs\IndonesianKtp\KTP;
use Carbon\Carbon;

// No asOf: every plausible century for YY that fits a 17–120 age window (evaluated at query build time)
KTP::nik('3315131501901235');

// With asOf: single resolved birth year for that pivot
KTP::nik('3315131501901235')->asOf(Carbon::parse('2026-01-01'));
```

**`matchAge` / minimum age** (needs the same pivot):

```
use Bensondevs\IndonesianKtp\KTP;
use Carbon\Carbon;

$query = KTP::nik('3315131501901235')->asOf(Carbon::parse('2026-01-01'));

$query->matchAge(35);
$query->matchAtLeastYears(21);
```

TopicBehaviourBirth / `expectBirthDate`Ambiguous: any matching candidate wins. Pivot: one resolved year.`parsed()->birthDate()``null` if multiple candidates; use `possibleBirthDates()`. Pivot: always set when structure is valid.`age` / `resolvedAge()`Can stay `null` until year is unique; use `asOf()` or derive from `possibleBirthDates()`.Minimum-age helpers**Conservative:** every plausible birth candidate must pass.**Edge cases:** 17–120 uses calendar boundaries; odd ages or midnight tests may need your own `asOf()`. Examples: [`tests/Feature/Ktp/KtpTwoDigitYearAndAsOfTest.php`](tests/Feature/Ktp/KtpTwoDigitYearAndAsOfTest.php), [`tests/Unit/NIK/ParserTest.php`](tests/Unit/NIK/ParserTest.php).

```
use Bensondevs\IndonesianKtp\KTP;
use Carbon\Carbon;

Carbon::setTestNow('2026-09-01');

$ambiguousNik = '3315130109090002';

KTP::nik($ambiguousNik)->parsed()->birthDate();              // null
count(KTP::nik($ambiguousNik)->parsed()->possibleBirthDates()); // 2

KTP::nik($ambiguousNik)->asOf(Carbon::parse('2026-09-01'))->parsed()->birthDate(); // single date

Carbon::setTestNow();
```

Eloquent: same behaviour via `indonesianKtpReferenceDate()` — [Reference date](#reference-date-indonesianktpreferencedate) under [NIK column and accessors](#nik-column-and-accessors).

### Region hierarchy lookup

[](#region-hierarchy-lookup)

- **Auto-discovery:** [`IndonesianKtpServiceProvider`](src/IndonesianKtpServiceProvider.php) registers [`RegionHierarchyLookup`](src/Regions/Lookup/RegionHierarchyLookup.php) → bundled [`data/wilayah.php`](data/wilayah.php) via [`FileRegionHierarchyLookup`](src/Regions/Lookup/FileRegionHierarchyLookup.php).
- **[`KTP::nik()`](src/KTP.php)** uses the container when the contract is bound; otherwise the bundled path (e.g. some unit tests).

Custom compiled file (same PHP array format), rebind **after** the package provider:

```
use Bensondevs\IndonesianKtp\Regions\Lookup\FileRegionHierarchyLookup;
use Bensondevs\IndonesianKtp\Regions\Lookup\RegionHierarchyLookup;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(RegionHierarchyLookup::class, function (): FileRegionHierarchyLookup {
            $path = storage_path('app/wilayah.php'); // your compiled file

            return new FileRegionHierarchyLookup($path);
        });
    }
}
```

### Eloquent HasIndonesianKtp

[](#eloquent-hasindonesianktp)

[`HasIndonesianKtp`](src/Concerns/HasIndonesianKtp.php) reads the **NIK column** via `getAttribute()` (casts and accessors apply). Comparisons against birth date, age, gender, and wilayah fields are **explicit**: you pass the value you want checked (for example from another column, a relation, or request input). The trait does not scan `nik_*` or fallback attribute names for you.

```
use Bensondevs\IndonesianKtp\Concerns\HasIndonesianKtp;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasIndonesianKtp;

    // Optional: if your NIK column is not named `nik`
    // protected function getIndonesianKtpNikColumn(): string
    // {
    //     return 'id_number';
    // }
}
```

```
// $model is an Eloquent model using HasIndonesianKtp

$model->hasValidNik();
$model->nikGenderIs($model->gender);
$model->nikProvinceIs($model->province);
$model->nikAgeIs((int) $model->age);
```

Default NIK attribute name: `nik` (override `getIndonesianKtpNikColumn()`). Non-digits are stripped after read.

#### Explicit matchers

[](#explicit-matchers)

Each `nik*Is($value)` method compares the **argument** to what is encoded or implied by the NIK. Extra attributes on the model are ignored unless you pass them in. Each short name has a long alias (`indonesianIdNumber*Is`) for consistency with the rest of the package.

#### Trait methods

[](#trait-methods)

MethodPurpose`hasValidNik()`Structure + district hierarchy; same as `hasValidIndonesianIdNumber()``nikBirthdateIs(mixed $birth)`NIK birth segment matches `$birth`; alias `indonesianIdNumberBirthdateIs()``nikGenderIs()`NIK gender matches the argument (`Gender` or `string`); alias `indonesianIdNumberGenderIs()``nikProvinceIs(mixed $expected)`Wilayah province expectation; alias `indonesianIdNumberProvinceIs()``nikRegencyIs(mixed $expected)`Regency expectation; aliases `indonesianIdNumberRegencyIs()`, `nikKabupatenIs()`, `nikCityIs()`, `nikDistrictIs()` and matching `indonesianIdNumber*` forms`nikSubdistrictIs(mixed $expected)`Subdistrict expectation; aliases `indonesianIdNumberSubdistrictIs()`, `nikKecamatanIs()`, `indonesianIdNumberKecamatanIs()``nikAgeIs(int $age)`Completed age from the NIK (per reference date rules) equals `$age` when unambiguous; alias `indonesianIdNumberAgeIs()``ageFromNik()`Completed full years from the NIK at the trait’s reference instant; `null` when ambiguous; alias `ageFromIndonesianIdNumber()``isSeventeenOrOlderFromNik()`Conservative 17+ check over all birth candidates; alias `isSeventeenOrOlderFromIndonesianIdNumber()``isTwentyOneOrOlderFromNik()`Conservative 21+ check; alias `isTwentyOneOrOlderFromIndonesianIdNumber()``isAtLeastYearsFromNik(int $years)`Conservative minimum-age check; alias `isAtLeastYearsFromIndonesianIdNumber()`### NIK column and accessors

[](#nik-column-and-accessors)

Override `getIndonesianKtpNikColumn()` and/or use an accessor on that column. The trait does not expose raw NIK normalization beyond stripping non-digits after `getAttribute`.

#### NIK column name

[](#nik-column-name)

```
protected function getIndonesianKtpNikColumn(): string
{
    return 'national_id';
}
```

#### NIK value (accessor on the configured column)

[](#nik-value-accessor-on-the-configured-column)

Formatted storage → normalize in an accessor; the trait still strips non-digits after `getAttribute`.

```
// With default getIndonesianKtpNikColumn() => 'nik'
protected function getNikAttribute(?string $value): ?string
{
    return $value !== null ? preg_replace('/\D/', '', $value) : null;
}
```

#### Applicant-style: custom column names, explicit matchers

[](#applicant-style-custom-column-names-explicit-matchers)

When birth date, gender, or wilayah live on other attributes or relations, read them yourself and pass them into `nik*Is`:

```
use Bensondevs\IndonesianKtp\Concerns\HasIndonesianKtp;
use Illuminate\Database\Eloquent\Model;

class Applicant extends Model
{
    use HasIndonesianKtp;

    protected function getIndonesianKtpNikColumn(): string
    {
        return 'identity_number';
    }
}

// Validation-style usage (attributes / casts apply on reads):
$applicant->hasValidNik()
    && $applicant->nikBirthdateIs($applicant->profile?->dob ?? $applicant->legacy_birthdate)
    && $applicant->nikGenderIs($applicant->sex_code)
    && $applicant->nikProvinceIs($applicant->prov_name)
    && $applicant->nikRegencyIs($applicant->kab_name)
    && $applicant->nikSubdistrictIs($applicant->kec_name);
```

#### Reference date (indonesianKtpReferenceDate)

[](#reference-date-indonesianktpreferencedate)

Default `null` → ambiguous two-digit years (like `KTP::nik($nik)` without `asOf()`). Return `Carbon::now()` (or any pivot) so every internal trait query uses `asOf()` for YY resolution.

```
use Carbon\Carbon;
use Carbon\CarbonInterface;

protected function indonesianKtpReferenceDate(): ?CarbonInterface
{
    return null; // ambiguous
}

protected function indonesianKtpReferenceDate(): ?CarbonInterface
{
    return Carbon::now(); // pivot on “now”
}
```

Develop and test 🧪
------------------

[](#develop-and-test-)

```
composer install && composer test
```

Support
-------

[](#support)

If this package saves you time, you can support ongoing maintenance on [GitHub Sponsors](https://github.com/sponsors/bensondevs) or [Trakteer](https://trakteer.id/bensonsimeon/tip).

Data source
-----------

[](#data-source)

Hierarchy file: [`data/wilayah.php`](data/wilayah.php) (from [cahyadsn/wilayah](https://github.com/cahyadsn/wilayah), MIT). Attribution: [`NOTICE`](NOTICE). Maintainers can compile from [upstream `db/wilayah.sql`](https://github.com/cahyadsn/wilayah) and ship their own `wilayah.php`; this repo has no compile script.

Security 🔒
----------

[](#security-)

Validation does **not** send NIKs off-device. Treat NIKs as sensitive in logs and traces. Disclosure: [`SECURITY.md`](SECURITY.md).

Versioning and support
----------------------

[](#versioning-and-support)

[Semantic Versioning](https://semver.org/). Upgrades: [`CHANGELOG.md`](CHANGELOG.md).

- **Releases:** [Packagist — bensondevs/indonesian-ktp](https://packagist.org/packages/bensondevs/indonesian-ktp)
- **Changelog:** [`CHANGELOG.md`](CHANGELOG.md)
- **Contributing:** [`CONTRIBUTING.md`](CONTRIBUTING.md)
- **Security:** [`SECURITY.md`](SECURITY.md)

Match supported Laravel majors to [`composer.json`](composer.json) `illuminate/*` constraints when upgrading.

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance95

Actively maintained with recent releases

Popularity25

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 57.1% 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 ~1 days

Total

3

Last Release

25d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6953e310af7dc10b68168564e03f24bfcc36b8dd0830dea1d1696f50cda16fff?d=identicon)[bensondevs](/maintainers/bensondevs)

---

Top Contributors

[![bensondevs](https://avatars.githubusercontent.com/u/76855099?v=4)](https://github.com/bensondevs "bensondevs (4 commits)")[![IlhamriSKY](https://avatars.githubusercontent.com/u/18723904?v=4)](https://github.com/IlhamriSKY "IlhamriSKY (3 commits)")

---

Tags

dukcapilindonesianktplaravelnik

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/bensondevs-indonesian-ktp/health.svg)

```
[![Health](https://phpackages.com/badges/bensondevs-indonesian-ktp/health.svg)](https://phpackages.com/packages/bensondevs-indonesian-ktp)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[laravel/horizon

Dashboard and code-driven configuration for Laravel queues.

4.1k91.3M277](/packages/laravel-horizon)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[spatie/laravel-health

Monitor the health of a Laravel application

88011.3M149](/packages/spatie-laravel-health)[watson/validating

Eloquent model validating trait.

9743.4M53](/packages/watson-validating)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8733.1M23](/packages/yajra-laravel-oci8)

PHPackages © 2026

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