Back to All Guides
Pharmacy Data

NDC Codes: National Drug Code Standards

Complete guide to National Drug Code (NDC) structure, database design, FDA data integration, drug product identification, and pharmacy dispensing systems.

Abbreviations Used in This Guide

ndc= National Drug Code
cd= code
desc= description
nbr= number
qty= quantity
dt= date
dttm= datetime
id= identifier

View complete glossary: Pharmacy Abbreviations | Pharmacy Definitions

1What is NDC?

The National Drug Code (NDC) is a unique product identifier used in the United States for drugs intended for human use. Assigned by the FDA, NDC numbers serve as the universal identifier for prescription and over-the-counter medications.

Key Uses of NDC:

  • • Pharmacy dispensing and inventory management
  • • Electronic prescribing (eRx) systems
  • • Insurance claims processing and reimbursement
  • • Drug recall tracking and safety monitoring
  • • Pricing and rebate calculations
  • • Clinical decision support systems

💡 Example NDC

0069-2587-10

This NDC identifies: Lipitor (atorvastatin calcium) 40mg tablets, 90-count bottle, manufactured by Pfizer

2NDC Structure & Format

NDC codes consist of 10 or 11 digits divided into three segments, separated by hyphens. The FDA recognizes three format configurations:

Format 1: 4-4-2 (Most Common)

0069-2587-10
Labeler (4)
0069 = Pfizer
Product (4)
2587 = Lipitor 40mg
Package (2)
10 = 90-count bottle

Format 2: 5-3-2

12345-678-90
Labeler (5)
12345
Product (3)
678
Package (2)
90

Format 3: 5-4-1

12345-6789-0
Labeler (5)
12345
Product (4)
6789
Package (1)
0

⚠️ 11-Digit NDC Format

For database storage and billing purposes (especially Medicare Part D), NDCs are often stored as 11 digits without hyphens by padding with a leading zero.

Display Format: 0069-2587-10 (10 digits with hyphens)
Storage Format: 00069258710 (11 digits, no hyphens)

3Segment Breakdown

Segment 1: Labeler Code

Definition: Identifies the manufacturer, repackager, or distributor of the drug product. Assigned by the FDA.

Length: 4 or 5 digits
Examples: 0069 (Pfizer), 0002 (Eli Lilly), 0013 (AstraZeneca)
Database Field: ndc_labeler_cd

Note: A single company may have multiple labeler codes for different divisions.

Segment 2: Product Code

Definition: Identifies the specific strength, dosage form, and formulation of the drug.

Length: 3 or 4 digits
Identifies: Drug name, strength, dosage form
Examples: 2587 (Lipitor 40mg tablets), 3000 (Lipitor 10mg tablets)
Database Field: ndc_product_cd

Important: Different strengths of the same drug have different product codes.

Segment 3: Package Code

Definition: Identifies the package size and type.

Length: 1 or 2 digits
Identifies: Package size, unit count, container type
Examples: 10 (90-count bottle), 41 (30-count bottle)
Database Field: ndc_package_cd

Note: The same drug/strength in different package sizes has different package codes.

4Database Design

