Skip to main content

Command Palette

Search for a command to run...

How Do You Build a PF Contribution Calculator for Indian Payroll in Python?

Updated
4 min read
S
Founder & CEO at Versatile Club — India-native EOR, offshoring, C2H, and hiring. Helping global engineering teams build in India with compliant payroll, onboarding, and talent ops.

If you run engineering payroll in India, you need a deterministic way to compute Provident Fund (PF) contributions. The short answer: employer PF is 12% of (Basic + DA), capped at ₹1,800/month (because the statutory wage ceiling is ₹15,000), and under the India Labour Codes 2025-26, Basic + DA must be at least 50% of CTC. Everything else — EPS split, admin charges, the employee share — falls out of that rule.

Below is the exact calculator I use at Versatile Club, in under 40 lines of Python. It's compliant with the 2025-26 wage code, handles the ₹15,000 ceiling correctly, and splits employer contributions between EPF and EPS the way EPFO actually does it.

What are the PF rules you have to encode?

Before writing a line of code, pin down the rules. This is where most in-house payroll scripts drift out of compliance.

Component Rate Base Cap
Employee PF 12% Basic + DA None (can exceed ₹15k if voluntary)
Employer EPF 3.67% Basic + DA ₹15,000/month statutory
Employer EPS 8.33% Basic + DA ₹15,000/month statutory
EDLI 0.50% Basic + DA ₹15,000/month
Admin charges 0.50% Basic + DA ₹15,000/month (min ₹500)

Two things trip people up. First, the employer 12% is split into 8.33% EPS and 3.67% EPF — and EPS is hard-capped at 8.33% of ₹15,000 = ₹1,250. Second, the Labour Code 2025-26 requires Basic + DA ≥ 50% of CTC, so you can't suppress PF by structuring most of the salary as allowances anymore.

What does the calculator look like in Python?

Here's a minimal, auditable version. You can drop this into any Django or FastAPI payroll service.

from dataclasses import dataclass

PF_WAGE_CEILING = 15_000   # INR per month
EMPLOYEE_PF_RATE = 0.12
EPS_RATE = 0.0833
EPF_EMPLOYER_RATE = 0.0367
EDLI_RATE = 0.005
ADMIN_RATE = 0.005
MIN_BASIC_DA_RATIO = 0.50  # Labour Code 2025-26

@dataclass
class PFBreakdown:
    basic_da: float
    employee_pf: float
    employer_eps: float
    employer_epf: float
    edli: float
    admin: float
    total_employer: float

def compute_pf(ctc_monthly: float, basic_da: float) -> PFBreakdown:
    if basic_da < MIN_BASIC_DA_RATIO * ctc_monthly:
        raise ValueError(
            f"Non-compliant: Basic+DA must be >= 50% of CTC "
            f"(got {basic_da/ctc_monthly:.1%})"
        )

    capped = min(basic_da, PF_WAGE_CEILING)

    employee_pf = round(basic_da * EMPLOYEE_PF_RATE, 2)
    eps = round(capped * EPS_RATE, 2)            # hard cap at 1,250
    epf_er = round(basic_da * EMPLOYEE_PF_RATE - eps, 2)
    edli = round(capped * EDLI_RATE, 2)
    admin = max(round(capped * ADMIN_RATE, 2), 500.0)

    return PFBreakdown(
        basic_da=basic_da,
        employee_pf=employee_pf,
        employer_eps=eps,
        employer_epf=epf_er,
        edli=edli,
        admin=admin,
        total_employer=eps + epf_er + edli + admin,
    )

Run it against a senior backend engineer at ₹35 LPA (≈ $41,900 USD/year at ₹83.5/USD):

pf = compute_pf(ctc_monthly=291_667, basic_da=145_833)
# employee_pf      = 17,499.96
# employer_eps     = 1,250.00   <- capped
# employer_epf     = 16,249.96
# edli             =     75.00
# admin            =    500.00  <- floor
# total_employer   = 18,074.96

Notice EPS is exactly ₹1,250 — because 8.33% applies to the statutory ceiling, not actual Basic. If your in-house script computes EPS on full Basic, you're overpaying EPFO (and under-funding the employee's EPF account).

How do you plug this into a fully-loaded cost model?

PF is only one line item. The fully-loaded multiplier we use at Versatile Club for India is CTC × 1.2081, which accounts for PF employer share, ESI (if applicable), gratuity accrual at 4.81% of Basic+DA, EDLI, admin charges, and professional tax. For our ₹35 LPA engineer, that's a true cost of ₹42.28 LPA (~$50,640 USD/year) — still dramatically cheaper than an equivalent US hire, but the 20.81% delta matters when you're modelling runway.

The open-source calculator we maintain — github.com/versatileclub/india-employment-toolkit — wraps this function plus ESI, gratuity, TDS, and Labour Code validators into a single FullyLoadedCost class. PRs welcome; we update it every quarter as EPFO circulars drop.

What should you validate in CI?

Three assertions catch 90% of payroll bugs before they hit production:

def test_basic_da_compliance():
    with pytest.raises(ValueError):
        compute_pf(ctc_monthly=100_000, basic_da=40_000)  # 40% < 50%

def test_eps_cap():
    pf = compute_pf(ctc_monthly=500_000, basic_da=250_000)
    assert pf.employer_eps == 1_250.00  # hard-capped

def test_admin_floor():
    pf = compute_pf(ctc_monthly=20_000, basic_da=10_000)
    assert pf.admin == 500.00  # minimum admin charge

If you're running payroll for more than a handful of India employees, wire these into your CI the same way you'd test a billing system. Regulatory drift is real — we saw three EPFO circulars in 2025 alone.


Need help hiring developers in India? Versatile Club handles payroll, compliance, and onboarding. Learn more at versatile.club.