DEV Community

Cover image for How to Export Paddle Data to a Database
ilshaad
ilshaad

Posted on • Originally published at codelesssync.com

How to Export Paddle Data to a Database

How to export Paddle data to a database: dashboard CSV reports, the Reports API, the Paddle API, Zapier, and no-code sync. Pros, cons, and real costs.

By Ilshaad Kheerdali · 27 July 2026


Paddle reports build asynchronously, carry up to 24 hours of lag, and delete themselves after 14 days. The API is fresher, but it hands you transactions 30 at a time behind a cursor. Neither one is an export, and if you run your billing on Paddle you have probably already hit that wall.

The awkward part is that "export Paddle data to a database" sounds like it should be a single button. It isn't. Five different methods exist, each solving a different slice of the problem, and most of them either go stale before you download them, expire before you use them, or leave you maintaining a pipeline that quietly breaks at 3am.

This guide walks through all five practical ways to get Paddle Billing data into a real, queryable database: dashboard CSV reports, the Reports API, the Paddle API directly, Zapier-style automation, and no-code sync. What each one costs, and where each one breaks.

Why Exporting Paddle Data Is Harder Than It Should Be

As a merchant of record, Paddle is the system of record for your revenue, not just another payment processor. It holds your customers, subscriptions, transactions, and adjustments, and it handles the sales tax filing on top. That makes a queryable local copy more valuable than it would be elsewhere, and it also makes the data harder to get at than most teams expect.

Built-in reports are asynchronous snapshots. Paddle Billing generates CSV reports for transactions, transaction line items, adjustments, adjustment line items, products and prices, discounts, checkouts, and payout reconciliation, but each report has to be built, then downloaded before it expires (files are kept for 14 days). And the data inside a report can be delayed by up to 24 hours, so even a fresh export isn't fully current.

There's no bulk "export everything" endpoint. Paddle's API is designed to run a billing system, so every entity (customers, subscriptions, transactions, products, prices) is its own set of cursor-paginated list calls. Page sizes vary by endpoint, and transactions cap at 30 records per page. A busy account means hundreds of sequential requests per entity, stitched back together in order.

Webhooks notify about new events only. Paddle notifications are push-based and start capturing from the moment you create the destination. Your historical transactions and existing subscribers never flow through them, so webhooks can't backfill a database. (For the full webhook trade-off, see Paddle Webhooks vs Database Sync.)

You configure everything twice. Sandbox and production are isolated Paddle environments with their own API keys, so a report you build or a pipeline you script against sandbox has to be recreated, and re-verified, against live data before you trust the numbers.

So the job falls to one of five approaches. Here is how each one holds up.

5 Ways to Export Paddle Data to a Database

Method 1: Manual Paddle CSV Export from the Dashboard

The simplest option. In the Paddle dashboard, go to Reports, find the report type you need under the Build reports tab (transactions, adjustments, transaction line items, products and prices, or discounts), click Build report, filter the date range, then click Generate report. Paddle emails you once the file is ready. The output is a UTF-8, comma-delimited CSV.

Once you have the file, you import it from your own machine with psql's \copy command (the server-side COPY FROM variant needs filesystem access on the database server, which managed hosts like Supabase, Neon, and RDS don't give you):

\copy paddle_transactions_export (transaction_id, customer_id, status, currency, total, billed_at) FROM 'paddle-transactions.csv' CSV HEADER
Enter fullscreen mode Exit fullscreen mode

Create the destination table before you run that, and check the column list against the file: CSV HEADER skips the header row, it does not match columns by name, so the order has to line up. Paddle's transaction report ships a lot more columns than the six above, so trim the file or list every column.

Pros:

  • Free and built into Paddle
  • No code, no API setup, no developer required
  • Useful for one-off analysis or a finance handoff

Cons:

  • Report data can lag up to 24 hours, so the export is stale before you even download it
  • Asynchronous: you request the report, wait for it to build, then download
  • Files expire 14 days after creation; miss the window and you rebuild the report
  • Manual every time. If you need fresh data weekly, you're clicking through this every week
  • Each report type is separate, so a full dataset means several builds and several imports
  • No automation, no incremental updates, no joins with your application data

Dashboard reports are fine when someone in finance needs a spreadsheet once a quarter. As a way to keep a database current, they fall over immediately.

Method 2: Automate the Reports API

Paddle exposes the same reporting engine over the API: POST /reports creates a report, you poll until its status is ready (or listen for the report.updated notification), then call the download-url endpoint to get a link to the CSV. That link expires after 72 hours, and the report itself stays available for 14 days, so you can request a fresh link at any point inside that window.

Pros:

  • Scriptable version of Method 1; can run on a schedule
  • Same broad report types as the dashboard
  • Good fit if you already have a data pipeline that ingests CSV files

Cons:

  • You're building a pipeline anyway: create, poll, download via an expiring link, parse CSV, load, dedupe
  • Inherits Method 1's data lag, so your database is always up to a day behind
  • CSV column layouts follow the report, so you still design the table schema and upserts yourself
  • Sandbox/live duplication, credential storage, and failure alerting are all on you

This is a legitimate middle path for data teams, but by the time it runs reliably on a schedule, you've built and now own a small ETL system.

Method 3: Direct Paddle API Integration

If you need current data and you're comfortable writing code, you can pull directly from the Paddle API's list endpoints and write the results into PostgreSQL yourself.

A minimal version looks like this:

import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

async function exportTransactions() {
  let url: string | null = 'https://api.paddle.com/transactions?per_page=30';

  while (url) {
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.PADDLE_API_KEY}` },
    });
    const json = await response.json();

    for (const tx of json.data ?? []) {
      await pool.query(
        `INSERT INTO paddle_transactions_raw (id, customer_id, status, currency_code, grand_total, billed_at, updated_at)
         VALUES ($1, $2, $3, $4, $5, $6, $7)
         ON CONFLICT (id) DO UPDATE
         SET status = $3, grand_total = $5, updated_at = $7`,
        [
          tx.id,
          tx.customer_id,
          tx.status,
          tx.currency_code,
          tx.details?.totals?.grand_total ?? 0,
          tx.billed_at,
          tx.updated_at,
        ],
      );
    }

    url = json.meta?.pagination?.has_more ? json.meta.pagination.next : null;
  }
}
Enter fullscreen mode Exit fullscreen mode

That hand-rolled schema is not the same as the table Codeless Sync creates, which stores details.totals.total in a column called total. Keep the two apart, or the SQL further down this page won't run against your table.

Pros:

  • Real, current data on demand, no report lag
  • Full control over which entities you export and how they map to your schema
  • Simple Bearer-key auth for your own account, so there are no OAuth tokens to refresh the way QuickBooks and Xero require

Cons:

  • Cursor pagination with small pages: transactions cap at 30 records per page, so large accounts mean many requests per run
  • Rate limiting, retries on 429s, and error recovery are all on you
  • Amounts arrive as strings of integer minor units inside nested objects (details.totals.grand_total), so schema mapping and type conversion are manual work
  • Each new entity (customers, subscriptions, products, prices, adjustments, discounts) is another loop, another schema, another set of edge cases
  • Sandbox and live need separate configuration and testing
  • Maintenance is forever. The build is the easy part

For most teams the arithmetic is simple: a weekend to build it, then an open-ended commitment to keep it alive as Paddle's API and your own schema both move.

Method 4: Zapier, Make, or Generic Automation Platforms

If you want fresh data without writing code, automation platforms can catch Paddle webhooks. Neither Zapier nor Make has a native Paddle trigger (Make's Paddle app is actions-only, and Zapier has no Paddle app at all), but both have generic webhook modules. You point Paddle's transaction.completed webhook at a catch-hook URL, then map the payload fields to a PostgreSQL insert action.

Pros:

  • No code required, though you do set up a webhook destination and map fields by hand
  • Both platforms have solid PostgreSQL insert actions
  • Reasonable for low-volume, single-trigger use cases

Cons:

  • No historical backfill; only future events trigger the automation. Your existing customers, subscriptions, and transaction history stay outside the database unless you export them separately
  • Per-task pricing scales with your sales volume; a growing subscription business generates a lot of events
  • Limited transformation logic; anything beyond direct field mapping needs custom code steps, which take you back toward Method 3
  • Failures retry, but silently; debugging a stuck automation is painful
  • Your "export pipeline" lives inside a third-party automation account, not your codebase

Automation platforms earn their place on single-trigger flows. Asking one to hold a complete, current copy of your Paddle data is asking it to act as a replication tool, which it was never built to be.

Method 5: A Purpose-Built No-Code Sync (Codeless Sync)

Codeless Sync was built for exactly this problem: getting API data into a PostgreSQL database without writing code and without a report-lag delay, then keeping it there without a pipeline to maintain.

How it works:

  1. Connect your PostgreSQL database via connection string (Supabase, Neon, AWS RDS, Railway, Heroku, or self-hosted)
  2. Add your Paddle API key (read access is enough)
  3. Pick which data to export (customers, subscriptions, transactions, products, prices, adjustments, or discounts)
  4. The destination table is auto-created with the right schema and indexes
  5. Run the first export: a full sync pulls your complete history. Schedule recurring syncs, or trigger them manually

Pros:

  • No code to write, and no report polling or pagination loops left running
  • Historical backfill plus ongoing incremental updates in one workflow (no 24-hour report lag)
  • Works with any PostgreSQL host
  • Free tier for small projects, flat predictable pricing as you scale
  • Setup takes about 5 minutes

Cons:

  • Runs in batches on a schedule (webhooks remain the right tool for instant reactions like access provisioning)
  • Currently focused on Stripe, QuickBooks, Xero, and Paddle; not a general-purpose ETL tool

If what you want is a current, queryable copy of your Paddle data sitting in your own database, this is the shortest route to it.

Comparison: Which Paddle Export Method Fits Your Use Case?

Method Setup time Keeps data current? Best for Cost
Dashboard CSV report Minutes No; async snapshot, up to 24h data lag A finance handoff you do once a quarter Free
Reports API pipeline Days Partial; scheduled but inherits report lag Teams already loading CSV feeds on a schedule Server time plus the build
Direct Paddle API Days to weeks Yes, if you maintain the polling A custom entity mix worth the engineering Server time plus ongoing upkeep
Zapier / Make Hours Partial; future events only, no backfill Reacting to one Paddle event at low volume Per-task, rises with sales volume
Codeless Sync ~5 minutes Yes; backfill plus scheduled incremental Anyone who wants the data there and current Free tier, then flat plans

Read down the table and the trade is clear enough. The free routes hand you a snapshot that is already stale and will delete itself. The API hands you freshness in exchange for permanent upkeep. The automation platforms only ever see what happens next. A purpose-built sync is the one option that gives you history and freshness without putting a pipeline in your name.

What You Can Do Once Paddle Data Is in PostgreSQL

Paddle's own reporting can tell you what happened inside Paddle. Once the same data is in Postgres, you can ask questions Paddle has no way to answer, because you can join your billing records against your application's tables and against each other.

The queries below run against the tables Codeless Sync creates. Paddle returns amounts as strings in the lowest denomination for the currency (cents for USD, pence for GBP), so totals are cast to numeric and divided by 100. Most currencies use two decimal places, but a few (JPY, for example) use none, so adjust the divisor if you sell in those. If you sell in more than one currency, add AND currency_code = 'USD' or group by currency_code, since every row is stored in its original currency. Add AND livemode = true to keep sandbox rows out of the numbers.

Monthly revenue with month-over-month growth:

WITH monthly_revenue AS (
  SELECT
    DATE_TRUNC('month', billed_at) AS month,
    SUM(total::NUMERIC) / 100.0 AS revenue,
    COUNT(*) AS transaction_count
  FROM paddle_transactions
  WHERE status = 'completed'
  GROUP BY 1
)
SELECT
  month,
  revenue,
  transaction_count,
  ROUND(
    100.0 * (revenue - LAG(revenue) OVER (ORDER BY month))
    / NULLIF(LAG(revenue) OVER (ORDER BY month), 0),
    1
  ) AS mom_growth_pct
FROM monthly_revenue
ORDER BY month DESC
LIMIT 12;
Enter fullscreen mode Exit fullscreen mode

Refund and chargeback rate from adjustments:

WITH adj AS (
  SELECT
    DATE_TRUNC('month', created_at) AS month,
    COUNT(*) FILTER (WHERE action = 'refund' AND status = 'approved') AS refunds,
    COUNT(*) FILTER (WHERE action = 'chargeback' AND status = 'approved') AS chargebacks
  FROM paddle_adjustments
  GROUP BY 1
),
tx AS (
  SELECT
    DATE_TRUNC('month', billed_at) AS month,
    COUNT(*) AS completed_transactions
  FROM paddle_transactions
  WHERE status = 'completed'
  GROUP BY 1
)
SELECT
  a.month,
  a.refunds,
  a.chargebacks,
  ROUND(
    100.0 * (a.refunds + a.chargebacks) / NULLIF(t.completed_transactions, 0),
    2
  ) AS refund_chargeback_rate_pct
FROM adj a
LEFT JOIN tx t ON t.month = a.month
ORDER BY a.month DESC;
Enter fullscreen mode Exit fullscreen mode

Trial-to-paid conversion by cohort (only possible with full history):

SELECT
  DATE_TRUNC('month', started_at) AS cohort_month,
  COUNT(*) AS subscriptions_started,
  COUNT(*) FILTER (WHERE first_billed_at IS NOT NULL) AS converted_to_paid,
  ROUND(
    100.0 * COUNT(*) FILTER (WHERE first_billed_at IS NOT NULL)
    / NULLIF(COUNT(*), 0),
    1
  ) AS conversion_pct
FROM paddle_subscriptions
WHERE started_at IS NOT NULL
  AND livemode = true
GROUP BY 1
ORDER BY cohort_month DESC
LIMIT 12;
Enter fullscreen mode Exit fullscreen mode

That last one is the query a webhook-fed table can never answer, because the cohorts you most want to measure started before you set the webhook up. That is what a real export gets you: a queryable dataset sitting next to your application data, ready for dashboards or alerts, instead of a CSV that deletes itself in 14 days. For ready-made metric queries, see How to Calculate MRR, Churn, and LTV in PostgreSQL.

Step-by-Step: Set Up an Automated Paddle Export in 5 Minutes

If Method 5 looks like the right fit, the whole setup is four things: paste your PostgreSQL connection string, add a Paddle API key with Permissions set to Read for All (it's encrypted at rest), let the wizard auto-create the destination table, then run the first export. Transactions is the data type to start with, since it's your revenue record, and the Paddle transactions SQL template shows the exact schema you'll get.

That first run is a full backfill, so your complete history lands in the table rather than only what happens from now on. After that you either trigger exports manually or put them on a schedule (every 12 hours, daily, weekly, or monthly, depending on your plan). Repeat the wizard for customers, subscriptions, or any other data type, and each one becomes its own table on the same cadence.

The free tier covers small projects without a credit card, and the full walkthrough, including which PostgreSQL hosts are supported, lives on the Paddle to PostgreSQL page.

Frequently Asked Questions

Can I export Paddle data without using the API?

Yes, via a Paddle CSV export from the dashboard: go to Reports, build the report type you need (transactions, adjustments, line items, products and prices, or discounts), and download the CSV once Paddle emails you that it's generated. This works for one-off analysis, but reports build asynchronously, the data inside can lag up to 24 hours, and files expire after 14 days. For an ongoing, current copy of your data, you'll want one of the automated methods, either built on the API yourself or handled by a sync tool like Codeless Sync.

Does Paddle have a full data export or backup option?

Not as a single button. Exports are per report type in the dashboard, or per entity through the API's paginated list endpoints. A complete picture of your billing data means several separate exports, repeated whenever you need fresh data. This is the main reason teams reach for a sync tool: a complete, continuously updated copy in one database is exactly what those tools produce.

What's the best way to export Paddle data to PostgreSQL?

It depends on freshness needs and engineering time. For a one-time snapshot, a dashboard report plus a psql \copy import is fastest. For a current, queryable copy with minimal maintenance, a no-code sync is the lowest-effort path and avoids the report lag entirely. Building directly against the Paddle API gives the most control, but you own cursor pagination (transactions cap at 30 records per page), rate-limit handling, and schema mapping forever.

How long are Paddle report exports available?

Generated report files are available to download for 14 days after creation, and each download URL the API returns expires after 72 hours, so you can request a fresh link at any point inside that 14-day window. Reports are best treated as one-off snapshots rather than an archive. If you need a permanent, queryable history, load the data into your own database, then the retention question disappears.

How fresh will my exported Paddle data be?

That depends entirely on the method. A dashboard report or a Reports API pull can be up to 24 hours behind before you even download it, because Paddle builds those asynchronously. Anything that reads the API directly, including a sync tool, is current as of the moment the run starts. So if the number you are looking at needs to match what Paddle's dashboard says right now, rule out the report-based methods. For monthly finance reviews a daily run is plenty; for internal dashboards and MRR tracking, twice a day keeps the numbers feeling live. Codeless Sync runs manual syncs on the free tier, and paid tiers add scheduled runs from every 12 hours down to monthly, depending on plan.


Need a current, queryable copy of your Paddle data without babysitting reports or pagination loops? Codeless Sync has a free tier, no credit card required. For the webhook side of the story, see Paddle Webhooks vs Database Sync.


Related:

Top comments (0)