BIR Form 1601-C — Monthly Remittance Return of Income Taxes Withheld on Compensation
How MotorPH generates the monthly 1601-C, where every figure comes from, and what each of
the form's 40 entry points contains. The layout reproduced is the January 2018 (ENCS)
revision — the authoritative blank form sits next to this file
(1601C final Jan 2018 with DPA.pdf).
1. Overview
BIR Form 1601-C is the return every employer (withholding agent) files monthly to remit the income tax withheld from employee compensation. MotorPH generates a format-only reproduction of the form pre-filled with the month's payroll aggregates — it is a working paper for the person who files, not an eFPS/eBIRForms submission. Actual filing and payment happen through eFPS or eBIRForms, due on or before the 10th day of the month following the month of withholding (some eFPS filer groups have later staggered dates).
- UI: Payroll → Government Forms (
/government-forms) → BIR 1601-C card → Generate. The dialog defaults to the previous calendar month and lets you pick any month within the last five years, then downloadBIR-1601C-YYYY-MM.pdf. - ATC:
WW010(withholding tax on compensation, private employer). - Related annual forms: BIR 1604-C Alphalist and BIR 2316 cover the same withholding annually — and they, not the monthly 1601-C, apply the ₱90,000 13th-month exemption cap (see §6).
2. Architecture and data flow
payslips (per payroll run) bonuses (paid outside payroll runs)
│ │
▼ ▼
PayslipRepository.findMonthlyFiguresForAllEmployees(year, month)
per-employee SUMs across every run in the BonusRepository
calendar month: grossIncome, totalBenefits, .findMonthlyBonusTotal(year, month)
SSS/PHIC/HDMF employee shares, withholdingTax │
│ │
└──────────────┬──────────────────────────────┘
▼
GovernmentFormsServiceImpl.getBir1601c(year, month) ← lines 14–25 computed here
▼
Bir1601cDto ── GET /api/government-forms/bir-1601c?year&month
▼
Bir1601cDialog.tsx (period pickers, uncapped-bonus warning, empty state)
▼
Bir1601cTemplate.tsx ← lines 26–36 derived here; renders two [data-pdf-page] divs
▼
renderPagedNodeToPdfBlob (html2canvas + jsPDF) → BIR-1601C-YYYY-MM.pdf
Key source files:
| Concern | File |
|---|---|
| Line 14–25 computation | backend/src/main/java/com/motorph/payroll/service/impl/GovernmentFormsServiceImpl.java (getBir1601c) |
| DTO (field ↔ line comments) | backend/src/main/java/com/motorph/payroll/dto/Bir1601cDto.java |
| Monthly payslip aggregate | backend/src/main/java/com/motorph/payroll/repository/PayslipRepository.java (findMonthlyFiguresForAllEmployees) |
| Monthly bonus total | backend/src/main/java/com/motorph/payroll/repository/BonusRepository.java (findMonthlyBonusTotal) |
| REST endpoint + RBAC | backend/src/main/java/com/motorph/payroll/controller/GovernmentFormsController.java (GET /api/government-forms/bir-1601c) |
| Dialog (period, warning, download) | frontend/src/components/government-forms/Bir1601cDialog.tsx |
| Form template + lines 26–36 | frontend/src/components/government-forms/Bir1601cTemplate.tsx |
| HTML → PDF renderer | frontend/src/components/payroll/payslipPdf.tsx (renderPagedNodeToPdfBlob) |
The upstream payslip columns are produced by PayrollServiceImpl during payroll run
generation: grossIncome includes regular/OT pay, allowances folded into gross, and night
shift differential; totalBenefits is the de minimis bundle (rice + phone + clothing
allowances) and is excluded from the withholding tax base; withholdingTax comes from
the TRAIN monthly bracket table (withholding_tax_brackets). Bonuses never pass through
payroll runs — they are recorded in the bonuses table and enter this form only through
the monthly bonus total.
3. Complete entry-point map (1–40)
Column meanings — Where: backend = computed in getBir1601c; frontend = derived
or hard-coded in Bir1601cTemplate.tsx; profile = taken from the Company Profile
(CompanyProfileDto, editable via the Employer Profile drawer on the Government Forms
page); blank = left empty for manual completion at filing time.
Part I — Background Information (entries 1–13)
| # | Form label | Where | Source / value |
|---|---|---|---|
| 1 | For the Month of (MM/YYYY) | frontend | Selected period from the dialog, rendered as a 2+4 comb box |
| 2 | Amended Return? | frontend | Always No (amended returns not supported) |
| 3 | Any Taxes Withheld? | frontend | Yes iff line 25 (totalTaxWithheld) > 0, else No |
| 4 | No. of Sheets Attached | blank | Not tracked |
| 5 | ATC | frontend | Hard-coded WW010 |
| 6 | Taxpayer Identification Number (TIN) | profile | tin (digits only, 3-3-3 comb) + branch code (defaults 0000) |
| 7 | RDO Code | profile | rdoCode (digits only) |
| 8 | Withholding Agent's Name | profile | registeredName |
| 9 | Registered Address | profile | registeredAddress |
| 9A | ZIP Code | profile | zipCode (digits only) |
| 10 | Contact Number | profile | phoneNumber (digits only, 4-3-4 comb) |
| 11 | Category of Withholding Agent | profile | Private / Government checkbox from withholdingAgentCategory |
| 12 | Email Address | blank | Renders empty even when the profile has one — known gap, see §6 |
| 13 / 13A | Payees availing of tax relief under Special Law / Tax Treaty? | frontend | Neither box checked; 13A blank (treaty relief not modeled) |
Part II — Computation of Tax (entries 14–36)
Lines 14–25 are computed server-side; each is summed at full precision and only then
rounded to 2 decimals, HALF_UP. Lines 26–36 are derived client-side purely to keep the
printed numbering contiguous — every input to them is zero in this system.
| # | Form label | Where | Formula / source |
|---|---|---|---|
| 14 | Total Amount of Compensation | backend | Σ payslip grossIncome + Σ payslip totalBenefits + monthly bonus total — everything actually paid in the month |
| 15 | Statutory Minimum Wage for MWEs | backend | Always 0 — MWE status is not tracked |
| 16 | Holiday Pay, OT, Night Shift Differential, Hazard Pay (MWEs only) | frontend | Always 0 — MWE status not tracked (non-MWE OT/NSD is already inside line 14's gross) |
| 17 | 13th Month Pay and Other Benefits | backend | findMonthlyBonusTotal(year, month) — the full bonus paid in the month, deliberately not capped at ₱90,000 (§6a) |
| 18 | De Minimis Benefits | backend | Σ payslip totalBenefits (rice + phone + clothing allowances) |
| 19 | SSS, GSIS, PHIC, HDMF Mandatory Contributions & Union Dues (employee share) | backend | Σ (SSS + PhilHealth + Pag-IBIG employee shares); no GSIS/union dues in this system |
| 20 | Other Non-Taxable Compensation (specify) | backend | Always 0 — not tracked |
| 21 | Total Non-Taxable Compensation (Sum of Items 15 to 20) | backend | 17 + 18 + 19 (15, 16, 20 are always 0) |
| 22 | Total Taxable Compensation (Item 14 Less Item 21) | backend | 14 − 21 |
| 23 | Less: Taxable compensation not subject to withholding (≤ ₱250,000/year, non-MWE) | frontend | Always 0 — annualized-income exemption not modeled |
| 24 | Net Taxable Compensation (Item 22 Less Item 23) | backend | Equals line 22 (since 23 = 0) |
| 25 | Total Taxes Withheld | backend | Σ payslip withholdingTax (TRAIN monthly brackets) |
| 26 | Add/(Less): Adjustment of Taxes Withheld from Previous Month/s (From Part IV Schedule 1, Item 4) | frontend | Always 0 — no prior-month adjustment tracking |
| 27 | Taxes Withheld for Remittance (Sum of Items 25 and 26) | frontend | 25 + 26 = line 25 |
| 28 | Less: Tax Remitted in Return Previously Filed (if amended) | frontend | Always 0 — amended returns not supported |
| 29 | Other Remittances Made (specify) | frontend | Always 0 |
| 30 | Total Tax Remittances Made (Sum of Items 28 and 29) | frontend | 0 |
| 31 | Tax Still Due/(Over-remittance) (Item 27 Less Item 30) | frontend | = line 27 = line 25 |
| 32 | Add: Penalties — Surcharge | frontend | Always 0 — penalties not tracked |
| 33 | Interest | frontend | Always 0 |
| 34 | Compromise | frontend | Always 0 |
| 35 | Total Penalties (Sum of Items 32 to 34) | frontend | 0 |
| 36 | TOTAL AMOUNT STILL DUE/(Over-remittance) (Sum of Items 31 and 35) | frontend | = line 31 = line 25. In practice 36 == 27 == 31 == 25 |
Part III — Details of Payment (entries 37–40)
All four rows (Drawee Bank/Agency, Number, Date, Amount) render blank — payment details only exist at filing/payment time and are completed manually.
| # | Particulars |
|---|---|
| 37 | Cash/Bank Debit Memo |
| 38 | Check |
| 39 | Tax Debit Memo |
| 40 | Others (specify below) |
Part IV — Schedule 1 (Adjustment of Taxes Withheld from Previous Months)
Rendered blank (three empty adjustment rows plus the "Should be Tax Due / Adjustments" grid). The schedule's Total Adjustment feeds Part II line 26 and is therefore always 0.00. Page 2 also carries the perjury/Data-Privacy-Act declaration and signature blocks (blank) and a footer note listing every line this system reports as zero.
4. Computation walkthrough (worked example)
One employee in June 2026, plus one bonus paid that month (this is the exact fixture in
GovernmentFormsServiceImplBir1601cTest.getBir1601c_singleEmployee_computesLines14Through25FromPayslipAggregates):
| Input (month aggregate) | Amount |
|---|---|
| Gross income (payslips) | 42,500.50 |
| De minimis benefits (rice/phone/clothing) | 1,500.00 |
| SSS employee share | 1,687.50 |
| PhilHealth employee share | 1,093.75 |
| Pag-IBIG employee share | 200.00 |
| Withholding tax | 4,321.99 |
| Bonus paid in the month | 10,000.00 |
| Line | Computation | Result |
|---|---|---|
| 14 | 42,500.50 + 1,500.00 + 10,000.00 | 54,000.50 |
| 17 | bonus | 10,000.00 |
| 18 | de minimis | 1,500.00 |
| 19 | 1,687.50 + 1,093.75 + 200.00 | 2,981.25 |
| 21 | 10,000.00 + 1,500.00 + 2,981.25 | 14,481.25 |
| 22 = 24 | 54,000.50 − 14,481.25 | 39,519.25 |
| 25 = 27 = 31 = 36 | withholding tax | 4,321.99 |
Rounding: the service sums raw payslip values at full precision and applies
setScale(2, RoundingMode.HALF_UP) per output line at the very end. Subtraction happens
before rounding — e.g. raw inputs 10,000.010 − 100.009 produce line 22 = 9,900.00
(raw 9,900.001), which is also consistent with the displayed 10,000.01 − 100.01. Null
aggregates (an empty month makes the bonus SUM return null) are treated as zero.
5. Behavior at the edges
- Empty month (no payslips, no bonuses): every line is 0.00, employee count 0; the dialog shows "No payroll data found for {Month} {Year}" and disables the download.
- Bonus-only month (bonuses paid, but no payroll run): the form still renders — line
14 = 17 = 21 = the bonus total and line 22 = 0. This is why the dialog's
hasDatacheck isemployeeCount > 0 || totalCompensation > 0, not the count alone. - Item 3 flips to "No" when nothing was withheld — a 1601-C must still be filed for months with no withholding.
- Amounts print into BIR-style comb boxes (one bordered cell per digit, 7 whole digits
- 2 cents). Anything beyond 9,999,999.99 would not fit the printed cells.
6. Known limitations and deliberate tradeoffs
- Line 17 is uncapped (deliberate). The full monthly bonus is reported non-taxable.
The ₱90,000 annual 13th-month/other-benefits exemption cap requires each employee's
year-to-date bonus total, which a single-month aggregate view does not have. The annual
forms — 1604-C Alphalist (
ReportingServiceImpl.getBirAlphalist, which splits the YTD bonus at ₱90,000 into non-taxable/taxable-excess) and 2316 — do apply the cap and are authoritative. For an employee who crosses ₱90,000 during the year, this month's 1601-C and the year-end forms will legitimately disagree. The dialog shows a permanent warning about this, and the tradeoff is documented in a long comment insidegetBir1601cand asserted byGovernmentFormsServiceImplTest. - MWE status is not tracked → lines 15 and 16 are always zero. If the company employs statutory minimum wage earners, their exempt pay is not broken out.
- No adjustment/amended/penalty tracking → lines 20, 23, 26, 28–29, 32–34 (and hence 27, 30–31, 35–36 beyond echoing line 25), Part III (37–40), and Part IV Schedule 1 are always zero or blank. Anyone filing an amended return or paying late must fill these in by hand.
- Entry 12 (email) renders blank even when the company profile has an email address.
- RBAC asymmetry: the backend endpoint accepts
hr.employees.vieworpayroll.manage(GovernmentFormsController), but the frontend route guard requireshr.employees.manageorpayroll.manage(frontend/src/App.tsx,/government-formsroute). A view-only HR user can call the API directly but cannot reach the page. - Format-only output. The generated PDF recreates the form layout in HTML (the official PDF has no fillable AcroForm fields); the page-1/page-2 break differs slightly from the officially typeset form, and the legal-boilerplate guidelines page is omitted. Filing still goes through eFPS/eBIRForms.
7. Testing
| Test | What it covers | Run |
|---|---|---|
backend/src/test/java/com/motorph/payroll/service/impl/GovernmentFormsServiceImplBir1601cTest.java | Part II line arithmetic (14–25): empty/null month, single- and multi-employee aggregation, null-field resilience, HALF_UP rounding order, bonus-only month, no-bonus month, always-zero lines 15/20 and the 24 == 22 invariant | cd backend && mvn test -Dtest=GovernmentFormsServiceImplBir1601cTest |
backend/src/test/java/com/motorph/payroll/service/impl/GovernmentFormsServiceImplTest.java | The uncapped line-17 policy, contrasted with 2316/alphalist which apply the ₱90k cap | cd backend && mvn test -Dtest=GovernmentFormsServiceImplTest |
e2e/HR_Recruitment/bir-1601c-form.spec.ts | Dialog defaults (previous month), uncapped-bonus warning, all Part I–IV sections and line labels rendering, both PDF pages, PDF download filename, period-change refetch | npx playwright test e2e/HR_Recruitment/bir-1601c-form.spec.ts --project=chromium |
e2e/HR_Recruitment/government-forms.spec.ts | Government Forms launchpad wiring (card → dialog opens) | npx playwright test e2e/HR_Recruitment/government-forms.spec.ts |
E2E prerequisites: the docker stack must be running on http://localhost:5173, and the
seeded demo data provides payroll runs for the previous calendar month — which is
exactly the dialog's default period. Amounts in the template render as per-character comb
boxes, so e2e asserts labels and structure, never amount text.