Shopify Theme
Contents |
Theme customizations with ThemeKit
https://www.shopify.com/partners/blog/78118150-4-essential-tips-for-building-your-first-shopify-theme https://www.youtube.com/watch?v=1xWFsYmBoX0
Install with using instructions on https://shopify.github.io/themekit/.
On your store admin create private app (name could be themekit
, email could be
your email) and in “Admin API” add read/write access
to Theme templates and
theme assets. Save password to SHOPIFY_PASSWORD
or in keys like
# https://business-casual-theme.myshopify.com/admin/apps/private/203168317509
export SHOPIFY_STORE_URL=business-casual-theme.myshopify.com
export SHOPIFY_API_KEY=123123
export SHOPIFY_PASSWORD=123123
# minimal theme
export SHOPIFY_THEME_ID=82209210448
Find theme id with
theme get --list -p $SHOPIFY_PASSWORD -s $SHOPIFY_STORE_URL
Available theme versions:
[177706181][live] debut
and save 177706181 to SHOPIFY_THEME_ID
. Download theme with
theme get -p $SHOPIFY_PASSWORD -s $SHOPIFY_STORE_URL -t $SHOPIFY_THEME_ID
# this will also create config.yml with password, theme_id and store url
# so you do not need to use params (and env variables)
or you can create new theme based on Timber.
theme new -p $SHOPIFY_PASSWORD -s $SHOPIFY_STORE_URL -n 'New Theme'
Timber template is outdated since it does not use sections or snippets so do not use it. Better is to look ata debut (default) theme create file one by one.
Publish current theme_id from config.yml
theme publish
You can save some variables to config.yml
(flags like -p will override config
values)
theme download # fetch all files (very slow)
theme deploy # remove destination, copy all files to destination
theme open # open in browser and show url
theme watch # deploy change when it occurs, enable hooks like LiveReload
Watching will show eventual errors in liquid tags, wrong schema in sections… Enable live reload https://shopify.github.io/themekit/faq/ with browser sync (I did not try prepros) https://medium.com/@jamesauble/auto-refresh-for-shopify-development-using-browser-sync-470b84e7e9cb Also this issue https://github.com/Shopify/slate/issues/1055#issuecomment-633019928
browser-sync start --proxy https://bootstrap-business-casual-theme.myshopify.com/ --files "*/*.*" --reload-delay 1000 --config bs-config.js
Folder structure
Root index home page is in templates/index.liquid
, assets goes to assets
.
Those routes maps templates.liquid. You can use liquid object routes
to
generate those routes https://shopify.dev/docs/themes/liquid/reference/objects/routes
/blogs/blog-name/article-id-handle
article.liquid (you can create another new template for exampletemplate/article.video.liquid
so on admin you can choose one of thow two templatesarticle
andarticle.video
)/blogs/blog-name
blog.liquid/cart
cart.liguidroutes.cart_url
,routes.cart_add_url
,routes.cart_change_url
,routes.cart_clear_url
/collections
list-collections.liquidroutes.collections_url
/collections/collection-hadle
/collections/collection-handle/tag
collection.liquidroutes.all_products_collection_url
/
index.liquidroutes.root_url
/pages/page-handle
page.liquid/products
list-collections.liquid/products/product-handle
product.liquid/search?q=search-term
search.liquidroutes.search_url
unknown
404.liquid
Inside template we can include small snipipet snippets/social-sharing.liquid
and provide params { % include 'social-sharing', share_title: product.title %}
If you need user to be able to customize snippet using browser than you need to
create section with schema
. For example they can upload logo, update copy.
For new type of section files, instead of include
we need to use render
like
{ % render "shopify://apps/product-reviews/snippets/star-reviews/5fb1e11a-9ef0-4898-892f-3feba729af78" %}
config/settings_data.json
and config/settings_schema.json
are used for
global theme settings (not local section settings). For example, when you change
global background color or add dynamic section, or change some section value, it
will be saved to config/settings_data.json
.
Sections
Include section with { % section 'my-section' %}
.
https://www.youtube.com/watch?v=jzhsYMxUp8s
https://shopify.dev/tutorials/add-an-app-section-to-your-app note that sections
could be defined in apps, so merchant can use them without changing theme (theme
has to be sections-compatible).
# sections/my-section.liquid
<div id="my-section">
<h1></h1>
<h3></h3>
</div>
{ % schema %}
{
"name" : "My Section",
"settings": [
{
"id": "header-id",
"label": "Header text",
"type": "text",
"default": "Header text here"
},
{
"id": "content-id",
"label": "Content text",
"type": "richtext",
"default": "<p>Add here</p>"
}
],
"blocks": [
{
"name": "Images",
"type": "image",
"settings": [
{
"type": "image_picker",
"id": "image",
"label": "Your image"
},
{
"type": "text",
"id": "image-name"
"label": "Name of image",
}
]
}
]
}
{ % endschema %}
{ % stylesheet %}
{ % endstylesheet %}
{ % javascript %}
{ % endjavascript %}
Home page is different because it contains { { content_for_index }}
which
means that it can use presets sections (sections can be added, removed,
reordered dynamically by online editor). When you define presets
than you can
include section on home page. So for example on home page you can have both ways
(one dynamically from content_for_index
and one statically - hardcoded
section 'my_section'
). Note that statically included section has global data,
for example on page1 and page2 section will look the same (if you update on
page1 it will be updated on page2 since it use the same global data).
Data for settings is single (can not contain nested settings), but for blocks
user can create more than one instance of settings properties.
Example of blocks and presets:
Glossary https://shopify.dev/docs/themes/sections#glossary-of-terms
Validate json shema https://jsonlint.com/
https://shopify.dev/tutorials/develop-theme-use-sections In schema you can define properties of section:
- name: required, it is shown in editor sidebar
- class: additional css classes to
shopify-section
class - tag: default is
<div>
it includes also the id and class, like<div id="shopify-section-[id]" class="shopify-section">
- locales: define translated words
- max_blocks: number of block items that user can create, same effect is done
with
limit
attribute inside"blocks": [ { "name": "1", "limit": 2 } ]
(limit is only usefull when you have several blocks with different max length) -
default: for sections that are statically included
- settings is array of: https://shopify.dev/docs/themes/settings
- id
- type
- label
- default (optional)
- info (optional, use like a placeholer)
- blocks is array of: name, type(not sure what is this), settings (array of elements so when adding new item, it will contain all those elements)
- presets is array of: name (this is shown on editor sidebar when selecting new
section), category (Look as sidebar for example categories case sensitive),
settings (object, not array) it will be assigned to the section when user adds
to a home page, it overrides default value
"settings": { "my-id": "Hi" }
, blocks (array of objects withtype
and eventualsettings
as object) this will assign default initial values for blocks, note that settings is object, not array https://shopify.github.io/liquid-code-examples/example/homepage-quotes<h1></h1> <ul> </ul> { % schema %} { "name": "Quotes", "settings": [ { "id": "quote-header", "type": "text", "label": "Quote header label", "default": "Defalt value" } ], "blocks": [ { "name": "My Quote", "type": "text", "settings": [ { "type": "text", "id": "quote-text", "label": "My quote text" } ] } ], "presets": [ { "name": "Quote preset name", "category": "Text", "settings": { "quote-header": "My example header" }, "blocks": [ { "type": "text", "settings": { "quote-text": "My first text" }}, { "type": "text", "settings": { "quote-text": "My second text" }} ] } ] } { % endschema %}
We can have several types: https://shopify.dev/docs/themes/settings#input-setting-types simple:
- text, richtext
- image_picker (use it with
img_url
filter assection.settings.my-image | img_url: "450x450"
). - radio for example
"type": "radio", "id": "display_type", "label": "Select collections to show", "default": "all", "options": [ { "value": "all", "label": "All" }, { "value": "selected", "label": "Selected" } ]
- select, checkbox, range
- header (this is just placeholder text on sidebar, it should have “content” attribute)
- link_list (picker for menus)
special:
- product it is just handler, so to access actual product you need to use ``
- collection (it contains ‘Edit collection’ link) it is just handler, so to
access actual collection you need to use
collections[block.settings.collection]
. Note that it could be empty so to skip those not selected collections you need to use{ % for block in section.blocks %} # do something with collections[block.settings.collection]
- url
- page
https://shopify.dev/tutorials/develop-theme-use-sections#javascript-and-stylesheet-tags
To pass custom properties to javascript, you can use
data-slide-speed=""
Tips
Design tutorials youtube list https://www.youtube.com/playlist?list=PLlMkWQ65HlcEJMRRdnqxpbGImqBkIOctd https://shopify.github.io/liquid-code-examples/
Accessible pagination
Adding cart custom properties
You need to pass properties[name-of-property]
to the /card/add
. If the
name-of-property
starts with underscore _
than it wont be shown on checkout.
You can use
https://ui-elements-generator.myshopify.com/pages/line-item-property
to generate something like
<p class="line-item-property__field">
<label>Layout</label><br>
<input required class="required" type="radio" name="properties[Layout]" value="left"> <span>left</span><br>
<input required class="required" type="radio" name="properties[Layout]" value="right"> <span>right</span><br>
</p>
Cart permalinks
To create checkout link (direct to give me money page). First you need to find
variant id using xml extension admin/products/123.xml
than add to /cart
path
comma separated list of variant-id:amount /cart/123:1.456:2
(note that this
will override existing cart items)
Liquid Tags
Liquid for designers and http://shopify.github.io/liquid/ are good for start. https://help.shopify.com/en/themes/liquid http://cheat.markdunkley.com/ image size, product variables…
For new liquid features on shopify look for example at https://shopify.dev/docs/themes/liquid/reference/filters/media-filters
Tags
Tags are for programming logic
{ % assign my_var = [1, 2] %}
assigns some value to variable. instead of checking if something is not empty you can use default, for example ``{ % capture my_id %} name-{ { item.name | handleize }} { % endcapture %}
captures block text{ % include some_file, my_variable: 'some_value' %}
include snippet, you can pass arguments to include and use it inside. Other way is to define variable in parent templateassign my_variable = 'some_value'
. Or you can use keywordwith
to assign local variable with the same name as section{ % include 'color' with 'red' %}
so you can use `` inside section.-
{ % comment %} my comment { % endcomment %}
{ % if statement %} { % elsif false %} { % endif %}
statement can be some of the operators- comparison
==
,!=
,<=
(product.type == 'Shirt'
) - arrays or string
contains
(product.tags contains 'outdoor'
orunless image contains featured_image
) - boolean operator
and
or
are evaluated from right to left. Note that there is no parenthesis so you need to use order (true or false and false
is true) or nestedif
statements - There is no negative, nor
!
so you can useunless
in this case.
- comparison
{ % for item in array %} { % break %} { % continue %} { % endfor %}
for loop- when iterating a hash
item[0]
is key anditem[1]
is value - iterating over ranges
{ % for i in (1..item.quantity) %}
or{ % for member in ste.data.members %}
- helper variables inside loop
forloop.length
,forloop.index0
(foorloop.first
)forloop.last
- 3 optional arguments
{ % for item in array limit:2 offset:3 reversed %}
- you can use
{ % else %}
to show when array is empty
- when iterating a hash
-
{ % cycle 'blue', 'white', 'red' %}
will repeat those colors, Usefull when iterating for bootstrap row col { % case my_var %} { % when 'dule' or 'mile' %} { % else } { % endcase %}
{ % tablerow product in collection.products cols:2 limit:3 offset:2 %}
Objects
Objects are used to show some informations { { product.title }}
. You can
access page using it’s handle. Handle is automatically created based on title,
(space is replaced with dash, number is added if already exists). This two are
equivalent
Product handle can be edited using ‘Edit website SEO’ on product page. Product
handle determines url ie path after /products/product-handle
.
Global variables on shopify are:
all_products['short'].title
articles['news/hello']
blogs.myblog.articles
cart
for examplecart.item_count
. You can add input for notesname="note"
collections.frontpage.products
collection
inside collection template you can usecollection
current_page
if available on paginated contentcurrent_tags
customer
linklists
for examplefor link in linklists.main-menu.links
,link.url
,link.active
,link.title
,link.links
images
pages
page_description
description of product, page or collection that is renderedpage_title
usually the name of product, page or collection, can be overridden with page title in Search enging listing preview sectionproduct
(intemplate/product.liquid
) https://shopify.dev/docs/themes/liquid/reference/objects/productproduct.selected_or_first_available_variant
first or based on?variant
parameter (also booleanproduct.has_only_default_variant
)product.options_with_values
returns some json with “name” and “values”[{"name":"Size","position":1,"values":["single","double","triple"]}]
product.variants
returns array of variant objects (number_of_values ** per each option), each variant hasvariant.option1
andvariant.option2
andvariant.option3
.product.url
is path (domain is inshop.url
) you can reference product inside collection using ``cart_item
is line_item ofcart.items
, it containsitem.options_with_values
array of hash for each variant type[{"name":"Size","value":"double"},{"name":"Color","value":"Red"}]
recommendations
settings
global settings object fromconfig/settings_schema.json
template
name without .liquid, used like<body class=''>
handle
returns handle for blogs, articles, collections, pages and productscanonical_url
return url without parametars (like collection or variant selected)shop
, for exampleshop.url
There are three content objects that need to be included in layout files:
content_for_header
(inside <head>
used for plugins) and content_for_layout
(similar to yield, in theme.liquid
inside <body>
) and content_for_index
(in templates/index.liquid
which is landing page and is used for dynamic
sections which can be edited with theme editor).
You can create another layout/second_layout.liquid
file and instruct template
to use it (for templates/404.liquid
you can use another layout)
# templates/product.liquid
{ % layout "second_layout.liquid" %}
To include assets you can use filters. Note that you can not have subdirectories subfolders under assets folder so all files should be inside same directory,
# layout/theme.liquid
application.scss.css
application.js
# or if you need custom attribute like defer, you can use
<script src="theme.js" defer="defer"></script>
Types:
- boolean, nil, string, number, array, hash and EmptyDrop
- number can be incremented
{ % increment my_int %}
or{ % decrement my_int %}
- range is defined similar to ruby
{ % for i in (1..my_int) %}
- to use sum operation, for example add two number you can use
{ % for i in (1..number_of_columns) %}
- array elements can be accessed only like
my_array[2]
- hash elements can be accessed with
my_hash['name']
ormy_hash.name
- you can call
my_array.size
ormy_hash.size
- all yml files from
_data
folder will be available undersite.data.file_name.item
- emptydropt exists when you access non existing object
pages.this-handle-does-not-exists
- to check if object exists you can use
or for strings you can use
blank
- to remove empty line around command tags you can use minus
{ %- -%}
Filters
Filters or pipes is used to process data inside { { }}
. Filter nam could be
followed with colon :
to pass additinal params, for example { { page.path |
split: '/' | first | alert }}
- strings
append
,prepend
,capitalize
,date
,escape
,lstrip
,replace
,strip_html
,truncate
,url_encode
,remove
,remove_last
To create id or class from title (ie replace space with dash), you can use filter handleize or handleid={ { page_title | handle }}
. { { page.date| date: 'B %d, %Y' }}
or{ { page.date | date_to_string }}
or{ { page.date | date: '%d-%m-%Y]]
- arrays
first
,join
,last
,map
,reverse
,size
,slice
,uniq
map
uses string as argument (" "
are required). Another example is liquid github- create array with
{ % assign my_array = "ants, bugs, bees, bugs, ants" | split: ", " %}
- append to array in for loop you can use two approaches
capture
orappend
with asplit
. Note that you can only create array of string. There isconcat
filter that joins two arrays, but you can not create array of object, you can create only array of strings.# first approach is using append (preferred) # another approach is using capture
- filter array using where condition
https://shopify.github.io/liquid/filters/where/
assign row_of_variants = product.variants | where: "option1", value_of_first_option_with_values
- url
like
article | img_url: '400x300' | img_tag: article.title
(width*height, if request “400x300” is smaller than original image’s dimension, shopify will scale the image for you and serve that scalled image). For product img_url will use featured image. If no dimension is specified, it will be small 100x100 so better is to use specific size (original image will preserve aspect ration when it is scalled down, shopify will never scale up image). You can also use crop and scale filter. - money (convert cents to number and currency)
variant.price | money
If you need to assign filter output to variable you can use { % capture
my_var %} { { var | my_filter }} { % endcapture %}
or use it inside
assign tag { % assign all_categories = site.posts | map: "categories" %}
.
Debug
Debug liquid is simply output { { my_var | inspect }}
or with objects you can
use { { product.variants | json }}
Somehow if I use contains_
variable name
{ % assign contains_sidebar = true %}
{ { contains_sidebar }}
than I got error like:
[:comparison, "contains"] is not a valid expression in "contains_sidebar ==
false" in /_layouts/page.html`
Sho I rename variable { % assign show_sidebar = true %}
Localization
https://shopify.dev/tutorials/develop-theme-localization-use-translation-keys i18n
Translated content is escaped by default (any html like <
is converted to
equivalent) so if you need raw html than use capture
# theme.liquid
<h1>layout.header.welcome</h1>
# locales/en.default.json
{
"layout": {
"header": {
"support_link": "support",
"welcome": "Welcome to my store. Please contact
<a href="https://support.mystore.com">layout.header.support_link</a>
should you
need any assistance."
}
}
}
Environment
You can switch theme id in config.yml
so you can theme download
or theme
deploy
.
Shopify app cli
Install based on instructions on https://shopify.github.io/shopify-app-cli/
eval "$(curl -sS https://raw.githubusercontent.com/Shopify/shopify-app-cli/master/install.sh)"
shopify create
shopify serve
but I receive an error https://github.com/Shopify/shopify-app-cli/issues/463
.shopify-app-cli/lib/shopify-cli/tasks/update_dashboard_urls.rb:12:in `call': undefined method `[]' for nil:NilClass (NoMethodError)
When I shopify logout
than I receive
/home/orlovic/.shopify-app-cli/lib/shopify-cli/tasks/update_dashboard_urls.rb:27:in `check_application_url': undefined method `match' for nil:NilClass (NoMethodError)
Note that this is not the same as gem shopify
(which does not include cli).
Tutorials
Videos: Shopify Partners https://www.youtube.com/channel/UCcYsEEKJtpxoO9T-keJZrEw for example Getting Started with Shopfy Themes https://www.youtube.com/watch?v=-IGvYvL86_M
Partner academy: https://partner-training.shopify.com/catalog https://partner-training.shopify.com/outline/r5uxatsx/cover https://partner-training.shopify.com/outline/uhxkymdg/cover
TODO: https://www.shopify.com/partners/blog/the-essential-list-of-resources-for-shopify-app-development