Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The NetSuite Pro

The NetSuite Pro Logo The NetSuite Pro Logo

The NetSuite Pro Navigation

  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask A Question
  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us
Home/ Real-World NetSuite Examples/Auto-Apply Tax/Nexus by Region (SuiteScript 2.1)

Auto-Apply Tax/Nexus by Region (SuiteScript 2.1)

πŸ’Ό Business problem

Ops wants correct tax without users picking codes by hand:

  • Different nexus by subsidiary/region
  • GST/HST/PST (Canada), state tax (US), VAT (EU)
  • Honor customer tax exemption
  • Must work on bulk loads (CSV, integrations)

🧠 Approach

  • User Event (beforeSubmit) on Sales Order and Invoice
  • Read Ship-To (country/state), Subsidiary, and Customer flags
  • Choose a rule from a JSON mapping (script parameter)
  • If SuiteTax is enabled β†’ set Nexus and (optionally) line tax code
  • If Legacy tax β†’ set taxitem at body or line taxcode
  • Skip if customer is exempt (or set exemption code)

πŸ”§ Prerequisites

  • (Optional) Customer checkbox custentity_tax_exempt (or use native fields/Exemption Certs)
  • Script parameter (Free-Form Text): custscript_tax_rules_json with region rules (see below)
  • (Optional) Body field custbody_tax_applied_note (text) to write what rule fired

Example JSON rules (put in the parameter)

[
  { "country":"CA", "state":"ON", "subsidiary": "2", "nexus": "5",  "taxcode": "1234", "taxitem": "",   "note":"Canada HST ON" },
  { "country":"CA", "state":"BC", "subsidiary": "2", "nexus": "6",  "taxcode": "2345", "taxitem": "",   "note":"Canada GST+PST BC" },
  { "country":"US", "state":"CA", "subsidiary": "1", "nexus": "10", "taxcode": "3456", "taxitem": "",   "note":"US CA State Tax" },
  { "country":"US", "state":"",   "subsidiary": "1", "nexus": "11", "taxcode": "4567", "taxitem": "",   "note":"US no-state default" },
  { "country":"GB", "state":"",   "subsidiary": "3", "nexus": "20", "taxcode": "5678", "taxitem": "",   "note":"UK VAT Std" }
]
  • nexus = internal ID of Nexus (SuiteTax)
  • taxcode = internal ID of Tax Code (SuiteTax or legacy line code)
  • taxitem = internal ID of Tax Item (legacy only; leave empty for SuiteTax)

Tip: Keep rules specific β†’ first match wins (country+state), then a country-only default.


🧩 User Event Script (SuiteScript 2.1)

Deploy on Sales Order and Invoice (both), beforeSubmit.

/**
 * @NApiVersion 2.1
 * @NScriptType UserEventScript
 * Title: UE | Auto-Apply Tax/Nexus by Region
 * Author: The NetSuite Pro
 */
