Skip to main content

GST & compliance

Bill-to-Overseas / Ship-to-India in ERPNext: Getting the GST Classification Right (2026)

Overseas customer, goods delivered in India: not an export. How India Compliance sets place of supply from the ship-to address, and a gate that checks it.

Updated 12 Oct 202611 min readBy ManojFirst published 24 Jul 2026

You raise a Sales Invoice in ERPNext for a US customer and ship the goods to their sister concern in Chennai, not to a US port. ERPNext picks Overseas as the GST Category, defaults the Place of Supply to 96-Other Countries, and the invoice lands in your GSTR-3B as a zero-rated export. It isn’t one: the goods never left India, so GST is due on it, and your 3B under-reported taxable supplies.

We find this misclassification on inherited ERPNext instances, and it was a tracked issue in 2023 (frappe/erpnext#35479). I am Manoj, and I lead ERPNext and Frappe work at MithTech, a team of Frappe specialists in Bengaluru. The code below is from the current India Compliance source (v16).

Try it: is this invoice really an export?

Pick the customer, supply type, and physical destination. The widget names the correct classification and which GST accounts should apply — following the same branching that india_compliance/gst_india/utils/__init__.py uses.

CalculatorGST Classifier

Country + place of supply + goods/services → the exact ERPNext classification and which GST accounts apply.

Interactive classifier

Is this invoice really an export?

POS is not the customer's billing state — it is the state where the goods are delivered (IGST Act §10(1)(b)) or, for services, the §13 test.

Classification

Domestic supply to an overseas customer

GST Category

Overseas (customer master) — POS is Indian state

Tax accounts

IGST (inter-state)

IGST §10(1)(b): bill-to ≠ ship-to → POS = ship-to state. Not an export. This is where ERPNext v14 used to misclassify. In current india_compliance, get_overseas_place_of_supply() correctly returns the Indian state when the shipping address is in India.

Is it an export? Export of goods means taking goods out of India to a place outside India (IGST Act s.2(5)). If the goods are delivered in Chennai, they were not exported, so the invoice cannot be zero-rated as an export, whatever the customer master says.

Which state is the place of supply? IGST Act s.10(1)(a) is the general rule: where the movement of goods ends for delivery. Section 10(1)(b) covers bill-to / ship-to: where goods are delivered to a person on the direction of a third person, the third person is deemed to have received them, and the place of supply is the third person's principal place of business. When that third person is overseas, with no place of business in India, how s.10(1)(b) applies is a question to settle with your CA. India Compliance sets the place of supply to the ship-to state, which this post follows.

For services: IGST Act s.13. When the supplier or recipient is outside India, s.13 sets the place of supply: s.13(2) is the general rule, s.13(3) covers services on goods physically made available in India, s.13(5) events, and so on. Export of services also needs every condition in s.2(6), including payment in convertible foreign exchange or in rupees where RBI permits.

Neither section turns on the customer’s billing address. The billing address is master data; the place of supply is a fact about the transaction.

What ERPNext actually does — pinpoint the classification function

Look at india_compliance/gst_india/utils/__init__.py. Two functions carry the whole burden.

get_place_of_supply(party_details, doctype) dispatches on GST Category. For sales, when the customer is Overseas it delegates to a specialised function:

Python
# india_compliance/gst_india/utils/__init__.py (v16.x)
def get_place_of_supply(party_details, doctype):
    if doctype in SALES_DOCTYPES or doctype == "Payment Entry":
        # for exports, Place of Supply is set using GST category in absence of GSTIN
        if party_details.gst_category == "Overseas":
            return get_overseas_place_of_supply(party_details)
        # ... regular POS via GSTIN state / address

get_overseas_place_of_supply(party_details) is where the bill-to / ship-to logic lives. It defaults POS to 96-Other Countries, then — if a Shipping Address is set and its country is India with a valid gst_state_number — overrides POS to the Indian state:

Python
# india_compliance/gst_india/utils/__init__.py (v16.x)
def get_overseas_place_of_supply(party_details):
    """
    As per definition the of Export, material should be shipped to a place outside India.
    Where the material is shipped in India, Place of Supply should be the location where
    the material is shipped to.
    """
    place_of_supply = "96-Other Countries"

    if not party_details.shipping_address_name:
        return place_of_supply

    shipping_address_details = frappe.get_value(
        "Address",
        party_details.shipping_address_name,
        ("country", "gst_state_number", "gst_state"),
        as_dict=True,
    )

    if shipping_address_details.country == "India" and shipping_address_details.gst_state_number:
        place_of_supply = f"{shipping_address_details.gst_state_number}-{shipping_address_details.gst_state}"

    return place_of_supply

Read the guard carefully. Two things must both be true for POS to switch to an Indian state:

  1. shipping_address_name must be populated on the Sales Invoice.
  2. The Address record must have country = "India" and a non-empty gst_state_number.

If either is missing (no Shipping Address, or an Indian address whose state wasn’t filled), POS stays at 96-Other Countries without a warning and the invoice is treated as an export. That is the trap.

The downstream "is this a foreign transaction?" test in the same file makes the consequence explicit:

Python
def is_foreign_transaction(gst_category, place_of_supply):
    return gst_category == "Overseas" and place_of_supply == "96-Other Countries"

Both conditions must hold. Fix POS to an Indian state and the invoice stops behaving as an export everywhere downstream — GST accounts, GSTR-1 table selection, e-invoice SUPTYP field — because they all funnel through this predicate.

Six real bill-to / ship-to scenarios

Tap through each — the widget shows the exact GST Category, POS, and tax accounts you should set on the Sales Invoice.

InteractiveBill-to / Ship-to Scenario Decider

Six real-world bill-to / ship-to combinations — tap any to see the correct ERPNext setup.

Real-world scenarios

Six bill-to / ship-to cases — get the setup right

Scenario B

US buyer, goods shipped to their Indian sister-concern in Chennai

GST CategoryOverseas
Place of Supply33-Tamil Nadu (from Shipping Address)
Customer GSTIN(empty)
Shipping AddressChennai address (India)
TaxIGST (inter-state — company in Karnataka, POS Tamil Nadu)

The bug case. IGST §10(1)(b): POS = ship-to. Not an export. get_overseas_place_of_supply() picks the Indian state automatically when shipping address is in India.

The setup that gets it right, field by field

1

Customer master — set GST Category first

For an overseas company: GST Category = Overseas, Customer GSTIN empty, country of billing address ≠ India. guess_gst_category() in the same utils file will pick this automatically if the billing address country is set correctly.

2

Address records — split billing and shipping

Create the billing address (overseas) and the shipping address (Indian, with gst_state and gst_state_number populated) as two separate Address records. The Sales Invoice needs to reference both — do NOT paste an Indian ship-to into the Notes field and hope it flows.

3

Sales Invoice — verify Shipping Address is set

get_overseas_place_of_supply() returns 96-Other Countries whenever shipping_address_name is empty. This is the most common failure. Confirm the field is populated before you compute taxes.

4

Trigger a POS recompute

Change of Shipping Address triggers set_place_of_supply client-side. If POS still reads 96-Other Countries, open the Shipping Address record and confirm both country = "India" and gst_state_number are filled — the guard needs both.

5

Tax template — pick IGST or CGST+SGST by POS state

POS state different from your company state → IGST. Same state → CGST + SGST. Don't use a zero-rated export template just because the customer master says Overseas; confirm the treatment with your CA for this case.

6

Currency and rate — separate concern

An overseas customer typically bills in USD/EUR. GST amount is computed in company currency. Verify the FX rate against your policy — see our Landed Cost + FX guide for the mechanics.

InteractiveSales Invoice Field Checklist

Every ERPNext Sales Invoice field that must be set correctly, in one tick-as-you-go list.

Pre-submit checklist

Every field that must be right on the Sales Invoice

0 / 10

Nothing here is saved — this is a checklist for your current thought, not your ERPNext record.

Where the invoice lands in your returns

Once the classification is right, everything downstream falls into place. If it’s wrong, the invoice lands in the wrong return section — which is what your CA sees at filing time.

InteractiveGSTR Section Mapper

Flow-arrow visualisation from your invoice classification to the GSTR-1 table, GSTR-3B section, and eInvoice IRN slot it lands in.

Downstream mapping

Where does this invoice actually land?

Your classification

Overseas customer, delivered inside India

GSTR-3B3.1(a) Outward taxable (other than zero-rated)
GSTR-1B2C(L) or B2C(S) depending on invoice value + POS state
eInvoice IRNSUPTYP: B2C (no IRN required if turnover threshold not breached; otherwise B2C QR)

Section numbers reflect the return format in effect as of 2026. Verify against the current GSTN utility before filing — the schema updates most quarters.

The line-item breakdown by return section is defined across india_compliance/gst_india/utils/gstr_1/ and gstr3b/. The classification is inherited from the Sales Invoice’s GST Category + POS — not recomputed at filing time. So fixing the classification after the invoice is submitted requires an amendment or a credit note; you cannot just "reclassify" a filed row.

See also: GST on services bought from abroad in ERPNext, the buying side of this: who pays the GST on a foreign vendor's bill, the self-invoice, and where the credit goes.

How do you fix invoices that were classified wrong?

Which path you take depends on whether the invoice is already in a filed return. Agree the route with your CA before you post anything.

Not filed yet, same period: cancel and amend. Open the Sales Invoice, click Amend, fix the GST Category, Shipping Address and POS, and resubmit. The new invoice takes a -1 suffix; the cancelled one stays in the audit trail. If an IRN or e-way bill was generated, they can be cancelled only within 24 hours of generation; after that, the correction goes through a credit note.

Already in a filed return: don't edit the historical invoice in ERPNext. The usual routes are amending the invoice in a later GSTR-1 (Table 9A, within the time the law allows) or a credit note against it with a fresh, correctly classified invoice. Either way the tax that should have been paid is due, with interest. Which route fits, and how it shows in GSTR-3B, is your CA's call.

Automation gate: the lasting fix

Humans forget to set Shipping Address. The gate is a before_save client script that warns whenever Customer country ≠ India but Shipping Address is in India — before the user submits.

JavaScript
// Custom Script → DocType: Sales Invoice
frappe.ui.form.on('Sales Invoice', {
  async before_save(frm) {
    if (!frm.doc.customer) return;

    // Pull the two country fields we need to compare
    const customer_country = await frappe.db.get_value(
      'Address', frm.doc.customer_address, 'country'
    );
    const shipping_country = frm.doc.shipping_address_name
      ? await frappe.db.get_value(
          'Address', frm.doc.shipping_address_name, 'country'
        )
      : { message: { country: null } };

    const bill_c = customer_country.message?.country;
    const ship_c = shipping_country.message?.country;

    // The classic bill-to-overseas / ship-to-India case
    if (bill_c && bill_c !== 'India' && ship_c === 'India') {
      // Confirm GST Category isn't stuck on Overseas + POS 96
      if (
        frm.doc.gst_category === 'Overseas' &&
        (frm.doc.place_of_supply || '').startsWith('96-')
      ) {
        frappe.msgprint({
          title: __('GST classification check'),
          indicator: 'orange',
          message: __(
            'Customer billing country is <b>{0}</b> but Shipping Address is <b>India</b>. ' +
            'This is a domestic supply — Place of Supply should be an Indian state, ' +
            'not 96-Other Countries. Open the Shipping Address and confirm gst_state is set, ' +
            'then trigger a POS recompute before submitting.',
            [bill_c]
          )
        });
      }
    }

    // SEZ sanity — POS must not be 96 for SEZ either
    if (
      frm.doc.gst_category === 'SEZ' &&
      (frm.doc.place_of_supply || '').startsWith('96-')
    ) {
      frappe.throw(__(
        'SEZ supplies are always inter-state IGST — Place of Supply must be an Indian state.'
      ));
    }
  }
});

Run it as a warning (msgprint) for two weeks, watch for false positives, then switch the classification check to frappe.throw once the accounts team has adjusted.

What judgement calls do you need to make?

Two things worth flagging where the source doesn’t answer the question by itself:

  • frappe/erpnext#35479 (May 2023) reported that an invoice to an overseas customer with supply in India was treated as an export. The current flow, get_overseas_place_of_supply reading the shipping address country, handles it. On an older install, update India Compliance before relying on this.
  • Export of services under s.13 has enough conditions (place of supply, foreign-exchange payment, the distinct-persons rule) that the classifier only flags it as "check with your CA". We don't automate it: the safe default is a taxable domestic supply, and the export treatment is claimed only with evidence.

At a glance

AspectWhat to know
The caseOverseas bill-to, delivery inside India
Legal testGoods that stay in India are not exported (IGST Act s.2(5))
What ERPNext needsShipping Address set, Indian state and state number filled
Gatebefore_save script warning on Overseas + POS 96 + Indian ship-to

FAQ: what do people ask most?

+If my customer master says Overseas but I ship inside India, do I still need to invoice in the customer's currency?

No — those are separate concerns. GST Category drives tax classification; the invoice currency drives payable/receivable valuation. You can invoice in INR for an overseas customer if your commercial contract allows, and you can invoice in USD for an Indian shipment if your contract does. The GST accounts still follow POS.

+Will an SEZ supply to a unit in my own state attract CGST + SGST?

No. A supply to an SEZ unit or developer is inter-state under s.7(5)(b) of the IGST Act, so it is IGST, zero-rated either under an LUT or with payment and a refund claim. In code, is_overseas_transaction() returns True for gst_category == "SEZ" regardless of state. The e-Waybill code even forces the billing-state override to 96 - Other Countries for SEZ per e_waybill.py.

+What if I set GST Category to Overseas but forget to set the Shipping Address?

get_overseas_place_of_supply() returns 96-Other Countries as its default when shipping_address_name is empty. The invoice is classified as an export and lands in GSTR-3B table 3.1(b). This is the most common failure: always set the Shipping Address for overseas customers.

+Does the classification change if the customer has an Indian GSTIN as well as an overseas billing address?

Yes — guess_gst_category() returns based on GSTIN presence and format, and an Indian GSTIN in Registered Regular format overrides the Overseas guess. The customer then behaves as a regular Indian party for classification. Most companies deliberately do NOT put an Indian GSTIN on an overseas customer master; if the same legal entity has both, model it as two Customer records.

+How do I fix an invoice that already went into a filed GSTR-3B under the wrong category?

Don't edit the historical invoice. The usual routes are an amendment in a later GSTR-1 (Table 9A) or a credit note with a fresh, correctly classified invoice, and the tax due is paid with interest. Agree the route with your CA, and record the reason on whichever document you raise so it stands up on audit.

+Is a Delivery Challan enough as ship-to evidence, or do I need an e-way bill?

Where the consignment value exceeds ₹50,000 (and, within a state, your state's own limit), an e-way bill is needed; a delivery challan does not replace it. The e-way bill’s Ship-to fields are what auditors match against your invoice’s Shipping Address, so keeping them consistent is what makes the bill-to / ship-to case defensible.

+Does the same logic apply to Purchase Invoices: bill-from-overseas, ship-from-India?

Related but not identical. is_import_of_goods() checks IMPORT_GST_CATEGORIES = ("Overseas", "SEZ") plus are_goods_supplied(). For purchases the place-of-supply logic uses the company GSTIN rather than the shipping address by default. If you buy from an overseas supplier who ships from within India (a high-seas sale, for example), model it explicitly and agree the treatment with your CA: the automatic classification won't catch every case.

+Which return section does an SEZ supply land in: 3.1(a) or 3.1(b)?

3.1(b), zero-rated outward taxable supplies, because a supply to an SEZ is zero-rated under s.16 of the IGST Act. In GSTR-1 it goes to Table 6B (SEZ supplies), separate from Table 6A (exports). The e-invoice SUPTYP is SEZWP or SEZWOP, from the Is Export With GST flag on the Sales Invoice.

What should you do next?

The bill-to / ship-to case is a costly classification error and an easy one to prevent. Current India Compliance does the right thing when the Shipping Address is set; the failure is almost always a missing Shipping Address or an Indian address with an empty gst_state.

Fix that habit and add the gate script, and these invoices stop landing in your GSTR-3B as exports.

Your next step

Inherited an ERPNext instance with tangled GST classification?

Next step · GST & compliance

Now see GST compliance

GST compliance

Comments & ratings

No comments yet. Start a new discussion.

0

Manoj · Published on 24 July 2026

Worked with us? Leave a Google review

Questions about this? Talk to the team.

Book a 30-minute Google Meet, WhatsApp us, or send a message. The engineer who works on this replies.

Send us a message
Your details go to our team's CRM and are used only to reply.