The Panda admin offers comprehensive tools for developing your own design templates.

In short we have support for;

  • Template based rendering

  • Serverside scripting language

  • Configurable theme settings

  • Theme specific language settings

  • SASS stylesheets with imports

  • Code editor with syntax highlighting.

  • Includable source code snippets

  • Automatic file versioning

  • Ajax API endpoints

  • Uploadable asset files

  • Image rescaling

The Code Editor

When working with templates you need to have the code editor open along with a preview of the shop. You can toggle the code editor by clicking on the </> button in Theme settings and open the preview in a separate window or browser tab. When you save any changes the preview is reloaded automatically.


If you need more space for editing you can hide the Theme settings by clicking the < Hide button.
Don’t worry when editing files, backups are automatically created for you. To restore a file, click on the Old versions button and select the timepoint you’d like to restore from.

Panda templating

All files with a .panda extension can contain code that is evaluated server-side. The server side language is based upon Liquid templates. There are lot of information on the internet on how to work with Liquid templates so we’ll just skim through the basics here.

Code blocks

Code is evaluated serverside in code blocks surrounded by handlebars {{ …​ }} or {% …​ %}. The first form is used when you want to output text, the latter form is used when you want to evaluate code without rendering any output.

Example of code evaluation and output
{% let x = 10 %}
{% if x > 5 %}
   {% if product != blank %}
      <div data-product="{{ product.handle }}">{{ product.title }}</div>
   {% endif %}
{% endif %}


Comments are meant for documentation but can also be useful when developing and testing.

Example of a comment
{% comment %}
This is a comment block which also can be used to turn of blocks of code.
{% let x = 1000 %} // this line is not evaluated
{% endcomment %}


The raw keyword is useful for outputting text that could interfere with the server-side language.

