Skip to content
adapters.io

NetSuite to Postgres: how to replicate ERP data into a database your apps can read

9 min read Data engineering The Adapters team

Last updated July 2026

Field mapping auto-plugged · tap a port to rewire

5 sample records ready

To replicate NetSuite into Postgres, read the records you care about (transactions, items, customers, subsidiaries) through SuiteQL over the REST query endpoint, filter each pull on lastmodifieddate so you only fetch changed rows, and upsert into Postgres with INSERT ... ON CONFLICT (id) DO UPDATE keyed on the NetSuite internal id. Store the max lastmodifieddate as a watermark, convert money to NUMERIC, cast every timestamp to timestamptz in UTC, and your apps and reports read the copy instead of hammering NetSuite live.

Key takeaways

  • Prefer SuiteQL over row-by-row SOAP. One query returns many rows per request, which is the single biggest lever against NetSuite governance limits.
  • Upsert on the internal id. ON CONFLICT (id) DO UPDATE makes a retry update in place instead of doubling rows.
  • Watermark on lastmodifieddate. Pull only changed records and overlap the window slightly so an out-of-order commit is never skipped.
  • Money is NUMERIC, dates are timestamptz. Never store amounts as FLOAT, and convert NetSuite account-timezone dates to UTC on the way in.

How do you connect NetSuite to Postgres?

You connect NetSuite to Postgres by reading NetSuite records through a web-services API and writing them into Postgres tables, because NetSuite has no native Postgres destination. Something sits in the middle: a managed connector, a scheduled script against SuiteTalk or SuiteQL, or the SuiteAnalytics Connect ODBC/JDBC driver. Each option is governed by NetSuite concurrency and usage limits per account.

NetSuite exposes three practical access paths. SuiteTalk (REST and SOAP web services) reads and writes records one type at a time. SuiteQL runs SQL-like queries over records through the REST query endpoint, which is the fastest way to pull many rows in a single request. SuiteAnalytics Connect is an extra-cost add-on that gives you an ODBC/JDBC interface against a read replica. The managed path is the NetSuite to Postgres integration, which owns the SuiteQL paging, the watermark, the staging table, the upsert, and the retries.

Extraction method What it is When to use
SuiteTalk REST / SOAP Record-level web services that read and write one record type per call Writes back to NetSuite, or record types SuiteQL does not expose cleanly
SuiteQL via REST query SQL-like queries over records that return many rows per request Bulk incremental reads. The default for replicating into Postgres
SuiteAnalytics Connect (ODBC/JDBC) Extra-cost add-on exposing a read replica over standard drivers Teams that already licensed it and want a JDBC source out of the box

Practically, the connection has three parts. First, an integration record and a token-based authentication role in NetSuite with least-privilege read access to the records you replicate. Second, a Postgres landing spot: a database, a schema, and a role that can create tables and run upserts. Third, the transfer itself, which pages through each record type with SuiteQL, lands each batch in a staging table, and upserts into the target. If your reporting lives in a warehouse instead of an application database, the sibling path is NetSuite to Snowflake.

How do you extract data from NetSuite incrementally?

Extract incrementally by filtering each SuiteQL query on lastmodifieddate, storing the maximum modified timestamp you saw as a watermark, and resuming from that value on the next run. Page the results with a stable ORDER BY plus LIMIT and OFFSET (or a keyset cursor), and overlap the window slightly so a commit that landed out of clock order is not skipped.

Most NetSuite records carry a lastmodifieddate, and System Notes track field-level changes for the record types that need audit-grade change capture. A typical run reads the stored watermark, issues SELECT ... FROM transaction WHERE lastmodifieddate >= :watermark ORDER BY lastmodifieddate, id, walks the pages until the batch is drained, and then advances the watermark to the max lastmodifieddate returned. The stable ordering matters: without an ORDER BY that includes a tiebreaker like the internal id, paging with OFFSET can skip or repeat rows as new records arrive mid-scan.

Overlap protects correctness. Subtract a few minutes from the watermark before you query, because a record can be committed with a modified timestamp slightly behind another that already loaded. Re-reading a handful of rows costs almost nothing when the load side is idempotent, which is the next section. Full reloads, by contrast, are what get accounts throttled, so reserve them for the first backfill and switch to the watermark for every run after that.

How do NetSuite fields map to Postgres types?

Map the NetSuite internal id to a BIGINT or TEXT primary key, amounts to NUMERIC (never FLOAT), dates and datetimes to timestamptz in UTC, free-text and memo fields to TEXT, T/F booleans to BOOLEAN, and list or record references (their internalId) to foreign-key columns. The two that cause silent errors are money stored as a float and timestamps kept in the account timezone.

