Walling's Stair Step Method - Shopify App Launched (Part 5)

November 25, 2023

Shopify App Launch Success

Introduction

Last post I got stuck thanks to Shopify updating their APIs and because I previously misunderstood a few things here and there in terms of how things work. So in this post I set up a new repo and by the end of it got it actually launched publicly.

New Repo

Upgraded Node from 16 to 20. Even though Prisma also tells me to update, I don’t - in order to keep things as vanilla as I possibly can this time. This time I also installed Javascript instead of the Typescript template, because I saw that I wasn’t really writing Typescript in my last repo. I’ll take a look if what was generated now by the template differs a lot config-wise from what I had previously:

  • I was given a docker-start config under package.json (I am not using docker right now, so shouldn’t matter)
  • Remix is upgraded to version 2.0!!! This might be a MASSIVE culprit! It’s because new Remix version does the routing differently, hence why Shopify’s environment might have erred on this (future me: actually, adapters to the routing format was being used, so the routing in particular did not matter). This is a good thing to note down for the future to keep packages on bleeding edge or else bad things might happen in the Shopify ecosystem.
  • Shopify’s Polaris (the frontend framework) was updated from v11 to v12
  • Cloudflare’s Shopify plugin is under trusted dependencies
  • Under shopify.server.js the restResources seem to have changed from version 2023-07 to 2023-10 too which probably signifies where Shopify’s APIs are located or the sort. There’s also a line setting v3_authenticatePublic to true, which may also point to one of the problems I had.
  • Amongst all the problems I had, I didn’t have a problem with the extension part of the app itself, so I can just consider that part fine

Step 1

Everything runs well having installed the template app and extension. I’ll start off by using my code in the extension part as it was before as it did not do a lot of useful work itself, but just made the call to the backend. On the backend side, I’ll start off by returning a simple response to the query. This works wonderfully.

Step 2

This step gets to the point that I had a problem with last time - querying graphql. This will now test whether the authentication part works correctly, because without that, you can’t query products. This works too and I am happy!

Step 3

Next major milestone is reading from the Prisma database and returning the response. While I am doing this, I will be doing some separation of the backend logic areas under my models folder. For some reason adding a utils file to Extensions app folder was not resolvable by the compiler, so I had to add the file extension .jsx to the import for it to find it. Huh, weird. But otherwise, this step also works well, so I am happy.

Step 4

Now to make sure I can make external requests and that my dotenv file works correctly, I need to connect with ChatGPT once again. And even though the response from Shopify was cryptic whenever I had a missing import or the sort, once I had all the pieces up there, it works well again.

Step 5

Now I need to revitalize the vision board, the app part, and make sure this information is being read properly. Looking at the template’s app._index.jsx I am not quite sure why there’s an empty loader block with just authentication. It seems like just a filter that would throw an error if it’s not authenticated. This is a difference with what I did previously and might actually have been the culprit of the errors I had gotten. Similarly, I don’t know what app.jsx ’s loader block does where it just returns the apiKey as response. So I will try to keep these weird behaviors as much as I can not to break anything this time.

No, it can’t be. The loader is meant to populate the view at hand with data and the action is meant to capture POST requests. That means the app._index.jsx does this only to test whether the user has rights to access that page.

Porting over the Polaris components though I have to change some of the elements used as VerticalStack does not exist anymore, so mostly will be using BlockStack instead. This might have been a part of my past problems too. At least the new components look a bit more refined!

Considering how much of the code had changed, I feel like I truly wasn’t at fault here, but Shopify just lost the plot at one point and broke backwards compatibility in some places and I already took the hit. It was just hard for me to see through at the time because I was just creating production configurations that messed with my head.

Step 6

Being careful not to break anything, it is important now to change the name of the app, so it shows correctly to the end-user, and to fill in some text here and there. I still don’t know exactly what to do with the template login page whenever you go to the main page of the app (outside of Shopify), but I feel like I can keep it as it is because nobody should see these pages. Usually I would clean up files and code not used, but this might have some weird auth flows with Shopify. Once this is done, we can start making it production-ready by providing webhooks and the sort. Because this is in code, I will include it in the dev repo, but configuration and environment variables I will not touch until I create the production app.

