L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Développement · SKILL.md

product-catalog-management

product-catalog-management

Dodo Payments product catalog management, covering products with one_time_price, recurring_price, or usage_based_price, add-ons, product collections, product images, and digital file delivery. Use when creating or updating products, plans, or prices, building a storefront, uploading images, attaching downloadable files, or debugging a failed product update.

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: product-catalog-management description: Dodo Payments product catalog management, covering products with one_time_price, recurring_price, or usage_based_price, add-ons, product collections, product images, and digital file delivery. Use when creating or updating products, plans, or prices, building a storefront, uploading images, attaching downloadable files, or debugging a failed product update.

Product Catalog Management

This skill covers the full product lifecycle: creating products with pricing models, managing add-ons and collections, uploading product images, and delivering digital files to customers.

When to use this skill

  • You need to create or update products with one-time, recurring, or usage-based pricing.
  • You're building a product collection or storefront.
  • You need to upload product images or deliver digital files to customers.
  • You're managing add-ons or product variants.
  • You need to understand why a product update failed or how to change a product's pricing.

Core concepts

Product model: Every product has exactly one pricing model selected at creation. The three models are:

  • one_time_price: charge once per purchase.
  • recurring_price: charge on a configurable payment frequency and subscription period.
  • usage_based_price: charge per unit consumed (see usage-based-billing skill for meter setup).

Pricing is nested inside the product object. There is no separate top-level Price resource.

Pricing structure: Each price object contains:

  • type: one of the three models above.
  • currency: ISO 4217 code (e.g., USD, AED, INR).
  • price: amount in the smallest currency unit (cents for USD).
  • discount: optional discount amount in the same unit.

Tax category: Required at product creation. Dodo uses this to calculate and collect sales tax. Supported values are digital_products, saas, e_book, and edtech.

Lifecycle: Products support list, retrieve, update, and archive/unarchive. There is no delete endpoint. Archived products remain in your history but don't appear in new checkouts.

Images: Presigned upload URLs expire after 60 seconds. Upload to the URL immediately after requesting it.

Digital delivery: Entitlements grant customers access to files. Download URLs expire after roughly 15 minutes.

Creating a product

All products require a name, a pricing model, and a tax category.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode',
});

// One-time purchase
const product = await client.products.create({
  name: 'Pro Bundle',
  tax_category: 'digital_products',
  price: {
    type: 'one_time_price',
    currency: 'USD',
    price: 9900, // $99.00
    discount: 0,
    purchasing_power_parity: false,
  },
});

console.log(product.product_id); // pdt_...

For recurring products, specify the billing cycle:

const subscription = await client.products.create({
  name: 'Premium Plan',
  tax_category: 'saas',
  price: {
    type: 'recurring_price',
    currency: 'USD',
    price: 2999, // $29.99/month
    discount: 0,
    payment_frequency_count: 1,
    payment_frequency_interval: 'Month',
    subscription_period_count: 1,
    subscription_period_interval: 'Month',
    purchasing_power_parity: false,
  },
});

This skill is the canonical source for product creation request shapes. For a usage-based product, create the meter as described in the usage-based-billing skill, then attach it through the singular price.meters array:

const metered = await client.products.create({
  name: 'API Calls',
  tax_category: 'saas',
  price: {
    type: 'usage_based_price',
    currency: 'USD',
    discount: 0,
    fixed_price: 0, // No fixed monthly charge; amounts use the smallest currency unit
    payment_frequency_count: 1,
    payment_frequency_interval: 'Month',
    subscription_period_count: 1,
    subscription_period_interval: 'Month',
    purchasing_power_parity: false,
    meters: [
      {
        meter_id: 'mtr_api_calls',
        price_per_unit: '0.01',
        free_threshold: 1000,
      },
    ],
  },
});

price_per_unit is a decimal string in the configured currency's smallest unit. The optional free_threshold excludes that many aggregated units before per-unit charging begins.

Listing and retrieving products

// List all products
const products = await client.products.list();

// Retrieve a single product
const product = await client.products.retrieve('pdt_pro_bundle');

Updating a product

PATCH /products/{id} (client.products.update) accepts the name, description, and metadata, and also price, tax_category, and pricing_mode. Every field is optional; omitted fields stay unchanged.

Two limits apply. The pricing model is immutable: you can edit the amount, but you cannot turn a one-time product into a subscription or usage-based one (or back) - create a new product instead. And a price edit only changes what new customers pay: existing subscriptions keep the price they were created with, so move them explicitly with Change Plan (client.subscriptions.changePlan(...)).

await client.products.update('pdt_pro_bundle', {
  name: 'Pro Bundle (Updated)',
  description: 'Includes all pro features',
  metadata: {
    category: 'software',
    tier: 'premium',
  },
});

Archiving and unarchiving

Archive a product to hide it from new checkouts without deleting it:

await client.products.archive('pdt_pro_bundle');

// Restore it later
await client.products.unarchive('pdt_pro_bundle');

Product images

Upload a product image via presigned URL. The URL is valid for 60 seconds.

import fs from 'node:fs';

// Request a presigned upload URL
const uploadUrl = await client.products.images.update('pdt_pro_bundle', {
  force_update: true,
});