PostgreSQL Schema with Standard Abbreviations
CREATE TABLE ndc_drugs (
  ndc_cd VARCHAR(11) PRIMARY KEY, -- 11-digit format (no hyphens)
  ndc_formatted VARCHAR(13), -- Display format with hyphens
  
  -- NDC Segments
  ndc_labeler_cd VARCHAR(5) NOT NULL,
  ndc_product_cd VARCHAR(4) NOT NULL,
  ndc_package_cd VARCHAR(2) NOT NULL,
  
  -- Drug Information
  drug_nm VARCHAR(500) NOT NULL,
  generic_nm VARCHAR(500),
  brand_nm VARCHAR(500),
  labeler_nm VARCHAR(255),
  
  -- Product Details
  dosage_form VARCHAR(100), -- TABLET, CAPSULE, INJECTION
  strength VARCHAR(100), -- 40 MG, 500 MG/10 ML
  route VARCHAR(100), -- ORAL, INTRAVENOUS
  
  -- Package Information
  package_desc VARCHAR(255),
  package_qty DECIMAL(10,2),
  package_unit VARCHAR(50), -- EA, ML, GM
  
  -- Classification
  dea_schedule VARCHAR(10), -- C2, C3, C4, C5 (controlled substances)
  rxcui VARCHAR(20), -- RxNorm concept ID
  
  -- Status
  active_ind CHAR(1) DEFAULT 'Y',
  marketing_category VARCHAR(50),
  approval_dt DATE,
  discontinue_dt DATE,
  
  -- Metadata
  fda_application_nbr VARCHAR(20),
  created_dttm TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_dttm TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Indexes for performance
CREATE INDEX idx_ndc_drugs_labeler ON ndc_drugs(ndc_labeler_cd);
CREATE INDEX idx_ndc_drugs_product ON ndc_drugs(ndc_product_cd);
CREATE INDEX idx_ndc_drugs_drug_nm ON ndc_drugs(drug_nm);
CREATE INDEX idx_ndc_drugs_generic ON ndc_drugs(generic_nm);
CREATE INDEX idx_ndc_drugs_active ON ndc_drugs(active_ind);
CREATE INDEX idx_ndc_drugs_rxcui ON ndc_drugs(rxcui);

💡 Database Design Best Practices

  • Store 11-digit format: Use ndc_cd as VARCHAR(11) without hyphens for consistency
  • Keep formatted version: Store ndc_formatted separately for display purposes
  • Index all segments: Enable fast lookups by labeler, product, or package
  • Track inactive drugs: Use active_ind flag instead of deleting records
  • Link to RxNorm: rxcui enables clinical decision support integration
  • Update regularly: FDA NDC database changes weekly; schedule updates

5FDA Data Integration

The FDA provides the NDC Directory as a free, downloadable dataset updated weekly. This is the authoritative source for all FDA-registered drug products.

FDA NDC Directory Files:

product.txt
Drug product information (name, strength, dosage form)
package.txt
Package configurations and NDC assignments
Python Script: FDA NDC Import
import pandas as pd
import psycopg2

# Download FDA NDC files
# https://www.fda.gov/drugs/drug-approvals-and-databases/ndc-directory

# Load package.txt (contains NDC codes)
df = pd.read_csv('package.txt', sep='\t', dtype=str)

# Connect to database
conn = psycopg2.connect("dbname=pharmacy user=postgres")
cur = conn.cursor()

# Process each NDC record
for idx, row in df.iterrows():
    # Extract NDC (11-digit format)
    ndc_11 = row['NDCPACKAGECODE'].replace('-', '').zfill(11)
    
    # Parse segments
    labeler = ndc_11[0:5].lstrip('0')
    product = ndc_11[5:9].lstrip('0')
    package = ndc_11[9:11].lstrip('0')
    
    # Insert into database
    cur.execute("""
        INSERT INTO ndc_drugs (
            ndc_cd, ndc_formatted,
            ndc_labeler_cd, ndc_product_cd, ndc_package_cd,
            drug_nm, package_desc, active_ind
        ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
        ON CONFLICT (ndc_cd) DO UPDATE SET
            updated_dttm = CURRENT_TIMESTAMP
    """, (
        ndc_11,
        row['NDCPACKAGECODE'],
        labeler, product, package,
        row['PROPRIETARYNAME'],
        row['PACKAGEDESCRIPTION'],
        'Y' if row['MARKETINGSTATUSNAME'] == 'Active' else 'N'
    ))

conn.commit()
print(f"Imported {len(df)} NDC records")

6NDC Lookup Methods

Method 1: Exact NDC Match

Most reliable method - lookup by complete 11-digit NDC code.

SELECT 
  ndc_formatted,
  drug_nm,
  strength,
  dosage_form,
  labeler_nm
FROM ndc_drugs
WHERE ndc_cd = '00069258710'
  AND active_ind = 'Y';

Method 2: Drug Name Search

Fuzzy search by brand or generic name - useful for user interfaces.

SELECT 
  ndc_cd,
  drug_nm,
  strength,
  dosage_form
FROM ndc_drugs
WHERE (
  drug_nm ILIKE '%lipitor%'
  OR generic_nm ILIKE '%atorvastatin%'
)
AND active_ind = 'Y'
ORDER BY drug_nm
LIMIT 20;

Method 3: Product Family Lookup

Find all package sizes for a specific drug/strength combination.

SELECT 
  ndc_formatted,
  package_desc,
  package_qty,
  package_unit
FROM ndc_drugs
WHERE ndc_labeler_cd = '0069'
  AND ndc_product_cd = '2587'
  AND active_ind = 'Y'
ORDER BY package_qty;

7SQL Query Examples

Find Controlled Substances by DEA Schedule

SELECT 
  ndc_formatted,
  drug_nm,
  strength,
  dea_schedule,
  labeler_nm
FROM ndc_drugs
WHERE dea_schedule = 'C2' -- Schedule II (highest control)
  AND active_ind = 'Y'
ORDER BY drug_nm;

List All Strengths of a Generic Drug

SELECT DISTINCT
  strength,
  dosage_form,
  COUNT(*) as package_count
FROM ndc_drugs
WHERE generic_nm ILIKE '%metformin%'
  AND dosage_form = 'TABLET'
  AND active_ind = 'Y'
GROUP BY strength, dosage_form
ORDER BY 
  CAST(REGEXP_REPLACE(strength, '[^0-9]', '', 'g') AS INTEGER);

Find Recently Discontinued Drugs

SELECT 
  ndc_formatted,
  drug_nm,
  labeler_nm,
  discontinue_dt
FROM ndc_drugs
WHERE discontinue_dt >= CURRENT_DATE - INTERVAL '90 days'
  AND discontinue_dt IS NOT NULL
ORDER BY discontinue_dt DESC;

8Integration with Dispensing Systems

NDC codes are critical for pharmacy dispensing systems, linking prescriptions to actual drug products and enabling accurate billing, inventory management, and clinical checks.

Common Integration Points:

  • Electronic Prescribing (eRx): Map RxNorm codes to NDC for dispensing
  • Inventory Management: Track stock levels by NDC package code
  • Insurance Claims: Submit NDC in NCPDP D.0 claim format
  • Drug Utilization Review (DUR): Verify correct drug/strength dispensed
  • Pricing: AWP/WAC pricing typically indexed by NDC
Example: Dispensing Record with NDC
CREATE TABLE rx_dispensing (
  dispense_id VARCHAR(50) PRIMARY KEY,
  rx_nbr VARCHAR(50) NOT NULL,
  patient_id VARCHAR(50) NOT NULL,
  
  -- NDC Information
  ndc_cd VARCHAR(11) NOT NULL,
  drug_nm VARCHAR(500),
  qty_dispensed DECIMAL(10,2),
  days_supply INTEGER,
  
  -- Prescription Details
  prescriber_npi VARCHAR(10),
  fill_dt DATE NOT NULL,
  refill_nbr INTEGER,
  
  -- Pricing
  ingredient_cost DECIMAL(10,2),
  dispensing_fee DECIMAL(10,2),
  
  -- References
  FOREIGN KEY (ndc_cd) REFERENCES ndc_drugs(ndc_cd),
  
  created_dttm TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);