After all this I also see that under the app.toml file Shopify had been starting to add /.shopify/graphiql/auth/callback to one of the redirect URLs, which might have been one of the culprits too.

Porting over to the production variant of the app, I am unsure how to create the client_id. Perhaps actually the app.toml isn’t really used anything other than pushing config up to Shopify, so I can just npm run start the app from my own server first without specifying the client_id. That should hopefully give me a chance on the Shopify UI side to generate this as an app. Or maybe I have to create the app on the Shopify side first and then write it in the conf. Turns out that the API_KEY is the same as the client_id, so I can first create the app on the Shopify UI side and use that client ID to push my app up hopefully (future me: that works).

And the last step would be to deploy my app to my own server, and initialize the Prisma database and run the app.

Seems like since Shopify caches configuration to your local filesystem, then I cannot risk the configuration mess anymore and have to leave these two configurations and repos completely separate, even though for a moment I thought this was not necessary.

For some reason, pushing the conf up says that the client ID does not exist. Which is weird, because it does. But I think it does not know that it belongs to me, so it might throw a 404? So how to fix this? But then I understand the previous way I did the app via the UI was just a wrong place even though it gave me similar results. It has to be done via the Shopify Partners dashboard, not as an app in the store.

Running Production

Environment Variables

From the Remix docs:

env files are only for development. You should not use them in production, so Remix doesn't load them when running remix serve.

PASSING ENVIRONMENT VARIABLES IS A NIGHTMARE because Remix thinks its users are stupid enough to commit .env files to my git repo SMH. That’s the only reason. So I literally need to make them into some magic env vars before I run the command to start the Remix process to pipe these into the app. For some reason these “environment” variables are different from the variables that are actually in the environment, seen by typing in env on a linux machine, or there’s some visibility separation going on. This gave me a headache, but at least I got that finally properly running.

Deploying & Listing

So you deploy/run your own app’s backend. You need to use Shopify CLI to deploy the extension though via the deploy command. That makes sense because the extension is hosted on Shopify’s side and hence cannot use any serious heavylifting itself, but has to connect to the backend.

With the new listing, they have now added a proxy for the webhooks via Google Pub/Sub. Not sure what that means, but I am not risking leaving anything out again, even though it sounds optional, so I go through the process. I guess it acts as a message queue so if my server is down they can be retried. But then I see that I have to also change the subscription code in my app, so I just drop the idea and ignore it (future me: seems like this was a good idea).

Sidenote: The categories you have to choose for the app might be a good indicator of the areas I can build my future app in. Perhaps I can hit an actual market better with that rather than shooting in the dark.

Listing Review Submission

I submitted the listing for the review and got it back a few days later. There are 3 required changes, and the changes have to be done within 14 days. I do like that it says that these are only minor changes that is required of me. But we’ll see…

Change required: Name Change

Update your app name to something unique. Using a generic descriptor as an app name doesn’t comply with our requirements. One simple solution is to include your company name at the beginning of your app name.

Seems like Product Vision Aligner is too generic for them. I guess it is in some way, but also there’s not many ways of describing this accurately without losing the purpose here. They suggested to use company name at the start of the name to make it unique. I could use like Silver’s Product Text Sentiment Analyzer, which both has my name in it, and I guess is slightly more accurate. But that swerves from the purpose of it, which is to align text from a sentiment standpoint. Silver’s Product Text Sentiment Aligner? I guess that would do for now. But I checked with ChatGPT and ended up with Brand Consistency Checker by Silver in the end. I quite like the “by Silver” in the end instead of the beginning. Once I tried using that config, I was met with an error that it shouldn’t be more than 30 characters. I then went with Brand Text Sync by Silver.

And then I read the guidelines again and noticed the line

“The app name can’t end with the name of your Shopify Partner account. For example, your app name can’t be “App name by Shopify Partner account name”.

It is completely ambiguous what partner account name here means, as there are no account names, just business names and individual names, and partner account IDs. But I want to avoid getting pushback, so I’m castling this to Silver’s Brand Text Sync.

Change required: Tags

Your tags should accurately reflect the primary function(s) of your app. One or more tags doesn’t fit the outlined classification listed in our guidelines.

