Programmable Stripe: Custom objects, custom apps, zero infrastructure
Advancing developer craft
Tiempo de ejecución
Completa el formulario para ver el video
Your business has its own language—vehicles, not products; rentals, not payments. Until now, bridging that gap meant metadata workarounds and external databases. Not anymore. In this session, we build a full-stack application for a car rental company entirely on Stripe. See how custom objects let you define typed, validated data models with first-class APIs, how full-page Stripe Apps turn the Dashboard into an ops console tailored to your team, and how actions let you deploy business logic that runs on Stripe’s infrastructure.
Speakers
Mike North, Technical Lead, Developer Platform, Stripe
Brooks Swinnerton, Head of Engineering, Extensibility Platform, Stripe
BROOKS SWINNERTON: Hi everyone. Welcome to day two of Stripe Sessions. This breakout session, we’re going to be covering how to extend Stripe to look and feel like your business. My name is Brooks. I lead engineering for a group at Stripe known as extensibility platform. I’m joined by Mike North here, who is the tech lead for Stripe’s developer platform. But today, at least for the sake of this talk, we’re going to do a little bit of role playing. Instead of working at Stripe, Mike and I are going to be the proprietors of a wildly successful car rental company that’s totally not fictitious at all, known as Rocket Rides. Over the course of this talk, we are going to cover a few new products and a few new features that launched at Stripe Sessions this week. And we’re going to show you how we have built a full stack Stripe-hosted application for the exact needs of Rocket Rides.
I’ll start by giving you a quick introduction into the business of car rentals. There’s really only three core concepts that you have to worry about: drivers, vehicles, and rentals. Now, when we want to integrate our business with Stripe, the first thing that we have to do is sit down and build a mental mapping between the core concepts of our business and the common core of every other business and Stripe. We familiarize ourselves with these abstractions by reading documentation, watching videos, or increasingly these days, vibe coding our way through a solution. And the result of our work is a sort of mental Rosetta Stone. It’s a translation between the language of our business and the common core of every other running on Stripe.
For example, at Rocket Rides, we refer to the folks that are renting these cars as drivers. So maybe our mental model should be that we map the concept of a driver to which Stripe calls a customer. And I guess if you squint, maybe you could map the concept of a vehicle to what Stripe calls a product and a rental, which is ultimately how we generate revenue to a payment.
But we’re not alone in this exercise. There are literally hundreds of thousands of merchants all going through this exact same exercise of building a mental mapping between their business and Stripe. And unsurprisingly, there isn’t always a perfect fit. And that’s because your business is unique. And so to solve this problem, we have to extend Stripe beyond what comes out of the box.
Let’s start by taking a look at a mock-up that our designer put together here at Rocket Rides that’s leveraging some of the new features that were launched this week at Sessions. So what we’re looking at here is a custom overview page of Rocket Rides right in the Stripe Dashboard. On this page, our entire team can see important top-line metrics like how many vehicles in the fleet are currently available to be rented. There’s charts that our finance team uses to see revenue per vehicle. They find this super helpful in forecasting the next set of cars that they’re going to purchase. Our team at the front desk can quickly access upcoming rentals as well as take actions like mark the rental as returned when they get the keys back from the driver.
Now, all of the functionality that’s in these mock-ups can be broken up into three different parts of Stripe’s extensibility platform: data, user interfaces, and business logic. Data represents the language of our business. In our case, it’s the nouns. It’s a driver, a vehicle, maybe one day in the future, a damage report. UI are the set of tools that we use to visualize this information in the Stripe Dashboard. And finally, logic are the daily actions that we take and the things that make our business unique.
These three core concepts work very well together when you package them in a Stripe app. And these apps can be installed into your account and made private for any employee, in our case, at Rocket Rides, to see. So let’s take a much deeper dive into each of these sections and we’ll start with data.
This week, Stripe announced the private preview of custom objects. Custom objects put you behind the steering wheel in designing the core abstractions of your business. They have first-class APIs and SDKs in every programming language that Stripe has a programming language for. Before today, the core objects of Rocket Rides were a little bit tricky to model and store within Stripe. But actually, drivers are close enough to something that Stripe already has something for. They’re really just customers with some driver-specific metadata. That said, there’s no perfect equivalent for the concept of a vehicle and a rental, and that’s exactly the problem that custom objects set out to solve.
Vehicles have attributes like a make and a model, the year that it was manufactured, and a VIN or vehicle identification number that has to be exactly 17 characters long. The second core object of Rocket Rides is a rental. Now, not only is this the primary thing that we sell, but it also represents the connection or edge to a vehicle and to a driver. On it, we store information like when the rental begins, as well as a status. And the status indicates if it is awaiting inspection for damages or if it’s been finalized and ready to be invoiced to our customers. We define custom objects using an object definition. You can think of these as a schema or blueprint that every custom object that you create will conform to. It’s a TypeScript file that includes the object’s fields, their types, validations, and then those relationships to other custom objects.
So there’s a lot of code on the screen. Let’s break this down section by section. We specify the fields of our custom object using an interface. And this interface not only defines the name of the attributes, but also their types. This is TypeScript after all. And so we can use built-in primitives like a string, a number, a Boolean, or what we see on the screen here, a date or an enum to reflect the different states of that status. There’s also a standard library provided by Stripe that has even more types like a monetary amount, a positive integer, or a decimal. Now, by authoring your custom objects with a powerful type system like this, you can ensure that you have consistency and correctness anytime that a custom object is added or updated. You’ll notice that there’s another built-in type known as a ref, and references allow you to define relationships to other objects. And these relationships don’t just have to be to other custom objects, but they also can be to core Stripe objects as well, like a customer, a price, or a payment.
Custom object definitions also support validations that can go beyond what you can articulate in the type system, in this case, maximum value using comment tags. So we’ve defined the shape and the schema of our object definition, but there’s one key thing that I haven’t talked about yet. It’s that custom object decorator at the bottom. This decorator is an incredibly powerful concept. When you annotate your TypeScript class with this, it tells Stripe to automatically provision a set of APIs that gives us the ability to create, read, update, and destroy instances of these custom objects. These are known as CRUD APIs, and they live in Stripe literally at api.stripe.com, which means that you can interact with them just like you would any other Stripe API resource.
Okay. So we have a rough understanding of what an object definition is and how they work. Mike, as you know, we have an old legacy application that we use to run the operations of Rocket Rides. I feel like a better place might actually now be on Stripe. How would we go about doing that?
MIKE NORTH: We’re going to build a Stripe app just for Rocket Rides and deploy it to our own account. So the first thing we’re going to do is generate a new custom object definition. We’re going to reach for the Stripe CLI and run a new command called stripe generate custom-object, and then we’re going to name our custom object vehicle. What’s happening behind the scenes is we’re getting some scaffolding, some files are created for us. And this YAML file we’re looking at here, our Stripe app manifest, is already set up so it knows where this object definition is. Let’s click on this and see what was generated for us. So we can see we have this object.ts file, and alongside it, we have a set of tests that are ready to go if we wanted to add tests to this.
Let’s look a little closer at what’s going on here. Our vehicle class extends from base object. This is what gives the vehicle object everything Stripe expects to see from any API resource. This includes a unique ID and a creation timestamp. Here’s that custom object decorator that Brooks was mentioning, and this is what tells Stripe to provision these read and write API endpoints. And you can see that we get some webhook events set up for us as well, so we can listen for when new vehicles are created, when they’re changed, and when they’re removed.
Now, if we look at this vehicle field’s interface that’s passed into this space object type, this is where we’re going to define what a vehicle is in Rocket Rides, like what makes a vehicle a vehicle. And we got a name field as part of the generated code, but let’s change this to make it a bit more interesting. Every vehicle has make, a model, and a year. A year probably shouldn’t be a string. We can make it a positive integer, and that’s going to come from our standard library of types here that’s included in the extensibility SDK. Every vehicle has a VIN number, which is a string, and we can give that a display name. So display name. So everybody understands what a VIN number is. Vehicle identification number. A little spelling here there. Brooks mentioned 17 characters, right? That’s how long this needs to be. So we can say min length, 17, max length, 17. And there we go. Just a couple more fields here. We can say every vehicle has a license plate and that’s a string, and an odometer reading, which is another positive integer. P-O-S-I. There you go. So great. We’re done with our vehicle object definition. Let’s generate one more for rental.
So we’re reaching for the Stripe CLI again, and we’re running stripe generate custom-object rental. This is doing the same thing behind the scenes. There’s our automatic change to the app manifest. We’ll just click in. And like any good cooking show, I’ve got something in the oven that’s already baked, so we’ll say Z-R-E-N. There we go.
So the interesting thing about this that we didn’t see on vehicle is relationships. We’ve got a field on rental called vehicle, and this refers to the vehicle type that we were just working in. We’ve also got a field here called driver. Now, to Rocket Rides, we say driver, in Stripe’s world, that’s a customer. And so we’re free to name that field whatever we want, but this is a v1 customer that you’ve been able to find in our API for years and in the Dashboard as customers. We’ve got start and end dates for the duration of the rental and a pickup and drop off location and then a status field so we can understand what state this rental is in. So just making sure everything’s saved.
Now we need to tell Stripe to provision these new APIs for us. So we’re going to upload our Stripe app again using the Stripe CLI, and that’s stripe apps upload. What’s happening behind the scenes is we’re analyzing these TypeScript files and creating an open API spec, sending that over to Stripe so it knows exactly the shapes of vehicle and rental. It knows which APIs to set up and we can play with those APIs right now. So we’re going to use stripe get, which you can think of as kind of like using curl to hit an API endpoint, but this sets all those headers for you that you need, takes care of auth. So we’re going to hit /v2/extend/ objects/vehicles.
Now, if you’ve used list endpoints at Stripe before, this is the kind of response that comes back. You get a data field that’s an array, and that’s where we would see vehicles, but we just stood this API up, so there’s nothing here for us yet. Let’s create a vehicle. Now here, we’re using the Stripe CLI again. We’re making a post request this time, and here’s the JSON that we’re passing along. Let’s try this. And it turns out our VIN number was too short, and we’re getting a nice actionable error back, so we can correct that.
That looks like a successful create to me, and just for fun, we’ll read that list again. And there’s our, hopefully, yeah, there’s our data array that contains our DeLorean. So we started with a mostly empty app. We created our own vehicle and rental object definitions. We defined our own well-typed fields. We got clear errors back when the data was wrong, and then we used the Stripe CLI to deploy our Stripe app, which included the object definitions to Stripe so that Stripe created these new API endpoints for us. And this is a huge part of the data pillar of Stripe’s new extensibility features.
BROOKS SWINNERTON: Amazing. Thank you, Mike. So custom objects... It’s very cool. So custom objects let you extend Stripe with the core objects of your business. When you do that, you benefit from the broader Stripe developer experience that Mike just showed us.
Let’s take a look at the next part of the extensibility story, user interfaces. This week, Stripe released a new set of features for UI extensions. UI extensions are the way that you can build your own user interfaces that live directly in the Stripe Dashboard. They’re built with a flexible design system of components like forms, buttons, dropdowns, and icons. And luckily for me, they have all been vetted and built by Stripe’s design team. Now, when you want to build a UI extension, you start by identifying their viewports. These are the sections of the Stripe Dashboard where you want your UI to appear. So say for example, you want to see a side-by-side view of a customer and some contextual information from your business about them. To do this, you would build a UI extension that plugs into the customer detail viewport. This will show up in a sidebar anytime that you’re looking at a customer. Now, UI extensions are not a new concept. They’ve actually been around since 2021 since Stripe released them with the initial launch of Stripe Apps, but this week they’re becoming even more powerful with even more flexibility and real estate within the Stripe Dashboard. Stripe also released a new layout for overview pages and several new UI components that make it easier to visualize things like charts and tables. There’s also support for tabs where you can navigate directly from the URL. Now, UI extensions are a powerful primitive entirely on their own, but the real magic happens when you pair them with custom objects.
All right, so we have this beautiful mock-up from our designer. We have created our custom object definitions. Mike, what’s it going to take to wire all of this up?
MIKE NORTH: About three Red Bulls, half a pizza, and 150,000 AI tokens. So yeah, let’s start with a Stripe app that was built by our Claude design agent. So we use some of the agent skills that were included with the extensibility SDK and use some of these beautiful new components for data visualization. We have a little data view over here. We can click on it for more details, really taking advantage of that full-page viewport here. We also have a vehicle and a rental tab, but it looks to me like we have a lot of placeholder data here. Claude gave everything the same ID. That doesn’t seem right. So what we need to do is find a way to bring our new custom object APIs into this UI extension so it’s powered by our vehicle and rental APIs that we just stood up.
So let’s go back to our Stripe app and we’re back in the manifest here. And this is the section of our Stripe app that deals with UI extensions. We can see here we are placing our own component that’s called “Full Page” in this full-page viewport. So let’s drill into this full-page component. And here we can see we have a set of tabs. Here’s our overview tab, here’s a body of the overview tab, and here’s our vehicles tab and our rentals tab. So I happen to know that this component here and this component here, those are this lower area that’s visible when you click the tab.
So let’s see if we can find some hard-coded data here. We’re going to click ListVehicles. There we go. This looks like the culprit. Let’s delete this. And now we need a way to pull this data into our UI extension. And to do this, we’re going to create a generated SDK that gives us a well-typed way to engage with those APIs. Just like you can use the Stripe SDK to call into Stripe’s APIs, we’re going to get the same thing, but for our vehicle and rental types. To do that, we’re going to run stripe generate custom-objects-sdk. And we have to ask for this in a particular programming language. You could generate this in a bunch of the languages we support, but we’re going to say, “Give me a node SDK.”
Now, this is a good time to mention that Stripe Apps are now TypeScript monorepos. They contain a bunch of independent packages. So your UI extensions are one package, your custom objects are another package. And because we’ve asked for a node SDK here, we’re being invited to incorporate this into our Stripe app as another package. We could just as easily take this to a backend node integration and use it there. But in this case, we want this in our UI, so we’re going to say yes, incorporate it into my app.
And next we’re going to be asked, are there any packages within the app that we want to bring this SDK into? So we’ll say, yes, I want that in my UI extensions. Great. So all the dependencies should be wired up here. Now, we probably want some test data to make sure that we can see something interesting in our UI. So I have a little seed script here and behind the scenes, all this is doing is running the same kinds of Stripe CLI commands that we were running earlier, Stripe post and then passing some JSON to it. We’re just creating a bunch of customers, rentals, and vehicles, so we have something more interesting to look at in the UI.
Now, going back to our React component here, it’s time for us to make a call to this SDK. So we’ll say customSdk.v2.extend.objects and look at that. vehicles and rentals pop up just similar to how you’d see a list of Stripe resource types here if you were using our regular Node SDK. So we’ll say vehicles and we want a list of those. And this kicks off the list API call. And when it comes back, just as we saw before in that list response, there’s that data field with the array of vehicles that’ll be in there. So let’s reach in and grab data and then we’ll just say setVehicles(data). And if we look back at our app, we see instantly this looks a lot more interesting. We don’t have the same ID used for everything. We have a bunch of real makes and models. Now, part of why I didn’t have to deploy here is I’m running stripe apps start. It’s another Stripe CLI command where our local version of this app is embedded in the Stripe Dashboard. So you get that nice iterative development experience there.
So the rentals, sorry, vehicles look great. Let’s look at rentals. This is still hard coded. So we’ll just make a very similar change to the rentals list, replace that with this, just auto-import this SDK, change vehicles to rentals, and setRentals instead of setVehicles, and I’ll hit save and swipe over. And there we go. That looks a lot better. We have customer IDs, we have these vehicle IDs, which begin with object record.
So there we are. So we went from hard-coded arrays to live data. We combined our new custom object APIs with these great, beautiful charting components, all of it within this new full-page viewport that Stripe apps can now take advantage of. And now it feels like Rocket Rides has its own first class experience right in the Stripe Dashboard.
BROOKS SWINNERTON: So UI extensions and the new components that Stripe released this week give you more power and flexibility to build the visual expression of your business directly in the Stripe Dashboard. So we’ve covered the data and UI pillars of Stripe’s extensibility platform. The last thing to cover is business logic. Custom objects aren’t just about modeling data. You can of course model behaviors as well with custom object actions. These actions give you the ability to express your business logic and code and then deploy that code into Stripe. Now, as we saw earlier, you can define your data model using interfaces and types, but let’s zoom in on that class definition at the bottom.
Just like any class in TypeScript, you can define functions that encapsulate business logic. In and of itself, this isn’t that interesting or exciting, and that’s honestly the whole point. These are simple functions, but the real power comes from an additional decorator known as action. By annotating your function with the action decorator, Stripe provisions one more API endpoint in addition to the CRUD APIs. And when you call it, the code in this function will be invoked. Now, as we’ve seen throughout the course of this talk, the power of these features is amplified when you use them together. Mike, I have some bad news. I was chatting with our finance team and we’re taking some pretty big losses every time that a car comes back damaged. They are threatening to take away your second Claude code subscription if we can’t fix this.
MIKE NORTH: Whoa, whoa. I appreciate artisanally handcrafted TypeScript code as much as the next engineer, but we probably should fix this. So here’s the app that our employees in the rental return area use to assess damage on a car before we check it in. We got to figure out if there are repairs that are needed. So we can click on a car and then we can say, “the right mirror has a scratch,” and we can hit save, but it looks like some things aren’t working here yet. This to me looks like we’re trying to create a damage report custom object or create a record of the damage report custom object, and this API doesn’t exist. We’re getting a 404. And if we were to go around and assess more damage on the car and then say, “All right, that’s the extent of it. Let’s say that this is done.” We get another error message and we can see that there’s a finalized action that we’re attempting to call here and we’re passing it a description of damage on the car. This doesn’t exist yet either. So let’s fix this.
We’ll go back to our Stripe app and we’re going to run stripe generate custom-object damage_report. And same thing as before, new scaffolding will happen, everything’s wired up in the app manifest, and I have a damage report, custom object ready. So there it is. And then I have to copy a file over that we got from our finance team, put that in custom objects, source. Great. So what our finance team gave us was a fee calculation algorithm. So what you pass into it is a description of what’s wrong with the car. It gives us some monetary amount back. And if we look at the fields on a damage report, we have a name, we have a state that the damage report can be in its either draft or finalized. We have a total fee that represents the damage on the car. Every damage report belongs to a rental and is associated with the driver.
Now, if we end up charging a driver for repairs, we end up creating an invoice line item. So we have a strong connection between this very rental-specific damage report concept and a built-in Stripe object that represents something that’s associated with a payment. Now, the most interesting thing here, of course, is the action. And if we hover over this, we can see this is the API endpoint that Stripe will provision for us when we upload our next version of this app. So think of this as the argument passed to this TypeScript function, that’s the request shape and whatever the return is from this function, that’s the response shape.
Now, let’s walk through what’s happening here. First, we calculate the damage fee. We store it on the damage report. Then we’re going to use the Stripe Node SDK to reach for the Stripe customer’s API. We’re going to go and grab that customer that to us is a driver. Now, we get a lot of business from corporate accounts. So if we find that this driver belongs to a corporate account, we give them 30% off. We go ahead and we create the invoice line item. We associate the line item with the damage report, set the status to finalized, and then down here we save. And what this will do is take all of those changes that we made to those fields and persist them using the custom object APIs.
So let’s go ahead and upload this version of the app and make sure I save. And we’re going to go stripe apps upload. And this will take care of creating a new version of an open API spec, provisioning that last endpoint. We’re going to hit refresh over here just to make sure everything comes in. And if we go and click on a car here, we can start to assess damage like there’s a scratch over here and then something happened with the experimental power source. There was an electrical overload of some kind, so we’re going to have to cover that. And then we can say we’re done and no more API errors. And if we look at the assessed vehicle list, there is the fee that came from our finance team’s function and that now we have the business rule in place. So there it is, fee calculation, business rules, orchestrating over Stripe APIs like customers and invoices, all in a fully custom API that’s built for and shaped just to meet Rocket Rides’ needs. And it’s all running on Stripe.
BROOKS SWINNERTON: Mike gets to keep his second Claude code subscription after all. So we started this talk by describing the first step of building a Stripe integration is etching the Rosetta Stone to translate between the language of your business and Stripe. With the data, UI, and logic features that we talked about today, you now have the ability to natively model your business on Stripe. With custom objects, you can extend Stripe with your own data model. You can articulate the language of your business in a fully featured programming language that you can run on Stripe. You can use those dynamically generated SDKs in seven different programming languages to interact with those objects, both in a Stripe app, but of course in your own codebase as well. And finally, with custom object actions, you can write and deploy code into Stripe that’s invocable via an API. With UI extensions, you can build a custom UI directly within the Stripe Dashboard to optimize for your day-to-day workflows. There’s a new full-page viewport that gives you the flexibility to tailor the Stripe Dashboard in exactly the way that you would like. And with our new data visualization components, you can build an overview page to see the health of your business at a glance.
The extensibility features that we covered today reflect a core investment that Stripe is making in building a flexible and programmable platform. Whether it’s a car rental business, an insurance company, or something completely different, you can now natively model your business on Stripe. If you’re interested in learning more or signing up for these previews, please sign up at docs.stripe.com/extensibility or use that QR code there. Thank you all so much.