stripe-sync-backfill
SKILL.md
Stripe Sync Engine Backfill
You are an expert in backfilling historical Stripe data using stripe-sync-engine. Your goal is to help users import their existing Stripe data into PostgreSQL.
Initial Assessment
Before proceeding, verify:
- Is stripe-sync-engine set up? (see setup skill)
- Are migrations completed? (see migrations skill)
- How much historical data do you need? (all time, last year, last 30 days?)
- How large is your Stripe account? (affects backfill strategy)
Basic Backfill
Method 1: Backfill Script (Recommended)
Create scripts/backfill-stripe.ts:
import { StripeSync } from "stripe-sync-engine";
const stripeSync = new StripeSync({
poolConfig: {
connectionString: process.env.DATABASE_URL!,
max: 10,
},
stripeSecretKey: process.env.STRIPE_SECRET_KEY!,
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
schema: "stripe",
autoExpandLists: true,
backfillRelatedEntities: true,
});
async function main() {
const startDateArg = process.argv[2];
if (!startDateArg) {
console.error("Usage: npm run stripe:backfill <start-date-unix-timestamp>");
console.error("Example: npm run stripe:backfill 1704067200");
process.exit(1);
}
const timestamp = parseInt(startDateArg, 10);
console.log(`Backfilling Stripe data from ${new Date(timestamp * 1000).toISOString()}...`);
await stripeSync.syncBackfill({
object: "all",
created: { gte: timestamp },
});
console.log("Backfill completed successfully");
}
main().catch((error) => {
console.error("Backfill failed:", error);
process.exit(1);
});
Add to package.json:
{
"scripts": {
"stripe:backfill": "tsx scripts/backfill-stripe.ts"
}
}
Run:
# Backfill from January 1, 2024 (Unix timestamp)
npm run stripe:backfill 1704067200
# Get Unix timestamp for a date (macOS/Linux)
date -d "2024-01-01" +%s
Method 2: API Endpoint
Create app/api/sync/backfill/route.ts:
import { NextResponse } from "next/server";
import { stripeSync } from "@/lib/stripeSync";
export async function POST(request: Request) {
const { object = "all", startDate } = await request.json();
try {
const result = await stripeSync.syncBackfill({
object,
created: startDate ? { gte: startDate } : undefined,
});
return NextResponse.json({ status: "completed", result });
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return NextResponse.json({ error: message }, { status: 500 });
}
}
Backfill Options
Object Types
The object parameter accepts:
| Value | Description |
|---|---|
all |
All supported object types |
customer |
Customer records |
product |
Product catalog |
price |
Price objects |
plan |
Legacy plan objects |
subscription |
Subscription records |
invoice |
Invoice records |
charge |
Charge records |
payment_intent |
Payment intents |
payment_method |
Payment methods |
setup_intent |
Setup intents |
dispute |
Dispute records |
Date Filters
The created parameter supports Stripe's RangeQueryParam:
// All objects created after a date
created: { gte: 1704067200 }
// Objects created before a date
created: { lte: 1735689599 }
// Objects in a date range
created: { gte: 1704067200, lte: 1735689599 }
// Exclusive comparisons
created: { gt: 1704067200 } // strictly after
created: { lt: 1735689599 } // strictly before
Backfilling Large Accounts (10,000+ Objects)
For large Stripe accounts, backfill in smaller chunks to avoid timeouts:
Day-by-Day Backfill
import { StripeSync } from "stripe-sync-engine";
const stripeSync = new StripeSync({
poolConfig: { connectionString: process.env.DATABASE_URL! },
stripeSecretKey: process.env.STRIPE_SECRET_KEY!,
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
});
async function backfillByDay(startDate: Date, endDate: Date) {
const currentDate = new Date(startDate);
while (currentDate <= endDate) {
const dayStart = Math.floor(currentDate.getTime() / 1000);
const dayEnd = dayStart + 86400 - 1; // End of day
console.log(`Syncing ${currentDate.toISOString().split('T')[0]}...`);
await stripeSync.syncBackfill({
object: "all",
created: { gte: dayStart, lte: dayEnd },
});
console.log(`Completed ${currentDate.toISOString().split('T')[0]}`);
currentDate.setDate(currentDate.getDate() + 1);
}
}
// Backfill all of 2024
backfillByDay(new Date("2024-01-01"), new Date("2024-12-31"));
Object-by-Object Backfill
const objects = [
"product",
"price",
"customer",
"subscription",
"invoice",
"payment_intent",
"charge",
];
for (const object of objects) {
console.log(`Backfilling ${object}s...`);
await stripeSync.syncBackfill({
object,
created: { gte: 1704067200 },
});
console.log(`Completed ${object}s`);
}
Syncing Single Entities
To sync or refresh a single Stripe object:
// Sync by Stripe ID (type is auto-detected from prefix)
await stripeSync.syncSingleEntity("cus_1234567890");
await stripeSync.syncSingleEntity("prod_1234567890");
await stripeSync.syncSingleEntity("sub_1234567890");
await stripeSync.syncSingleEntity("in_1234567890");
await stripeSync.syncSingleEntity("pi_1234567890");
API Endpoint for Single Entity Sync
Create app/api/sync/entity/[id]/route.ts:
import { NextResponse } from "next/server";
import { stripeSync } from "@/lib/stripeSync";
export async function POST(
request: Request,
{ params }: { params: { id: string } }
) {
try {
await stripeSync.syncSingleEntity(params.id);
return NextResponse.json({ status: "synced", id: params.id });
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return NextResponse.json({ error: message }, { status: 500 });
}
}
Revalidation on Sync
For critical objects, always fetch fresh data from Stripe API instead of trusting webhook payloads:
const stripeSync = new StripeSync({
// ... other config
revalidateObjectsViaStripeApi: [
"customer",
"subscription",
"invoice",
"payment_intent",
],
});
Verifying Backfill Results
After backfill completes, verify data in your database:
-- Count synced objects
SELECT
'customers' as type, COUNT(*) as count FROM stripe.customers
UNION ALL
SELECT 'products', COUNT(*) FROM stripe.products
UNION ALL
SELECT 'subscriptions', COUNT(*) FROM stripe.subscriptions
UNION ALL
SELECT 'invoices', COUNT(*) FROM stripe.invoices;
-- Check date range of synced data
SELECT
MIN(to_timestamp(created)) as earliest,
MAX(to_timestamp(created)) as latest
FROM stripe.customers;
Troubleshooting
Timeout Errors
- Backfill in smaller date ranges (daily or weekly)
- Backfill specific object types separately
- Increase function timeout if using serverless
Rate Limits
- stripe-sync-engine respects Stripe rate limits automatically
- For very large backfills, run during off-peak hours
Missing Related Data
- Enable
backfillRelatedEntities: truein configuration - This ensures foreign key relationships are maintained
Related Skills
- setup: Install and configure stripe-sync-engine
- migrations: Create database schema before backfill
- query: Query the backfilled data
Weekly Installs
14
Repository
ashutoshpw/striā¦c-engineFirst Seen
Jan 21, 2026
Security Audits
Installed on
claude-code12
gemini-cli12
opencode12
codex11
cursor9
antigravity9