define(['N/runtime','N/record','N/log'], (runtime, record, log) => {

  const P_RULES = 'custscript_tax_rules_json';
  const F_EXEMPT = 'custentity_tax_exempt';                 // change if you use another flag
  const F_APPLIED_NOTE = 'custbody_tax_applied_note';        // optional
  const F_NEXUS = 'nexus';                                   // SuiteTax body field
  const F_TAXITEM_BODY = 'taxitem';                          // Legacy body tax
  const F_SHIP_COUNTRY = 'shipcountry';
  const F_SHIP_STATE = 'shipstate';

  const SUITETAX = runtime.isFeatureInEffect({ feature: runtime.Feature.SUITETAX });

  const beforeSubmit = (ctx) => {
    if (![ctx.UserEventType.CREATE, ctx.UserEventType.EDIT, ctx.UserEventType.COPY].includes(ctx.type)) return;

    const rec = ctx.newRecord;
    const type = rec.type; // salesorder or invoice

    try {
      // Bail out if no shipping country; e.g., service invoices
      const shipCountry = (rec.getValue(F_SHIP_COUNTRY) || '').toString().toUpperCase();
      const shipState = (rec.getValue(F_SHIP_STATE) || '').toString().toUpperCase();
      const subsidiary = (rec.getValue('subsidiary') || '').toString();
      if (!shipCountry || !subsidiary) return;

      // Customer exemption?
      const customerId = rec.getValue('entity');
      const isExempt = safeGetCustomerExempt(customerId);

      const rules = loadRules();
      const rule = findRule(rules, shipCountry, shipState, subsidiary);
      if (!rule) {
        log.debug('No matching tax rule', { shipCountry, shipState, subsidiary });
        return;
      }

      if (isExempt) {
        // For exempt customers: ensure no tax. Behavior differs per setup.
        applyExempt(rec);
        setNote(rec, `Exempt: no tax applied (${rule.note || 'by rule'})`);
        return;
      }

      if (SUITETAX) {
        applySuiteTax(rec, rule);
        setNote(rec, rule.note || `SuiteTax: nexus ${rule.nexus}, taxcode ${rule.taxcode}`);
      } else {
        applyLegacyTax(rec, rule);
        setNote(rec, rule.note || `Legacy tax: taxitem ${rule.taxitem || '(none)'} / taxcode ${rule.taxcode || '(line)'}`);
      }

    } catch (e) {
      log.error('Auto Tax UE error', e);
    }
  };

  function loadRules() {
    try {
      const json = runtime.getCurrentScript().getParameter({ name: P_RULES }) || '[]';
      return JSON.parse(json);
    } catch (_e) { return []; }
  }

  function findRule(rules, country, state, subsidiary) {
    // First pass: country+state+subsidiary
    let r = rules.find(x => eq(x.country,country) && eq(x.state,state) && eq(x.subsidiary,subsidiary));
    if (r) return r;
    // Fallback: country-only+subsidiary
    r = rules.find(x => eq(x.country,country) && !x.state && eq(x.subsidiary,subsidiary));
    return r || null;
  }

  function eq(a,b){ return String(a||'').toUpperCase() === String(b||'').toUpperCase(); }

  function safeGetCustomerExempt(customerId) {
    try {
      if (!customerId) return false;
      const c = record.load({ type: record.Type.CUSTOMER, id: customerId });
      return !!c.getValue(F_EXEMPT);
    } catch (_e) {
      return false;
    }
  }

  // ---- SuiteTax path ----
  function applySuiteTax(rec, rule) {
    if (rule.nexus) rec.setValue({ fieldId: F_NEXUS, value: Number(rule.nexus) });

    // If you also maintain explicit tax codes per line:
    if (rule.taxcode) {
      const lineCount = rec.getLineCount({ sublistId: 'item' });
      for (let i = 0; i < lineCount; i++) {
        // skip non-taxable lines (e.g., description-only) if needed
        const item = rec.getSublistValue({ sublistId:'item', fieldId:'item', line:i });
        if (!item) continue;
        // In SuiteTax, many accounts allow setting 'taxcode' per line still:
        try {
          rec.setSublistValue({ sublistId:'item', fieldId:'taxcode', line:i, value: Number(rule.taxcode) });
        } catch (_ignore) {
          // Some tenants lock taxcode (calculated by engine) β€” safe to ignore
        }
      }
    }
  }

  // ---- Legacy tax path ----
  function applyLegacyTax(rec, rule) {
    if (rule.taxitem) {
      rec.setValue({ fieldId: F_TAXITEM_BODY, value: Number(rule.taxitem) });
    }
    if (rule.taxcode) {
      const cnt = rec.getLineCount({ sublistId:'item' });
      for (let i = 0; i < cnt; i++) {
        const item = rec.getSublistValue({ sublistId:'item', fieldId:'item', line:i });
        if (!item) continue;
        rec.setSublistValue({ sublistId:'item', fieldId:'taxcode', line:i, value: Number(rule.taxcode) });
      }
    }
  }

  // ---- Exempt handling (generic) ----
  function applyExempt(rec) {
    // Approach 1: set an exempt tax code on lines (preferred if you manage a specific exempt code)
    const cnt = rec.getLineCount({ sublistId:'item' });
    for (let i = 0; i < cnt; i++) {
      try {
        // Replace with your exempt code internal ID if you manage one:
        // rec.setSublistValue({ sublistId:'item', fieldId:'taxcode', line:i, value: 999 }); 
        // Or mark non-taxable item logic; leave blank here as many tenants calculate zero via certificates.
      } catch (_e) {}
    }
  }

  function setNote(rec, msg) {
    try {
      if (F_APPLIED_NOTE) rec.setValue({ fieldId: F_APPLIED_NOTE, value: msg });
    } catch (_e) {}
  }

  return { beforeSubmit };
});

βœ… Testing checklist

  1. Set parameter Tax Rules JSON with your IDs.
  2. Create SO with ship-to CA / ON under subsidiary 2 β†’ verify Nexus/Tax populate.
  3. Flip to BC β†’ rules switch.
  4. Mark customer exempt β†’ no tax per your policy.
  5. CSV/REST import several orders β†’ script runs on CREATE (bulk-safe).
  6. Transform to Invoice β†’ UE on Invoice enforces the same rule.

🧩 Enhancement ideas

  • Add custscript_tax_rules_url to fetch JSON from a file so Finance can edit without deployments.
  • Extend matcher to include location or class if tax varies by warehouse.
  • Write a nightly audit (MR) that logs/flags transactions missing a nexus/tax code.
  • If using exemption certificates, you can check native SuiteTax certificate status instead of a custom flag.

🧠 Gotchas & tips

  • In some SuiteTax setups, line taxcode is lockedβ€”it’s fine to only set Nexus and let the engine determine codes.
  • Always normalize country/state codes to uppercase.
  • If ship-to is blank (services), consider billing address or customer default as a fallback.
  • Avoid loading the customer repeatedly in bulk flowsβ€”cache if you later convert this to MR.

πŸ“Œ Summary

ObjectiveResult
Correct tax without manual selectionAuto nexus/tax set from JSON rules
Works across create/edit/copy & bulkUE beforeSubmit covers all
SuiteTax & legacy compatibleFeature-detect and branch safely
Share
  • Facebook

Leave a ReplyCancel reply

Sidebar

Ask A Question

Stats

  • Questions 6
  • Answers 6
  • Best Answers 0
  • Users 2
  • Popular
  • Answers
  • Rocky

    Issue in running a client script in NetSuite SuiteScript 2.0 ...

    • 1 Answer
  • admin

    How can I send an email with an attachment in ...

    • 1 Answer
  • admin

    How do I avoid SSS_USAGE_LIMIT_EXCEEDED in a Map/Reduce script?

    • 1 Answer
  • admin
    admin added an answer The issue is usually caused by following Wrong script file… September 14, 2025 at 10:33 pm
  • admin
    admin added an answer Steps to send an Invoice PDF by email: define(['N/email', 'N/render',… August 28, 2025 at 3:05 am
  • admin
    admin added an answer This error means your script hit NetSuite’s governance usage limit… August 28, 2025 at 3:02 am

Top Members

Rocky

Rocky

  • 1 Question
  • 22 Points
Begginer
admin

admin

  • 5 Questions
  • 2 Points

Trending Tags

clientscript netsuite scripting suitescript

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

© 2025 The NetSuite Pro. All Rights Reserved