# Best practices Let’s explore how to leverage commercetools Shopping Lists, particularly Custom Fields, to build advanced functionality like public sharing (for wishlists or gift registries), manage list lifecycles, and implement related features. While commercetools provides flexible base Shopping List functionality, features like publicly sharing a list or building a full gift registry require custom implementation. We'll focus on the core mechanisms using Custom Fields. In a B2B context, Shopping Lists become shared, permissioned procurement tools scoped to a Business Unit. To see how Associate roles govern list access and how lists convert into buyer Carts, see [Shopping Lists for B2B purchasing](/learning-model-b2b-commerce/discover-and-order-products-in-b2b/shopping-lists-for-b2b-purchasing.md) in the Model B2B commerce path. ## Foundation: Understanding Custom Fields and Types Before extending Shopping Lists, it's essential to understand Custom Fields and Types in commercetools. - **Custom Fields**: Allow you to add your project-specific data to standard commercetools resources. - **Types**: Define the structure (schema) of your Custom Fields. A Type specifies the field names, data types (String, Boolean, or Set), labels, and the resources it applies to. - **Resource Association (`resourceTypeIds`)**: When creating a Type, the `resourceTypeIds` property determines which commercetools resources can use the custom fields defined in that Type. To add custom fields directly to a Shopping List, the Type must include `"shopping-list"` in its `resourceTypeIds`. Similarly, `"line-item"` or `"text-line-item"` can be used for fields on items within the list. You must define a Type before applying custom fields. This involves specifying a key, name, the `resourceTypeIds`, and the definitions of the fields you want to add (like the `isPublic` field discussed next). This is typically done via the Composable Commerce API or SDKs. ## Implementing public sharing for Shopping Lists To allow users to share their Shopping Lists via a public URL (like a wishlist or gift registry), you need two main components: a public identifier and an access control mechanism. ### The shareable identifier: `slug` or `key` - Each Shopping List has unique identifiers: - `key`: Developer-defined, unique string. - `slug`: Localized, potentially more user-friendly string often derived from the list name. - Use either the `slug` or `key` in your application's URL structure. - **Recommendation**: Use the `slug` for cleaner, more readable URLs (for example, `yourshop.com/wishlist/john-doe-birthday-wishlist`). - **Example URL Structure**: - `https://yourshop.com/shared-list/{slug}` - `https://yourshop.com/shared-list/{key}` ### Access control: The `isPublic` Custom Field Crucially, you need explicit control over which lists are public. Never assume a list is public by default. - **Mechanism**: Define a **Boolean Custom Field** (for example, named `isPublic`) on the `shopping-list` resource type using a Custom Type (as described in Section 1). - **Purpose**: This field acts as a flag. Only lists where this flag is explicitly set to `true` should be considered publicly accessible via the shareable URL. - **Default State**: If the `isPublic` field is absent or `false`, the list remains private (accessible only to the owner). This secure default prevents accidental exposure. #### Custom Field definition draft example (`Type` definition): This is the structure you'd use when defining the Type to create the `isPublic` field. ```json // Type Draft Snippet for shopping-list-settings Type { "key": "shopping-list-settings", // Choose a meaningful key "name": { "en": "Shopping List Settings" }, "description": { "en": "Custom settings including visibility." }, "resourceTypeIds": ["shopping-list"], // Applies to Shopping Lists "fieldDefinitions": [ { "name": "isPublic", // The custom field name "label": { "en": "Is Publicly Shareable" }, "type": { "name": "Boolean" }, // Data type is Boolean "required": false, // Not required; absence means private "inputHint": "SingleLine" } // Add other settings fields here later if needed ] } ``` ### Setting the `isPublic` field value Once the Type and field are defined, you update a specific Shopping List instance to set its `isPublic` field value (to `true` or `false`). This is done using the `setCustomField` update action via the API, providing the list ID, its current version, the field name (`isPublic`), and the desired boolean value. ### Backend logic: Fetching and displaying the public list Your backend (for example, a Backend-for-Frontend or microservice) needs logic to handle requests for shared lists (for example, `GET /shared-list/{slug}`). - **Approach A: Fetch by Identifier, then Verify `isPublic`** - **Query**: Fetch the Shopping List using only its `slug` or `key`. - **Verify (in your code)**: Check if the list was found and if its `custom.fields.isPublic` is `true`. Handle `404 Not Found` and `403 Forbidden` (or `404` for private lists) appropriately. - Pros: Clear distinction between non-existent and private lists. - Cons: Requires backend logic post-API call. - **Approach B: Query by Identifier AND `isPublic` Status** - **Query**: Fetch using both identifier and `isPublic = true` in the API query predicate. - **Verify**: If the API returns a list, display it. If not, return `404 Not Found`. - Pros: Simpler API interaction; platform does filtering. - Cons: Backend cannot distinguish why no list was returned (wrong slug versus private list). - **Query Optimization**: When combining filters in API query predicates, place highly selective identifiers (`slug`, `key`, `id`) before less selective ones (`custom(fields(isPublic = true))`) for better performance. ### Frontend implementation - Create a dedicated route, such as `/shared-list/:slug`, in your frontend application. - This page calls your backend endpoint (which uses one of the logic approaches above) to fetch and display the list data. ## Expanding sharing: Specific users Beyond simple public sharing, you can share lists with specific individuals. - **Mechanism**: Use a Custom Field of type `Set` of `String` (for example, named `sharedWith`) on the Shopping List Type. Store user identifiers (for example, customer IDs, emails) in this set. - **Implementation Steps**: 1. Define the `sharedWith` field in your Custom Type. 2. Use the `setCustomField` API action to add or remove user identifiers from this set on a specific list. 3. Your application logic must check if the currently logged-in user's identifier is present in the `sharedWith` custom field before granting access. - **Considerations**: Ensure robust security, clear UI for managing shares, consistent identifier choice, and handle optimistic concurrency (using the list `version`) during updates. ## Advanced Custom Field Applications Custom Fields unlock many possibilities beyond basic sharing, including: - **List Metadata**: Store extra info directly on the list: `listType` ("Wishlist", "Grocery"), `occasion` ("Birthday", "Wedding"), `eventDate`, `eventLocation`, `shippingAddress` (for registries). - **Partial Purchase Tracking**: In shared lists (like registries), track who bought what to prevent duplicates. Add a Custom Field (such as `purchasedBy` as a `Set` of `String`) to the Line Item Type (`resourceTypeIds: ["line-item"]`). Your application would update this field when an item is purchased from the shared list. - **Localization**: Use `LocalizedString` types for custom fields needing language-specific values. - **Text Line Item Notes**: Add metadata (for example, `priority`) to text-based items using a Type associated with `text-line-item`. (Applying custom fields to Line Items or Text Line Items follows similar principles: define a Type for that resource, then use specific API actions like `setLineItemCustomField` or `setTextLineItemCustomField`.) ## List lifecycle management You can automatically delete inactive Shopping Lists using the `deleteDaysAfterLastModification` field. - **Mechanism**: Set this field on the Shopping List via the `setDeleteDaysAfterLastModification` API update action. Provide the number of days of inactivity before deletion. Setting it to `null` or omitting it disables auto-deletion. ## Related pages - [Area overview page with navigation](/learning-implement-carts-and-shopping-lists.md) - [Previous page: Update Shopping Lists](/learning-implement-carts-and-shopping-lists/implement-shopping-lists/update-shopping-lists.md) - [Next page: Learning check](/learning-implement-carts-and-shopping-lists/implement-shopping-lists/learning-check.md) - [Search documentation and API specs](/search.md)