For discussing the development and usage of Checkout UI extensions, post-purchase extensions, web pixels, Customer Accounts UI extensions, and POS UI extensions
I've been trying to learn how to create a discount function using this tutorial: https://shopify.dev/docs/apps/build/discounts/experience/build-discounts-function
I've successfully installed the app in my development store, but when I try to test it I keep getting InvalidOutputError.
I feel like I've followed the tutorial correctly and copied the Javascript provided into run.js and I'm not sure what the issue is. Any ideas?
Here is the code for run.js from the tutorial:
// @TS-check
import { DiscountApplicationStrategy } from "../generated/api";
// Use JSDoc annotations for type safety
/**
* @typedef {import("../generated/api").RunInput} RunInput
* @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
* @typedef {import("../generated/api").Target} Target
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/
/**
* @type {FunctionRunResult}
*/
const EMPTY_DISCOUNT = {
discountApplicationStrategy: DiscountApplicationStrategy.First,
discounts: [],
};
// The configured entrypoint for the 'purchase.product-discount.run' extension target
/**
* @param {RunInput} input
* @returns {FunctionRunResult}
*/
export function run(input) {
const targets = input.cart.lines
// Only include cart lines with a quantity of two or more
.filter(line => line.quantity >= 2)
.map(line => {
return /** @type {Target} */ ({
// Use the cart line ID to create a discount target
cartLine: {
id: line.id
}
});
});
if (!targets.length) {
// You can use STDERR for debug logs in your function
console.error("No cart lines qualify for volume discount.");
return EMPTY_DISCOUNT;
}
// The @Shopify/shopify_function package applies JSON.stringify() to your function result
// and writes it to STDOUT
return {
discounts: [
{
// Apply the discount to the collected targets
targets: targets,
// Define a percentage-based discount
value: {
percentage: {
value: "10.0"
}
}
}
],
discountApplicationStrategy: DiscountApplicationStrategy.First
};
};
Here is run.graphql:
query RunInput {
cart {
lines {
id
quantity
}
}
}
Here's information from a run log:
Input (STDIN)
{
"cart": {
"lines": [
{
"id": "gid://shopify/CartLine/0",
"quantity": 2
}
]
}
}
Output (STDOUT)
{
"discounts": [
{
"targets": [
{
"cartLine": {
"id": "gid://shopify/CartLine/0"
}
}
],
"value": {
"percentage": {
"value": "10.0"
}
}
}
],
"discountApplicationStrategy": "FIRST"
}
Error message
[
{
"path": [
"discounts",
0,
"targets",
0
],
"explanation": "Expected one of valid values: productVariant. Got: cartLine"
}
]
Solved! Go to the solution
This is an accepted solution.
Try changing
cartLine: {
id: line.id
}
to
productVariant: {
id: variant.id
}
This is an accepted solution.
Thanks Liam. What functions example did you change on your end?
After seeing @SomeUsernameHe comment, I looked up the tutorial again and saw there was a link to a git repository and the run.js code on there was written a little differently than the tutorial I had found on the shopify.dev site: https://github.com/Shopify/function-examples/blob/main/sample-apps/discounts/extensions/product-disc...
Vs the tutorial I was going by: https://shopify.dev/docs/apps/build/discounts/experience/build-discounts-function
The code on Git had implemented user configuration but I just took what I needed to modify my run.graphql and run.js files and this what I ended up with (below). After running "shopify app function typegen" command and running the app again it worked!
Here is my modified run.js:
// @ts-check
import { DiscountApplicationStrategy } from "../generated/api";
// Use JSDoc annotations for type safety
/**
* @typedef {import("../generated/api").RunInput} RunInput
* @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
* @typedef {import("../generated/api").Target} Target
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/
/**
* @type {FunctionRunResult}
*/
const EMPTY_DISCOUNT = {
discountApplicationStrategy: DiscountApplicationStrategy.First,
discounts: [],
};
// The configured entrypoint for the 'purchase.product-discount.run' extension target
/**
* @param {RunInput} input
* @returns {FunctionRunResult}
*/
export function run(input) {
const targets = input.cart.lines
// Only include cart lines with a quantity of two or more
.filter(line => line.quantity >= 2 &&
line.merchandise.__typename == "ProductVariant")
.map(line => {
const variant = /** @type {ProductVariant} */ (line.merchandise);
return /** @type {Target} */ ({
// Use the cart line ID to create a discount target
productVariant: {
id: variant.id
}
});
});
if (!targets.length) {
// You can use STDERR for debug logs in your function
console.error("No cart lines qualify for volume discount.");
return EMPTY_DISCOUNT;
}
// The @shopify/shopify_function package applies JSON.stringify() to your function result
// and writes it to STDOUT
return {
discounts: [
{
// Apply the discount to the collected targets
targets: targets,
// Define a percentage-based discount
value: {
percentage: {
value: 10.0
}
},
message: "10% OFF"
}
],
discountApplicationStrategy: DiscountApplicationStrategy.First
};
};
And my modified run.graphql:
query RunInput {
cart {
lines {
quantity
merchandise {
__typename
... on ProductVariant {
id
}
}
}
}
}
Hi Vbjornsson,
This could be due to the version you're calling in your toml file. You should update your toml file to make sure you're using 2024-07 and it should work then. Let me know if you're still seeing issues.
Liam | Developer Advocate @ Shopify
- Was my reply helpful? Click Like to let me know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog
Thanks for the reply Liam, but it looks like I already have that api version set:
Hi again Vbjornsson,
We also updated the functions example on our side so it should work with 2024-07 now.
Liam | Developer Advocate @ Shopify
- Was my reply helpful? Click Like to let me know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog
This is an accepted solution.
Thanks Liam. What functions example did you change on your end?
After seeing @SomeUsernameHe comment, I looked up the tutorial again and saw there was a link to a git repository and the run.js code on there was written a little differently than the tutorial I had found on the shopify.dev site: https://github.com/Shopify/function-examples/blob/main/sample-apps/discounts/extensions/product-disc...
Vs the tutorial I was going by: https://shopify.dev/docs/apps/build/discounts/experience/build-discounts-function
The code on Git had implemented user configuration but I just took what I needed to modify my run.graphql and run.js files and this what I ended up with (below). After running "shopify app function typegen" command and running the app again it worked!
Here is my modified run.js:
// @ts-check
import { DiscountApplicationStrategy } from "../generated/api";
// Use JSDoc annotations for type safety
/**
* @typedef {import("../generated/api").RunInput} RunInput
* @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
* @typedef {import("../generated/api").Target} Target
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/
/**
* @type {FunctionRunResult}
*/
const EMPTY_DISCOUNT = {
discountApplicationStrategy: DiscountApplicationStrategy.First,
discounts: [],
};
// The configured entrypoint for the 'purchase.product-discount.run' extension target
/**
* @param {RunInput} input
* @returns {FunctionRunResult}
*/
export function run(input) {
const targets = input.cart.lines
// Only include cart lines with a quantity of two or more
.filter(line => line.quantity >= 2 &&
line.merchandise.__typename == "ProductVariant")
.map(line => {
const variant = /** @type {ProductVariant} */ (line.merchandise);
return /** @type {Target} */ ({
// Use the cart line ID to create a discount target
productVariant: {
id: variant.id
}
});
});
if (!targets.length) {
// You can use STDERR for debug logs in your function
console.error("No cart lines qualify for volume discount.");
return EMPTY_DISCOUNT;
}
// The @shopify/shopify_function package applies JSON.stringify() to your function result
// and writes it to STDOUT
return {
discounts: [
{
// Apply the discount to the collected targets
targets: targets,
// Define a percentage-based discount
value: {
percentage: {
value: 10.0
}
},
message: "10% OFF"
}
],
discountApplicationStrategy: DiscountApplicationStrategy.First
};
};
And my modified run.graphql:
query RunInput {
cart {
lines {
quantity
merchandise {
__typename
... on ProductVariant {
id
}
}
}
}
}
This is an accepted solution.
Try changing
cartLine: {
id: line.id
}
to
productVariant: {
id: variant.id
}