// Upload immediately (within 60 seconds)
const response = await fetch(uploadUrl.url, {
  method: 'PUT',
  body: fs.readFileSync('./product.png'),
  headers: { 'Content-Type': 'image/png' },
});

if (!response.ok) {
  throw new Error('Image upload failed');
}

Add-ons

Add-ons are optional extras customers can purchase alongside a product. Create them independently, then reference them in checkout.

// Create an add-on
const addon = await client.addons.create({
  name: 'Priority Support',
  tax_category: 'saas',
  currency: 'USD',
  price: 4900, // $49.00
});

// Upload an image for the add-on
const addonImageUrl = await client.addons.updateImages(addon.id);

// List add-ons
const addons = await client.addons.list();

Product collections

Group products into collections for themed checkouts. Collections have nested groups and items.

// Create a collection
const collection = await client.productCollections.create({
  name: 'Starter Bundle',
  description: 'Everything you need to get started',
  groups: [
    {
      group_name: 'Core Tools',
      products: [
        { product_id: 'pdt_tool_a' },
        { product_id: 'pdt_tool_b' },
      ],
    },
  ],
});

// Add another group later
await client.productCollections.groups.create(collection.id, {
  group_name: 'Optional Tools',
  products: [
    { product_id: 'pdt_tool_c' },
    { product_id: 'pdt_tool_d' },
  ],
});

// Server route (e.g. POST /api/checkout): create the checkout session and return its URL
export async function POST() {
  const session = await client.checkoutSessions.create({
    product_collection_id: collection.id,
    product_cart: [], // Required: pass an empty array for collection checkout
    return_url: 'https://yoursite.com/return',
  });

  return Response.json({ checkout_url: session.checkout_url });
}

Never call the SDK or expose the API key in browser code. The browser only receives the URL from your server route and redirects:

// Browser
const res = await fetch('/api/checkout', { method: 'POST' });
const { checkout_url } = (await res.json()) as { checkout_url: string };
window.location.href = checkout_url;

Digital product delivery

Entitlements grant customers access to downloadable files. Create an entitlement, upload files, and generate download links.

// openAsBlob requires Node.js 20 or later
import { openAsBlob } from 'node:fs';

// Create an entitlement
const entitlement = await client.entitlements.create({
  name: 'Pro Bundle Files',
  integration_type: 'digital_files',
  integration_config: {
    digital_file_ids: [],
  },
});

// Upload and attach a file as multipart/form-data (append `filename` before `file`)
const form = new FormData();
form.append('filename', 'pro-bundle.zip');
form.append('file', await openAsBlob('./pro-bundle.zip'), 'pro-bundle.zip');
const { file_id } = await client.entitlements.files.upload(entitlement.id, { body: form });

// Attach the entitlement to a product.
// `entitlements` REPLACES every entitlement already attached to the product:
// include all of them, send [] to remove all, or omit the field to leave them unchanged.
await client.products.update('pdt_pro_bundle', {
  entitlements: [{ entitlement_id: entitlement.id }],
});

// A grant is produced by fulfillment when the customer purchases the product.
// Grants can be listed, revoked, or fulfilled with a manually managed license key;
// they cannot be created directly through the SDK.

// List grants for a customer
const grants = await client.customers.listEntitlementGrants('cus_customer_123');

// Generate a download URL (valid for roughly 15 minutes)
const grant = grants.items[0];
const downloadUrl = grant?.digital_product_delivery?.files[0]?.download_url;

Short links

Create short, shareable links to products:

const shortLink = await client.products.shortLinks.create('pdt_pro_bundle', {
  slug: 'pro-bundle-deal',
});

console.log(shortLink.short_url); // https://dodo.link/pro-bundle-deal

Localized pricing

For country or currency-specific pricing, use the localized-pricing skill. Do not hardcode prices in the client.

Common mistakes

Expecting a delete endpoint: Products don't have a delete method. Archive them instead. Archived products remain in your history for reconciliation.

Trying to change the pricing model: You cannot switch a product between one-time, subscription, and usage-based pricing (for example one_time_price to recurring_price). Create a new product with the model you need.

Expecting a price edit to reprice existing subscribers: client.products.update(...) changes the amount for new customers only. Existing subscriptions renew at the price they were created with; move them with client.subscriptions.changePlan(...).

Hardcoding prices in the client: Always fetch the product catalog from the API. Prices change, and your client code will become stale. Read client.products.retrieve(id) to get the current price.

Forgetting the tax category: Every product requires a tax_category. Dodo uses this to calculate and remit sales tax. Omitting it will cause the create request to fail.

Uploading images after the 60-second window: Request the presigned URL and upload immediately. If the URL expires, request a new one.

Passing a non-empty product_cart for collection checkout: Collection checkout requires product_cart: []. The collection itself defines what's in the cart.

Confusing purchasing_power_parity with localized pricing: PPP is native. Set price.purchasing_power_parity: true to charge a business-wide, per-country percentage of the price (default false). It requires Adaptive Currency to be enabled, and a matching localized price always wins over PPP. See the localized-pricing skill.

Resources

Skills enregistrées