How to pass data between "THEME-APP-EXTENSION" and the backend of the app?

I am trying to figure out how to pass data between the theme-app-extension and the backend of the application that the theme-app-extension is connected to. The theme-app-extension is all liquid, css, and javascript so I wasn’t sure if there was a built in way to pass data between the two. For example is there a suggested method to pull data into the theme-app-extension from the database and is there a suggested way to send data to the database from the code running the theme-app-extension? I am fairly new to doing anything with theme-app-extensions as well as building Shopify applications. I have built Shopify applications that was admin facing or just cosmetic, this is my first time building a Shopify application that takes user input and sends it to the database and retrieves that data for the end-user to see.

Any suggestions would be greatly appreciated.

Thank You.

For anybody else reading this in the future with the same problem.

The answer is to use shopify app proxies, https://shopify.dev/apps/online-store/app-proxies

a rough outline of the steps I took to do this.

  1. Set up the proxy in the shopify dashboard
  2. Make a route in the app to handle the requests sent to that endpoint
  3. Save that data to the database of the application

I don’t really know but the way I got it to work was just by putting the ngrok url (APP URL) in the App Proxy proxy url field in the APP SETUP panel. in the subpath prefix I put apps and in the subpath field I put the app name. That worked for me and I am able to send data from the theme-app-extensions to the backend api route and save the data into the database and retrieve that data for the shopify admin area dashboard. Let me know if this helps or not.

Hello Jameshagood, your answers are very helpful.

The ngrok URL changes frequently, do you update the APP URL manually? If not, how do you handle this change? Do you think using the ngrok URL would work in production?

yes I update every time it changes which is like once a day. The app I am building isn’t in production yet but in production you should just be able to put whatever the URL is for the app once it is on servers somewhere with a HTTPS URL

I have built a couple different shopify apps when I was trying to learn how to do it, so I have never put one in production. the one I am currently building will be my first live shopify application that I built.

This sounds very helpful! Exactly same question I had, I was wondering if you know anything about this but when I make a fetch request like shown in the video, it routes to a different ip address then ngrok even if I added that in the proxy configuration. Did you ever get that? I am perhaps starting my app wrong.

Hello James, could you provide some insight into the naming of routes and the verification of incoming requests from the proxy. I have followed the same steps, I am able to send a request to the app-proxy but unable to get it through to the backend api. I receive no errors It simply doesnt connect to the backend.

This has been a little while ago since I done this. I looked at the code I had to see the routes I had set up and I had a POST route to /proxy

@jameshagood Thanks for sharing your knowledge with the community, I am still facing the same issue and have posted the details here - https://github.com/Shopify/shopify-api-js/issues/802

Could you please have a look and if possible can you share snippet code which worked for you ?

Thanks

I managed to get everything working by simply deploying the app to gcp, the proxy requests simply wouldn’t go through for a ngrok url, initially they were blocked by ngroks default browser check but even disabling that changed nothing. So the routes were defined correctly only to begin with thus no blockers on that front.

@jameshagood @raultoks-peppy

### Set-up:

I managed to get things working as well:

  1. Create App Proxy
  2. Create Remix App Bridge app
  3. Create a Theme App Extension, inside of the Remix App [add extension to project root, not app root]
  4. └── extensions
    └── my-theme-app-extension
    ├── assets/extension.js [this has the fetch() method in it]
    ├── blocks/extension.liquid [add form HTML in it]
    ├── snippets
    ├── locales
    ├── package.json
    └── shopify.extension.toml
  5. Create a form inside the theme app extension and add a script in the assets folder that uses fetch(), to hit the online store address + the proxy prefix + proxy subpath. This will then transfer the data to your App Proxy URL, which should represent the raw IFRAME URL to your Remix App
  6. Use the relevant action() method of a new jsx/tsx route file [I created app.api.jsx], in your Remix App. The action() method gets targeted only by POST requests. Use request.json() to extract the fetch() body data.
  7. I had to remove the authenticate.admin() check, at the head of the action() method. I know this is probably very dangerous, but I couldn’t get the request to authenticate. If anyone has an answer how to get the authentication to work, I would be most grateful.

A tale of two App Proxies:

The only question, that I have is, how do I get the Theme App Extension to work with two different App Proxies?

Essentially, I have a development [app bridge] & production version [fly.io] of my app.

I have the same Theme App Extension, embedded in both apps.

Each app has a different App Proxy URL value.

When I try and send the data to my development version of the Remix App, everything works fine.

When I try and send the data to my production Remix App on fly.io, it keeps trying to send it to my development App Proxy URL, even though I have definitely set the two different App Proxy URLs, correctly.

Now, it seems like the Theme App Extension is agnostic with respect to the URL that is used, within the fetch() method.

This fetch() method param URL is:

online store address + the proxy prefix + proxy subpath

Essentially, the fetch() method URL param, is the same for both the development & production app.

It should then be able to work out which environment it is in, by referring to it’s parent app. It should then be able to access the correct App Proxy URL.

It then transfers the data from the online store address URL, to the App Proxy URL.

But, my system seems to fail, at this transfer point.

Does anyone know how to resolve this?

i can’t send data from my sign up form to back end, could you tell me how?

OK. The first thing to do, is to get your App Proxy setup correctly.

I am assuming you have created some kind of form, in your Theme App Extension. You need to make sure that you use something like fetch() or axios() to post your data to your Admin App.

App Proxy setup:

You should point the proxy at a Remix route, so, for example:

app/routes folder:

app.api.jsx

Then the App Proxy URL would be:

