A full-featured wishlist/favorites plugin for Vendure e-commerce framework. Let your customers save products for later with persistent storage and a clean GraphQL API.
- ๐๏ธ Customer Wishlists - Let customers save products they want to buy later
- ๐ Authentication - Secure wishlist operations for logged-in users only
- ๐พ Persistent Storage - Wishlists stored in database, survive across sessions
- ๐ Notes Support - Optional notes field for each wishlist item
- ๐ซ Duplicate Prevention - Unique constraint prevents duplicate entries
- ๐ Pagination - Built-in pagination support for large wishlists
- ๐ Admin API - View customer wishlists from admin panel
- ๐จ GraphQL API - Clean, type-safe GraphQL mutations and queries
- ๐ TypeScript - Full TypeScript support with type definitions
- ๐ฅ Zero Config - Just add to plugins array and you're good to go
npm install @dylanmurzello/vendure-plugin-wishlist
# or
yarn add @dylanmurzello/vendure-plugin-wishlist
# or
pnpm add @dylanmurzello/vendure-plugin-wishlistimport { WishlistPlugin } from '@dylanmurzello/vendure-plugin-wishlist';
const config: VendureConfig = {
// ... other config
plugins: [
WishlistPlugin,
// ... other plugins
],
};The plugin will automatically create the wishlist_item table on first run. If you're using migrations:
npm run migration:generate add-wishlist
npm run migration:runThat's it! The plugin is now active and your customers can start building wishlists.
All Shop API operations require authentication and automatically use the active customer.
query {
wishlistItems {
items {
id
productVariant {
id
name
sku
price
product {
name
featuredAsset {
preview
}
}
}
addedAt
notes
}
totalItems
}
}mutation {
addToWishlist(
productVariantId: "123"
notes: "Birthday gift for mom"
) {
id
productVariant {
name
}
addedAt
}
}mutation {
removeFromWishlist(productVariantId: "123")
}query {
isInWishlist(productVariantId: "123")
}query {
wishlistCount
}mutation {
clearWishlist
}Admin operations require ReadCustomer permission.
query {
customerWishlistItems(customerId: "456") {
items {
id
productVariant {
name
sku
}
customer {
emailAddress
firstName
lastName
}
addedAt
notes
}
totalItems
}
}query {
customerWishlistCount(customerId: "456")
}import { useMutation, useQuery } from '@apollo/client';
import gql from 'graphql-tag';
const GET_WISHLIST = gql`
query GetWishlist {
wishlistItems {
items {
id
productVariant {
id
name
product {
name
featuredAsset {
preview
}
}
}
}
totalItems
}
}
`;
const ADD_TO_WISHLIST = gql`
mutation AddToWishlist($productVariantId: ID!, $notes: String) {
addToWishlist(productVariantId: $productVariantId, notes: $notes) {
id
}
}
`;
function WishlistButton({ productVariantId }) {
const { data } = useQuery(GET_WISHLIST);
const [addToWishlist] = useMutation(ADD_TO_WISHLIST, {
refetchQueries: [{ query: GET_WISHLIST }],
});
const isInWishlist = data?.wishlistItems?.items?.some(
item => item.productVariant.id === productVariantId
);
return (
<button
onClick={() => addToWishlist({ variables: { productVariantId } })}
className={isInWishlist ? 'active' : ''}
>
{isInWishlist ? 'โค๏ธ In Wishlist' : '๐ค Add to Wishlist'}
</button>
);
}'use client';
import { useState } from 'react';
export function WishlistButton({ variantId }: { variantId: string }) {
const [inWishlist, setInWishlist] = useState(false);
const toggleWishlist = async () => {
const mutation = inWishlist ? 'removeFromWishlist' : 'addToWishlist';
const response = await fetch('/shop-api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
mutation {
${mutation}(productVariantId: "${variantId}") ${
inWishlist ? '' : '{ id }'
}
}
`,
}),
});
if (response.ok) {
setInWishlist(!inWishlist);
}
};
return (
<button onClick={toggleWishlist}>
{inWishlist ? 'โค๏ธ In Wishlist' : '๐ค Add to Wishlist'}
</button>
);
}The plugin creates a wishlist_item table with the following structure:
| Column | Type | Description |
|---|---|---|
id |
ID | Primary key |
customerId |
ID | Foreign key to customer |
productVariantId |
ID | Foreign key to product variant |
addedAt |
DateTime | Timestamp when item was added |
notes |
String | Optional notes (nullable) |
Unique Constraint: (customerId, productVariantId) - Prevents duplicate entries.
The WishlistService provides the following methods:
class WishlistService {
// Get paginated wishlist items
getWishlistItems(ctx: RequestContext, customerId: ID, options?: ListQueryOptions): Promise<PaginatedList<WishlistItem>>
// Add item to wishlist (updates notes if already exists)
addToWishlist(ctx: RequestContext, customerId: ID, productVariantId: ID, notes?: string): Promise<WishlistItem>
// Remove item from wishlist
removeFromWishlist(ctx: RequestContext, customerId: ID, productVariantId: ID): Promise<boolean>
// Clear all items
clearWishlist(ctx: RequestContext, customerId: ID): Promise<boolean>
// Check if item is in wishlist
isInWishlist(ctx: RequestContext, customerId: ID, productVariantId: ID): Promise<boolean>
// Get total count
getWishlistCount(ctx: RequestContext, customerId: ID): Promise<number>
}You can inject WishlistService into your own plugins or resolvers:
import { WishlistService } from '@gbros/vendure-plugin-wishlist';
@Injectable()
export class MyCustomService {
constructor(private wishlistService: WishlistService) {}
async checkCustomerInterest(ctx: RequestContext, customerId: ID) {
const count = await this.wishlistService.getWishlistCount(ctx, customerId);
return count > 5; // Customer is highly engaged!
}
}Extend the plugin's functionality with your own resolvers:
@Resolver()
export class CustomWishlistResolver {
constructor(private wishlistService: WishlistService) {}
@Query()
@Allow(Permission.Authenticated)
async popularWishlistItems(@Ctx() ctx: RequestContext) {
// Implement your own logic
// Query all wishlist items and aggregate by product
}
}Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built for the Vendure e-commerce framework
- Inspired by real-world e-commerce needs
- Developed with โค๏ธ
- ๐ Report a bug
- ๐ก Request a feature
- ๐ฌ Ask a question
Made with ๐ฅ by Dylan Murzello | Supporting your "I'll buy it later" customers since 2025