Skip to content

Commit 3cd0bd7

Browse files
Added ability to extend SDK plus improved documentation and tutorial (#13913)
1 parent 852364a commit 3cd0bd7

File tree

11 files changed

+1079
-68
lines changed

11 files changed

+1079
-68
lines changed
Lines changed: 153 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,193 @@
11
---
22
title: API
33
description: >-
4-
This page covers adding new Spree API endpoints and customization of
5-
existing ones
4+
Add new Store API endpoints and customize existing API responses
65
---
76

87
Before you start customizing Spree API endpoints, make sure you reviewed all existing API endpoints in the [Spree API docs](/api-reference).
98

10-
## Customizing JSON response
9+
For a step-by-step walkthrough of adding a complete new resource (model, serializer, controller, routes), see the [Store API tutorial](/developer/tutorial/store-api).
1110

12-
Spree uses a library called [JSON API serializers](https://github.com/jsonapi-serializer/jsonapi-serializer) to represent data returned by API endpoints.
11+
## Customizing JSON Responses
1312

14-
You can easily replace existing Spree serializers with your own thanks to [Spree Dependencies](dependencies). Here's a list of all serializers that you can override:
13+
Spree uses [Alba](https://github.com/okuramasafumi/alba) serializers to build API v3 responses. You can replace any serializer via [Spree Dependencies](dependencies).
1514

16-
| Key | Value |
17-
|------------------------------------------|-----------------------------------------------------|
18-
| `storefront_address_serializer` | [Spree::V2::Storefront::AddressSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/address_serializer.rb) |
19-
| `storefront_cart_serializer` | [Spree::V2::Storefront::CartSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/cart_serializer.rb) |
20-
| `storefront_credit_card_serializer` | [Spree::V2::Storefront::CreditCardSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/credit_card_serializer.rb) |
21-
| `storefront_country_serializer` | [Spree::V2::Storefront::CountrySerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/country_serializer.rb) |
22-
| `storefront_user_serializer` | [Spree::V2::Storefront::UserSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/user_serializer.rb) |
23-
| `storefront_shipment_serializer` | [Spree::V2::Storefront::ShipmentSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/shipment_serializer.rb) |
24-
| `storefront_taxon_serializer` | [Spree::V2::Storefront::TaxonSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/taxon_serializer.rb) |
25-
| `storefront_payment_method_serializer` | [Spree::V2::Storefront::PaymentMethodSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/payment_method_serializer.rb) |
26-
| `storefront_payment_serializer` | [Spree::V2::Storefront::PaymentSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/payment_serializer.rb) |
27-
| `storefront_product_serializer` | [Spree::V2::Storefront::ProductSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/product_serializer.rb) |
28-
| `storefront_estimated_shipment_serializer` | [Spree::V2::Storefront::EstimatedShippingRateSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/estimated_shipping_rate_serializer.rb) |
29-
| `storefront_store_serializer` | [Spree::V2::Storefront::StoreSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/store_serializer.rb) |
30-
| `storefront_order_serializer` | [Spree::V2::Storefront::OrderSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/order_serializer.rb) |
31-
| `storefront_variant_serializer` | [Spree::V2::Storefront::VariantSerializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/variant_serializer.rb) |
15+
### Adding Custom Attributes
3216

33-
### Adding custom attributes
17+
Let's say you want to add a `my_custom_attribute` column to the Product API response.
3418

35-
<Note>
36-
As a rule of thumb it's recommended to use [Properties](/developer/core-concepts/products#product-properties) and [OptionTypes/Option Values](/developer/core-concepts/products#option-types-and-option-values) for custom attributes and not to modify the Spree database schema
37-
</Note>
19+
Create a custom serializer that inherits from the core one:
3820

39-
Let's say you want to customize the Storefront API's [Product serializer](https://github.com/spree/spree/blob/main/api/app/serializers/spree/v2/storefront/product_serializer.rb) to include you custom database column `my_newcustom_attribute` that you've added to the `spree_products` database table.
21+
```ruby app/serializers/my_app/product_serializer.rb
22+
module MyApp
23+
class ProductSerializer < Spree::Api::V3::ProductSerializer
24+
attribute :my_custom_attribute
25+
end
26+
end
27+
```
4028

41-
Let's start with creating a new serializer file:
29+
Register it in `config/initializers/spree.rb`:
4230

43-
```bash
44-
mkdir -p app/serializers && touch app/serializers/my_product_serializer.rb
31+
```ruby
32+
Spree::Api::Dependencies.product_serializer = 'MyApp::ProductSerializer'
4533
```
4634

47-
Place the following code in your new serializer file:
35+
Restart the server and the Product API will include your new attribute.
4836

49-
```ruby
50-
class MyProductSerializer < Spree::V2::Storefront::ProductSerializer
51-
attribute :my_new_custom_attribute
37+
### Adding an Association
38+
39+
Let's say you've created a `Spree::Brand` model that belongs to `Product` (see the [tutorial](/developer/tutorial/store-api) for the full example).
40+
41+
Create a serializer for the new model:
42+
43+
```ruby app/serializers/spree/api/v3/brand_serializer.rb
44+
module Spree
45+
module Api
46+
module V3
47+
class BrandSerializer < BaseSerializer
48+
typelize name: :string, slug: [:string, nullable: true]
49+
50+
attributes :name, :slug
51+
end
52+
end
53+
end
5254
end
5355
```
5456

55-
<Info>
56-
This serializer will inherit from the `Spree::V2::Storefront::ProductSerializer` serializer, so you don't need to rewrite the whole serializer.
57-
</Info>
57+
Then subclass the Product serializer to include the brand association:
5858

59-
Now let's tell Spree to use this new serializer, in `config/initializers/spree.rb` please set:
59+
```ruby app/serializers/my_app/product_serializer.rb
60+
module MyApp
61+
class ProductSerializer < Spree::Api::V3::ProductSerializer
62+
typelize brand_id: [:string, nullable: true]
6063

61-
```ruby
62-
Spree::Api::Dependencies.storefront_product_serializer = 'MyProductSerializer'
64+
attribute :brand_id do |product|
65+
product.brand&.prefixed_id
66+
end
67+
68+
one :brand,
69+
resource: Spree::Api::V3::BrandSerializer,
70+
if: proc { expand?('brand') }
71+
end
72+
end
6373
```
6474

65-
Restart the webserver and hit the [Products API](/api-reference/storefront/products/list-all-products) to notice that the payload now includes your new attribute.
75+
Register it in `config/initializers/spree.rb`:
6676

67-
### Adding a new association
77+
```ruby
78+
Spree::Api::Dependencies.product_serializer = 'MyApp::ProductSerializer'
79+
```
6880

69-
Let's say you've created a new model called `Video` that belongs to `Product` (_Product has multiple Videos_).
81+
The brand data is available via `?expand=brand`:
7082

71-
Let's create a new serializer `app/serializers/video_serializer.rb`:
83+
```
84+
GET /api/v3/store/products/prod_86Rf07xd4z?expand=brand
85+
```
7286

73-
```ruby
74-
class VideoSerializer < Spree::Api::V2::BaseSerializer
75-
set_type: :video
76-
77-
attributes :url
87+
### Serializer Dependency Keys
88+
89+
Here are the most commonly customized v3 serializers:
90+
91+
| Key | Default |
92+
|-----|---------|
93+
| `product_serializer` | `Spree::Api::V3::ProductSerializer` |
94+
| `variant_serializer` | `Spree::Api::V3::VariantSerializer` |
95+
| `cart_serializer` | `Spree::Api::V3::CartSerializer` |
96+
| `order_serializer` | `Spree::Api::V3::OrderSerializer` |
97+
| `line_item_serializer` | `Spree::Api::V3::LineItemSerializer` |
98+
| `category_serializer` | `Spree::Api::V3::CategorySerializer` |
99+
| `customer_serializer` | `Spree::Api::V3::CustomerSerializer` |
100+
| `address_serializer` | `Spree::Api::V3::AddressSerializer` |
101+
| `payment_serializer` | `Spree::Api::V3::PaymentSerializer` |
102+
| `fulfillment_serializer` | `Spree::Api::V3::FulfillmentSerializer` |
103+
104+
See `Spree::Api::ApiDependencies` for the full list.
105+
106+
## Adding New Endpoints
107+
108+
### Controller
109+
110+
Inherit from `Spree::Api::V3::Store::ResourceController` for Store API endpoints:
111+
112+
```ruby app/controllers/spree/api/v3/store/brands_controller.rb
113+
module Spree
114+
module Api
115+
module V3
116+
module Store
117+
class BrandsController < ResourceController
118+
protected
119+
120+
def model_class
121+
Spree::Brand
122+
end
123+
124+
def serializer_class
125+
Spree::Api::V3::BrandSerializer
126+
end
127+
128+
def scope
129+
Spree::Brand.all
130+
end
131+
end
132+
end
133+
end
134+
end
78135
end
79136
```
80137

81-
Now in your `app/serializers/my_product_serializer.rb`
138+
The base `ResourceController` provides:
139+
140+
| Feature | How |
141+
|---------|-----|
142+
| Pagination | [Pagy](https://github.com/ddnexus/pagy)`?page=2&limit=25` |
143+
| Filtering | [Ransack](https://github.com/activerecord-hackery/ransack)`?q[name_cont]=nike` |
144+
| Sorting | JSON:API style — `?sort=-name` |
145+
| Authorization | [CanCanCan](https://github.com/CanCanCommunity/cancancan) |
146+
| Prefixed IDs | Stripe-style — `brand_k5nR8xLq` |
147+
148+
Override these methods to customize:
149+
150+
| Method | Purpose |
151+
|--------|---------|
152+
| `model_class` | ActiveRecord model (required) |
153+
| `serializer_class` | Alba serializer (required) |
154+
| `scope` | Base query — add `.where()` to filter |
155+
| `find_resource` | Custom ID lookup (slug fallback, etc.) |
156+
| `permitted_params` | Custom strong params |
157+
| `collection_includes` | Eager loading for `index` |
158+
159+
### Routes
160+
161+
Add routes in your app's `config/routes.rb`:
82162

83163
```ruby
84-
class MyProductSerializer < Spree::V2::Storefront::ProductSerializer
85-
attribute :my_new_custom_attribute
86-
87-
has_many :videos, serializer: :video
164+
Spree::Core::Engine.add_routes do
165+
namespace :api, defaults: { format: 'json' } do
166+
namespace :v3 do
167+
namespace :store do
168+
resources :brands, only: [:index, :show]
169+
end
170+
end
171+
end
88172
end
89173
```
90174

91-
Hitting the Products API you will notice that in the [relationships](https://jsonapi.org/format/#document-resource-object-relationships) key there will be a new key called `videos`
175+
### Permitted Attributes
92176

93-
To include Video response in the Product API add `?includes=videos` in the API URL, eg.
177+
For endpoints that accept writes (create/update), add your attributes:
94178

95-
```http
96-
GET https://localhost:3000/api/v2/storefront/products?include=videos
179+
```ruby config/initializers/spree.rb
180+
Spree::PermittedAttributes.brand_attributes = [:name, :slug, :description, :logo]
97181
```
182+
183+
Or override `permitted_params` in the controller:
184+
185+
```ruby
186+
def permitted_params
187+
params.permit(:name, :slug, :description, :logo)
188+
end
189+
```
190+
191+
## Consuming Custom Endpoints with the SDK
192+
193+
See the [SDK tutorial](/developer/tutorial/sdk) for how to call custom endpoints from TypeScript and extend the generated types.

docs/developer/sdk/extending.mdx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: Custom Endpoints
3+
sidebarTitle: Custom Endpoints
4+
description: Call custom Store API endpoints using the SDK's built-in request method
5+
---
6+
7+
The client exposes a `request` method — the same function that powers all built-in resources. Use it to call any Store API endpoint, including ones you've added yourself.
8+
9+
```typescript
10+
import { createClient } from '@spree/sdk'
11+
import type { PaginatedResponse } from '@spree/sdk'
12+
13+
const client = createClient({
14+
baseUrl: 'https://api.mystore.com',
15+
publishableKey: 'pk_YOUR_KEY',
16+
})
17+
18+
interface Brand {
19+
id: string
20+
name: string
21+
slug: string | null
22+
description: string | null
23+
logo_url: string | null
24+
}
25+
26+
// Paths are relative to /api/v3/store
27+
const brands = await client.request<PaginatedResponse<Brand>>('GET', '/brands')
28+
const nike = await client.request<Brand>('GET', '/brands/nike')
29+
```
30+
31+
`client.request` uses the same auth headers, retry logic, and locale/currency defaults as `client.products.list()` or any other built-in resource.
32+
33+
For a complete walkthrough — creating the API endpoints on the backend, defining TypeScript types, filtering, and expanding associations — see the [Store API](/developer/tutorial/store-api) and [SDK](/developer/tutorial/sdk) tutorials.

docs/developer/tutorial/extending-models.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,10 +389,19 @@ Spree::PermittedAttributes.product_attributes << :brand_id
389389
</Tab>
390390
</Tabs>
391391

392+
## Next Step
393+
394+
Now that Brands are connected to Products, let's expose them through the Store API:
395+
396+
<Card title="6. Store API" icon="code" href="/developer/tutorial/store-api">
397+
Create API endpoints for brands and extend the Product API response
398+
</Card>
399+
392400
## Related Documentation
393401

394402
- [Model Tutorial](/developer/tutorial/model) - Creating the Brand model
395403
- [Admin Tutorial](/developer/tutorial/admin) - Building the admin interface
404+
- [Store API Tutorial](/developer/tutorial/store-api) - Exposing brands through the API
396405
- [Events](/developer/core-concepts/events) - Subscribe to model changes without decorators
397406
- [Webhooks](/developer/core-concepts/webhooks) - HTTP callbacks for external integrations
398407
- [Decorators](/developer/customization/decorators) - Full decorator reference

docs/developer/tutorial/introduction.mdx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
title: Tutorial
33
sidebarTitle: Introduction
4-
description: Learn how to build a custom feature from scratch, covering models, Admin dashboard, API and Storefront.
4+
description: Learn how to build a custom feature from scratch, covering models, Admin dashboard and API.
55
---
66

7-
This tutorial walks you through creating a complete Spree feature from scratch, covering models, Admin dashboard and API.
7+
This tutorial walks you through creating a complete Spree feature from scratch, covering models, Admin dashboard, Store API, and TypeScript SDK integration.
88
You will also learn how to extend core Spree features to connect them with your new feature.
99
By the end, you'll understand how to add new manageable features to the Spree platform.
1010

@@ -14,7 +14,8 @@ To fully implement a new feature, you will typically create the following compon
1414

1515
* Database model
1616
* Admin Dashboard controllers and views
17-
* Storefront views
17+
* Store API endpoints and serializers
18+
* TypeScript SDK integration
1819
* Automated tests
1920

2021
## Tutorial Sections
@@ -35,7 +36,13 @@ To fully implement a new feature, you will typically create the following compon
3536
<Card title="5. Extending Core Models" icon="plug" href="/developer/tutorial/extending-models">
3637
Connect Brands to Products using associations
3738
</Card>
38-
<Card title="7. Testing" icon="flask-vial" href="/developer/tutorial/testing">
39+
<Card title="6. Store API" icon="code" href="/developer/tutorial/store-api">
40+
Expose Brands through the Store API with serializers, controllers, and routes
41+
</Card>
42+
<Card title="7. SDK" icon="js" href="/developer/tutorial/sdk">
43+
Consume Brand endpoints from TypeScript using the SDK
44+
</Card>
45+
<Card title="8. Testing" icon="flask-vial" href="/developer/tutorial/testing">
3946
Write automated tests for your feature
4047
</Card>
4148
</CardGroup>

0 commit comments

Comments
 (0)