Connect Retool to Stripe using the native Stripe Resource in the Resources tab. Authenticate with a restricted API key, then use the operation dropdown to list customers, view charges, issue refunds, and manage subscriptions — all without memorizing Stripe's endpoint paths. Includes a dedicated Stripe Card Form component for secure payment collection.
| Fact | Value |
|---|---|
| Tool | Stripe |
| Category | Payment |
| Method | Retool Native Resource |
| Difficulty | Intermediate |
| Time required | 15 minutes |
| Last updated | April 2026 |
Why Build a Retool Stripe Dashboard?
Stripe's built-in dashboard excels at giving founders and finance teams a broad view of payment activity, but operations teams — support agents, customer success managers, billing engineers — need a more focused, action-oriented interface. A Retool Stripe dashboard lets you combine Stripe payment data with your internal customer records, exposing exactly the context and actions your team needs: look up a customer, see their full payment history, issue a refund, or update their subscription, all in one screen without navigating Stripe's multi-page dashboard.
Retool's native Stripe connector removes the engineering overhead of building a custom admin interface from scratch. The operation dropdown handles endpoint paths and request formats; transformers reshape raw Stripe responses into display-ready data; and Retool's Table, Chart, and Form components provide the UI layer. A support engineer can build a working refund tool in under an hour, and a finance engineer can build an MRR chart panel in the same session.
Security is a first-class concern when building Stripe admin tools. Retool proxies all Stripe API calls server-side, meaning your secret API key never reaches the browser. Using Stripe's restricted key feature, you can limit the Retool resource to only the specific operations your admin panel actually performs — a refund tool doesn't need subscription management permissions, and a read-only revenue dashboard doesn't need any write permissions at all.
Integration method
Retool includes a dedicated Stripe connector that authenticates via API key and exposes Stripe's core payment operations through an operation dropdown — List Customers, List Charges, Create Refund, Update Subscription, and more. The connector handles request formatting and response parsing automatically, supports both test and live mode keys, and includes built-in auto-pagination for large result sets. All API calls are proxied server-side through Retool's backend, keeping your Stripe keys off the browser.
Prerequisites
- A Retool account (Cloud or self-hosted) with permission to add Resources
- A Stripe account (test mode is sufficient for initial setup and development)
- A Stripe restricted API key with the permissions your dashboard needs — create this in Stripe Dashboard → Developers → API keys → Create restricted key
- Understanding of Stripe's core objects: Customers, Charges, PaymentIntents, Subscriptions, Refunds
Step-by-step guide
Create a restricted Stripe API key and configure the Resource
Creating a restricted API key before connecting Retool is the most important security step. Navigate to the Stripe Dashboard (dashboard.stripe.com) and go to Developers → API keys. Click Create restricted key and name it 'Retool Admin Panel' or similar. Configure permissions based on what your dashboard will do: For a read-only revenue dashboard: - Customers: Read - Charges: Read - Subscriptions: Read - Invoices: Read For a billing support tool with refund capability: - All of the above, plus: - Refunds: Write For subscription management: - All of the above, plus: - Subscriptions: Write Avoid giving Write access to anything you don't actively need — a refund dashboard doesn't need the ability to create charges or modify products. Copy the generated key (it's only shown once). Open Retool and go to the Resources tab. Click Add Resource, search for Stripe, and click it. In the configuration form: - Name: 'Stripe Production' or 'Stripe Test' to make the environment clear - API Key: paste your restricted key - Environment: the UI label is cosmetic only — the actual environment (test vs. live) is determined by whether your key starts with sk_test or sk_live Click Save Changes. Create a second resource for the other environment (test key if you set up live first, or vice versa) so you can test safely.
Pro tip: When creating restricted keys, Stripe shows a preview of exactly which API endpoints the permissions map to. Review this list carefully — it's an easy way to verify that your key covers everything your Retool app needs without granting excess access.
Expected result: A Stripe resource appears in the Resources list. Opening the query editor and selecting this resource shows the Action Type dropdown with Stripe operations like List Customers, List Charges, and Create Refund.
Query customers and charges for a billing support panel
Create a new query in your Retool app and select the Stripe resource. Set Action Type to List Customers. Configure the query parameters: - Limit: 25 (start small; increase with pagination) - Email: {{ customerSearchInput.value }} — wire to a Text Input component on the canvas so agents can search by email address Set the query to Run this query automatically when inputs change so the table updates as the agent types. Add a delay (150ms debounce) in the query's Advanced settings to avoid firing on every keystroke. Add a Transformer to the query to reshape the Stripe response into a clean table format. Create a second query: set Action Type to List Charges and add a Customer parameter set to {{ customersQuery.selectedRow?.id }} — this scopes charges to the currently selected customer. Set the charges query to Run this query automatically when inputs change, so it fires when a customer row is selected. Add a condition in the query's Advanced settings: Only run when {{ customersQuery.selectedRow?.id !== undefined }}. Drag two Table components onto the canvas: the top table shows customers (bound to {{ customersQuery.data }}), and the bottom table shows charges for the selected customer (bound to {{ chargesQuery.data }}). Set the top table to Row select mode: single, so clicking a row selects a customer and triggers the charges query.
1// JavaScript transformer for Stripe charges query2// Reshapes raw Stripe charge objects into display-ready rows3const charges = data; // raw array of Stripe charge objects4return charges.map(charge => ({5 id: charge.id,6 amount: '$' + (charge.amount / 100).toFixed(2),7 currency: charge.currency.toUpperCase(),8 status: charge.status,9 description: charge.description || 'No description',10 receipt_email: charge.receipt_email || '',11 payment_method: charge.payment_method_details?.type || '',12 card_last4: charge.payment_method_details?.card?.last4 || '',13 created: new Date(charge.created * 1000).toLocaleString(),14 refunded: charge.refunded ? 'Yes' : 'No',15 refund_amount: charge.amount_refunded > 016 ? '$' + (charge.amount_refunded / 100).toFixed(2)17 : ''18}));Pro tip: Add a small text label above the charges table that reads 'Charges for: {{ customersQuery.selectedRow?.email || "Select a customer above" }}' — this confirms to agents which customer's charges they are viewing and prompts them to select a row when none is chosen.
Expected result: Typing in the search input updates the customers table with matching Stripe customers. Clicking a customer row loads that customer's charges in the bottom table with formatted amounts, statuses, and card details.
Add a refund flow with confirmation and audit logging
Refund functionality is the most common write operation in Stripe admin panels. The key is to make it easy but not accidental — a refund button with a confirmation dialog and post-refund audit logging strikes the right balance. Create a new query with Action Type: Create Refund. Configure: - Charge: {{ chargesTable.selectedRow.id }} — the Stripe charge ID from the selected charges table row - Amount: {{ refundAmountInput.value * 100 }} — convert dollars to cents; leave blank for full refund - Reason: {{ refundReasonSelect.value }} — map to a Select component with options: 'duplicate', 'fraudulent', 'requested_by_customer' Set this query to Manual trigger. Add event handlers: On Success → trigger an audit log query (insert a row into your database's refunds_log table), then trigger chargesQuery to refresh the charges table, then show a notification 'Refund issued successfully'. Drag the needed components onto the canvas: a Number Input for refund amount (label: 'Refund amount ($ — leave blank for full refund)'), a Select component for refund reason, and a Button labeled 'Issue Refund'. Wire the button to trigger the refund query with Confirm before running enabled. For the audit log query, configure an INSERT statement targeting your database with the agent's name, the charge ID, the refund amount, the reason, and a timestamp. The On Success chain ensures this runs every time a refund is issued.
1-- Audit log INSERT query — runs after every successful refund2-- Configure this as a separate database query triggered on refund success3INSERT INTO refunds_log (4 agent_name,5 charge_id,6 refund_amount_cents,7 refund_reason,8 customer_email,9 created_at10) VALUES (11 {{ retoolContext.currentUser.email }},12 {{ chargesTable.selectedRow.id }},13 {{ refundAmountInput.value14 ? Math.round(refundAmountInput.value * 100)15 : chargesTable.selectedRow.amount_cents }},16 {{ refundReasonSelect.value }},17 {{ customersQuery.selectedRow.email }},18 NOW()19);Pro tip: Add a 'Refund amount' input that defaults to the full charge amount ({{ chargesTable.selectedRow.amount }}) so agents can see the maximum refundable amount before editing. This reduces errors from agents manually typing amounts.
Expected result: Selecting a charge row enables the refund panel. Clicking Issue Refund shows a confirmation dialog. After confirming, the refund is created in Stripe, the charges table refreshes showing the updated refund status, and a success notification appears. The refund is recorded in the internal audit log.
Build a subscription management view
Customer subscriptions are a critical part of Stripe for SaaS businesses. Build a subscriptions panel that lets your team view, modify, and cancel subscriptions for any customer. Create a query with Action Type: List Subscriptions and Customer parameter: {{ customersQuery.selectedRow?.id }}. Set to auto-run when the selected customer changes with an 'only run when a customer is selected' condition. Add a transformer to extract the most operationally relevant fields from each subscription object: plan name, status, billing period, next invoice date, and current amount. For subscription cancellation, create a Cancel Subscription query with Action Type: Cancel Subscription and Subscription ID: {{ subscriptionsTable.selectedRow.id }}. Add a parameter for cancel_at_period_end: true (cancels at the end of the billing period rather than immediately — usually the right choice for customer experience). For plan changes (upgrades/downgrades), create an Update Subscription query. The key parameter is items — an array of objects with the price ID to change to. Create a Select component with your Stripe price IDs loaded from a List Prices query, and reference the selected value in the Update Subscription query's items parameter. Add a subscriptions Table component bound to {{ subscriptionsQuery.data }}. Below it, add a subscription actions panel with: a plan change Select, an Update Plan button wired to the update query, and a Cancel Subscription button wired to the cancel query. Both buttons should have confirmation dialogs with clear descriptions of what will happen.
1// JavaScript transformer for Stripe subscriptions response2const subs = data;3return subs.map(sub => ({4 id: sub.id,5 status: sub.status,6 plan_name: sub.items?.data?.[0]?.price?.nickname7 || sub.items?.data?.[0]?.price?.id8 || 'Unknown Plan',9 amount: '$' + ((sub.items?.data?.[0]?.price?.unit_amount || 0) / 100).toFixed(2)10 + '/' + (sub.items?.data?.[0]?.price?.recurring?.interval || ''),11 current_period_end: sub.current_period_end12 ? new Date(sub.current_period_end * 1000).toLocaleDateString()13 : '',14 cancel_at_period_end: sub.cancel_at_period_end ? 'Cancels at period end' : 'Active',15 trial_end: sub.trial_end16 ? new Date(sub.trial_end * 1000).toLocaleDateString()17 : 'No trial',18 created: new Date(sub.created * 1000).toLocaleDateString()19}));Pro tip: Set cancel_at_period_end: true for most cancellations — this honors the customer's paid period and reduces disputes. If an immediate cancellation is required (fraud, abuse), add a secondary 'Cancel Immediately' button with an even more explicit confirmation dialog.
Expected result: The subscriptions panel shows all active subscriptions for the selected customer. Plan change and cancel actions work correctly and update the subscription status in Stripe. Success notifications confirm each action, and the table refreshes to show the updated state.
Add a Stripe Card Form component for payment collection
Retool includes a dedicated Stripe Card Form component that renders Stripe's official payment element UI inside your Retool app — allowing you to securely collect payment information without handling raw card numbers. This component uses Stripe.js under the hood, ensuring PCI DSS compliance. In the Component panel, search for 'Stripe Card Form' or find it under the Payments section. Drag it onto the canvas. In the component's Inspector: - Stripe publishable key: enter your Stripe publishable key (pk_test_... or pk_live_...). Note: this is the publishable key, not the secret key — it is safe to embed in the frontend. - Payment Intent client secret: this must come from a server-side query that creates a PaymentIntent first Create a query using Action Type: Create Payment Intent. Configure: - Amount: {{ amountInput.value * 100 }} — the charge amount in cents - Currency: 'usd' (or appropriate currency code) - Automatic payment methods: enabled: true Set this query to run when the user enters an amount and clicks 'Prepare Payment'. Wire the Card Form's Payment Intent client secret to {{ createPaymentIntentQuery.data.client_secret }}. Create a 'Confirm Payment' button that calls the Card Form component's built-in confirmPayment() method via a JavaScript query: retoolStoreCardForm.confirmPayment(). Handle the result with On Success and On Failure event handlers to show appropriate feedback. This pattern — creating the PaymentIntent server-side through Retool's proxy, then confirming it client-side via the Card Form — matches Stripe's recommended integration approach and maintains security throughout.
1// JavaScript query to confirm payment via the Stripe Card Form component2// Wire this to a 'Confirm Payment' button's event handler3try {4 const result = await stripeCardForm.confirmPayment({5 return_url: window.location.href // redirect URL after 3DS authentication6 });7 8 if (result.error) {9 // Show error notification to user10 showNotification({11 title: 'Payment failed',12 description: result.error.message,13 notificationType: 'error'14 });15 } else if (result.paymentIntent.status === 'succeeded') {16 // Trigger success flow17 refreshChargesQuery.trigger();18 showNotification({19 title: 'Payment successful',20 description: 'Charge ID: ' + result.paymentIntent.id,21 notificationType: 'success'22 });23 }24} catch (err) {25 console.error('Payment confirmation error:', err);26}Pro tip: Use the Stripe Card Form component only for legitimate internal use cases like manually processing payments for phone orders or adding payment methods to existing customer accounts. For customer-facing payment flows, use Stripe Checkout or Stripe Payment Links instead.
Expected result: The Stripe Card Form renders a secure input panel for card details. Entering valid test card details (4242 4242 4242 4242, any future date, any CVC) and clicking Confirm Payment successfully creates a charge. The Card Form handles PCI compliance automatically.
Common use cases
Build a customer billing support tool
Create a Retool app where support agents search for a customer by email, see their full charge history with amounts, dates, and statuses, and can issue a refund on any charge with a single click and a confirmation dialog. The app also shows the customer's active subscriptions and lets agents cancel or pause a subscription. All actions are logged in an internal audit table in your database.
Build a billing support panel with a customer email search input. When a customer is found, display their Stripe charges in a table with refund buttons. Add a subscriptions panel showing active plans with Cancel and Pause buttons. Log every refund and subscription change to the internal audit_log table with the agent name, timestamp, and action taken.
Copy this prompt to try it in Retool
Build a revenue metrics dashboard for finance teams
Create a Retool dashboard that queries Stripe for the last 90 days of charges and subscriptions, then uses JavaScript transformers to calculate MRR, churn rate, and new customer count by week. Display these metrics as bar and line charts using Retool's Chart component, with date range filters that let finance teams drill into specific periods. A top-level summary row shows current MRR, trial conversions, and active subscriber count.
Create a revenue dashboard with a date range picker. Query Stripe for all charges and subscription events in the selected range. Use a transformer to calculate weekly revenue totals, new vs churned subscribers, and average revenue per user. Display a line chart for weekly revenue trends, a bar chart for new vs churned customers by week, and summary cards for current MRR and total active subscribers.
Copy this prompt to try it in Retool
Build a subscription management panel
Build a Retool panel for managing customer subscriptions at scale — search customers, view their current plan and billing cycle, upgrade or downgrade their subscription tier, apply coupons or discounts, and see upcoming invoice amounts. This is useful for sales and customer success teams who negotiate custom pricing and need to apply plan changes without asking engineering for help every time.
Create a subscription management panel with a customer search. For the selected customer, show their active subscription with plan name, price, billing period, and next invoice date. Add a plan change dropdown to upgrade or downgrade the subscription, a coupon code input field with an Apply button, and a Cancel Subscription button with a confirmation dialog asking for a cancellation reason.
Copy this prompt to try it in Retool
Troubleshooting
Create Refund query returns 'This charge has already been refunded' or 'The charge is already fully refunded'
Cause: The selected charge has been fully refunded previously, or the refund amount entered exceeds the remaining refundable amount on a partially refunded charge.
Solution: Check the charge's amount_refunded field (visible in the transformer output or raw query data) before attempting a refund. Add a conditional to the Refund button that disables it when {{ chargesTable.selectedRow.refunded === 'Yes' }} to prevent this error visually. For partial refunds, calculate the remaining refundable amount as the original amount minus amount_refunded.
List Customers query runs slowly or times out when the Stripe account has thousands of customers
Cause: Stripe's List Customers endpoint returns at most 100 records per request. If your app is trying to load all customers without pagination, it may be making many chained requests through auto-pagination, which is slow for large accounts.
Solution: Limit initial queries to 100 records and implement search-first patterns — require agents to enter at least 3 characters in the email search field before the query runs, preventing unfiltered full-list loads. Use the 'Only run when' condition: {{ customerSearchInput.value.length >= 3 }}. This dramatically reduces response times and keeps the UI responsive.
1// Add this as the 'Only run when' condition on the List Customers query2// Prevents the query from running until the user enters at least 3 characters3customerSearchInput.value.length >= 3Cancel Subscription query succeeds but the subscription still shows as 'active' in the Retool table after refreshing
Cause: When cancel_at_period_end: true is used, Stripe marks the subscription's cancel_at_period_end field as true but keeps the status as 'active' until the billing period ends. The transformer may not be reflecting this distinction.
Solution: Update the subscriptions transformer to check the cancel_at_period_end boolean and display 'Active (cancels on [date])' rather than just 'Active' when it is true. This gives agents accurate visibility into the subscription's actual state.
1// Update the status display in your subscriptions transformer2cancel_at_period_end: sub.cancel_at_period_end3 ? 'Cancels on ' + new Date(sub.current_period_end * 1000).toLocaleDateString()4 : sub.statusBest practices
- Always use restricted API keys with the minimum permissions your dashboard needs — a read-only revenue dashboard should never have refund or subscription write permissions.
- Create separate Retool resources for Stripe test mode (sk_test_...) and live mode (sk_live_...) keys, and use Retool's resource environments to automatically switch between them based on app environment.
- Add confirmation dialogs with contextual messages to all write operations (refunds, cancellations, plan changes) — 'Issue $49.99 refund to john@example.com for charge ch_xxx?' is much clearer than a generic 'Are you sure?'.
- Store Stripe API keys in Retool Configuration Variables marked as secret rather than directly in resource configuration, so key rotation doesn't require editing resource settings.
- Log all write operations (refunds, subscription changes) to an internal audit table in your database with agent name, timestamp, and action details — this is essential for compliance and debugging.
- Use Stripe's test mode with test card numbers (4242 4242 4242 4242) to build and verify all functionality before pointing the resource at your live API key.
- Add 'Only run when' conditions to subscription and charge queries that depend on a customer being selected — this prevents empty-state errors and unnecessary API calls.
- For high-volume Stripe accounts, implement search-first patterns that require a search term before loading data, rather than loading all records on page open.
Alternatives
Use the Stripe Connect integration instead if your business is a marketplace or platform managing payments across multiple connected seller or vendor accounts.
Choose Square if your business processes payments primarily through in-person point-of-sale hardware and you need Retool to interface with Square's POS transaction data.
Choose Plaid if you need to verify bank account details or pull bank transaction history for financial operations rather than managing card-based payment processing.
Frequently asked questions
Is it safe to connect Retool to Stripe with a live API key?
Yes, when done correctly. Always use Stripe's restricted API key feature (Stripe Dashboard → Developers → API keys → Create restricted key) to limit permissions to only what your Retool app actually needs. Retool proxies all API calls server-side, so the key never reaches the browser. Additionally, mark your API key as a secret configuration variable in Retool so it is only accessible to resource configurations, not to frontend JavaScript.
Can I build a Stripe webhook handler in Retool to process real-time payment events?
Yes, using Retool Workflows. Create a Workflow with a Webhook trigger — Retool provides a unique endpoint URL you can register in Stripe's Webhook settings under Developers → Webhooks. The Workflow receives the Stripe event payload in startTrigger.data. However, note that Retool Workflows do not perform Stripe webhook signature verification out of the box — add a JavaScript code block at the start of the workflow to validate the Stripe-Signature header using the webhook signing secret if your use case requires it.
What is the Stripe Card Form component in Retool and when should I use it?
The Stripe Card Form is a Retool component that embeds Stripe's official payment element UI for securely collecting card details within a Retool app. It uses Stripe.js and is PCI DSS compliant because card data is tokenized by Stripe's servers, not your own. Use it for internal tools like manually processing phone orders or adding payment methods to customer accounts. For customer-facing payment flows, use Stripe Checkout or Stripe Payment Links instead.
How do I handle Stripe's pagination to show all customers when the account has more than 100?
Stripe's API uses cursor-based pagination with a has_more boolean and an ending_before / starting_after cursor parameter. In Retool, create a state variable to hold the pagination cursor, add Previous/Next buttons that update the cursor and trigger the query with the appropriate parameter, and display a page indicator showing whether more results are available. For most support use cases, search-first patterns (requiring an email search before loading results) are more practical than full pagination.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation