docs/developer/core-concepts/customers.mdx
Customers interact with your store through the Store API. They can register, log in, manage their profile, and view order history.
erDiagram
Customer ||--o{ Order : "places"
Customer ||--o{ Address : "has many"
Customer ||--o{ Wishlist : "has many"
Customer ||--o{ StoreCredit : "has many"
Customer ||--o{ PaymentSource : "has many"
Customer {
string id
string email
string first_name
string last_name
}
Order {
string number
string state
}
Address {
string firstname
string lastname
string address1
string city
}
Wishlist {
string name
boolean is_default
}
const { token, user } = await client.customers.create({
email: '[email protected]',
password: 'password123',
password_confirmation: 'password123',
first_name: 'John',
last_name: 'Doe',
})
// token => JWT token for subsequent authenticated requests
// user => { id: "usr_xxx", email: "[email protected]", first_name: "John", ... }
curl -X POST 'https://api.mystore.com/api/v3/store/customers' \
-H 'X-Spree-Api-Key: pk_xxx' \
-H 'Content-Type: application/json' \
-d '{
"email": "[email protected]",
"password": "password123",
"password_confirmation": "password123",
"first_name": "John",
"last_name": "Doe"
}'
const { token, user } = await client.auth.login({
email: '[email protected]',
password: 'password123',
})
// Use the token for authenticated requests
curl -X POST 'https://api.mystore.com/api/v3/store/auth/login' \
-H 'Authorization: Bearer pk_xxx' \
-H 'Content-Type: application/json' \
-d '{
"email": "[email protected]",
"password": "password123"
}'
The response includes a JWT token and a user object. Pass the token in subsequent requests via the Authorization: Bearer <token> header.
Refresh an expiring token to keep the session alive:
<CodeGroup>const { token } = await client.auth.refresh({
token: existingToken,
})
curl -X POST 'https://api.mystore.com/api/v3/store/auth/refresh' \
-H 'Authorization: Bearer <jwt_token>'
Password reset is a two-step flow. First, request a reset email. Then, use the token from the email to set a new password.
await client.customer.passwordResets.create({
email: '[email protected]',
redirect_url: 'https://myshop.com/reset-password',
})
// Always returns { message: "..." } — even if the email doesn't exist
// This prevents email enumeration
curl -X POST 'https://api.mystore.com/api/v3/store/customer/password_resets' \
-H 'X-Spree-Api-Key: pk_xxx' \
-H 'Content-Type: application/json' \
-d '{
"email": "[email protected]",
"redirect_url": "https://myshop.com/reset-password"
}'
The optional redirect_url parameter specifies where the password reset link in the email should point to. The token will be appended as a query parameter (e.g., https://myshop.com/reset-password?token=...). If the store has Allowed Origins configured, the redirect_url must match one of them.
This fires a customer.password_reset_requested event with the reset token in the payload. If you're using the spree_emails package, the email is sent automatically. Otherwise, subscribe to this event to send the reset email yourself (see Events).
const { token, user } = await client.customer.passwordResets.update(
'reset-token-from-email',
{
password: 'newsecurepassword',
password_confirmation: 'newsecurepassword',
}
)
// Returns JWT token + user (auto-login)
curl -X PATCH 'https://api.mystore.com/api/v3/store/customer/password_resets/RESET_TOKEN' \
-H 'X-Spree-Api-Key: pk_xxx' \
-H 'Content-Type: application/json' \
-d '{
"password": "newsecurepassword",
"password_confirmation": "newsecurepassword"
}'
On success, the user is automatically logged in and a JWT token is returned. The reset token expires after 15 minutes (configurable via Spree::Config.customer_password_reset_expires_in) and is single-use (changing the password invalidates it).
// Get current customer
const customer = await client.customer.get()
// {
// id: "usr_xxx",
// email: "[email protected]",
// first_name: "John",
// last_name: "Doe",
// default_shipping_address: { ... },
// default_billing_address: { ... },
// addresses: [{ ... }, { ... }],
// }
// Update profile
const updated = await client.customer.update({
first_name: 'Jonathan',
accepts_email_marketing: true,
})
# Get current customer
curl 'https://api.mystore.com/api/v3/store/customer' \
-H 'Authorization: Bearer <jwt_token>'
# Update profile
curl -X PATCH 'https://api.mystore.com/api/v3/store/customer' \
-H 'Authorization: Bearer <jwt_token>' \
-H 'Content-Type: application/json' \
-d '{ "first_name": "Jonathan", "accepts_email_marketing": true }'
Authenticated customers have access to these resources:
| Resource | Description |
|---|---|
| Addresses | Billing and shipping addresses with default selection |
| Orders | Past order history |
| Credit Cards | Saved credit cards for checkout |
| Payment Sources | Other saved payment methods (PayPal, Klarna, etc.) |
| Store Credits | Balance assigned by the store, usable at checkout |
| Gift Cards | Gift cards owned by or assigned to the customer |
| Wishlists | Saved product lists |
Customers don't need to register to purchase. Guest checkout uses an order token (X-Spree-Token) to identify the cart. See Orders — Cart for details.