database: best practice countries, country codes, country phone codes

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Internationalization: The Best Practice for Storing Country Codes in Your Database

As developers building applications intended for a global audience, one of the most critical yet often overlooked aspects is robust internationalization (i18n) and localization (l10n). When it comes to storing geographical data like country codes and phone numbers, simply dropping them into a single field rarely satisfies the demands of a scalable, multi-lingual application.

You are asking about the "best practice" for structuring this information—specifically, how to manage two-letter ISO codes (like 'DE') alongside international dialing prefixes (like '+49'), while also planning for dynamic language translations within a framework like Laravel.

Let's dissect your proposed schema and build a truly scalable solution.

Why Simple Storage Fails: The Need for Normalization

Your initial thought to split data into separate tables (countries and country_codes) is fundamentally correct. This process—known as database normalization—is the cornerstone of good relational design. Storing related, but distinct, entities in separate tables prevents data redundancy, ensures consistency, and makes updates much easier.

The primary issue with just storing a concatenated string (e.g., "DE+49") is that it creates an inflexible data type. If you later need to query all countries starting with 'D', or if you need to manage the country name separately for localization, this single field becomes a bottleneck.

The Recommended Best Practice: A Normalized Relational Model

The best practice is to treat every distinct piece of information as its own entity. For international data, this means separating static geographical facts from dynamic contact details and linguistic translations.

Here is a more robust structure that supports global expansion seamlessly:

1. The countries Table (The Source of Truth)

This table should be the master list of all countries, focusing on standardized identifiers. This is where you handle localization needs.

CREATE TABLE countries (
    id INT(11) PRIMARY KEY AUTO_INCREMENT,
    iso_code CHAR(2) UNIQUE NOT NULL, -- e.g., 'DE', 'US'
    name VARCHAR(100),              -- The official country name (used for localization)
    language_code CHAR(2)           -- e.g., 'en', 'de' (for language association)
);

2. The phone_codes Table (Handling Specific Data)

This table should link back to the main country entity and manage specific phone number structures, allowing for multiple codes if necessary or complex formatting rules.

CREATE TABLE phone_codes (
    id INT(11) PRIMARY KEY AUTO_INCREMENT,
    country_id INT(11) NOT NULL,
    dialing_code VARCHAR(10),       -- The country-specific dialing code (e.g., '+49')
    national_number VARCHAR(20),    -- The actual local number
    is_international BOOLEAN DEFAULT TRUE,
    FOREIGN KEY (country_id) REFERENCES countries(id) ON DELETE CASCADE
);

Implementing with Laravel and Eloquent

When working within the Laravel ecosystem, this relational structure maps perfectly to Eloquent models. You would define relationships between your models:

In your Country model, you would have a one-to-many relationship with PhoneCode records. This approach ensures that when you fetch a country, you can easily access its name for translation (using Laravel localization features) and all associated phone details. As seen in best practices for database design, understanding these relationships is key to building maintainable systems, much like the principles advocated on platforms like laravelcompany.com.

Example Eloquent Relationship:

// In your Country Model
public function phoneCodes()
{
    return $this->hasMany(PhoneCode::class);
}

Conclusion: Scalability Through Separation

Splitting the data into normalized tables is not just a preference; it is a necessity for any application aiming for global reach. By separating the static country information (names, ISO codes) from the dynamic contact details (phone numbers), you achieve three major benefits:

  1. Data Integrity: You eliminate the risk of inconsistent data entry across your system.
  2. Flexibility: Adding new features—like supporting a new language or a complex phone formatting rule—becomes a simple matter of adding a new field or table, rather than restructuring existing monolithic fields.
  3. Localization Ready: By keeping country names and codes separate, you create a clean foundation for integrating localization packages and ensuring your application can scale across any market without major rewrites.

Embrace normalization when dealing with international data; it is the most reliable path to building future-proof software.