Focuses on API authentication, access scopes, and permission management.
I followed a YT video on how to set up an app proxy. I also looked at a lot of Shopify forum posts to no avail
I get this error in the dev tools console (I think the Youtuber deleted my comment when I tried asking about this error)
GET https://testingwishlist.myshopify.com/auth/login 404 (Not Found)
and this from my terminal
GET /app/proxy/?shop=testingwishlist.myshopify.com&logged_in_customer_id=&path_prefix=%2Fapps%2Fproxytest×tamp=1718050314&signature=819a839
ceb80bf73ef9b978ad39c7ddcf7324744ea00b4d06710ffefb5cbff4f 302
Here is my proxy.liquid and my proxy.js
<body> <button onclick="testProxy()">test proxy</button> </body> <script async src={{ "proxy.js" | asset_url }} defer></script> {% schema %} { "name": "Proxy Embed", "target": "section", "settings": [ { "type": "header", "content": "Proxy button to customer wish list" } ] } {% endschema %}
function testProxy() { return new Promise((resolve, reject) => { fetch("https://testingwishlist.myshopify.com/apps/proxytest"), { method: 'POST', redirect: 'manual', headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', } } }).then((response) => { console.log(response, 'response'); }).then((data) => { resolve(data) }).catch((error) => { reject(error) }) }
Here is my app.proxy.jsx which is in the routes folder
import { authenticate } from '../shopify.server' import { Page } from '@shopify/polaris' import { cors } from 'remix-utils' import { json } from '@remix-run/node'; export const loader = async ({request}) => { console.log('---hit app proxy----') const {session} = await authenticate.public.appProxy(request) return await cors(request, json({status: 200})) } const Proxy = () => { return <Page>proxy </Page> } export default Proxy
The console.log in my app.proxy.jsx does turn up in the terminal along with the error, so I at least know that its connected to the fetch in proxy.js
My App proxy settings in Shopify Partner is :
Subpath prefix: apps
Subpath: proxytest
Proxy URL: https://journalists-attribute-slim-paste.trycloudflare.com/app/proxy
The URL changes each time I restart the app from the terminal, so I always make sure to update the Proxy URL
The App Proxy Url from the admin settings is: https://testingwishlist.myshopify.com/apps/proxytest
I did add this to my remix.config.js file but it didn't change anything
serverDependenciesToBundle: [ /^remix-utils.*/, ],
I'm getting this error too! is this Di Stephano's tutorial hahah? Do you have hosting setup on yours, I'm thinking it might have to do with how my app is built up to fly.io. But I'm not sure. I'm getting a 404 error on my proxy GET, same as you
I don't have a dedicated hosting setup, just been using application_url from the .toml file for my proxy url
Maybe try adding this under the [webhooks] section in the shopify.app.toml ?
[app_proxy]
url = "yourcloudflarelink.com"
subpath = "yoursubpath"
prefix = "apps"
This from someone in the discord server where they actually have their proxy url only set with the main directory. Unlike the video where he does the full proxy app url.
here's the discord discussion. https://discord.com/channels/597504637167468564/1251292194631843952
I'll be trying out and debugging more tomorrow
I just looked and my shopify.app.toml file already has that section in there. I think it auto updates whenever I restart the server.
gotcha, gotcha. Yeah so the discord steps didn't fix anything? I can try messaging the guy on discord to see if he has an example
Hope it will help someone in future.
I'm able to successfully fetch data using App Proxy, I followed Di Stephano's tutorial initially but there was some issue. So I need to do some adjustments.
The first thing I did is use "shopify.app.toml" file to put the proxy settings because it will auto update the URL as needed. Example code I put there:
# start of code
For the frontend, it will be like: my-store-url/{prefix}/{subpath}, so something like that: https://my-store-url.myshopify.com/apps/styledata
So now I can send a request by like that, I put the URL relative so it will match with store URL:
# start of code
# end of code
And finally code for the backend (app.proxy.jsx)
// start of code
# end of code
I'm using a loader because I need to fetch the data, if you need POST/PUT/DELETE etc you need to use action then.
And finally I'm getting the data the attached screenshot.
oh wow! Thank you so much @arifkpi for the code sample and assist! Do you happen to have the app pushed to the partner store, or are you running the app locally with 'npm run dev'?
I setup a new test app to follow your setup, but I'm still hitting 500 errors. This is in context of running the app locally with 'npm run dev'. It looks like this discussion was maybe relevant for the error we're still seeing, but I'm not too sure after seeing your screenshots that its working on your machine: https://community.shopify.com/c/online-store-and-theme/application-proxy-there-was-an-error-in-the-t...
Thanks again!
The 500 error code indicates that there is a server error. I think your proxy code contains an error; please double-check the code because I got the same error as well when my code was faulty.
I think I was able to get it, and it may have something to do with my access_scopes in shopify.app.toml file? What access scopes are you using @arifkpi ? I currently have: read_customers,read_products,write_customers,write_products
For the app I'm working on, I don't need any shop/customer data; for that reason, access_scopes is empty in my config.
Hey! it worked. Thank you.
hey did you use shopify's implementation of AuthenticateAppProxy?
from my snippets above and OP's we're using this method:
authenticate.public.appProxy()
this is with the Remix app template with jsx
https://shopify.dev/docs/api/shopify-app-remix/v1/authenticate/public/app-proxy
if you don't mind, I noticed you also are utilizing npm run dev. Im in early stages of development so im wondering how my .env would get read. is there a common method of passing the environment variables in development? ive been looking everywhere
thank you for getting back this quick btw
no problem! as for envs, for local dev you basically use the shopify.app.toml and the .env file that gets created after your initial:
shopify app init
I'd also say, even if you're just in dev/test stage, definitely move your app up to it's hosting service. It'll ensure that your connection to the app hosting and the basic default app is working with the env configurations. I ran into hiccups during those steps, and it would've saved us so much time just making sure things were squared away then.
Your App Hosting service usually has a way to config envs. I use Render.com for app hosting as they have a free development tier (if you notice your app is down, you'll have to manually spin it back up on the free tier). I also believe Render became a new partner with Shopify recently in the last couple of months. We previously used Fly.io and often had issues with spin-up, Render was seamless.
https://docs.render.com/deploy-shopify-app
lmk if anything else, best of luck.
Believe it or not, I've been in a one month hiccup after changing from .NET to React Remix for Shopify Development. I feel like if you took a quick look at my issue you may have solved this many times by now:
Essentially, we leverage a static ngrok_domain.
made my app proxy settings as
url: shopifystore.com/app/api
prefix: apps
subpath: member
Prior to this I created app.api.jsx, and I'm currently seeing the shopify 404 page with the url as (auth/login) despite the fact I authenticated the proxy request (or so I assume..).
my app.api.jsx :
import { json } from "@remix-run/node";
import { authenticate } from "../shopify.server";
export const loader = async ({ request }) => {
// Parse the URL to extract query parameters
const url = new URL(request.url);
// Get query parameters from the URL
const shop = url.searchParams.get("shop");
const signature = url.searchParams.get("signature");
const timestamp = url.searchParams.get("timestamp");
console.log("Incoming Query Parameters:");
console.log("Shop:", shop);
console.log("Signature:", signature);
console.log("Timestamp:", timestamp);
// Check if the required parameters are missing
if (!shop || !signature) {
console.log("Missing required parameters.");
return json({ error: "Missing required parameters" });
}
try {
// Authenticate the app proxy request
const { admin, session} = await authenticate.public.appProxy(request);
// Log success
console.log("Authentication successful:", { shop, session });
// Respond with success
return json({
ok: true,
message: "App Proxy Authentication Successful",
shop,
});
} catch (error) {
// Log any errors during authentication
console.log("Authentication failed:", error);
return json({ error: "Authentication failed", details: error.message });
}
};
export default function AppApi() {
return (
<div>
<h1>App Proxy Route - JSON Data</h1>
<p>This route handles the app proxy requests.</p>
</div>
);
}
I have a feeling Im overcomplicating it 😧 my remix terminal gave me the following:
15:31:20 │ remix │ Incoming Query Parameters:
15:31:20 │ remix │ Shop: non-null-value
15:31:20 │ remix │ Signature:
non-null-value
15:31:20 │ remix │ Timestamp: non-null-value
15:31:20 │ remix │ [shopify-app/INFO] Authenticating app proxy request
15:31:20 │ remix │ Authentication successful: {
15:31:20 │ remix │ shop: 'non-null-value',
15:31:20 │ remix │ session: Session {
15:31:20 │ remix │ id: 'non-null-value',
15:31:20 │ remix │ shop: 'non-null-value',
15:31:20 │ remix │ state: '',
15:31:20 │ remix │ isOnline: false,
15:31:20 │ remix │ scope: 'write_products',
15:31:20 │ remix │ expires: undefined,
15:31:20 │ remix │ accessToken: 'non-null-value',
15:31:20 │ remix │ onlineAccessInfo: undefined
15:31:20 │ remix │ }
edit: i also didnt see a .env file after my initial shopify app init. i just made one manually
oh you know what, I also had issues with the loader function throwing errors when I tried to hit the shop and admin during the await authenticate.public.appProxy(request)! could you maybe delete your current loader and just try this snippet to confirm app proxy is coming through correctly:
export const loader = async ({ request }) => {
const { liquid } = await authenticate.public.appProxy(request);
return liquid("Wow {{shop.name}} goes here");
};
that worked!
that's so interesting.. So if I were to add buttons to this proxy page now it can still be in this app.api.jsx correct? My goal is to do simple GET/POST from my external api and have it display on my proxy page - can all that logic be done in one file?
thanks so much for your time, truly you helped me make a breakthrough.
awesome, glad it worked! There definitely must be something bugged with the {admin, shop} then. But yeah technically if you wanted to load data and FE on the one proxy page you can do it there. just make sure to have the return() JSX function with your Polaris components. For loading data, I use graphQL on the loader() function which also had some quirks, but ofc, I think you're set from there! if you run into anything I may be able to assist, all the best
Well i do have a 100th question to ask 😂
if you had any reason to call an external api, aside from :
were there any caveats/necessities needed to ensure successfull http requests?
nah it should work as expected. Just make sure if you're using .envs to have it both on your local and add it during the setup stage to your Prod hosting. If you're making the API yourself, make sure to whitelist your local app's and shop URL, and also your App's Prod hosting URL. The data formatting might just be the only "caveat".
You'll basically use the loader in conjunction with the useLoaderData() function. And then you can just map the data returned in any of the Polaris components as needed:
function Component() {
const data = useLoaderData();
}
but lmk once you get to that step if there's an specific hiccup you run into.
dw about asking questions, because I'm certain someone else in the future will have it too! These forums are the only way I've been successful in completing a Shopify app hahah, the official docs are kinda an abyss.
So I managed to make a button by dynamically adding js with liquid (for some reason liquid seems to be the only thing my page likes..)
import { authenticate } from "../shopify.server"; // Assuming authenticate method is defined here
export const loader = async ({ request }) => {
const { liquid } = await authenticate.public.appProxy(request);
// Return Liquid template with a button and a placeholder for the token
const liquidTemplate = `
<html>
<body>
<h1>Wow {{shop.name}} goes here</h1>
<button id="fetchToken">Fetch Token</button>
<p id="tokenOutput"></p>
<script>
document.getElementById('fetchToken').addEventListener('click', async function() {
const response = await fetch('/api/authorize');
const data = await response.json();
if (data.token) {
document.getElementById('tokenOutput').innerText = "Bearer Token: " + data.token;
} else {
document.getElementById('tokenOutput').innerText = "Failed to retrieve token.";
}
});
</script>
</body>
</html>
`;
return liquid(liquidTemplate);
};
this resulted in a 404 for some reason, even though i named my jsx file app.api.authorize.jsx:
this is the route im planning to call with the button, that is responsible for making the actual call to my external api:
import { json } from "@remix-run/node"; // For handling loader data
import { useLoaderData } from "@remix-run/react"; // For accessing data in the component
import Polaris from "@shopify/polaris"; // Import Polaris components
const { Page, Layout, Card, Heading } = Polaris;
export const loader = async () => {
// Hardcoded API URL and headers for token retrieval
const apiUrl = "hidden"; // Replace with actual API URL
const headers = {
"Content-Type": "application/json",
AppKey: "hidden", // Replace with actual AppKey
SecretKey: "hidden" // Replace with actual SecretKey
};
// Make the GET request to the external API
const response = await fetch(apiUrl, { headers });
const data = await response.json();
// Check if the request was successful and if a token was received
if (data.isSuccessful && data.data && data.data.jwToken) {
// Return the token and other relevant data
return json({
token: data.data.jwToken,
userName: data.data.userName,
});
} else {
// Handle error or unsuccessful response
return json({ error: "Token retrieval failed", message: data.message });
}
};
export default function AuthorizePage() {
const data = useLoaderData(); // Access the loader data
return (
<Page>
<Layout>
<Layout.Section>
<Card sectioned>
<Heading>Token Retrieval</Heading>
{data.token ? (
<div>
<p><strong>User Name:</strong> {data.userName}</p>
<p><strong>Bearer Token:</strong> {data.token}</p>
</div>
) : (
<p>{data.error || "No token found."}</p>
)}
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
i actually changed my approach after doing some research. apparently if i have a route thats dedicated to making a get request, i can encapsulate that in a function. then, import said function in another route and use any evenhandler to trigger the call. Im running into errors nonetheless:
app.api.authorize.jsx:
import { json } from "@remix-run/node"; // For handling loader data
import { useLoaderData } from "@remix-run/react"; // For accessing data in the component
import Polaris from "@shopify/polaris"; // Import Polaris components
const { Page, Layout, Card, Heading } = Polaris;
export async function fetchToken() {
// Hardcoded API URL and headers for token retrieval
const apiUrl = "https://external_api.com/endpoint"; // Replace with actual API URL
const headers = {
"Content-Type": "application/json",
header1: hidden, // Replace with actual AppKey
header2: hidden // Replace with actual SecretKey
};
console.log("Sending request to API:");
console.log("API URL:", apiUrl);
console.log("Headers:", headers);
// Make the POST request to the external API
const response = await fetch(apiUrl, {
method: "POST",
headers,
});
// Parse the response JSON
const data = await response.json();
// Return the entire JSON response from the API
return data;
}
then i use this button to try and trigger it:
import { fetchToken } from "./app.api.authorize"; // Import the function from app.api.authorize.jsx
import { json } from "@remix-run/node"; // Handle the POST request and return JSON
import { authenticate } from "../shopify.server"; // Assuming authenticate method is defined here
export const loader = async ({ request }) => {
const { liquid } = await authenticate.public.appProxy(request);
const liquidTemplate = `
<html>
<body>
<h1>Wow {{shop.name}} goes here</h1>
<button id="fetchToken">Fetch Token</button>
<p id="responseOutput"></p>
<script>
document.getElementById('fetchToken').addEventListener('click', async function() {
const response = await fetch('', { method: 'post' }); // Use current route
const data = await response.json();
// Display the entire response
document.getElementById('responseOutput').innerText = JSON.stringify(data, null, 2);
});
</script>
</body>
</html>
`;
return liquid(liquidTemplate);
};
// Action method to handle POST request and return the full JSON response
export const action = async () => {
// Fetch token using the function from app.api.authorize.jsx
const result = await fetchToken();
// Return the full response without modifying it
return json(result);
};
this works! the next challenge is seeing if i can call this, use the token to pass as a paraeter to another function that i implemented the same way (importing from another route)