[https://mydomain.trycloudflare.com/app/api](https://mydomain.trycoludflare.com/app)

Then your fetch() URL would be like:

/subpath_prefix/subpath[/route_path]

So let’s say your subpath_prefix value is apps and your subpath is app-proxy, then the form action or fetch() URL will be:

/apps/app-proxy/api

So what actually happens when the data is posted by a fetch() XHR request? Well, the request is intercepted by Shopify and the URL is analysed to see if it matches an App Proxy value. If there is a match, the data is forwarded to the App Proxy proxy URL value. Then, query params are added onto the end of the proxy URL.
These params include:

  • logged_in_customer_id
  • path_prefix
  • timestamp
  • signature

Example:

GET /?index=value&example.myshopify.com&logged_in_customer_id=1234567890&path_prefix=%2Fa%2Fexample&timestamp=1234567890&signature=XXXXXXXXXX

The signature is important. It is made up of all the other query param values, which are sorted & then hashed. The signature is created using:

[createSHA256HMAC()](https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/runtime/crypto/utils.ts#L6)

https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/runtime/crypto/utils.ts#L6

App Proxy fetch() URL:

Note that, you don’t have to add the domain part of the URL, in the fetch() URL param.

Please remember that form POST requests, hit the:

Remix action() method

And GET requests, hit the:

Remix loader() method

If you still have problems, let me know and I will go through other potential problems, including invalid signature.

I have this sign-up form in my app extension:

Name:
Email:
Phone:
Address:

Gender:



Let's Talk

When i click the button, it send post request but it doesn’t send data in submit form to my back-end code . And in my back-end code, i can’t send response data to front-end

import { type ActionFunction } from ‘@remix-run/node’;
import { Page } from ‘@shopify/polaris’;
import type { RowDataPacket } from ‘mysql2/promise’;
import mysql from ‘mysql2/promise’;

interface Row extends RowDataPacket {
access_token: string;
company_token: string;
}

export const action: ActionFunction = async ({ request }) => {
console.log(“-------------------------hir app proxy--------------------------”)
console.log(request)
console.log(request.url, “shopi”)
const baseUrl = new URL(request.url).origin;
console.log(baseUrl);
const connection = await mysql.createConnection({
host: ‘localhost’,
user: ‘root’,
password: ‘’,
database: ‘workchat_db’
});
// Execute the SQL query to fetch access_token and company_token
const [rows] = await connection.execute<Row>(SELECT access_token, company_token FROM admins WHERE online_store_url = ?, [baseUrl]);

// Check if any rows were returned
if (rows.length > 0) {
const access_token = rows[0].access_token;
const company_token = rows[0].company_token;
console.log(access_token, “access_token”);
console.log(company_token, “company_token”);
const dataToSend = {
access_token: access_token,
company_token: company_token
};
console.log(dataToSend, “data send”)
// Send data to the proxy endpoint
const response = await fetch(‘https://minima-world.myshopify.com/apps/proxytest’, {
method: ‘POST’,
headers: {
‘Content-Type’: ‘application/json’
},
body: JSON.stringify(dataToSend)
});

// Check if the request was successful
if (response.ok) {
console.log(‘Data sent successfully.’);
} else {
console.error(‘Failed to send data:’, response.statusText);
}
} else {
console.log(“No rows found for the given URL.”);
}
// Close the MySQL connection
await connection.end();
return null;
}

const Proxy = () => {
return Proxy
}
export default Proxy

Try this:

fetch('/[apps/proxytest](https://minima-world.myshopify.com/apps/proxytest)', {
      method: 'POST',
      body: JSON.stringify(Object.fromEntries(formData)),
      headers: {
        'Content-Type': 'application/json
      },
    })

You should NOT set:

'Access-Control-Allow-Origin': '*',

It won’t help you here. Anyway, this can only be set on the server. It won’t really do anything when sent from the client. You can set this header in the loader()/action() method of your Remix app, if you wish to do so, but you won’t need to:

https://shopify.dev/docs/api/shopify-app-remix/v2/authenticate/public/checkout#example-cors

Is your Remix route in the app folder?

Can you show me, where your Remix route is? I want to make sure that your fetch() URL param is correct?

Also you need to authenticate your request in your Remix app:

export async function action({ request, params }) {
    const { admin, session } = await authenticate.public.appProxy(request);
    const formData = await request.json();
    ...
}

If you don’t authenticate you will have trouble carrying out certain tasks. Although, in your case, I see you are not using any of the Shopify Admin Rest or GraphQl APIs, so you should be OK. However, it is good practice to authenticate, because you don’t want to expose your app, to malicious activity.

Thank you so much, my back now has data response. but why the field response is all null? please let mr know. i have console log all data but they are all null.

Name:
Email:
Phone:
Address:
Gender:


Let's Talk

I think you need to use the name attribute in each form field like:

<input type="text" name="name" value="foo" />

If you want to collect form data via:

const formData = new FormData(form);

i have change like you said but the data return is always default value, even when i has change string in input form. The data request to back-ens always is:
13:44:00 │ remix │ {
13:44:00 │ remix │ name: ‘foo’,
13:44:00 │ remix │ email: ‘foo’,
13:44:00 │ remix │ mobile: ‘foo’,
13:44:00 │ remix │ address: ‘foo’,
13:44:00 │ remix │ gender: ‘male’
13:44:00 │ remix │ }
this show even when i have used:

const name = document.getElementById(‘name’).value;
const email = document.getElementById(‘email’).value;
const mobile = document.getElementById(‘mobile’).value;
const address = document.getElementById(‘address’).value;
const gender = document.getElementById(‘gender’).value;

// Create an object with the form data
const formData = {
name: name,
email: email,
mobile: mobile,
address: address,
gender: gender
};

Are you using:

const formData = await request.json();
    ...

Inside your Remix App action() method, to parse the data?
This is very important.