When building theme app extensions you’ll definitely need a way to detect the version of the theme (Vintage or Online Store 2.0). Because the integration for every version will be different.
The official Shopify docs provide only instruction for Node.js. So I’d like to share the approach for Rails that I was using in my latest Product Color Swatches app.
To detect the version I use shopify_api
and extend Theme
object by adding #version
method. I found this solution the cleanest and easy to use. Doing the same in GraphQL will be extremely verbose so I stick with REST and shopify_api
for this task.
You’ll need to add the following code to config/initializers/shopify_app.rb
:
class ShopifyAPI::Theme
# Specify templates required by the app (where app block will be installed).
# E.g. %w[product collection index]
APP_BLOCK_TEMPLATES = %w[product]
def assets
ShopifyAPI::Asset.find(:all, params: { theme_id: self.id })
end
def version
@version ||= begin
theme_assets = assets
template_json_files = theme_assets.select do |asset|
APP_BLOCK_TEMPLATES.any? { |t| asset.key == "templates/#{t}.json"}
end
return :vintage if template_json_files.size != APP_BLOCK_TEMPLATES.size
template_main_sections = template_json_files.map do |template|
asset = ShopifyAPI::Asset.find(template.key, params: { theme_id: self.id })
json = JSON.parse(asset.value)
main = json["sections"].find { |k, v| k == "main" || v["type"].start_with?("main-") }
if main
theme_assets.find { |asset| asset.key == "sections/#{main[1]["type"]}.liquid" }
elsif template.key == "templates/index.json"
theme_assets.find { |asset| asset.key == "sections/apps.liquid" }
else
nil
end
end.compact
sections_with_app_block = template_main_sections.map do |asset|
accepts_app_block = false
file = ShopifyAPI::Asset.find(asset.key, params: { theme_id: self.id} )
match = file.value.match(/\{\%[-\s]+schema[-\s]+\%\}([\s\S]*?)\{\%[-\s]+endschema[-\s]+\%\}/m)
schema = match && JSON.parse(match[1])
if schema && schema["blocks"]
accepts_app_block = schema["blocks"].any? { |b| b["type"] == "@app" }
end
accepts_app_block ? file : nil
end.compact
if sections_with_app_block.size > 0
:os2
else
:vintage
end
end
end
end
You can customize which templates should be checked for app blocks support in APP_BLOCK_TEMPLATES
constant. It will be different for every app. In my app users can only add app block to the product page so I need to check only product
template for blocks support.
After adding this code you can use it like this:
theme = ShopifyAPI::Theme.find(THEME_ID)
theme.version
# => :os2 or :vintage
Very easy. I like that solution and I wish we could add this code to shopify_api
gem eventually. In the next articles, I will tell more about my experience building theme extension.
If you like reading about Shopify app development follow me on Twitter.
UPD Nov 20, 2022:
I’ve updated the code to support ShopifyAPI 10+:
# config/initializers/shopify_app.rb
Rails.application.config.after_initialize do
class ShopifyAPI::Theme
# Specify templates required by the app (where app block will be installed).
# E.g. %w[product collection index]
APP_BLOCK_TEMPLATES = %w[product]
def assets
ShopifyAPI::Asset.all(theme_id: self.id)
end
def version
@version ||= begin
theme_assets = assets
template_json_files = theme_assets.select do |asset|
APP_BLOCK_TEMPLATES.any? { |t| asset.key == "templates/#{t}.json"}
end
return :vintage if template_json_files.size != APP_BLOCK_TEMPLATES.size
template_main_sections = template_json_files.map do |template|
asset = ShopifyAPI::Asset.all(theme_id: self.id, asset: {key: template.key}).first
json = JSON.parse(asset.value)
main = json["sections"].find { |k, v| k == "main" || v["type"].start_with?("main-") }
if main
theme_assets.find { |asset| asset.key == "sections/#{main[1]["type"]}.liquid" }
elsif template.key == "templates/index.json"
theme_assets.find { |asset| asset.key == "sections/apps.liquid" }
else
nil
end
end.compact
sections_with_app_block = template_main_sections.map do |asset|
accepts_app_block = false
file = ShopifyAPI::Asset.all(theme_id: self.id, asset: {key: asset.key}).first
match = file.value.match(/\{\%[-\s]+schema[-\s]+\%\}([\s\S]*?)\{\%[-\s]+endschema[-\s]+\%\}/m)
schema = match && JSON.parse(match[1])
if schema && schema["blocks"]
accepts_app_block = schema["blocks"].any? { |b| b["type"] == "@app" }
end
accepts_app_block ? file : nil
end.compact
if sections_with_app_block.size > 0
:os2
else
:vintage
end
end
end
end
end