Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 0
Personalized products#39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| { | ||
| "title": "Guides", | ||
| "pages": ["custom-page-templates", "custom-product-templates", "product-variants"] | ||
| "pages": ["custom-page-templates", "custom-product-templates", "product-variants", "personalized-products"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| --- | ||
| title: Personalized Products Guide | ||
| sidebar_label: Personalized Products | ||
| tags: | ||
| - Guide | ||
| --- | ||
| import { Callout } from 'fumadocs-ui/components/callout'; | ||
| import { Step, Steps } from 'fumadocs-ui/components/steps'; | ||
| import { Cards, Card } from 'fumadocs-ui/components/card'; | ||
| Some products need customer input at the time of purchase — an engraving on a mug, a monogram on a bag, a gift message on a card. This information is captured as **line item properties**: name and value pairs attached to a cart line rather than to the product itself. No variant, SKU, or inventory is required for each possible value. | ||
| <Steps> | ||
| <Step> | ||
| ### Add Property Inputs to the Product Template | ||
| Property inputs are named `properties[<name>]`, where `<name>` is the label stored with the line. Add them inside the [add-to-cart form](/docs/storefront/themes/templates/tags#cart_form) alongside the fields generated by the `cart_form` tag. | ||
| The engraving label and input are the only addition to the add-to-cart form. | ||
| ```django title="templates/catalogue/product.html" | ||
| {% purchase_info_for_product request product as session %} | ||
| {% if session.availability.is_available_to_buy %} | ||
| {% cart_form request product 'single' as cart_form %} | ||
| <form id="add-to-cart" action="{% url 'cart:add' pk=product.pk %}" method="post"> | ||
| {% csrf_token %} | ||
| {% include "partials/form_fields.html" with form=cart_form %} | ||
| <label for="engraving">Engraving</label> | ||
| <input type="text" id="engraving" name="properties[Engraving]"> | ||
| <button type="submit">{% t "store.catalogue.add_to_cart" %}</button> | ||
| </form> | ||
| {% else %} | ||
| {% t "store.catalogue.out_of_stock" %} | ||
| {% endif %} | ||
| ``` | ||
| Add one input per property. A mug with an engraving and a font choice uses `properties[Engraving]` and `properties[Font]`. | ||
| </Step> | ||
| <Step> | ||
| ### Display Properties in the Cart | ||
| The cart template receives a `formset` of cart line forms. Each `form.instance` is a [line](/docs/storefront/themes/templates/objects#line) with a `properties` list of `key` and `value` pairs. | ||
| ```django title="partials/cart_line_properties.html" | ||
| {% for property in properties %} | ||
| {% if property.value %} | ||
| <div>{{ property.key }}: {{ property.value }}</div> | ||
| {% endif %} | ||
| {% endfor %} | ||
| ``` | ||
| ```django title="templates/cart.html" | ||
| {% for form in formset %} | ||
| {% with line=form.instance %} | ||
| {% include "partials/cart_line_properties.html" with properties=line.properties %} | ||
| {% endwith %} | ||
| {% endfor %} | ||
| ``` | ||
| <Callout type="idea"> | ||
| Themes that ship their own side cart JavaScript need to render properties there too. Request `properties { key value }` on the cart lines in your side cart query and output them alongside the product title. | ||
| </Callout> | ||
| </Step> | ||
| </Steps> | ||
| ### Behavior to Expect | ||
| | Behavior | Detail | | ||
| | ----- | ------ | | ||
| | Unique lines | The same product added with different property values creates a separate cart line for each combination. Adding it again with identical values increases the quantity of the existing line. | | ||
| | Empty values | A property submitted with an empty value does not create a separate line. It is still recorded on the line, so guard your display with `{% if property.value %}`. | | ||
| | Hidden properties | Property names starting with an underscore, such as `properties[_source]`, are stored but excluded from `line.properties` and from the Storefront GraphQL API. Use them for data that should not be shown to customers. | | ||
| | Value length | Values longer than 500 characters are truncated to 500 characters. | | ||
| | Checkboxes | When a checkbox named `properties[...]` changes, the value submitted is `yes` or `no`. | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: The "Checkboxes" row promises that a checkbox named Reply with | ||
| | File uploads | File inputs are not supported. Inputs with `type="file"` are ignored. | | ||
| | Set once | Properties are captured when the line is added to the cart. There is no update path for changing them afterwards. | | ||
| ### Add Properties with the Storefront GraphQL API | ||
| Themes that add to the cart through the [Storefront GraphQL API](/docs/storefront/graphql) pass properties on each line as a JSON object of name and value pairs. The field is available on [createCart](/docs/storefront/graphql/mutations/create-cart) and [addCartLines](/docs/storefront/graphql/mutations/add-cart-lines), and is returned on cart lines as `properties { key value }`. | ||
| ```json title="addCartLines Variables" | ||
| { | ||
| "input": { | ||
| "cartId": "<cart-id>", | ||
| "lines": [ | ||
| { | ||
| "productPk": 1, | ||
| "quantity": 1, | ||
| "properties": { "Engraving": "Alex" } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
| <Callout type="warn"> | ||
| `properties` must be a JSON object. Any other value returns the error `Properties must be a JSON object.` | ||
| </Callout> | ||
| ### Related | ||
| <Cards> | ||
| <Card | ||
| title="line Object" | ||
| description="Cart line object and the properties list available in templates." | ||
| href="/docs/storefront/themes/templates/objects#line" | ||
| /> | ||
| <Card | ||
| title="cart_form Tag" | ||
| description="Generate the add-to-cart form for a product template." | ||
| href="/docs/storefront/themes/templates/tags#cart_form" | ||
| /> | ||
| <Card | ||
| title="Product Variants Guide" | ||
| description="Map variant attribute choices to product IDs when inventory is tracked per option." | ||
| href="/docs/storefront/themes/guides/product-variants" | ||
| /> | ||
| <Card | ||
| title="Storefront GraphQL API" | ||
| description="Add lines to the cart from JavaScript with the createCart and addCartLines mutations." | ||
| href="/docs/storefront/graphql" | ||
| /> | ||
| </Cards> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -758,13 +758,40 @@ There are three filter types: `price_range`, `boolean`, and `list`. Each type ha | ||
| | `label` | String | Display label for this value. | | ||
| ### line | ||
| Cart line object available on the cart page (`templates/cart.html`) through the `formset` context. Each form in the formset exposes its cart line as `form.instance`. | ||
| ```django title="Example Cart Line Properties" | ||
| {% for form in formset %} | ||
| {% with line=form.instance %} | ||
| {% for property in line.properties %} | ||
| <div>{{ property.key }}: {{ property.value }}</div> | ||
| {% endfor %} | ||
| {% endwith %} | ||
| {% endfor %} | ||
| ``` | ||
| | Property | Type | Description | | ||
| | ----- | ------ | ------ | | ||
| | `properties` | List | Line item properties captured on the product page, see [Personalized Products](/docs/storefront/themes/guides/personalized-products). Property names starting with an underscore are excluded. | | ||
| **line.properties** | ||
| | Property | Type | Description | | ||
| | ----- | ------ | ------ | | ||
| | `key` | String | The property name, taken from the `properties[<name>]` input on the product page. | | ||
| | `value` | String | The value submitted by the customer. | | ||
| ## Template Contexts | ||
| All templates receive the [Global Objects](#global-objects) (`store`, `settings`, `currencies`, `languages_active_storefront`, `menus`, `products`, `product_categories`, `posts`, `post_categories`, `privacy_policy`, `terms_and_conditions`, `subscription_terms_and_conditions`, `request`, `storefront_geos`). The table below lists additional view-specific context variables passed to each template. | ||
| | Template | View-Specific Context | | ||
| | ----- | ------ | | ||
| | `templates/index.html` | Global objects only. Use the [`where`](/docs/storefront/themes/templates/tags#where) tag to query products and categories. | | ||
| | `templates/cart.html` | `formset` (cart line forms, each `form.instance` is a [line](#line)) | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: The added Reply with | ||
| | `templates/catalogue/product.html` | `product`, `variant_form`, `interval_count_choices` | | ||
| | `templates/catalogue/index.html` | `products` (paginated), `paginator`, `page_obj` | | ||
| | `templates/catalogue/category.html` | `category`, `products` (paginated), `filters`, `has_active_filter`, `paginator`, `page_obj` | | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -100,24 +100,61 @@ The `cart_form` tag generates an add-to-cart form for a product. Required on eve | ||
| {% cart_form request product 'single' as cart_form %} | ||
| ``` | ||
| ```django title="Example Product Add to Cart Form" | ||
| {% cart_form request product 'single' as cart_form %} | ||
| <form method="post" action="{% url 'cart:add' product.slug %}"> | ||
| {% csrf_token %} | ||
| {{ cart_form }} | ||
| <button type="submit" class="btn btn-primary"> | ||
| Add to Cart | ||
| </button> | ||
| </form> | ||
| The tag returns a form object, not rendered HTML. Loop over it to render each field in your own markup, as the Intro Bootstrap theme does through its `partials/form_fields.html` partial. | ||
| ```django title="templates/catalogue/product.html" | ||
| {% block product_cart_form %} | ||
| <div class="catalogue_product-action"> | ||
| {% purchase_info_for_product request product as session %} | ||
| {% if session.availability.is_available_to_buy %} | ||
| {% cart_form request product 'single' as cart_form %} | ||
| <form id="add-to-cart" class="row g-3" action="{% url 'cart:add' pk=product.pk %}" method="post"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: The form action now uses Pick one (PK or slug) and make both examples use it, or call out explicitly when the add endpoint expects Reply with | ||
| {% csrf_token %} | ||
| {% include "partials/form_fields.html" with form=cart_form breakpoint_col='col-3 col-md-2' size='lg' nolabel=True %} | ||
| <div class="col-9 col-md-10"> | ||
| <div class="d-grid"> | ||
| <button type="submit" class="btn btn-primary btn-lg" data-loading-text='{% t "store.catalogue.add_to_cart_loading" %}' data-disabled-text='{% t "store.catalogue.product_unavailable" %}'>{% t "store.catalogue.add_to_cart" %}</button> | ||
| </div> | ||
| </div> | ||
| </form> | ||
| {% else %} | ||
| <div class="fs-6">{% t "store.catalogue.out_of_stock" %}</div> | ||
| {% endif %} | ||
| </div> | ||
| {% endblock %} | ||
| ``` | ||
| ```django title="partials/form_fields.html" | ||
| {% if form.is_bound and not form.is_valid %} | ||
| <div class="alert alert-danger alert-dismissible fade show" role="alert"> | ||
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | ||
| {% t "global.error.please_check_error" %} | ||
| </div> | ||
| {% endif %} | ||
| {% if form.non_field_errors %} | ||
| {% for error in form.non_field_errors %} | ||
| <div class="alert alert-danger alert-dismissible fade show" role="alert"> | ||
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | ||
| {{ error }} | ||
| </div> | ||
| {% endfor %} | ||
| {% endif %} | ||
| {% for field in form %} | ||
| {% include 'partials/form_field.html' with field=field style=style %} | ||
| {% endfor %} | ||
| ``` | ||
| | Argument | Description | | ||
| | --- | --- | | ||
| | request | The current `request` context object. | | ||
| | product | The `product` context object. | | ||
| | form_type | The form type, typically `'single'`. | | ||
| | quantity_type | Accepts `'single'` or `'multiple'`. | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: The third positional argument to Verify against the source of the Reply with | ||
| | variable | Assigned template variable name for the form. | | ||
| Add [line item property](/docs/storefront/themes/guides/personalized-products) inputs to this form to capture customer personalization such as an engraving or gift message. | ||
| ### comment | ||
| Ignores everything between `{% comment %}` and `{% endcomment %}`. An optional note may be inserted in the first tag. For example, this is useful when commenting out code for documenting why the code was disabled. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING: Same URL-kwargs inconsistency surfaces again: this guide uses
{% url 'cart:add' pk=product.pk %}while thecart_formtag reference still usesproduct.slugon tags.mdx:100. Make the two pages agree.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.