Download the PHP package aditto/netsuite-client without Composer
On this page you can find all versions of the php package aditto/netsuite-client. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package netsuite-client
aditto/netsuite-client
Framework-agnostic PHP client for NetSuite's SuiteTalk REST API, with an optional
Laravel bridge (service provider + config publishing). Built to replace SOAP-based
NetSuite integrations (e.g. netsuitephp/netsuite-laravel) ahead of Oracle's SOAP
deprecation (2026.1 stops including new SOAP endpoints by default, 2027.1 disallows
new SOAP integrations).
Status
All v1 features are implemented and unit tested: NetSuiteConfig, the exception
hierarchy, the Laravel service provider + NetSuite facade, both authentication
methods (OAuth 2.0 Client Credentials/M2M and Token-Based Authentication), HTTP
transport (auth-applying HttpClient + 429-retry RetryMiddleware), response parsing
(ResponseParser + NetSuiteRecord + NetSuiteCollection, with status/error-payload
→ exception mapping), and RecordClient itself
(get/create/update/replace/delete/query).
Token-Based Authentication has been validated against a real NetSuite sandbox: full
customer CRUD round-trip and paginated SuiteQL both pass (composer test:sandbox —
see tests/Sandbox/README.md; these tests aren't committed to the repo by design).
OAuth 2.0 M2M is built to NetSuite's documented spec but not yet confirmed against a
live account.
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12 (only if using the Laravel bridge — the core client has no hard Laravel dependency)
Installation
In a Laravel app, publish the config file:
NetSuite-side setup
You need an Integration record and a way to authenticate as it — either Token-Based Authentication (TBA) or OAuth 2.0 Client Credentials (M2M). TBA is simpler to set up (no certificate management) and is what this package has actually been validated against; OAuth2 M2M is built to NetSuite's documented spec but not yet confirmed live (see Status above).
Full step-by-step walkthroughs for each method live in docs/:
- Token-Based Authentication setup
- OAuth 2.0 M2M setup
The summary below covers both together; use the dedicated guide above for the full click-by-click process.
1. Enable features
Setup → Company → Enable Features → SuiteCloud, turn on:
- REST Web Services
- Token-Based Authentication (if using TBA)
- OAuth 2.0 (if using OAuth2 M2M)
2. Create an Integration record
Setup → Integration → Manage Integrations → New.
- Name it (e.g. "netsuite-client").
- Under Authentication, check Token-Based Authentication for TBA, or OAuth 2.0 with the Client Credentials (Machine to Machine) Grant checkbox for OAuth2 M2M.
- Save. NetSuite shows a Consumer Key/Consumer Secret (TBA) or Client ID (OAuth2) — copy these now, the secret is only shown once.
3a. TBA: create an Access Token
Setup → Users/Roles → Access Tokens → New.
- Pick the Integration record from step 2, a user, and a role with REST Web Services login access plus whatever record-level permissions you need (see step 4).
- Save. NetSuite shows a Token ID/Token Secret — copy these now, the secret is
only shown once. These map to
NETSUITE_TOKEN_ID/NETSUITE_TOKEN_SECRET; the Consumer Key/Secret from step 2 map toNETSUITE_CONSUMER_KEY/NETSUITE_CONSUMER_SECRET.
3b. OAuth2 M2M: generate and upload a certificate
M2M authenticates via an ES256-signed JWT, so you need an EC (P-256) keypair — NetSuite verifies the JWT against a certificate you upload, it never sees your private key.
Upload netsuite-cert.pem under the Integration record's Certificates tab —
NetSuite assigns a Certificate ID (NETSUITE_CERTIFICATE_ID). The Client ID from
step 2 is NETSUITE_CLIENT_ID. Keep netsuite-private-key.pem out of version control
(NETSUITE_PRIVATE_KEY_PATH points to it on disk) — certificates are valid up to 2
years and rotation is manual for v1 (see the project outline's non-goals).
4. Grant role permissions
Whichever role is tied to the Access Token (TBA) or the Integration record's user
(OAuth2) needs REST Web Services login access, plus standard View/Create/Edit/Full
permissions for every record type you'll touch through this client — NetSuite
enforces these the same way for REST as it does for the UI. If query() fails with a
permissions-flavored error, check this role has access to the tables referenced in
your SuiteQL.
Sandbox vs. production
There's no separate sandbox/production endpoint config — the API host is derived
entirely from NETSUITE_ACCOUNT_ID:
- Sandbox account IDs look like
1234567_SB1→ host1234567-sb1.suitetalk.api.netsuite.com - Production account IDs look like
1234567→ host1234567.suitetalk.api.netsuite.com
NETSUITE_ENVIRONMENT (sandbox/production) doesn't affect the URL — it's a
declared-intent safety flag. In particular, tests/Sandbox/SandboxTestCase refuses to
run against a config with environment=production, as a backstop against ever
pointing the live-sandbox test harness at a real account by accident.
Configuration
Set the following in .env (see .env.example for the full list):
Usage
Outside Laravel, construct the pieces directly:
A note on field names
NetSuite's REST API returns field names in camelCase (companyName,
entityId) in every response body, regardless of what casing was accepted on a
write. NetSuiteRecord and ResponseParser pass fields through exactly as NetSuite
sent them — this client is intentionally schema-agnostic, it doesn't know or
normalize any record type's field names. Always read fields back using the casing
NetSuite actually returns (check a real response, or NetSuite's REST record browser),
not whatever casing happened to work on a write.
Logging requests and responses
For local debugging, every REST request/response pair can be logged to disk as a
correlated pair of plain-text files ({id}.request.txt / {id}.response.txt), one
attempt per pair — including each retried attempt on a 429, not just the final one.
Authorization headers are redacted before writing. This is off by default and meant
for local debugging, not production traffic.
In Laravel, enable it via config/env:
Outside Laravel, compose LoggingMiddleware into the client chain yourself:
Migrating from SOAP (netsuitephp/netsuite-php)
Most SOAP calls collapse to a single get/create/update/replace/delete/query
call, since SOAP's typed request/response objects mostly boil down to "record type +
field values." Example — a method that unsubscribes a contact from email by clearing a
custom field:
The if (! ...isSuccess) { throw ... } check has no equivalent to write — update()
already throws a typed exception (NetSuiteValidationException, NetSuiteAuthException,
etc., all extending NetSuiteException) on any non-2xx response, so a plain call that
returns normally means it succeeded. RecordClient never needed to know anything
about the Contact record type or the custentity_email_subscribed field — that's the
point of the generic-client design (see the project outline's "explicitly out of
scope" section on typed per-record classes).
Troubleshooting auth
NetSuiteAuthExceptionon every request — usually bad credentials, a role missing REST Web Services login access, or (TBA specifically) system clock skew: TBA signs each request with a timestamp, and NetSuite rejects requests signed too far from its own server time.NetSuiteValidationExceptionwith "Please enter value(s) for: X" on create/update — this is NetSuite telling you a field is mandatory for this account's configuration (subsidiary, custom mandatory fields, the form in use) — not a client bug. Inspect$exception->errorDetailsfor NetSuite's full structured error list. OneWorld (multi-subsidiary) accounts commonly requiresubsidiary, and sometimes an account-specific custom field for location — there's no generic way to know these ahead of time; discover them from the error details, or from NetSuite's UI record form for that type.- SuiteQL (
query()) failing withINVALID_HEADER/ "The required request header 'Prefer' is missing" — already handled internally (RecordClient::query()always sendsPrefer: transient); this only bites if you're calling the query endpoint some other way. NetSuiteRateLimitException— a 429 that persisted pastRetryMiddleware's retry budget (NetSuiteConfig::$maxAttempts, default 3).$exception->retryAfterSecondscarries what NetSuite'sRetry-Afterheader said, if present.
Testing
Tests against a real NetSuite sandbox (composer test:sandbox) are intentionally not
part of this repo — see tests/Sandbox/README.md.
All versions of netsuite-client with dependencies
guzzlehttp/guzzle Version ^7.8
psr/http-client Version ^1.0
psr/http-client-implementation Version ^1.0
psr/http-factory Version ^1.0
psr/http-message Version ^1.1|^2.0
psr/simple-cache Version ^2.0|^3.0
firebase/php-jwt Version ^7.1