NetSuite field / type Postgres type Notes
internal id BIGINT or TEXT (primary key) Stable and unique. The natural upsert key for ON CONFLICT
amount / rate NUMERIC Never FLOAT for money. Keep transaction currency and base currency separate
trandate / lastmodifieddate timestamptz NetSuite stores in account timezone. Convert to UTC on the way in
memo / free text TEXT No length cap needed. Preserve the raw value for search and export
checkbox (T / F) BOOLEAN Cast the T/F string to a real boolean so filters read cleanly
list / record reference (internalId) FK column (BIGINT / TEXT) Store the referenced internal id and join to its own replicated table

The money and timezone traps are worth repeating because both are correctness bugs, not cosmetics. Storing an amount as FLOAT introduces rounding drift that shows up when you sum a ledger, so use NUMERIC and let Postgres keep exact decimals. Timestamps are the other one: NetSuite renders dates in the account timezone, so a trandate that reads as midnight in your account is not midnight UTC. Convert on ingest and store timestamptz, and keep the transaction currency alongside the base currency so multi-currency transactions reconcile against the right rate.

How do you avoid hitting NetSuite API limits?

Avoid NetSuite governance limits by pulling many rows per request with SuiteQL instead of row-by-row SOAP, fetching only changed records through the watermark rather than full reloads, scheduling heavy runs off-peak, and using a single least-privilege integration role. When a throttle or governance error comes back, back off and retry with exponential delay rather than hammering the endpoint.

NetSuite governs by concurrency and request or usage cost per account and integration, so the goal is fewer, denser calls. SuiteQL is the lever: a single query can return a full page of transactions where SOAP would need one call per record. The watermark compounds the saving, because a steady-state run touches only the rows that changed since last time, which is a tiny fraction of the table. Reserve full-table scans for the initial backfill, and run that backfill in chunks (by date range or by record type) so no single window blows the concurrency budget.

Treat throttling as expected, not exceptional. Wrap each request in retry logic that recognizes governance and rate errors, waits, and resumes from the same page. Keep the integration role scoped to read only the records you replicate, both for security and to keep the account's request budget spent on your pipeline rather than shared with unrelated work. If you later add a warehouse target, the same discipline applies to NetSuite to BigQuery.

How do you keep a NetSuite replica in sync without duplicating rows?

Keep the replica in sync without duplicates by upserting on the NetSuite internal id: INSERT ... ON CONFLICT (id) DO UPDATE. Because the internal id is a stable primary key, a retry after a timeout updates the existing row in place instead of inserting a second copy. Pair the upsert with the lastmodifieddate watermark so each run applies only the rows that actually changed.

The load has a consistent shape. Land each SuiteQL batch in a staging table, then upsert from staging into the target with ON CONFLICT (id) DO UPDATE SET ... so inserts and updates happen in one statement. Idempotence is the whole point: if the pipeline dies mid-run and replays the last window, every conflicting id updates rather than duplicates, and the target converges to the same state it would have reached on a clean run. That is why the watermark can safely overlap without producing double rows.

Add a loaded_at column so you always know when each row was last written, which makes debugging drift and building freshness checks trivial. Once the ERP data lives in Postgres, a non-technical teammate can ask questions of that database in plain English and get an answer without waiting on an analyst, while your applications and internal tools read the replica instead of querying NetSuite live. Watch the accounting nuances as you model: multi-subsidiary and multi-currency data, and posting period versus transaction date, all change how a number should be grouped.

How much does it cost to replicate NetSuite to Postgres?

The cost has two parts: the Postgres infrastructure you run and the pipeline that feeds it. Postgres itself is inexpensive at NetSuite volumes, since ERP tables are modest compared to event or log data, so a standard managed instance handles a full replica comfortably. If you licensed SuiteAnalytics Connect, that add-on carries its own NetSuite fee, but SuiteQL over the REST query endpoint needs no extra license.

Storage and compute for the database scale with how much history you keep and how heavily apps query it, both of which you control with indexes and a partitioning strategy on the large transaction tables. The pipeline is the other line item, and this is where metered per-row billing can surprise you: a busy accounting month means more changed rows, which on a usage-priced connector means a bigger invoice for the exact month you least want one.

On the pipeline side, our pricing is flat by plan (Starter $49, Growth $149, Scale $399, Enterprise custom) rather than metered per row, so a heavy close does not produce a spike. See flat pricing for the full breakdown. Keep the database right sized and indexed, keep the pipeline plan flat, and the total cost of a NetSuite to Postgres replica stays predictable as your transaction volume grows.

Replicate NetSuite into Postgres with incremental SuiteQL and idempotent upserts

Watermark reads, staging, ON CONFLICT upserts, and retries. Map the pair in the browser and pay a flat price from $49 a month.

Try the live demo

No credit card required.