Should You Put Feature-Request Tags in Stripe Customer Metadata?
No. Stripe's own use-cases documentation shows the mechanism working for exactly this: write metadata[service_tier]=premium on a Customer, then use the Search API to pull back every account tagged premium. A feature-request tag like fr_caregiver_login: true uses the same mechanism, so the docs don't rule it out. What they don't give you is anything built on top of that mechanism: a controlled vocabulary, a count broken out by plan, or a place a product team already looks. And it comes with limits that make a feedback taxonomy uncomfortable fast. Stripe caps every object at 50 key-value pairs, 40 characters per key, 500 characters per value.
That's not a reason it's impossible. Plenty of teams do write feature_request: multi_carrier_rates onto a Customer object, because the customer record is already open in the Stripe Dashboard and adding a field feels free. It isn't free. Metadata doesn't get you a taxonomy, a count, or a place your product team already looks. It gets you a string sitting next to a subscription ID, waiting for someone to write the code that reads it.
What Stripe's metadata is actually built for
Metadata is a key-value bag Stripe attaches to most objects (Customers, Subscriptions, PaymentIntents, Invoices) for data that belongs to you, not Stripe. The documented pattern is to store your CMS record ID on the Customer, a cart ID on the Checkout Session, a fulfillment status on the PaymentIntent. Stripe copies it into webhook events for the object it's attached to, and that's the whole mechanism. It's a lookup key that rides along with billing events so you don't have to make a second API call to your own database.
Stripe is direct about the boundary. Outside of Radar fraud rules, "Stripe doesn't use metadata," and it isn't shown to customers unless you build a UI that shows it to them. It's inert by design. That's correct for an order ID. It's a worse fit for something you want your product team acting on, because acting on it means someone already knows to go look.
The one place metadata does get queried back out is the Search API, and it can genuinely do segmentation: metadata['service_tier']:'premium' returns every Customer carrying that value, which is a real filter, not just an ID lookup. It only runs against seven object types (Charges, Customers, Invoices, PaymentIntents, Prices, Products, and Subscriptions), though, and what it hands back is a list of matching objects, not a count grouped by plan or a taxonomy a second teammate can add to without first agreeing on the key name.
Why it's tempting anyway
The appeal is real. A Customer object is already the place plan, billing history, and payment status live, and every subscription change already fires a webhook you might already be listening to. Writing plan_tier: enterprise onto that same object feels like consolidation, one record instead of two.
But the Customer object doesn't natively carry a plan tier at all. That's derived from the Subscription's price, not stored on the Customer. If you want plan_tier sitting in customer metadata, you write it there yourself, and you have to rewrite it every time the subscription changes, using the same customer.subscription.updated webhook a Stripe cancellation-reason router needs anyway. Skip that upkeep and the tag goes stale the first time an account upgrades, which is worse than no tag, because a stale "starter" label on an enterprise account reads as true until someone checks.
A concrete case: Larkhollow's fr_ prefix
The scenario below is illustrative, not an account of a real customer. It's built from the pattern most metadata-as-taxonomy setups follow.
Priya Solvang runs product at Larkhollow, a scheduling and intake platform for outpatient physical therapy clinics. Support kept fielding the same request: a caregiver login, so a patient's family member could see upcoming appointment times without creating their own account. Priya's fix, in isolation, was reasonable. Add fr_caregiver_login: true to the Customer metadata of every clinic account that had asked.
Three weeks in, the taxonomy had grown to fr_caregiver_login, fr_insurance_prefill, fr_recurring_series, fr_sms_reminders, and six more, spread across roughly 40 of Larkhollow's 300 clinic accounts. Priya wanted a count of how many clinics wanted the caregiver login, weighted by plan, and there was no button for that. The Search API can match metadata['fr_caregiver_login']:'true', but it's one of the seven searchable object types, not all of them, indexes with a lag rather than reading live, and returns a list of Customer objects, not a count broken out by plan. Getting the number meant exporting Sigma output to a spreadsheet and doing the join by hand.
Priya put it plainly during a product review:
"I know fourteen clinics want the caregiver login because I wrote the tag on all fourteen. I don't know if that's growing, and I can't tell you which three are on the Growth plan without pulling a CSV."
The tag had done exactly what metadata is built to do. It held a fact Priya already knew, next to the object she already had open. It hadn't done what a feedback system does, which is make the fact visible to people who didn't write it and countable without a manual export.
What a Customer-object tag can't give you
The failure modes show up in a predictable order as the tag list grows:
- No shared taxonomy enforcement. Two teammates write
fr_caregiver_loginandfr-caregiver-access, and Stripe has no concept of a controlled vocabulary to catch the drift. - The 50-key ceiling is real and shared. Every integration touching that Customer (your CMS sync, your fraud rules, your own scripts) writes into the same 50 slots. A feedback taxonomy competing for that space with billing plumbing is a fight it shouldn't have to have.
- No cross-customer view without custom code. There's no dashboard filter that says "show me every account tagged
fr_caregiver_login, grouped by plan." Sigma or the Search API gets you the raw matches; the count and the grouping are still yours to build. - Nothing connects the tag to the actual conversation. The metadata value is
true. It doesn't carry the sentence the customer wrote, who wrote it, or the ticket where they wrote it. You'd need a separate lookup ID and a separate system to hold that, which is the whole problem restated.
None of that is a bug in Stripe. A 500-character value with no reporting layer on top is exactly what the docs describe. It's the wrong shape for what a product team actually needs, which is a taxonomy, a count, and the source quote, together.
What holds up instead
The fix isn't a bigger metadata schema. It's keeping the two jobs separate. Stripe stays the system of record for billing, and something built for feedback holds the taxonomy, the count, and the plan context together, refreshed from Stripe rather than typed by hand.
That's the point where we'd bring up Modem. Stripe is a documented integration: a read-only OAuth connection mirrors seven kinds of billing data (customers, subscriptions, products, prices, invoices, charges, disputes, refunds) in real time, and Stripe customers are matched to the people and companies already in Modem, so a request that lands in Slack or a support ticket carries plan and revenue context automatically, refreshed from the mirrored data rather than typed into a metadata key by hand. Automations also fire on new customers, subscriptions, cancellations, trials ending, and failed payments. That mechanism is covered in more depth in connecting Stripe revenue to feedback. We build Modem, so weigh that the way you'd weigh any vendor describing its own product.
For a single tag on a handful of accounts, a metadata field someone remembers to check is genuinely enough, and the overhead of a second system isn't worth it yet. Larkhollow was already past that: ten-plus tags, 40 accounts, and a manual export every time anyone wanted a count. That's the point to stop extending Stripe's Customer object and start using something built for the job.
The one change that keeps this from getting worse
If you're already tempted to write into Customer metadata, write the ID instead of the tag, a lookup key pointing at wherever your actual feedback taxonomy lives, the same pattern Stripe's own docs recommend for anything too big for 500 characters. Keep the plan tier itself out of metadata entirely. Read it from the subscription at the moment you need it, or let a tool that reads Stripe directly keep it current. The Customer object stays clean, and the taxonomy lives somewhere built to hold one.