The screenshot provided outlines the “Analytics - Other” tag and said it is not in line with the functionality. But they’re not giving me any other options either, so I am wholly confused. Ugh, the lack of clarity here.

I asked what ChatGPT thought because the list of potential tags is so huge, and it came to the same conclusion as me, but still offered something that might be better suitable for my Shopify overlords.

While there isn’t a specific subcategory or tag that perfectly fits your app’s description, choosing a main category like “Store Design” or “Marketing and Conversion” should adequately capture its primary function and help potential users understand what it offers.

But not really. None of the app categories really fit the bill here. At least I’ll avoid analytics section because it is meant for the whole store. Store Data - Other is one option. I went with that and that worked.

Change required: Billing API

Apps that do not use the Shopify Billing API cannot be distributed through the Shopify App store, unless you’ve been notified otherwise by Shopify. We can see in the app listing that the app offers a $4.99/month plan however, there is not functionality in the app UI that allows us to activate this plan.

This is interesting to me, because I would have guessed Shopify does proper user gating itself, but turns out that it’s not the case. I wonder what is it that I actually have to do then.

The implementation document for time-based subscriptions is here. But I still don’t wholly understand the logic of it. It seems like you make a request to the Admin API to create a new subscription with your inputs like price, interval and return URL, including trialDays. Ah, I think every app install is probably ought to create a new Subscription row. So I wonder if I just have to listen to install events and call the API every time for this if the user is new.

I searched for the right way to do this for a long time, but ended up finding the Remix shorthand for this all and the resulting code is pretty simple.

This is the end result:

export const loader = async ({ request }) => {  
  const { billing } = await authenticate.admin(request);  
  await billing.require({  
    plans: [MONTHLY_PLAN],  
    isTest: process.env.SHOPIFY_APP_ENV !== 'production',  
    onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),  
  });  
};

After having written this, I found out apparently there’s even a command called shopify generate billing, or at least there used to be one, because for me that did not work.

I tried launching the development environment again, but did not get any prompts for billing, unfortunately. So where should that check actually be? Is it on every request, or should I reinstall it to get it working? One standard would be to only ask it once, but that does not account for all the cases where I want to update billing for existing customers. But really the place I care to gate it behind billing would be the actual requests to ChatGPT. So I could invoke it there, but I think the Shopify reviewers-testers might find it confusing. And honestly I prefer it to be the first thing the customer agrees to - a trial and willingness to pay if they keep it.

But I found a GitHub issue that described how they’ve done it successfully multiple times, and they’d put it at app.jsx, so I follow suit and move it from the auth file. But then I get Apps without a public distribution cannot use the Billing API on my development environment. So I just went to the Partner dashboard and set it to Public distribution, without going any further. This allowed me to see the billing page.

Shopify Billing Testing

Success

I got the listing approved and now everyone can install it on their stores. For this I also had to set the isTest variable in billing code to false. But doing that, I am unable to test the shop on my test store anymore, because the test store only lets through test payments. So it goes into a loop where it expects a real payment, but only sees the fake payment. I think this is probably fine because I have my local setup to test with, and if I really need to use the production environment for some purpose, I can make a secret request to check whether the isTest should be true for my particular user.

Here’s the listing and a screenshot of what it looks like.

First Public Listing

Next Steps

This series has concluded, because the goal of getting an app successfully out on the Shopify marketplace was achieved. However, the Stair Step Method’s first step needs to actually get to a point where the apps start generating some decent revenue. For this, I will need to understand how to analyze performance of the apps I create both through normal analysis of the users’ needs and behavior, but also before I get to any users also how is one userless app better performing than the next one. Would that be the number of views of my listing page, are there some search analytics I could use? I need to set some metrics up in order for me to move closer to my goal with every app I create.

Another thing I could discover is these badges and achievements which might bring more attention to the listing, but I am not sure of that. But if this is a not-so-serious and relatively quick process, it might be worth it.


Profile picture

Hi, I'm Silver Taza! I write at the intersection of technology and entrepreneurship.  Creating value 🌟 with love ❤️ and beauty 🌹
Say hello to me on X/Twitter, so I can say hello back 🐦!

© 2024 Silver Taza