Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

French Postal Code Laravel Package

stanislas-poisson/french-postal-code

A Laravel/PHP package for working with French postal codes, providing validation and lookup of codes and associated cities. Useful for address forms and data normalization.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require stanislas-poisson/french-postal-code
php artisan vendor:publish   # then select the package's provider/tag from the prompt
php artisan migrate

Because the package ships migrations plus a data loader, the publish step matters: check vendor/stanislas-poisson/french-postal-code/ for the service provider's publish tags (typically config, migrations, or a single french-postal-code tag) and publish only what you need. Confirm auto-discovery worked by running php artisan list | grep -i postal — the loader should appear as a command (or as a seeder class if the package only exposes one).

First use case: a validated postal-code field

The fastest win is replacing a free-text "Code postal / Ville" field on a checkout or signup form. Load the dataset once, then:

  1. Validate the input shape with preg_match('/^\d{5}$/', $value) — shape only, not existence.
  2. Look the code up in the postal_codes table (or via the package model) to confirm it exists and to retrieve the associated commune, department and region.
  3. Autofill city/region from the lookup and store the postal code string plus the foreign key to the commune (or postal-code record).

Where to look first

Read the migrations before the models: the schema tells you whether the package models a postal code as a first-class table with a latitude/longitude pair (as the description implies) and whether communes link to postal codes directly or through a pivot. That single detail determines every query you write afterwards.

Implementation Patterns

Model graph and relationships. Expect the canonical French hierarchy: Region → Department → Commune, with postal codes attached to communes. Model your own Address/Company/Store with a postal_code_id (or a plain 5-char string plus an index) rather than duplicating city names — the package becomes your reference table, and your app stays normalised.

A lookup service with caching. This data is effectively read-only between deployments, so wrap it:

class PostalCodeLookup
{
    public function find(string $code): ?PostalCodeData
    {
        return Cache::remember("cp:{$code}", now()->addDay(), function () use ($code) {
            return PostalCode::with('communes.department.region')
                ->where('code', $code)
                ->first()
                ?->toData();
        });
    }
}

Cache by postal code, not by department — lookups are overwhelmingly single-code.

Autocomplete endpoint. For a typeahead, query WHERE code LIKE '75%' with a LIMIT, and dedupe by commune for display. Return {code, city, department, region} and let the front end fill three fields from one selection. Index the code column and, if you search by name, a normalised (accent-stripped, lowercased) city column — accents will bite you otherwise.

Radius / proximity search. With one GPS point per postal code, the honest pattern is: resolve the user's postal code to its point, then run a bounding-box prefilter on latitude/longitude and refine with a Haversine expression. Treat results as "postal-code centroid within X km", not "address within X km" — good enough for store locators and delivery-zone checks, not for routing.

Seeding strategy. Load the dataset in a deploy step or a dedicated seeder, not in DatabaseSeeder on every test run. For tests, either seed a small subset (if the loader accepts a department filter) or use a handful of factories, and assert against known codes rather than the whole table.

Deployment. Add the loader to your release script after migrate. If it is slow, run it inside a transaction with model events disabled, or chunk by department. Re-run it on every package upgrade — French communes merge and reassign codes regularly (communes nouvelles), so a stale table will silently reject valid input.

Gotchas and Tips

Postal codes are not 1:1 with communes. Large cities have many codes; rural codes often cover several communes. Never assume where('code', $cp)->first() gives you the city — design for a collection or pick a documented default. This is the single most common source of bad address data.

Don't derive the department from the first two digits. Corsica (20xxx spanning 2A/2B) breaks that shortcut, and overseas codes (971–976, plus 98xxx) don't follow the metropolitan pattern. Always join to the department record.

Validate shape, then existence. A regex accepts 99999; only a DB lookup rejects it. Do both, in that order, and give a friendly "code postal inconnu" message.

Strip CEDEX before lookup. Users paste 75008 CEDEX 08. Normalise with something like preg_replace('/\s*CEDEX.*$/i', '', $input) and keep the suffix in a separate field if you need it for shipping.

One point per code is coarse. Do not feed these coordinates into distance-based pricing or turn-by-turn navigation. For precise geocoding, keep an external geocoder and use this package for the canonical administrative hierarchy.

Monaco and foreign codes. 98000 is Monaco, not France. If your form is France-only, decide explicitly whether to accept it; if it's international, gate this package's lookup behind a country selector.

Loader performance and idempotency. Tens of thousands of communes plus postal codes is a multi-minute import on shared hosting. Confirm the loader truncates or upserts cleanly before running it twice; run it with --no-interaction in CI so a stray prompt doesn't hang the pipeline.

Package maturity. With no dependents and a single maintainer, pin the version in composer.json, keep a smoke test that asserts a few well-known lookups (75001 → Paris, 13001 → Marseille), and consider snapshotting the data into your own seeder so a broken release can't block deploys. If you use Symfony-style loader classes elsewhere, don't wire them into Laravel — use the Laravel service provider path.

Extension points. If the package exposes configurable table names or model bindings, override them in config/french-postal-code.php rather than editing vendor files; if not, add a thin repository layer so you can swap the source later without touching controllers.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style