Example of using raw for outputting handlebars
{% raw %}
  var person = {
    firstName: "Richard",
    lastName: "Benson",
    blogURL: ""
  var template = "<h1>{{firstName}} {{lastName}}</h1>Blog: {{blogURL}}";
  var html = Mustache.to_html(template, person);
{% endraw %}

Variable assignment

Variables can (mainly) be created in two ways.

  • Use {% assign <variable> = <assignment> %} when declaring variables that should have global scope.

  • Use {% let <variable> = <assignment> %} when declaring variables that should have local scope.

You can also create variables/objects in for-loop declarations (and other code constructs).
Example of local and global variables
{% let localX = 123 %}
{% assign globalX = 123 %}

Local variables are only accessible inside the code block and file they are declared in. Global variables are accessible from the point of declaration to the end of execution.

let assignments inherits its value from parent scope but cannot modify it.
Example comparison let and assign
{% let x = 10 %}
{% assign y = 10 %}
{% for z in (10..1) %}
x = {{ x }}
y = {{ y }}
{% let x = z %}
{% assign y = z %}
{% endfor %}
x is {{ x }} // x = 10
y is {{ y }} // y = 1

Capture blocks

A capture block allows you to assign rendered output into a variable. It is handy for combining strings and variables when rendering html output.

Example of a capture block
{% let x = 10 %}
{% let y = 20 %}
{% capture data_attributes %}href="link-{{x | plus:y}}.html" data-from="{{x}}" data-to="{{y}}"{% endcapture %}
<a {{data_attributes}}>This is a link<a/>

Control flow

Execution branching can be handled by using if elsif else blocks using expressions with comparison operators.

Example of if, elsif, else statements
{% if x > 10 %}
   {{ x }} is larger than 10
{% elsif x > 5 %}
   {{ x }} is larger than 5
{% else %}
   {{ x }} is less than 6
{% endif %}
Table 1. Logical operators
Operator Operation




does not equal


greater than


less than


greater than or equal to

less than or equal to


logical or


logical and

You can also invert the expression by using the unless statement, sometimes that makes more sense.

Example of an unless statement
{% unless x > 10 %}
   {{ x }} is less than 10
{% elsunless x > 5 %}
   {{ x }} is smaller than 5
{% else %}
   {{ x }} is larger than 6
{% endunless %}

When you need to evaluate the same variable many times you can use a case block instead of many if or unless statements.

Example of a case block
{% case x %}
  {% when 10 %}
     x is 10
  {% when 9 %}
     x is 9
  {% else %}
     x is not 10 or 9
{% endcase %}


Looping over code can be done using for blocks. For loops can iterate over arrays, hashes, and ranges of integers.

Example of a for loop iterating over an array
{% for product in collection.products %}
{% endfor %}
A local variable product was created and assigned from the products array iteration.

You can also iterate over a range of numbers instead of arrays.

Example of a for loop using break
{% for i in (1..10) %}
  {% if i > 8 %}
     {% break %}
  {% else %}
    {{ i }}
  {% endif %}
{% endfor %}
Use break to exit the loop early.

You can also use limit and offset conditionals in the for-loop.

Example of a for loop using limit and offset
{% for i in (1..10) offset:5 limit:3 %}
    {{ i }}
{% endfor %}
Use limit and offset parameters to constraint the looping.

You can reverse iterate by including the reversed parameter.

Example of a reversed for loop iteration
{% for i in (1..10) reversed %}
    {{ i }}
{% endfor %}

For loops also create additional variables that can be handy.

Table 2. Forloop variables
Variable Description


index of the current iteration, starting with 1


zero based index of the current iteration, starting with 0


number of iterations


remaining iterations, ending on 1


remaining iterations, ending on 0


true on first iteration


true on last iteration


Filters are used for transforming variables and can be used for stepping up/down integers, string conversions, array manipulations and much more. A filter is applied by using a pipe character | followed by the filter name an optional colon : and one or more comma separated arguments.

Example using a filter
{{ 'Hello World' | slice: 1,5 | uppercase }}
Table 3. Standard filters
Filter Description Example Result


Appends a string

{{ 'test' | append:'-' | append:'me' }}



capitalizes words

{{ 'hello world' | capitalize }}

"Hello World"


rounds number up to closest integer

{{ 4.6 | ceil }}



formats a date string see reference

{{ article.published_at | date:"%A, %d %Y" }}

"Thursday 20 October 2016"


returns a default value if the left side is undefined

{{ something_undefined | def:"The variable was not defined" }}

"The variable was not defined"


integer division

{{ 7 | divided_by:3 }}



converts input to lowercase

{{ "WasCamelCased" | downcase }}



html escape a string

{{ "Click > Here & There" | escape }}

"Click > Here & There"


returns the first element of an array

{% let first_product = products | first %}

first_product now contains the first element from the products array.


rounds number down to closest integer

{{ 4.6 | floor }}



transforms string to a handle format

{{ 'jeans collection' | handleize }}



joins elements of array with a separator

{{ RGB_colors | join:', '}}

"red, green, blue"


returns the last element of an array

{% let last_product = products | last %}

last_product now contains the last element from the products array.


strips whitespace from the left side of string

{{ " hello " | lstrip}}

"hello "


select/map a given property from an associative array (hash)

{% assign product_titles = products | map: 'title' %}

product_titles is an array containing only titles e.g ["Camera","Shoe","Car"]


subtraction by an integer

{{ 3 | minus:1 }}



returns the remainder after division

{{ 7 | modulo:4 }}



replace newline \n with a html break <br/>

{{ "I have a multiline string!\nThat I want to output in html\n" | newline_to_br }}

"I have a multiline string!<br/>That I want to output in html<br/>"


return the second word if the input is not 1

{{ 2 | pluralize: 'car', 'cars' }}



addition by integer

{{ '1' | plus:'2' }}



prepend a string

{{ 'world' | prepend:'hello ' }}

"hello world"


remove the first occurrence

{{ 'hohoho' | remove_first:'ho' }}



remove each occurrence

{{ 'foobarfoo' | remove:'foo' }}



replace the first occurrence

{{ 'barbar' | replace_first:'bar','foo' }}



replace each occurrence

{{ 'foofoo' | replace:'foo','bar' }}



reverses an array

{% let a = ['a','b','c'] | reverse %}

The variable a now contains ['c','b','a']


rounds input to the number of decimals

{{ 3.141562 | round: 4 }}



strips whitespace from the right side of string

{{ " hello " | lstrip}}

" hello"


return the length of an array or string

{{ "what is the size of this" | size }}



slice an array or string using an offset and a length

`{{ "hello world" | slice: 1, 5 }}



sort array elements

{% assign s = ['d','a','f'] | sort %}

The variable s now contains ['a','d','f']


returns an array by spliting a string on a matching separator

{% let a = "a,b,c,d" | split:"," %}

The array a now contains ['a','b','c']


strip html from string

{{ "<b>Testing</b>" | strip_html }}



strip all newlines \n from string

{{ "Lorem\n ipsum\n dolor\n sit\n amet" | strip_newlines }}

"Lorem ipsum dolor sit amet"


strips whitespace from left and right side of string

{{ " hello " | strip }}



multiplication by integer

{{ 2 | times:3 }}



truncates a string to number of characters with optional ellipsis parameter

{{ 'this is to long for me' | truncate: 16, '…​' }}

"this is to long…​"


truncates a string to number of words with optional ellipsis parameter

{{ 'this is to long for me' | truncate: 4, '…​' }}

"this is to long…​"


removed duplicates from an array, optionally using a property parameter to test with

{% let p = products | uniq:'price'

The array p now contains products with a unique price.


converts input to uppercase

{{ "WasCamelCased" | upcase }}



url encodes a string

{% let product_url = product.title | url_encode %}

The variable product_url now contains an url encoded version of the product title

Table 4. Date format specifiers
Specifier Description


The abbreviated weekday name Sun


The full weekday name Sunday


The abbreviated month name Jan


The full month name January


Local date and time formating (UTC)


Day of the month 1..31


Hour of the day,24-hour clock 0..23


Hour of the day,12-hour clock 1..12


Day of the year 1..365


Month of the year 1..12


Minute of the hour 0..59


Meridian indicator AM or PM


Second of the minute 0..60`


Week number counting from the first Sunday of the first week 0..53


Week number counting from the first Monday of the first week 0..53


Day of the week counting from Sunday 0..6


Only the date, no time


Only the time, no date


Year without a century (short) 0..99


Year with century (long) 0..9999


Time zone name


Output a % character

Template objects

When working with templates the system instantiates some objects that you can use depending on the context.

Let’s assume you are working with the product template product.panda, when using that template you can access the current product object.

Some objects are always available such as collections and products but there’s also a customer object which is only defined when the user has been logged in.

These objects are made available server side when using panda code but can be made available to the client using var o = {{ pandaObject | json}}; javascript assignment.
Example of using a template object
{% comment %}
   Note that we can reference a product object since we are in a product context.
{% endcomment %}

{% let name = product.title | upcase %}
{% if product.compare_at_price > product.price %}
   {% let onSale = true %}
{% else %}
   {% let onSale = false %}
{% endif %}

Product {{name}} is {% unless onSale %}not {% endunless %}on sale!
If you want to see all properties of an object you can do a recursive print out using {{ <object> | print_r }}
The name of the object is the same as the current context i.e. page,product, cart, collection.

Theme settings

You control the theme settings from the files found in the config directory.

  • settings.json contains definitions for the settings.

  • settings_data.json contains stored settings data.

If you accidentaly mess up your theme settings you can recover by restoring the settings_data.json from an earlier timepoint.


This file control all settings that theme users see when clicking on the theme-settings.

Example of a basic settings.json
    "description":"Settings for my custom theme",
    "My settings":{
        "caption":"My settings",
        "settings": {
                "caption":"My properties",
                "caption":"Enable myproperty",

The settings file contains a small header with a unique_theme_id, used for identifying a published theme and a short description of the theme. The description property should contain a link to support documentation of the theme. The settings header is followed by one or several sections that the users can click into.


On published themes the unique_theme_id is coupled with a git repository used for syncing theme updates.

You can name the sections as you like but some sections have been "standardised" with predefined icons.

Table 5. List of standard theme setting sections
section Glyph Description



Color settings


Font settings


Site header


Site footer


Social media settings


Frontpage of the site


Product settings


Product collection settings


Cart settings


Blog settings

Accessing theme settings

Theme settings are access in panda template files through the settings object.

Example accessing a theme setting
<input name="mycheckbox" {% if settings.myproperty_enable %}checked{% endif %}/>


You group the settings into sections which are displayed in a menu under theme settings.

Example of a settings section
         "caption":"My settings",
         "settings": {

Each setting has a type that specifyes the input type and a caption for displaying a heading for the setting.


A header settings is used for displaying a title in the theme settings.

Example of a header setting
     "settings": {
         "colors": {
             "caption": "Colors",
             "type": "header"


Text settings are used for displaying text input fields.

Example of a text setting
     "settings": {
         "myinput": {
            "type": "text",
            "caption": "Enter some text"
            "max-lenght": 32
By using the max-length attribute you can specify max length of the text.


The number type is used for integer value inputs.

Example of a number setting
     "settings": {
         "mynumber": {
            "type": "number",
            "caption": "Enter number between 1 and 10",
            "min-value": 1
            "max-value": 10,
            "default": 1

On a number setting you have the option of specifying min and max values.

The default attribute is available on all input settings.


The checkbox type is used for displaying a checkbox input.

Example of a checkbox setting
     "settings": {
         "my_checkbox": {
            "type": "checkbox",
            "caption": "Enable something",
            "default": false


The color setting displayes a color selector in the theme settings.

Example of a color setting
     "settings": {
         "my_bg_color": {
            "type": "color",
            "caption": "Background color",
            "default": "#ffffff"


The asset setting allows for file uploads.

Example of a asset setting
     "settings": {
         "my_logo_image": {
            "type": "asset",
            "caption": "Logo image",
            "filename": "logo.png",
            "max-width": 420,
            "max-height": 200,
            "description": "420x200 png file"
The description attribute can be used on all settings and it shows up as a help text under the input field.


The font setting shows a list of available fonts.

Example of a font setting
     "settings": {
         "my_page_font": {
            "type": "font",
            "caption": "Page font",
            "extra_fonts": [
              	"'Source Sans Pro', sans-serif",
	        "'Roboto', 'HelveticaNeue','Helvetica Neue',sans-serif"

The extra_fonts array allows you to define your own font sets.


The select settings displays a select box with options.

Example of a select setting
     "settings": {
                "type": "select",
                "options": [{
                   "value": "1",
                   "caption": "One"
                }, {
	           "value": "2",
	           "caption": "Two"


The blog setting allows users to select an available blog.

Example of a blog setting
     "settings": {
         "my_blog": {
            "type": "blog",
            "caption": "Select a blog",
            "default": "blog"


The page setting allows users to select an available page.

Example of a page setting
     "settings": {
         "my_page": {
            "type": "page",
            "caption": "Select a page"


The collection setting allows users to select a collection.

Example of a collection setting
     "settings": {
         "my_collection": {
            "type": "collection",
            "caption": "Select a collection"

The linklist setting allows users to select a navigation linklist.

Example of a linklist setting
     "settings": {
         "my_menu": {
            "type": "linklist",
            "caption": "Select a menu",
            "default": "menu"


The struct setting allows you to define a group of settings that can be added or removed in blocks.

Example of a struct setting
     "settings": {
         "my_slider": {
            "type": "struct",
            "caption": "Slides",
            "struct" {
              "slide_image": {
                "caption": "Slide image",
		"type": "asset",
		"filename": "slide.jpg",
		"max-width": 1900,
		"max-height": 700,
		"description": "max 1900x700 jpg"
	      "slide_title": {
		"caption": "Slide title",
		"type": "text"

Theme language settings

It is possible to create language settings that are specific for the Theme. In settings.json create a locale section as described below.

Example of locale configuration
            "results_for_x":"results for '%s'"
        "general": {
            "thanks": "Thank you for contacting us!",
            "notice": "We'll get back to you shortly"
    "common": {
       "general": {
            "read_more": "Read more"


Then you’ll find new language settings under each context such as products.

In panda templates you can now reference the new language setting as described below.

Example of using locale
<article id="article-{{ article.handle }}">
<h3>{{ article.title }}</h3>
<p>{{ article.excerpt }}</p>
<a href="{{ article.url }}" alt="{{ article.title | escape }}" class="btn">{{ lang.read_more }}</a>
Use common when you need a language setting that should be available in all contexts.
Our settings are placed under a subheading named general.
Only the current language in use for the shop gets the theme specific settings.


All content uploaded from the theme settings are placed in the assets folder. If you have settings for a slideshow, the slide images are uploaded into assets, renamed and reformated as configured by your settings.json. Assets can also be used for uploading additional files by clicking on the upload asset button. You can upload any file (up to a size limit) from your local harddrive and access that file from within your theme.

Asset files are referenced by its name through the asset_url filter like this {{ 'file.jpg' | asset_url }}.


You can upload any javascript file into assets and ``include┬┤┬┤ that in your theme layout.

Example getting an asset URL
<script src="{{ 'asset.js' | asset_url }}"></script>

By adding the .panda extension to the script file you can work with server-side scripting which is useful to get access to theme settings.

Example of including assets.js.panda
<script src="{{ 'asset.js' | asset_url }}"></script>
Don’t reference the file with a .panda suffix as the server generates the corresponding .js file internally.
Example of using assets.js.panda
var backgroundColor = "{{ settings.pageBackgroundColor | def: '#fff' }}";
Set default values with the def filter to avoid unecessary javascript errors.
Strings are returned as plain text in panda code so you need to include the surrounding apostrophes ".

SASS stylesheets

Panda supports SASS stylesheets which is a more programmer friendly syntax compared to plain CSS. You can read more about sass syntax by going to the homepage.

Example using scss
@import 'panda-icons';
@import 'normalize.3.0.1';
@import 'grid';
@import 'globals';
@import 'mixin';

html, body {
  padding: 0;
  margin: 0;

body {
    @include font-smoothing(off);
    background-color: $colorBody;
    color: $colorTextBody;
    overflow-x: hidden;
    {% if settings.background_image_enable %}
        background-image: url("{{'background.png' | asset_url}}");
    {% endif %}

@import 'forms';
@import 'header';
@import 'footer';
@import 'product';
@import 'blog';
The use of @import statements, embedded panda code and sass-variables $<variable> containing theme preset values.
Be careful when using @import statements so that you don’t accidentaly include files over and over again i.e creating a circular reference.


Code snippets are handy for reusing blocks of code and included as partials from your theme files. You create a snippet by clicking on Create snippet found under the Snippets folder in the editor. To use a snippet you include the file with an optional argument that is passed to the snippet as a variable under its own name.

Example including a snippet
{% for var i in (1..10) %}
<div class="test">
  {% include 'mysnippet' with i %}
{% endfor %}
Example of a snippet file called mysnippet.panda
{% comment %}
The variable i passed above is called 'mysnippet' here
{% endcomment %}
i is set to: {{ mysnippet }}
You can also pass named variables to snippets.
Example of passing named variables
{% for var i in (1..10) %}
<div class="test">
  {% include 'mysnippet' i:i, show:true %}
{% endfor %}
Two variables are created and assigned values which can then be accessed by the snippet.

You can also combine with and named variables like this.

Examble of using with and named variables
{% for var i in (1..10) %}
<div class="test">
  {% include 'mysnippet' with i show:true %}
{% endfor %}
It’s good practice to set default values in the snippet.
Example of snippet variables and using default values
{% comment %}
The variable i passed above is called 'mysnippet' here and a named variable called 'show' has been created if set.
{% endcomment %}
{% let my_i = mysnippet | def:0 %}
{% let my_show = show | def:false %}

Image rescaling

Image rescaling is an important feature for theme developers when creating designs that adapts to different screen sizes, commonly known as responsive design. Panda offers serverside rescaling of images, with some tweaks, so that you can get optimal performance from your design.

Product images

Product images can be rescaled using the following code snippet.

Example of rescaling a products featured image
{{ product.featured_image.src | product_img_url:'200','200' }}

The product_img_url filter takes three arguments, width, height and an additional cropped or whitefit (optional). When the width or height is zero or null the image is scaled to maintain its original aspect ratio.

Example of rescaling a product image with fixed height
{{ product.featured_image.src | product_img_url:'0','200' }}

The above code would render an image with a fixed height of 200 pixels.

When rescaling with a fixed width and height it is useful to center crop the image. That can be done by adding cropped argument.

Example of rescaling a product image with fixed height
{{ product.featured_image.src | product_img_url:'200','200','cropped' }}

If cropping is not an option you may try the whitefit argument instead which maintains the aspect ratio by including a white border.

Asset files

Images in assets can be rescaled in the same manner by using the asset_img_url filter.

Example of rescaling an asset image with fixed height
{{ 'myimage.png' | asset_img_url:'200','200','cropped' }}
When creating a responsive design, use lazyloading of images for different screen sizes

Ajax API

There are some API endpoints that can be called client-side by shop users.

HTTP method Endpoint Description



Get cart contents



Add article(s) to cart



Change cart contents



Search products


To read all products in a users cart use the /cart.json API which returns a json object containing an array of line_items.

Example calling /cart.json using jQuery
$.getJSON('/cart.json', function(response) {
  if( response.line_items_count > 0){
     $.each(response.line_items, function(i,item) {
       console.log("line item:", item);
   } else {
     console.log("cart is empty");

Cart add

To add an item into to the cart you use the /cart/add.json API with a body containing product variant_id and quantity.

Example of adding an item to the cart
var quantity = 1,
    variant_id = {{ }};

$.post('/cart/add.json', {
  "quantity": quantity
}, function(response) {
   console.log("cart add", response);

Cart change

To change the number of items in the cart you may use the /cart/change.json endpoint and POST a body containing line number to change and quantity.

Example of changing cart
$.post('/cart/change.json', {
  "quantity": 0
}, function(response) {
   console.log("cart change",response);
You need to keep track of the line number, which can be handled using the /cart.json request.

The search endpoint allows searching for product, article and blog data using a selection of fields.

Example of a search query with url-encoded options
var query = {
 search: "<search string>",
 type: "<search type>" || ["product", "article", "blog"],
 fields: "<field>'" || ['title','url','thumbnail','price','meta_description','compare_at_price'],
 limit: 0

  url: '/search.json',
  data: query,
  method: 'get',
  dataType: 'json'
}).done(function(result) {
  console.log("Search result:",result);

The resulting search url could end up looking like this /search.json?type=product&fields=title&limit=0&search=test

The query string is case (and umlaut) insensitive.

Custom endpoints

You can create your own ajax endpoints by returning json in a template instead of html. Start by adding a new template in the code editor by clicking on the Create Template link.


As an example, if you want to create a product endpoint you select the product template and call it json. You then get a new template file called product.json.panda containing content from the default product.panda file. Replace all text with the content show below.

Example of a custom product json endpoint
{% layout 'none' %}
{{ product | json }}

After saving the file you can call the endpoint using javascript ajax GET request.

Example of calling a custom product json endpoint using jQuery
    console.log(response); // output response to console
You select the name of the template by using ?template=json url-option.
You need the product:handle for the request url.

Javascript helpers

We offer a set of optional helper libraries that can be included in the theme.panda file.

Include {{ 'shop.common.js' | shop_asset_url | script_tag }} to get access to our shop.js library containing helpers for money conversion, image scaling and much more.

Theme publishing

Publishing a theme requires that you send us a copy of your theme with atleast two named presets and a short feature list. Your theme must be compatible with all the common browsers and devices out there, so make sure your theme is tested thoroughly. When you submit a theme to us we do our own tests. However, if our tests fails after two submissions we reject the theme.


Here are the main requirements that we are looking for in a new theme.

  • Must work in all common browsers, with two major versions backward compability.

  • Must work on Android and Apple phones and tablets.

  • The Theme should have at least two different presets including demo content.

  • Server resource consumption should be low, rendering code must be optimized.

  • You need to use a git repository such as bitbucket/github/gitlab.

  • A landing page for the theme and email support is also required.