# How to store metadata on Stripe charges

- Tool: Stripe
- Difficulty: Beginner
- Time required: 10 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+
- Last updated: March 2026

## TL;DR

Stripe metadata lets you attach up to 50 key-value pairs to any object — charges, customers, subscriptions, invoices, and more. Use metadata to store internal references like order IDs, user IDs, and campaign tags. Metadata is searchable in the Dashboard and accessible via the API, making it essential for reconciliation, analytics, and debugging.

## Using Metadata to Track Custom Data on Stripe Objects

Stripe metadata is a powerful feature that lets you attach custom key-value pairs to charges, PaymentIntents, customers, subscriptions, and other objects. This bridges the gap between Stripe data and your internal systems. Store your order ID, user ID, campaign source, or any reference you need for reconciliation, reporting, and support. Metadata is visible in the Dashboard and returned in API responses and webhooks.

## Before you start

- A Stripe account in test mode
- Node.js 18+ with the Stripe npm package
- An understanding of your internal reference system (order IDs, user IDs, etc.)

## Step-by-step guide

### 1. Add metadata when creating a PaymentIntent

Pass a metadata object with key-value pairs when creating a PaymentIntent. Keys and values must be strings. You can store up to 50 keys, with key names up to 40 characters and values up to 500 characters.

```
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const paymentIntent = await stripe.paymentIntents.create({
  amount: 4999,         // $49.99
  currency: 'usd',
  metadata: {
    order_id: 'ORD-2026-1234',
    user_id: 'usr_abc123',
    product: 'Pro Plan Annual',
    campaign: 'spring_sale_2026',
  },
  automatic_payment_methods: { enabled: true },
});
```

**Expected result:** The PaymentIntent is created with custom metadata attached. This metadata appears in the Dashboard and webhook events.

### 2. Add metadata to customers

Attach metadata to customer objects to store user IDs, account types, signup sources, or any customer-level tracking data.

```
const customer = await stripe.customers.create({
  email: 'user@example.com',
  name: 'Jane Smith',
  metadata: {
    internal_user_id: 'usr_abc123',
    plan_tier: 'pro',
    signup_source: 'referral',
    company: 'Acme Corp',
  },
});
```

**Expected result:** The customer object has metadata accessible from the Dashboard, API, and webhook events.

### 3. Update metadata on existing objects

You can update metadata at any time. Setting a key to an empty string removes it. Other existing keys are preserved.

```
// Add or update metadata keys
await stripe.paymentIntents.update('pi_abc123', {
  metadata: {
    fulfillment_status: 'shipped',
    tracking_number: 'UPS-1Z999AA10123456784',
  },
});

// Remove a specific key by setting it to empty string
await stripe.paymentIntents.update('pi_abc123', {
  metadata: {
    campaign: '',  // This removes the 'campaign' key
  },
});
```

**Expected result:** Metadata is updated on the existing PaymentIntent. New keys are added, and empty-string values remove keys.

### 4. Search by metadata in the Dashboard

In the Stripe Dashboard, go to Payments and use the search bar. You can search by metadata values. For example, searching for an order ID will find the payment with that metadata. This is invaluable for customer support.

**Expected result:** You can find specific payments by searching for metadata values in the Dashboard search bar.

### 5. Read metadata in webhook events

Metadata is included in webhook event payloads. This lets you correlate Stripe events back to your internal systems without making additional API calls. Teams at RapidDev use this to automate order fulfillment and CRM updates.

```
// In your webhook handler
if (event.type === 'payment_intent.succeeded') {
  const paymentIntent = event.data.object;
  const orderId = paymentIntent.metadata.order_id;
  const userId = paymentIntent.metadata.user_id;

  console.log(`Payment succeeded for order ${orderId}, user ${userId}`);
  // Trigger order fulfillment, update your database, etc.
}
```

**Expected result:** Your webhook handler reads metadata from the event to identify the associated order and user.

## Complete code example

File: `metadata-example.js`

```javascript
require('dotenv').config();
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express();

// Create payment with metadata
app.post('/api/create-payment',
  express.json(),
  async (req, res) => {
    const { amount, currency, orderId, userId, productName } = req.body;

    try {
      const paymentIntent = await stripe.paymentIntents.create({
        amount,
        currency: currency || 'usd',
        metadata: {
          order_id: orderId,
          user_id: userId,
          product: productName,
          created_at: new Date().toISOString(),
        },
        automatic_payment_methods: { enabled: true },
      });

      res.json({ clientSecret: paymentIntent.client_secret });
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  }
);

// Webhook reads metadata
app.post('/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      return res.status(400).send(`Webhook Error: ${err.message}`);
    }

    if (event.type === 'payment_intent.succeeded') {
      const pi = event.data.object;
      console.log('Payment succeeded:', {
        stripe_id: pi.id,
        order_id: pi.metadata.order_id,
        user_id: pi.metadata.user_id,
        product: pi.metadata.product,
        amount: pi.amount / 100,
        currency: pi.currency,
      });
      // TODO: fulfill order, update database
    }

    res.json({ received: true });
  }
);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server on port ${PORT}`));
```

## Common mistakes

- **Storing sensitive data like passwords or full credit card numbers in metadata** — undefined Fix: Never store sensitive data in metadata. It's visible in the Dashboard and API responses. Store only non-sensitive references like IDs and labels.
- **Using non-string values in metadata** — undefined Fix: All metadata values must be strings. Convert numbers and booleans to strings: metadata: { count: '5', active: 'true' }.
- **Exceeding the 50-key limit** — undefined Fix: Stripe allows a maximum of 50 metadata keys per object. Store high-cardinality data in your own database and use a single reference ID in metadata.
- **Using inconsistent key names across your codebase** — undefined Fix: Standardize your metadata keys (e.g., always use 'order_id' not sometimes 'orderId' or 'order-id'). Document your metadata schema.

## Best practices

- Always include your internal order/user ID in payment metadata for reconciliation
- Use consistent, snake_case key names across all Stripe objects
- Document your metadata schema so your team uses the same keys
- Never store sensitive data (PII, passwords, full card numbers) in metadata
- Use metadata in webhooks to correlate Stripe events to your internal records
- Keep values under 500 characters — use IDs and references, not full data
- Search by metadata in the Dashboard to quickly find payments for support
- Include a timestamp in metadata for debugging timing issues

## Frequently asked questions

### What is Stripe metadata?

Metadata is a set of up to 50 key-value pairs you can attach to most Stripe objects. Keys can be up to 40 characters and values up to 500 characters. All values must be strings.

### Can I search by metadata in the Stripe Dashboard?

Yes. Use the search bar in the Payments, Customers, or Subscriptions sections. You can search by metadata values to find specific objects.

### Is metadata included in webhook events?

Yes. Metadata is included in the event.data.object payload for all webhook events, allowing you to correlate Stripe events to your internal systems.

### Can I update metadata after creating an object?

Yes. Use the update method (e.g., stripe.paymentIntents.update) to add, change, or remove metadata keys. Setting a key's value to an empty string removes it.

### Is there a cost for using metadata?

No. Metadata is a free feature available on all Stripe objects and plans. There is no additional charge for storing or querying metadata.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-store-metadata-on-stripe-charges
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-store-metadata-on-stripe-charges
