Now that you got your Shopify website up and running. Why not add some branding on your checkout page? In this example, we'll add a custom font to our checkout page. This blog assumes you have read my past blogs about Shopify Checkout Extensibility and Shopify in general. I will not write about how to set up a development store and a checkout app. Please read my past blogs on how to do it. I will just go straight into how to configure the custom font for your checkout app.
Default Font
Below is the default font for the checkout page. This is what it looks like before branding our checkout page.
Upload the WOFF File
Upload the custom font (e.g. Playmaker_D.woff) to Shopify Admin > Content > Files. Custom fonts must be in either Web Open Font Format (WOFF) or WOFF2. Once uploaded, we'll need the gid of the WOFF file. There are two ways of grabbing the gid. One is by inspecting it in web tools and it's usually the ID of the TR element. Second is by sending a GraphQL query. We use GraphiQL to run our queries. Make sure the GraphiQL app has the read_checkout_branding_settings and write_checkout_branding_settings access scopes enabled. As you will see below, they both show the same gid.
Next, we'll need the checkout profile ID. This is the target for the custom font changes. Go to Checkout > Customize. The profile ID should appear on the URL address bar after /profiles as can be seen below.
Apply Custom Font
Apply the custom font to primary and secondary surfaces. Primary surfaces include text and buttons. Secondary surfaces include headings. You should see the changes right away. And that is all there is to it. Super simple, isn't it?
If you are working on a Shopify project, chances are you'll have multiple stores. One for development, one for pre-production and one for production or live which is accessible to the public. Because of this, you'ld probably want these stores to have the same products. Although there is an option to import/export products in Shopify admin, it does not copy over the metafields. And you're in deep equine effluence if rely heavily on metafields.
In this example, we are going to learn how to copy a product to another store using Shopify GraphQL. This example builds upon my previous blog, Shopify Bulk Query Example. I would recommend you read the previous blog if you want to know more about the cached products. I will only talk about how to copy a product to another store in this article. Okay, let's get to it.
Target Store
As mentioned above, we are building upon the previous example, Shopify Bulk Query Example. So we already have a Quickstart store in place. Let's create a target development store. On the Shopify partners page go to Stores -> Add store -> Create development store. Choose "Create a store to test and build", fill in the form as appropriate. Example Copy Product store:
After the target store has been created, it shouldn't have any products like below.
Custom App on Target Store
On the target store, go to Settings -> Apps and sales channels -> Develop apps. Allow custom app development and Create an app. Let's name it Product API. On the Configuration tab you must have the access scopes write_products and read_products. As of this writing, the webhook version is 2024-01. Install the app. On the API credentials tab, take note of your Admin API access token (you'll need to add this in the request header). Keep your access tokens secure. Only share them with developers that you trust to safely access your data. Take note of your API key and secret key as well. Our Copy Product store is now configured and ready to create products. For detailed steps on how to create a custom app, please take a look at my previous blog, Consuming Shopify Product API Example.
Config Changes
First off, we'll need to add the target store to our config file and update our code. Should be self explanatory based on the names. We have a source store and a target store. We copy the product from the source store (i.e. endpoint) over to the target store (i.e. targetEndpoint)
In this part of the code, we pull the gid of the product that we are going to copy from the query parameter. We then find it if that product exists in the product cache. We call the CopyProduct function which returns an error if the copy was unsuccessful and then we send back a Copy product failed message to the caller in JSON format. For a successful copy, the source product information is returned.
Nothing fancy. Just an update on the data model based on Shopify documentation. For more information, go to Shopify Docs.
bulk-query.go
// snipped...
type ProductCreate struct {
Product Product `json:"product,omitempty"`
}
type Data struct {
BulkOperationRunQuery BulkOperationRunQuery `json:"bulkOperationRunQuery,omitempty"`
CurrentBulkOperation BulkOperation `json:"currentBulkOperation,omitempty"`
ProductCreate ProductCreate `json:"productCreate,omitempty"`
}
// snipped...
This is where all the grunt work happens. The first lines of the copy product function will create the metafields portion of the GraphQL query. The metafields portion is then inserted to the productCreate GraphQL mutation query. For this example, we are going to assume all metafields are of type single_line_text_field. Because of this, the caveat is, this will not work if the metafield of the product you are going to copy is of a different type. The request is then sent to the target endpoint with the target accss token in the header. If all goes well, it will print the gid of the newly created product. Otherwise the error is passed to the caller to handle.
I'm assuming you have read my previous blogs, Consuming Shopify Product API Example and Shopify Bulk Query Example. So you should be able to run the golang app and hit the endpoints with Postman. First, let's fetch the cached products and I've chosen gid://shopify/Product/8787070452004 as my source product like so.
Second, let's hit the copy product endpoint and provide the gid query parameter. A successful copy returns the source product information like so.
On the logs, we should see something like below.
Found: gid://shopify/Product/8787070452004
++ copy product
Response status: 200 OK
New product copied: gid://shopify/Product/8343228219651
2024/03/16 22:35:45 "GET http://localhost:4000/copy-product?gid=gid://shopify/Product/8787070452004 HTTP/1.1" from [::1]:51397 - 200 283B in 22.7344888s
Before we can see the metafields on the Shopify admin product page, we need some bit of configuration for the metafields to show on the product page. Go to Settings -> Custom Data -> Products -> Metafields without a definition. Our namespace and key have been created. Add the definition (i.e. name, description and type). After saving, it will appear in the product page.
Finally, we can see our copied product along with the tags and metafields in all its glory.
Copy Shopify Product Wrap Up
There you have it. A way to copy a product from store to store in Shopify using Shopify GraphQL Admin API. This should form as a base for copying products between stores. Grab the full repo here, github.com/jpllosa/shopify-product-api/tree/copy-product.
When should I use bulk query? Read on and let's analyse it together.
This blog builds upon my previous blog, Consuming Shopify Product API Example. We'll compare the bulk query with the non-bulk query we did on that example.
Shopify Bulk Query Changes
These are the changes we made to the previous example so we can do Shopify bulk query.
Just a few changes here. We created a new package named service to handle the bulk query operations and a new path to hit to pull the cached products. Quick and easy.
model.go
package service
type Node struct {
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Handle string `json:"handle,omitempty"`
Vendor string `json:"vendor,omitempty"`
ProductType string `json:"producType,omitempty"`
Tags []string `json:"tags,omitempty"`
Namespace string `json:"namespace,omitempty"`
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
ParentID string `json:"__parentId,omitempty"`
}
type Product struct {
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Handle string `json:"handle,omitempty"`
Vendor string `json:"vendor,omitempty"`
ProductType string `json:"producType,omitempty"`
Tags []string `json:"tags,omitempty"`
Metafields []Metafield `json:"metafields,omitempty"`
}
type Metafield struct {
Namespace string `json:"namespace,omitempty"`
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
ParentID string `json:"__parentId,omitempty"`
}
This is where all the magic happens. We issue a bulk query request via mutation operation. We then unmarshall the response and check the bulk operation status if it has been created. If it was created, we then poll the current bulk operation until it is completed, canceled, failed, etc. Once it is completed, we download it and save it into a temporary file. This file will be in JSONL (JSON Lines) format, then we will have to parse the file in order to build the product tree.
There is also a webhook way of checking the bulk operation status. It is recommended over polling as it limits the number of redundant API calls. But for the purposes of this example, we'll do polling.
On start up, you should see somethingl like below. Shopify has returned with a download URL. Which means the bulk query has completed and we are ready to serve the cached products.
Comparison with Non-Bulk Query
Now let's compare the new way of pulling data to the old way.
Can you spot the difference? In terms of speed, the response time of the new way was clearly super fast. Just 4ms compared to 279ms, imagine that? What's more is that not only did the old way take longer, it returned less data. It return 639 B compared to 4.65 KB. In other words, in the old way we only received 3 products while in the new way we received all products including metafields. That's an icing on the cake. As for start up time of the app, it was negligible.
Shopify Bulk Query Wrap Up
Would you do bulk query now or not? It is up to you to identify a potential bulk query. Queries that use pagination to get all pages of results are the most common candidates. There are limitations on a bulk query though. For example, you can't pull data that's nested two levels deep. Check the Shopify documentation for more information.
There you have it. Another way to pull product data from the Shopify GraphQL Admin API. If you got a better way of doing things (e.g. how to parse the JSONL better), just raise a pull request. Happy to look at it. Grab the repo here, github.com/jpllosa/shopify-product-api, it's on the bulk-query branch.
In this blog, we are going to learn how to pull product data from the Shopify API. To build upon previous examples like Starting Out on the go-chi Framework and GraphQL with Postman, we will implement this in Go using the go-chi framework and hit the Shopify GraphQL endpoint. Let's begin.
User Story
As a user, I want to retrieve my Shopify products in JSON format, so I can use it in my analytics software.
Shopify Setup
First off, I will not go into too much detail in setting Shopify up. I will let the Shopify documentation do that for me. To get this example running, you'll need a Shopify Partner account. When I created my Shopify partner account, it was free and I don't see any reason that it won't be in the future. Once you have a pertner account, create a development store or use the quickstart store with test data preloaded.
Go into your store, then Settings, then Apps and sales channels, then Develop apps and then Create an app.
On the Configuration tab you must have the access scopes write_products and read_products. As of this writing, the webhook version is 2023-10.
On the API credentials tab, take note of your Admin API access token (you'll need to add this in the request header). Keep your access tokens secure. Only share them with developers that you trust to safely access your data. Take note of your API key and secret key as well.
There you have it. We are all set on the Shopify configuration end.
Testing the Shopify API
Before we go straight into coding, let's test the API call first. Fire up Postman and let's hit the GraphQL endpoint. The URL is of the form https://<storename>.myshopify.com/admin/api/<version>/graphql.json. Add X-Shopify-Access-Token in your header and the value is your Admin API access token. We'll do a query for the first 5 products. The image below shows we can happily make a request. Go to GraphQL Admin API reference for more details.
The first thing the program does is load up the configuration. As mentioned above, we are building upon a previous example, Starting Out on the go-chi Framework. We utilize the go-chi framework for routing incoming HTTP reqeusts. Any requests to the root resource will return a status in JSON format. Any requests to the /products resource will return a list of products in JSON format. I will not expand on router.GetStatus because I have explained it in my previously mentioned blog, Starting Out on the go-chi Framework. I just moved it into a new package making it more organized.
Making the configuration file in JSON format will make it easy for us to use. We simply read the file and unmarshal the byte array into our Config type and bob's your uncle. Then we easily get the value accessing the fields as seen in main.go(e.g. fmt.Sprintf(":%v", config.Port)).
JSON Marshaller
json.go
func Marshal(v any) []byte {
jsonBytes, err := json.Marshal(v)
if err != nil {
panic(err)
}
return jsonBytes
}
func Unmarshal[T any](data []byte) T {
var result T
err := json.Unmarshal(data, &result)
if err != nil {
panic(err)
}
return result
}
Nothing special with this file. It is just a wrapper of the standard JSON Go library. Didn't want to muddy the resource handlers with JSON error handling.
Get Products
products.go
// snipped...
type GqlQuery struct {
Query string `json:"query"`
}
type GqlResponse struct {
Data Data `json:"data,omitempty"`
Extensions Extensions `json:"extensions,omitempty"`
}
type Data struct {
Products Products `json:"products,omitempty"`
}
type Products struct {
Edges []Edge `json:"edges,omitempty"`
}
type Edge struct {
Node Node `json:"node,omitempty"`
}
type Node struct {
Id string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Handle string `json:"handle,omitempty"`
Vendor string `json:"vendor,omitempty"`
ProductType string `json:"productType,omitempty"`
Tags []string `json:"tags,omitempty"`
}
type Extensions struct {
Cost Cost `json:"cost,omitempty"`
}
type Cost struct {
RequestedQueryCost int `json:"requestedQueryCost,omitempty"`
ActualQueryCost int `json:"actualQueryCost,omitempty"`
ThrottleStatus ThrottleStatus `json:"throttleStatus,omitempty"`
}
type ThrottleStatus struct {
MaximumAvailable float32 `json:"maximumAvailable,omitempty"`
CurrentlyAvailable int `json:"currentlyAvailable,omitempty"`
RestoreRate float32 `json:"restoreRate,omitempty"`
}
func GetProducts(config config.Config) chi.Router {
router := chi.NewRouter()
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
productsGql := fmt.Sprintf(`{
products(first: 3, reverse: false) {
edges {
node {
id
title
handle
vendor
productType
tags
}
}
}
}`)
query := GqlQuery{
Query: productsGql,
}
q := marshaller.Marshal(query)
body := strings.NewReader(string(q))
client := &http.Client{}
req, err := http.NewRequest(http.MethodPost, config.Shopify.Endpoint, body)
if err != nil {
panic(err)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("X-Shopify-Access-Token", config.Shopify.AccessToken)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status)
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
gqlResp := marshaller.Unmarshal[GqlResponse](responseBody)
jsonBytes := marshaller.Marshal(gqlResp.Data.Products.Edges)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(jsonBytes)
})
return router
}
Welcome to the heart of this article. We start by creating the type struct of the GraphQL response so we can easily unmarshal it. As you saw earlier in Testing the Shopify API, the Postman image shows a snapshot of the GraphQL response from Shopify. When GetProducts is called, it starts by creating the GraphQL query for products. Marshal the query into a byte array then passes it into a new HTTP request. We create an HTTP client so we can add in the Shopify Access Token in the header and the content type. We then send the request by calling client.Do(req) and read the response. Converting the response into a byte array so we can unmarshal it. After that we strip out the stuff we don't need. We only need the Nodes. Once we have the nodes, we convert it into bytes and send it back. That is all there is to it. Oh yeah, don't forget to close response after using it.
Output
I've set my port to 4000 so hitting http://localhost:4000 will return a response like below:
{
"id": 1,
"message": "API ready and waiting"
}
Hitting http://localhost:4000/products will return a response like below:
There you have it. A way to pull data from the Shopify GraphQL Admin API and spitting it out in a slightly different format. In between we utilized the go-chi framework for routing requests. We used the standard Go library for JSON manipulation, HTTP communication and file access. Lastly, did we satisfy the user story? Grab the full repo here, github.com/jpllosa/shopify-product-api.
This article is an example of how to create GraphQL requests with Postman. We will be utilizing the public Star Wars GraphQL API for this practice.
GraphQL
What is GraphQL? graphql.org states that it is a query language for APIs and a runtime for fulfilling those queries with your existing data. It provides a complete and understandable description of the data in your API. Gives clients the power to ask for exactly what they need and nothing more. Makes it easier to evolve APIs over time. Enables powerful developer tools. Did that make sense? Maybe some examples might help.
GraphQL came to be because of the shortcomings of RESTful APIs. One of the issue with a RESTful API is that they overfetch or underfetch data. That means, a single request either gets too much or too little data. Another problem is you might need to hit multpile endpoints just to get the data that you want.
Enter GraphQL. You'll only need one endpoint, supply it with the query language data and receive only the data that you asked for. Another advantage is its type system. This typed schema ensures that we only ask for what's possible and provide clear and helpful error even before we send a request via tools (e.g. Postman).
There it is, a better succuessor to REST.
Configuring Postman
I'm using Postman for Windows v10.17.4. There are two ways of making a GraphQL request in Postman. First is the good old fashioned HTTP request and the other is the GraphQL request. Clicking New, you should be able to see something like below:
GraphQL New
HTTP Way
We'll use the public Star Wars GraphQL API for this example. We'll hit the graph endpoint, https://swapi-graphql.netlify.app/.netlify/functions/index with the query:
query Query {
allFilms {
films {
title
}
}
}
To understand the query in more detail, we'll have to read the Star Wars Schema Reference. But this example is not about that. We requested all the Star Wars film titles. We got back something like below:
GraphQL HTTP
Things to note on the above image. The selected radio button is GraphQL. Next is the Schema Fetched text. This tells us that Postman will do type checking based on the schema and will offer suggestion and syntax checking (red line under the letter d of dir). Even before we send the request, we already know that it will cause an error.
GraphQL Context Suggestion
GraphQL Error Field
GraphQL Way
The GraphQL way is even better. We can create the query just by ticking the checkboxes. Thank you typed schema! Need I say more?
GraphQL Way
We can also do variables. Take note of the id of "A New Hope". We can pass in arguments like so:
GraphQL Way Variable
GraphQL with Postman Summary
So far, we have only touched up on query. We haven't done any mutatation since the free public Star Wars API doesn't support it. But to explain it succinctly, query is fetching data while mutation is writing data. Congratulations! You have now taken the first few steps in using Postman for your GraphQL needs. You have learned to create a request. Add variables to vary your request. All done in just a few minutes. There you have it. A simple example of how to use GraphQL with Postman.