Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 451 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

Docs

Loading...

Get Started

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Portal

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Developer Tools

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Quickstart Guides

We're constantly putting together Tutorials and Guides to help you get started with each area of Siteglide.

Build Better AND Faster with SiteBuilder

Want to build from scratch?

Siteglide Command Line Interface (CLI)

Build and Manage Pages

Build and Manage Forms

Automate Processes with Automations

Use Our API to Integrate with Other Tools

Welcome

The home of Siteglide Docs - Build Limitless Digital Experiences

Siteglide allows agencies to take control over code and data via API, CLI or UI. With the right blend of out-of-the-box tools and freedom to develop - without limits.

Where do I start?

If you're new to Siteglide we recommend starting from a pre-built Template that leverages our SiteBuilder tool: Create Site From Template.

If you're looking for help with a site and not sure where to look you can check out our Quickstart Guides.

If you're already working on a site and you're code savvy we strongly recommend setting up our CLI so you can get the most from Siteglide via VSCode or your preferred IDE: Quickstart: CLI

Looking for something else? Use the Search (top right) or navigate through the left menu which is organised to match the menu you will see in Siteglide.

Types of Articles

  1. 🚀 Quickstart: Bare essential guides with links to more detailed articles if you get stuck

  2. ℹ️ About / Introduction: A brief overview of the topic

  3. 📋 Step by Step Articles: How to Implement a Feature or Solve a Problem

  4. 💡 Concept: Will take a feature and explain it in a little more depth

  5. 🏗️ SiteBuilder: How to get setup and get the most from the SiteBuilder module

  6. 💻 Reference: A brief reference guide to this feature for developers and anyone interested in code

  7. 🌳 File Structure: This will show you which folders and files are necessary and where to add them to your codebase. This will be presented in the format from which you would see it in Siteglide CLI

  8. ❔ Troubleshooting: Find answers to common challenges

  9. 🧙‍♂️ Go Further: This is where you'd find more detailed docs that help you really push the boundaries of all things web development

  10. 🔗 API: Endpoints and articles related to our API

  11. 🛳️ Changelogs: Releases, Updates and any important changes to Siteglide

  12. 💳 Billing/Account: Articles related to Billing, Payments and Subscriptions

  13. 👩‍💻 Agencies: Articles specific to Digital Agency partners

Users

Manage Users in Siteglide

The Users page in Siteglide Portal shows you all the Users in your company:

It's important to understand User Roles and how to control what other users can see and do:

Ready to invite Users to your account?

Quickstart: SiteBuilder
Quickstart: Create a Site
Quickstart: CLI
Quickstart: Pages
Quickstart: Forms
Quickstart: Automations
Site API

New Users

Existing Users

Developers

Cover
Cover
Cover

Support & FAQs

User Roles
Invite & Manage Users

Reference

Some useful Liquid tags:

Site Users

You can view who has access to the site on the Users tab as well as invite existing users to the site.

You can only invite existing Siteglide users to Sites, you might be working with an external Siteglide Expert, you would give them access to this Site here.

Want to invite new Users to your account?

If you're looking to invite your own team to manage the site you would need to add them to your Account:

Subdomain on a separate instance

This example is only applicable where your top level domain is fully delegated to Siteglide. If using External DNS Step 2 would not be done in Siteglide but instead wherever your DNS is managed.

You might want to have www.example.com on one Siteglide instance and app.example.com (or any other subdomain) on another Siteglide instance.

  1. First add your subdomain to the second instance as a new domain:

  1. Then go to the DNS records of your fully delegated domain and create a CNAME as follows:

Name
Type
Value

app

CNAME

example.com

This will use the SSL certificate from the top level domain and resolve correctly to the new instance.

Invite & Manage Users

This is where you manage Account users, they would get access to all sites in your Portal

Invite Portal Users

To invite users to your Account you can only do this from the Users tab on your Account (your company name in the left menu):

You will not see the blue invite button on the Users page (left hand menu Users link):

If you click a user you can see what Sites they have access to and their User Role:

Find out more on Users and User Roles:

Billing

Want to Check what Plans are Available?

Ready to Go Live? Add a Payment Method

Looking for Invoices?

Billing Setup

Simply fill in the form to add your card details to the Account. Please note this card will be used for ALL payments unless otherwise stated on the Go Live tab.

If you haven't previously setup Billing on Siteglide it will ask for your Company Name and Address:

If you already have a card on file with us it'll show the last 4 digits but you can replace this card with a new one:

Domains

Manage Domains on Siteglide

You can only manage custom domains on a Live Production Site on Siteglide, if you see the Go Live tab and not the Domains tab then the site is not Live. Please follow the Go Live steps: Go Live

Want to Add a New Domain?

You can either Fully Delegate your DNS to Siteglide or manage it externally:

Ensure Email Deliverability

With the increasing challenges faced with spam there are now stricter rules for email deliverability but there are some simple steps you can take to ensure your emails hit the inbox:

Sites

Create and Manage Sites on Siteglide

Ready to build a Site?

Need to Invite or Manage Users?

Want to Install or Manage Modules?

Ready to Go Live or Need to Manage Domains?

GraphQL

What is GraphQL?

Ready for some Tutorials?

CRM Users

Updating Modules

Here I will outline key tips and information in maintaing your existing module once it has been created.

Maintaining Your Git Repo

When anyone (yourself or someone else) installs your module, it will automatically take a copy of anything currently within the master branch of your Git Repo.

If you would like to work on updates or new additions to your module, it is strongly recommended that you create a new branch in your repository so that it does not impact what is installed until your are ready.

Create a new branch and call staging for example. From now on, send your initial commits to staging while you are testing. When you are ready to release an update to your module, merge into the master branch in Git.

Notifiying Users Of An Update

Once you have committed a new version to your master branch in Git, edit your module in Siteglide Portal to update the version number and let people know there is a new version available to install.

Modules

Front-end Browser Support

June 25, 2024

We strive to support all recent versions of major browsers front-end JavaScript features that we deliver to your site, such as eCommerce or Forms.

For the sake of security and providing the best experience to the majority of customers, we do not support browsers that are no longer receiving security updates and represent a small minority of traffic.

Supported browser versions

  • We support Edge v79 onwards per . We do not support Legacy Edge or Internet Explorer versions as they are .

  • We support Chrome, Safari and Firefox on desktop platforms.

  • We support the Android native browser on Android 4.4 and later.

  • iOS and iPadOS version 10 and later. This also includes browser variants on iOS such as Chrome, Firefox, Brave etc.

  • We respond to bug reports but do not proactively test other browsers.

If you have any system related issues in the browsers above then please don't hesitate to contact us for support.

Check and update your browser version

We recommend always using the latest browser version available to your operating system for the best support and security while using the internet. You can use the links below for your browser’s documentation to learn more about checking and updating your version:

For Safari, and Microsoft Edge browsers, this may depend on your device's operating system so check your OS is up to date as well.

Site Details

Manage sites in Siteglide Portal

Click the 'Sites' button in the left hand menu in Portal to see your list of sites, then click the Name of the site you want to view the settings for:

This is where you manage the site including Users, Modules and the Subscription:

Details Fields:

  • ID - Unique identifier for this site

  • Site Name - Name of the site - this can be edited

  • URL - The site's live or developmental URL - this can be edited (keeping the same format. e.g. Staging/Trial Sites = https://sitename.staging.oregon.platform-os.com/ and Production/Live Sites = https://sitename.prod01.oregon.platform-os.com/ )

  • Status - This will be 'Trial', 'Trial Expired - Locked', 'Trial Expired - Redirected' or 'Live'.

  • Datacenter - One of three datacenters that was picked when this site was created (London, Oregon, Sydney)

  • Company (listed as Owner in 'Sites' area) - The Agency or Client that owns this site

  • Create Date - The date that this site was created

  • Created By - Email address of the user that created this site

  • Latest Activity - The date that this site was last edited

  • API Key - You can click here to reveal your key

  • CLI Command - A shortcut to add this Site to CLI to begin working with, e.g:

Note: Here, you can also see how long is left on your trial. When your trial has reached 0, an email will be sent out to the agency who owns this site, and this site will enter the expiry flow.

  • You can extend this trial by clicking 'extend' next to the Status. Sites will automatically extend their trial when users visit this site's admin or connect to this site via CLI.

  • After 14 days of this trial being locked and with no user extending this trial; a reminder email is sent out and this Site will now redirect to https://www.siteglide.com/ .

  • After 13 days of this trial being redirected; another reminder email is sent to notify the agency owner that this Site will be deleted if no action is taken.

  • After 1 day of this second email being sent out, this trial Site will be locked and greyed out in Siteglide.

  • Within 24 hours of this site being greyed out, this Site will be fully deleted.

Using the buttons at the bottom, you can also 'View' this site, edit this Site's 'Admin', 'Save' any changes made to the name or URL, or 'Delete Site'.

User Roles

User Roles determine what users can see and do within Siteglide. Account owners have granular control over this and can set others as Admins or limit what they can do.

Manage Roles

You can then change or add additional Roles for that User:

Roles stack, so you can have multiple different roles and receive a combination of permissions. This allows you to split your desired permissions into blocks, and apply them where needed without having to create specific roles for every user.

Create a New User Role

To create a New Role just give it a name and select the granular controls they have access to:

You can also copy permissions from an existing role to save time:

See Roles Assigned to Users

You can see what Role a User has by clicking the Roles tab on a User:

Access limits based on 'User Level'

A user has a 'User Level' of either 'Agency' or 'Client'. This is independent of their precise roles, and inflicts some limitations that are permanent and cannot be overcome by editing User Roles.

If the User Level is set to 'Client', then the Portal view is limited to:

Site List Site Details Client Profile User Profile Billing Setup, Plans, and Invoices (only shows if paying Siteglide directly)

Other restrictions:

Gist/Live Chat - Cannot access, so they have no direct line of communication with Siteglide and must instead to their agency Sites - Cannot see status, so they're not informed of any billing issues (Payment Failed) Billing - Cannot see anything related to Reseller Programme Client - Cannot edit 'Payee' to set to themselves as paying Siteglide directly

Site view has no limitations, and a client user has the same feature access as an agency user (unless limited via User Roles)

Next Steps:

Automatic Site Upgrades

Sites can be automatically upgraded to the next available plan on the 1st of the following month if the cost of Extras exceeds the difference in cost to upgrade to the appropriate plan. Automatic upgrades by the system ensure that if usage increases on a site, the cheapest option is chosen and charged.

The default "Agency Global Preferences" is to not allow automatic upgrades. The "Site Preferences Override" has no override set and so will follow your Agency Preference. You can change this setting by following the information below.


Setting Up Your Automatic Upgrade Preferences

Site owners and agencies have granular control over automatic upgrades via Siteglide Portal. You can choose to control the setting either Globally for all of your sites, or individually for specific sites.

Please ensure you set this up to suit your billing preferences.

Agency Global Preferences

Within your in Portal, you can choose for the default setting to enable or disable automatic upgrades for all sites within your Agency and Clients.

The "Automatic Site Upgrades?" toggle can be set to two different states:

  • Yes - Automatic upgrades are turned on for all sites unless you override at site level. Upgrades will take place if it is cheaper than paying Extras

  • No - Automatic upgrades are turned off for all sites unless you override at site level. No upgrades will take place unless you action them yourself

Once you have chosen a state, make sure to save.

Note: This preference can be overridden by a selected state applied at the site level.

Site Preferences Override

Within the for a site, you can choose to override the global preferences and control each site separately.

Above the peak usage table, you'll see the current state for this site and an option to change this.

Click "Change" to open a pop up modal, where you'll be shown the current state for this site and an option to override Global Preferences in two ways.

To override, tick the "Override?" checkbox and select the appropriate state you would like to apply as an override.

To remove an override, untick the "Override?" checkbox.

An override can be set to either of the following states:

  • Automatic - Automatic upgrades are turned on for this site only. Upgrades will take place if it is cheaper than paying Extras

  • Manual - Automatic upgrades are turned off for this site only. No upgrades will take place unless you action them yourself

Once you have chosen an override state, make sure you click save.

Marketplace

The Siteglide Marketplace has a variety of useful Modules, Templates, Themes and Integrations.

Core Modules

Some of which have been built by Siteglide so that you can pick and choose which to install onto each site and reduce clutter/usage if they're not required. These are called Core Modules and are denoted by the 'by Siteglide' text under each title:

Find out more about the Core Modules:

Community Modules, Templates, Themes & Integrations

Other Modules, Templates, Themes and Integrations have been built by the Siteglide Community of Experts including our services team, Sitegurus.

Sitegurus have built SiteBuilder, a tool to speed up development of web projects while ensuring best practice and quality. Check out the Marketplace Templates that leverage SiteBuilder:

Agency Whitelabelling

Add your own branding to the Siteglide Admin

​Whitelabelling enables your Agency to re-brand the Siteglide portal by replacing our logo with your own. Re-branding the platform in this way means you are able to provide a more consistent, branded and streamlined service to your clients and maintain a single point of contact with them.

Over time we will continue increasing coverage of the whitelabling capabilities to include things like domain name, among others.

Admin Logo

To re-brand your Portal and Admin, head over to the Agency Details page, upload your logo and enable "whitelabelling".

Login

Below is an Endpoint and example snippets of code you can use to enable your Clients to login to Admin directly from your own Agency website.

You'll need the code below as a minimum. You can then add your own HTML/CSS/JS designs and branding on top.

HTML

JavaScript

About

What is the CLI and Why use it?

Siteglide CLI (Command Line Interface) is a tool that enables you to work on your project from your local IDE or Code Editor; it has similar behaviours to FTP (File Transfer Protocol), in that you can sync up and pull down changes from your website.

Who is it for?

Developers will be at home with the Siteglide CLI. It allows them to work faster than they can using the User Interface of the website. It also allows them to access the full suite of time-saving extensions and search tools in IDEs like and .

But it's not just for Developers! Designers, SEO specialists and Agency Leaders may also find benefits to learning and using Siteglide CLI - allowing them to see an overview over their entire project and quickly find the files they're looking for.

How to Navigate Around?

Check the "File Structure" in each documentation topic, and this will help you find your way around the folders and files.

Benefits of CLI over FTP:

Validation

With CLI, we can validate code sent to the site. This is especially useful if you are syncing Liquid or GraphQL code where there are fewer 3rd party tools for linting. If there is a problem we can pick up automatically- the sync will fail and an error message will try to help you find the problem.

Version Control

With Siteglide CLI we can keep track of the Code pushed to the Site from Admin and from local machines and make sure the Site always contains the most up to date pushes. It gives us scope to improve this functionality in the future.

Developer Tools

Other CLI commands like siteglide-cli graphql and siteglide-cli logs provide developers with advanced tools that would not be available with an FTP.

Ready to Start Using CLI?

Go Further: CLI

Site Copies

An Agency only feature is being able to make copies of existing sites either to a blank Staging or to an existing Site (can be a Production Site):

To copy to a new Staging Site click the first radio button and then choose the Site Owner (your Agency or a Client folder) and give the Site a Name/URL:

To copy to an existing site (Trial or Production) click the second radio button and then choose the Site from the dropdown:

Tickets

Log and Manage Support Tickets in Siteglide

Live Chat or Tickets? We recommend using Tickets over Live Chat when it's something that needs further investigation.

This is where you can contact us about an issue by logging a Support Ticket:

Just give it a title, select which site it relates to (if applicable) and please provide as much information as you can:

Subscription

Your Siteglide Subscription

The Subscription tab will show your Site Usage and what plan you're on or which plan we recommend if the site is not yet Live:

The Records Table shows a breakdown of all Records and how many have been deleted this month:

About

The pOS documentation covers Liquid comprehensibly and includes Liquid functionality specific to the pOS Platform and Siteglide.

Siteglide runs on platformOS and we use their implementation of Liquid which adds a large range of useful functionality.

The best place to start learning is their documentation, which will explain the different types of Liquid syntax. From there, you can find more helpful documentation on the Liquid topic you are interested in.

Site API

Docs to use the Siteglide Site API

Welcome to the Siteglide Site API Docs!

Siteglide is designed to give you total access to your data and to platform capabilities.

If you don't see an endpoint you need you can create your own or contact us if you think it should be platform wide.

Each Siteglide feature has it's own page with the various endpoints:

To find documentation for Siteglide Portal API, please follow this link -

CLI

Siteglide Command Line Interface

Comfortable with code?

We recommend using our CLI (Command Line Interface).

New to Command Line Interfaces?

You can go further, faster with CLI but it's not for everyone, we recommend reading the following article before deciding:

Already installed CLI?

Useful links once CLI is installed:

Forms

Categories

Adding Payment to a Module

You can set your Module up to require payment before a user can install it.

All payments are handled through your Stripe account.

Follow the steps below to add this requirement:

  1. You need to link your Stripe account to your Agency in order to take payments. Go to your Agency edit view, and click the 'Marketplace Payment Details' tab. Read the details shown on this screen and then enter your Stripe Secret Key ()

  2. Go to the Module edit view, and click the 'Payment Details' tab. Here you can toggle 'Take Payment?' to 'Yes' if it is required.

  3. Enter the Stripe Product ID for your Module. This Product will need a Price attached to it in Stripe.

  4. Select the Access Type you want to grant. There's 2 options:

    1. User only - This will grant access to the paying User

    2. Agency-wide - This will grant access to the entire Agency of the paying User

  5. You can manually manage User/Agency IDs in the 'Access List' field if you need to do so. This field will auto-update when a User pays for your Module.

  6. That's it! Now, when a User sees your Module in the Marketplace they'll be required to pay before they can install the Module.

Zapier Integration

Our Zapier Integration is easy to use straight out of the box and there are workflows setup that you can use:

Formatting Arrays

Developer Marketplace

This section is for developers that want to build Templates and Modules for the Marketplace.

Configuration

Customise Siteglide to suit your project requirements:

Field Types

Subscriptions and Changes
Billing Setup
Invoices
Add a Fully Delegated Domain
Add an External Domain
Steps to Authenticating Sendgrid Emails on Live Sites
Quickstart: Create a Site
Site Users
Install & Manage Modules
Domains
About GraphQL
Tutorials
Microsoft's lifecycle policy
no longer supported by Microsoft
Chrome
Firefox
Safari
Microsoft Edge
Introduction to Liquid
found here
siteglide-cli add staging --email [email protected] --url https://my-new-site-pied-piper.staging.oregon.platform-os.com/
Agency Account Details
Subscriptions tab
<script src="{{ 'js/s_login.js' | asset_url }}"></script>
<form onsubmit="s_login(this)">
	Email <input type="text" name="email" />
	Password <input type="password" name="password" />
	<input type="submit" value="Login" />
</form>
function s_login(el){
	event.preventDefault();
	let form = el.closest('form');
	let data = {
		email: form.querySelector('[name="email"]').value,
		password: form.querySelector('[name="password"]').value
	};
	var xReq = new XMLHttpRequest();
	xReq.open("POST", "https://api.siteglide.co.uk/api/public/general/sessions/login");
	xReq.setRequestHeader("content-type", "application/json");
	xReq.onreadystatechange = function(){
		if(xReq.readyState === 4){
			let response = JSON.parse(xReq.response);
			if(response.error){
				alert(response.error);
			}else if(response.session){
				window.location.href = 'https://admin.siteglide.com/#/public/login?s='+response.session;
			}
		}
	}
	xReq.send(JSON.stringify(data));
}
Invite & Manage Users
Users

Site Backups and Disaster Recovery

Backup your sites and data

We recommend using the Sitegurus Protect Backup Tool: https://www.sitegurus.io/protect

There are 3 main components that make up your clients sites that we need to consider in terms of backups and disaster recovery: Code, Assets and Data. We will outline how each are handled below:

Code

  • Every code change whether through CLI or Admin is stored in a Github repo controlled by Siteglide

  • This also stores the UserID of the user that took the action for traceability

  • This is mainly stored for disaster recovery purposes

  • You can export using siteglide-cli pull <env>

Assets

  • Stored on S3 by platformOS

  • AWS/Google Cloud mirror assets across multiple data centres

  • You can export using siteglide-cli export <env> -w

Data

  • Stored in databases by platformOS

  • platformOS have backups and store changelogs/versions of what happened

  • Upon deletion, the data is recoverable for 30 days. After 30 days of being soft deleted the data is permanently deleted at 5am UTC.

  • You can export data using siteglide-cli export <env>

We will be offering additional backup tools and automations in the future.

PlatformOS also have documentation on Backups and Disaster Recovery.

The following are key points taken from the platformOS documentation:

platformOS automatically backs up your applications and databases using real-time READ REPLICAs which exist across multiple Zones for further physical disaster recovery within a data-center.

Additionally, incremental transaction logs, daily and weekly backups are taken.

The retention period for monthly backups is 60 days.

These processes are internal to the platformOS DevOps team.

Learn more:

  • Data Backup and Removal: Learn about how deleted data is backed up and when it is removed permanently. Includes explanation of automatic and manual permanent removal.

  • GDPR Compliance in platformOS: Learn how platformOS approaches GDPR as just one of many compliance requirements and ensures that your project can easily comply with any number of government legislated privacy rules.

  • AWS Backup: AWS Backup is a fully managed backup service that makes it easy to centralize and automate the backup of data across AWS services.

  • Google Cloud Backups: How backups of your Cloud SQL instance work, and how they can be used to restore your data to the same or another instance.

Truthiness - Using Liquid to determine if a field is empty or blank

How to check if a variable exists/ is null/ is false?

Not all the variables on Siteglide will be stored as either "true" or "false". In many scenarios something may exist in one location, and simply not exist in another . To better explain this i'll use a homepage as an example:

Whether a page is the homepage or not is determined by the is_homepage variable: is_homepage which has the value true. However if a Page is not the homepage, rather than is_homepage being set to false, the is_homepage variable doesn't exist.

Say I wanted to run some code on every Page that isn't the homepage, I'd write something like this:

{% if context.page.metadata.is_homepage == false %}
   output content.
{% else %}
   output homepage content.
{% endif %}


This would work fine if we were on the Homepage, as the variable's value would be true. However, on other Pages, it would not behave as expected, as is_homepage would be undefined..

Now lets look at the Liquid keyword "blank". This refers to having a value of either: Empty, Null or False.

If I now check "is_homepage" like so:

{% if context.page.metadata.is_homepage == blank %}
  {% comment %}output content.{% endcomment %}
{% else %}
  {% comment %}output homepage content.{% endcomment %}
{% endif %}

This will now work!

Custom Field IDs

Each WebApp and Module has specific custom fields. Each field has a human-readable name and a database ID.

Introduction

WebApp custom fields are designed to be used by Agencies and Clients to store whatever they need.

Each field is designed to be flexible and reliable. To achieve this we give each field two names by which they can be referred to in the code:

  • Custom Field ID e.g. "webapp_field_1_2". Usually in the syntax, you refer to this prefixed by properties e.g. properties.webapp_field_1_2.

  • Name - the human-readable, editable name. e.g. "Manufacturer's Instructions"

How do I find a field's Custom Field ID?

***With the Siteglide Admin ***In Admin, you can find the Custom Field ID by navigating to any WebApp Item in the WebApp. You can then display the Custom Field ID against each field by turning on the toggle at the top of the page.

The Custom Field ID will always have the following format:

webapp_field_x_y

...where x is the ID of the WebApp the custom field belongs to and y is given to the field in the order the Custom Field was created. For example, the third field created on webapp_5, would have the Custom Field ID of webapp_field_5_3.

***With Liquid ***Inside your WebApp Layout, you can output the entire JSON data structure for the Item using:{{this}} Inside this, the Custom Field IDs tend to be stored inside the properties object- this shows the original structure of fields in the results. Outputting the following will show you a list of Custom Field IDs and their values for this item: {{this.properties}}

Using your favourite JSON parsing/ prettifier tool, you can read this JSON and identify each field by its contents. You can see a third party comparison of JSON tools here, to help you pick your favourite: https://geekflare.com/json-online-tools/. You can also install a VSCode extension to do this.

Why do we need two ways to refer to the field?

The actual name of the field in the Database is the Custom Field ID. This fulfils an important functional role. The key points are:

  • It always stays the same - meaning you can always reliably access the data stored in the field

  • It has a format easily accessed by code- which in some specific cases can mean using it will make your Pages faster. (We'll list some common Use Cases later.)

We also provide a human-readable Name for each field. That's what is chosen when Agency or Client create the WebApp. This lets you:

  • Know exactly what the field is for when managing your WebApp items in Admin and outputting them in Layouts.

  • Change the Name whenever you like

  • Use spaces in the name

When is the Custom Field ID useful?

Most of the time when you're working in Layouts, you can refer to the field by its name. This is easier for the developer as it becomes self-evident what the field's role is.

However, when you're working on some features, the documentation will ask you to refer to the field by its Custom Field ID. This is because for some features, better Page load speed is achieved using this more direct way to refer to a field and we want to pass on the benefits of this advantage to you. Here are examples of some of the Articles where we'll ask you to use the Custom Field ID:

  • When sorting WebApps

  • When filtering WebApps with "use_adv_search"

  • When pre-fetching Datasources

What about Modules, Custom Fields, and Custom Field Sets?

When working with Modules and their Custom Fields; you can use the Toolbox within Code Editor to find these Custom Fields and their associated code.

Many of their fields will also have Custom Field IDs. These are also used when sorting and filtering for better performance.

The main difference is in the format:

module_field_x_y instead of webapp_field_x_y.

Custom Field Sets also have Custom Field IDs which are commonly used when accessing them.

Site API Changelog

2.0.0 - 25th June 2025

  • Migrated to site-hosted API endpoints for more user control and better stability

1.1.2 - 14th April 2021

  • added - Get all Categories

  • added - Get Individual Category

  • added - Create Category

  • added - Update Category

1.1.1 - 30th March 2021

  • added - Create Product

  • added - Update Product

  • fixed - When using Get Product endpoints, all attribute options are now displayed as an array.

  • fixed - Added missing fields for Product Attribute Options

  • improved - Improved the naming of ID's and Related Attribute ID to distinguish between the item ID and its parent. This is on both Product Attributes and Product Attribute Options

1.1.0 - 9th March 2021

  • added - Get Module Item(s)

  • added - Create Module Item

  • added - Update Module Item

  • fixed - Zapier - WebApp custom fields would not display when creating/updating an item

  • fixed - CRM Custom Fields would not display when creating/updating a CRM user

Invite & Manage Users
Core Modules
Marketplace Themes & Templates
VSCode
Atom
Quickstart: CLI
Creating WebApps via CLI
Portal API docs
Forms
Categories
Modules
WebApps
CRM Users
eCommerce
Quickstart: CLI
About
Site Setup
Set Up Tailwind CSS with the recommended CLI method
Troubleshooting
Reference
Formatting arrays correctly
About Building Modules
Module Setup
Reference
File Structure
Field Types

Account

Manage your Siteglide Account

Looking for Agency Account information? Agency Account

Account Details

You can manage your company profile and account settings here:

Account Users

This is also where you invite Account level users who have access to all sites:

Want to invite users to an individual Site only?: Site Users

Simply add a name and email address of a person you'd like to add to the Account:

Sites

This shows all the Sites directly under your account (this will not show any Sites in Client folders):

Want to see all sites you have access to? Sites

Launchpad

Launchpad shows you how much of Siteglide you've used and setup. It's a great way to ensure your team learns the platform:

Quickstart: Create a Site

This guide will walk you through the process of creating a new site using Siteglide's admin area.

Step 1: Access the Dashboard

Once you're logged into the portal, you'll find yourself on the dashboard.

If you don't see a 'Create a Website' button (as per 2nd screenshot) please contact us via Live Chat to prove you're human and we'll get you setup!

Contact us to prove you're human and we'll get your account approved!

Once you're account is approved you should see the Create a Website button:

Click 'Create a Website'

If you already have 1 or more site you'll see options to create a site, manage existing sites, or manage your company:

Click 'Create New Site'

Step 2: Choose Site Creation Method

You have the option to create a new site from scratch or use a Template.

If you're new to Siteglide or less technical, it's recommended to start with a template because they're built with SiteBuilder, a tool to help you rapidly build and customise websites:

Please follow the above link if you're new to Siteglide

The rest of this Quickstart guide will focus on building a Custom Site. Click the 'Build a Custom Site' button.

Step 3: Name your Custom Site

Give the site a name and it will automatically create the URL. You will need to ensure it's unique, if it turns red please amend by making it more unique. Then click Create Site.

Step 5: Manage Your New Site Once Ready

And that's it! You've successfully created a new site, you will need to wait for it to be ready to use:

Once you get the email confirming it's ready you will need to refresh this page or click the link from the email

You will receive an email when it's done but can also find it under the Sites area:

The Status will show as Trial and there will be a new blue button in the bottom bar that will likely say Sign up for Admin Access if you've not yet used it, otherwise it'll say Admin going forwards:

Click Sign up for Admin Access and you'll then be on the Site Dashboard

Next Steps:

Click through to Administer the Site and you'll end up on the Dashboard:

If you're a developer you'll likely want to set up our CLI:

Install & Manage Modules

Go to a specific site in Portal and click the Modules tab. This will show all Modules in the Marketplace but it will not show Marketplace Templates because they cannot be installed onto an existing site).

You can filter to see which Modules are installed:

We recommend keeping Modules Updated (yellow Update button shows an update is available), read more on managing Modules: Updating Modules

You can filter in the Name field if you're looking for a specific module:

Clicking a module will give you more information:

When ready just click install to install it to that site.

Next Steps:

Agency Account

If you're not an Agency and have a Business account visit: Account

If you are an approved Siteglide Agency you will have additional features accessible from the tabs across the top:

Community Expert Details

The Community Expert Details tab is where you can brand Siteglide to your Agency and create a page on the Experts area so users in need of your services can find you:

Read more about Whitelabelling Siteglide to your Agency:

Marketplace Payment Details

The Marketplace Payment Details tab allows you to take payments for any Modules you sell in the Marketplace. You would need to use Stripe to take payments and insert your secret key:

Partner Programme

The Partner Programme tab provides more information on how you can partner with us:

Affiliate Programme

The Affiliate Programme tab lets you setup and control the affiliate commission for referring new users to Siteglide:

Liquid

How to use Liquid to get the most from Siteglide

New to Liquid markup?

Liquid is a templating language (created by Shopify) that helps you build out Siteglide functionality.

Looking for a specific tag/function?

Check out our Reference guide:

Tutorials

Formatting arrays correctly

Background

When working with Zapier, adding data to arrays needs some extra JavaScript to convert the data. Zapier calls arrays "Line Items" and formats them like so, [123,456,789] whereas Siteglide API expects a JavaScript formatted array such as, ["123","456","789"]

To convert these two data types, use the screenshots and code below. In this example we are getting a pre-existing user and their Secure Zones, then adding in a new item.

Zapier Setup

Finding an individual user and retrieving their data
Finding an individual user and retrieving their data
Pass that data into a "Run Javascript" action and run the following JavaScript
let data = inputData.sz.split(','); //Where sz is the name of the input data you picked above
let array = [];

data.forEach(item => array.push(item));
array.push("178375") //The ID of your secure zone

output = {output: JSON.stringify(array)};
Pass the output of the "Run Javascript" action into the "Update User" action

The above javascript code and flow can be used for anywhere that you need to add an item to Line Items within Zapier

eCommerce

Orders

Products

Siteglide Technology Stack

Which Tools Should I Use to Work with Siteglide?

There are a few main ways to work with your Siteglide Site:

  • Using the - Create an Account and use the User Interface to explore and build your Site. If you have any questions, use our live chat button to get help.

  • Using the - Efficiently work directly with your Site's code on your own machine.

Which Coding Languages are used in Siteglide?

Wait I'm not a developer? Don't worry, Siteglide has low-code tools like Studio, Toolbox and SiteBuilder to help Designers and Agents get stuck in as well!

In the course of this Documentation, you will probably come across the following languages that we use in many of our features and examples:

  • HTML - The standard Markup Language of the web.

  • JavaScript - The standard client-side scripting language of the web.

  • YAML - Used by Siteglide to store settings like page metadata, normally at the top of Liquid files.

  • Liquid - Used by Siteglide for dynamic server-side rendering of HTML and other languages.

  • GraphQL - Used by Siteglide to query the database and modify data. Often for beginners this will be tucked away inside our features, but learning it allows you to unlock more powerful possibilities.

Partner and 3rd Party Documentation

works very closely with our partners at to provide you with all the benefits of a modern cloud-based infrastructure like AWS, while at the same time making your life easier so you can focus on delivering the best for your clients.

platformOS are behind a lot of the code used on Siteglide sites, developing everything from new Liquid filters on top of Liquid's open-source starting point and a fully functional GraphQL schema you can plug into for making database queries and mutating to modify data. We also work with a range of other open-source and 3rd party technologies to bring you our full suite of features.

In our documentation we will often direct you to the and those of other 3rd parties to help you dig deeper into what it is possible to build. platformOS have won awards for their innovative documentation and we will try not to re-invent the wheel when we cover the same topics as them, but instead hope to present topics like GraphQL with a more beginner-friendly slant.

Most platformOS features can be built on Siteglide - if you find one you like and are not sure if it's supported yet, we encourage you to let us know about it.

Add a Fully Delegated Domain

We strongly recommend Fully Delegating your domain to Siteglide (vs ) for best results and to avoid additional steps redirecting the non-www to www.

Step 1: Add a Domain

Navigate to the Domains tab on the Site and click Add Domain:

Step 2: Choose Fully Delegated

Not sure which? Please read the pros and cons of each first:

Just click the Fully Delegated box:

Once you select Fully Delegated you need to type in your root domain (without www) and then decide if it's going to be your default domain (most likely):

You should also ensure 'Enable WWW Redirect is on unless you're adding a subdomain. Finally you can choose to add other DNS records but you can always do this later too. If you toggle to add them now you just add records and fill in the fields.

Step 3: Wait for it to process

Sit back and relax or go and make a coffee while the system does the work!

If you click the domain at this stage you'll just see the records you've added as Pending, there's nothing you can do at this stage so go grab that coffee!

Step 4: Change the Nameservers

You can refresh the page until you see the Status as 'Awaiting Propagation':

Then click into the domain and you'll see any records you've added plus the NS Records you need to add in your Domain Control Panel (GoDaddy etc):

Please ensure you've copied ALL existing DNS records from your current provider into Siteglide otherwise important services such as Email might fail.

To add more Records just click Edit Records and add new rows as required:

Step 5: Wait and Check it's Propagated

No matter how many times you've put sites live you'll no doubt be refreshing and questioning if it's worked from the moment you change the Nameservers!

You're looking for the Status to show as 'Live' in Siteglide but there are other ways to check progress listed below:

Typically you'll see some positive signals within minutes but you really do need to give it an hour or so (registrars will say 24-48 hours) before stepping in.

We recommend using the following tools to monitor propagation:

How to setup a multi domain start page

The following code example can be added to a Page Template and applied to the home/start page of a site to load a different start page for different domains added to the site.

This method does however remove some of the ease of editing for the client, as they will not be able to use Visual Editor to manage the pages that are setup this way. We'll look at adding a smoother point and click version to Admin later.

One use case for this approach would be where a client has a small chain of businesses that each have their own domain to target the area closest to them. Each of the sites are very small and the website structure is reasonably similar, and so it makes sense for the client to be able to manage all of them from the same Admin.

I'll now explain the code snippet above and how it works.

On the first line we get the current domain (context.location.host ) when the page loads and assign it so that it has a name of domain .

Next, we open a case to check the result of domain .

For each of the alternate domains we would like to check for, we create a when within the case . We include two versions of the domain to catch the majority of users. One that includes the . and another that does not.

Inside each when we call in a content section that should contain all of the page content. We also define an SEO page title to match our page and wrap that in siteglide_head_scripts to automatically move it to the head on page load (Check out this document to find out more: ).

We then add an else at the end of the case to cover the default domain e.g. "if neither of these alternate domains are used, then do this" which acts somewhat like a catch all.

Finally, we close the case.

Using the above example, visitors will see unique page content to each domain when they visit. When they click on links on the page, they will continue to other pages on the site while keeping the same domain unless you hard code a full domain URL on a link. Please note that by adding more than one domain to a site, technically all content is accessible via all domains. Though you can effectively make content hidden from domains by not adding it to pages, it could still be accessed.

Go Live

How to Go Live with a Site on Siteglide

Great news! You're ready to Go Live? Just follow these steps or contact us if you get stuck.

Step 1: Billing Setup

Head to the Go Live tab on the Site in the Siteglide Portal, if this is your first Site you'll likely see a message asking you to add your billing information:

Just click the link which will take you to the Billing Setup page, you just need to fill out the form:

More information on Billing Setup:

Step 2: Check Usage

The Subscription tab will show your current usage and recommend which plan you need, it'll only suggest the lowest plan that your Site currently fits into. We'd recommend considering picking a higher plan if you know usage is going to increase, this will help avoid paying overages:

Step 3: Go Live

On the Go Live tab there are a few options, firstly pick your currency then select the Plan (based on Step 2) and finally pick your preferred/closest Datacenter (this cannot be changed).

The button will not turn blue until you've selected a Plan:

It will then confirm the last 4 digits of the card that will be used and that you will be charged pro-rata for the remainder of the month based on the selected plan. When ready click Start Subscription. Please contact us if you are unsure about any of these steps, we cannot make changes once the site is Live.

Step 4: Site Copy to Production

You don't need to do anything but once you click Start Subscription the site will automatically be copied from Staging to Production. You will need to wait until this has been completed before you can work with the site or add domains.

If you use CLI you will need to ensure you no longer connect to the Staging site and instead connect to the Production site, see screenshot below (note 'production' not 'staging'):

Next Steps:

Ready to add your domain?

Ensure email deliverability via Sendgrid:

Siteglide Support Policy

Our Agency Success team here at Siteglide are dedicated to providing exceptional support and assistance to Digital Agencies and Siteglide Experts which ensures they are able to deliver the best solutions for their customers.

Please note: Website owners need to contact their Digital Agency for assistance, if you do not have one let us know and we'll recommend a reliable local agency or you can use our services team, .

Here we'll outline the ways in which we can help you and a few things that fall outside of our scope that we cannot assist with but we'll point you in the right direction whether it's via our Community or external resources.

How do I access Siteglide Support?

  • Live Chat - Click the blue bubble in the bottom right hand corner of our services to speak with the team. Please leave as much information as possible by answering any questions that appear.

  • Documentation - Access our growing library of Docs, FAQs, examples, videos and more, right here

  • Discord Community - Have a question that needs a creative solution or a project scope you'd would like some suggestions on from other experienced partners?

Please note our working hours are 8am - 6pm GMT (London). We aim to answer queries outside these hours where possible.

What kind of Support do we offer?

We assist agencies and developers with the following types of issues/queries:

  1. Platform Issues/Bugs: If you experience an issue with the core platform please report it to us and we'll help resolve it asap.

  2. High Level Platform Capabilities: We're always here to help answer questions about the platform and confirm whether it has specific features/capabilities.

  3. Account and Sales Questions: If you have a billing, account or sales related question we're always here to assist.

  4. Troubleshooting: If you're struggling with a feature in Siteglide we'll point you in the right direction.

  5. Technical Queries: Initial guidance but then you would need to use Sitegurus for assistance implementing Siteglide features.

What can we not assist with?

  1. 3rd Party Code and Custom Development: We only support the core platform code and functionality supplied out of the box as described in our documentation. We cannot support you on any 3rd party code or custom development even if we've recommended looking at 3rd party options. You are responsible for the implementation and maintenance of any custom code, design, bespoke functionality and any 3rd-party integrations. Note: Any custom code samples are provided as a guide only. We cannot provide support on use or implementation of any code that is not in our documentation. We want to focus on providing the best support of our code and platform that we possibly can. See below.

  2. Specific project issues/assistance: We cannot help you build projects or scope out exactly how you will build each project. We will help answer high level capability questions but cannot map out how to implement Siteglide for specific project requirements. If you need help with using Siteglide for a project please review our options below.

  3. Client/business specific questions: Siteglide is built primarily to help Digital Agencies and Expert Partners and to ensure we deliver on this mission we only provide basic account related support to end clients or Business plan users. See below for our options.

Community Support and Resources

We have a fantastic community of Siteglide Experts that can assist in various ways where we simply cannot (we want to stay focused on supporting you with the platform itself):

  1. Discord Forum: The forum is the best place to start. Search to see if your question/issue already exists and post a new one if not! You can access the Forum via the Single Sign On link from the Welcome page in the Portal:

  2. Siteglide Experts: We have various experts all over the globe ready to assist whether it's help scoping projects, solving issues, building projects, building specific functionality or even providing pre-built Modules or code. Browse our List of Experts here:

  3. Sitegurus: Our services team, , are here to assist you with any task big or small.

  4. External Resources: There are thousands of websites and resources to help with anything from project planning through to custom development and integrations. We've curated the ones that tend to be most useful to our partners here:

External Resources

Introduction

A collection of 3rd party links that our partners find useful when developing websites.

These sites are full of useful content, but any advice and examples published by 3rd party Sites is their own. We do not recommend any particular Front End library and encourage your agency to find the front-end tools and techniques that work for you.

platformOS Features

Siteglide is built upon platformOS, so their documentation is a fantastic resource for adding custom functionality to your Siteglide Website:

Liquid Documentation:

We also recommend the platformOS docs as the ultimate authority on using Liquid in your Siteglide Site. They include documentation on the original Liquid features and the additional features platformOS have added themselves:

Working with JSON

You'll come across JSON when you are working on Migrations and commonly when working with Liquid. It's a common language for storing data-structures, so if you output {{this}} in a Layout- we'll show you the JSON which stores the fields you need.

Using a third party JSON tool is useful to actually take the raw data and make it easy to read. Everyone has a different favourite tool, but you can see a third party comparison here to help you get started and find one:

Front End Resources (CSS & JavaScript)

General Front End Reference:

w3schools give a fantastic introduction to all web development topics, including reference guides and examples on HTML, JS, CSS, accessibility and more. https://www.w3schools.com/

MDN is another fantastic reference guide. It's useful if you need a little bit of extra depth and context. https://developer.mozilla.org/en-US/

Bootstrap is a useful library of Front-End components and features that we've made fully compatible with the Siteglide Design System because it is so widely used and familiar. If you have Design System enabled on your Page, there is no need to install

Stack Overflow is fantastic for FAQs on every aspect of development.

Code Pen is a great source of inspiration for all things CSS and JavaScript. It is full of fancinating community generated examples of everything from User-interfaces to animations:

JavaScript

Wes Bos has earned a reputation for teaching courses in JavaScript that are accessible to beginners and experts alike:

We use the Glide.js plugin to power out Slider Module Layouts. Check out the docs to customise your Layouts and build new ones:

Google Maps are used by the Events Module. Read the docs and customise here:

FullCalendar.io is the plugin which powers the Calendar Layout of the Events Module. Read the docs and customise here:

CSS

CSS tricks is a great resource for staying up to date on the latest in CSS and specialises in helping you tackle those issues that leave you scratching your head:

Tutorial 4 - (Answers)

In our Tutorial 4 challenge, we asked you to write a query which returned items matching multiple filter rules. Here's a possible solution.

Prerequisites

  • This Article shows the Answers to a Challenge. If you've not had a go at the challenge yet, we'd recommend you

  • - optional- Read more about GraphQL and when it might be best used.

Introduction

Last time, we asked you to write a single query which utilised a combination of filters to find recordswhich meet these criteria:

  • They are Module Items

  • They are enabled

  • They have already been released

  • They have not yet expired

  • They have a `weighting` between 1 and 3

  • They have a meta_title

  • They fall into the posters Category

This challenge required you to modify and combine the queries we'd already looked at. If you were able to match at least some of the criteria, good work.

Challenge Answers

To find the category_id for the posters Category. One way to do it would have been to go to the Siteglide Admin:

After that, it was a case of combining what you'd learned so far to add multiple filters to a query:

Notes:

  • For the range of weighting we've added a gte and lte setting, in order to demonstrate the possibility. This is not really necessary as there won't be values less than 0, but it's not a bad idea to rule these out, should data be entered incorrectly.

  • The category_array ID may be different from one site to another. Check your site's category_id in the Admin.

Explorer: Unfortunately, as mentioned last time, Explorer does not yet support arrays, so it's not possible to show an Explorer demo for this challenge.

Next Time

It's time for the real thing! We'll look at how you can save your GraphQL query in a File and use Liquid to run it on a website Page.

Let's go!

Tutorial 3 - (Answers)

We look at a possible answer to Tutorial 3's challenge. This shows how to write a query which fetches all WebApp items, not Module items.

Prerequisites

  • This Article shows the Answers to a Challenge. If you've not had a go at the challenge yet, we'd recommend you .

  • - optional- Read more about GraphQL and when it might be best used.

Challenge Answers

The trick here was to examine the tablesand spot the common patterns in their values.

The two types of recordswe wanted had these tables beginning with "webapp_":

  • webapp_1

  • webapp_2

The types of records we don't want have table values without "webapp_", for example:

  • module_3 - (Blog Module)

  • module_14 - (eCommerce Module)

  • form_1 - Newsletter Sign Up Form Submissions

So, in order to filter for the recordswe do want and not the recordswe don't want, we need recordswhich start with the string webapp_.

Code:

Notes:

  • In this method, there is no need to write one filter to include webapp items and another to remove module and form items from the Results. This is because the given rule efficiently achieves both at once.

Explorer:

This is just one possible answer, you may have found a different method.

Try and make sure you choose the best method for your use case. You should always be looking out for a more efficient way of doing things.

Next Time

We'll continue to look at filtering queries in more detail, including:

  • filtering by different fields, or properties

  • filtering with different kinds of rules

  • using more than one filter at once

Let's go!

Alternatives to Storing and Executing Liquid from Database Items

This Article will show how to use custom fields to fetch dynamic content in a WebApp Layout.

Introduction

Partners often ask us about outputting Liquid from a Rich Text Editor in their WebApps. Unfortunately it's not currently possible to do this- but here we'll explain a little about why- and a technique you can use to achieve the same effects.

Answer

Currently platformOS doesn't allow Liquid code to be executed within Liquid Fields (such as a Blog's description field). This policy improves security, by making sure it's impossible for user-submitted content to inject malicious Liquid code into your Site- giving you peace of mind that any Liquid code you write is for your eyes only.

We're working on a secure method to allow something like this in the future, but in the meantime, this Article will show you how to use custom fields to fetch dynamic content in the Layout- either above or below your Rich Text field output.

In this example, we'll be adding a Form below the WebApp rich text field.

Step 1) Add custom fields

You'll need to add the following fields to your WebApp structure:

  • Show Form - Checkbox containing values True & False.

  • Form ID - String field containing the ID of the Form you'd like to output.

  • Form Layout - String field containing the name of the Forms Layout.

Step 2) Add data to a WebApp Item

Now the fields we'll be using have been defined, add the relevant information to the fields.

Step 3) Add Liquid to the WebApp Layout where the content will be displayed

Next, locate the WebApp Layout Folder where the Form will be outputted.

Here we can output the Form using the fields that have just been set- wrap the whole include within an IF statement checking whether "Show Form" is "true" if so the Form will be outputted (with the parameters being pulled in from our WebApp). In our example, the Form will be added underneath a Rich Text field.

Now the fields within the WebApp control whether a Form is outputted for each item.

Alternative options for moderating which content the Client can add

*Hardcoding parameters *If you only wish the Client to be able to display a single type of Form, you could hardcode the ID and Layout name of this Form straight to the WebApp Layout.

The Client would have control over whether or not to show a Form, but the Form type would always be the one you approved.

*Using Content Sections *Content Sections and Code Snippets can also be outputted depending on ID and could provide a range of ready-built content which you could allow the Client to add into their WebApp items.

*Expanding the Logic *You could use Liquid Logic in the Layout to only allow certain Form / Content Section IDs to be displayed and forbid others. In this example, Form 2 will never be displayed, even if it is selected:

You can also use GraphQL queries and mutations to modify and read Pages and Partial files containing Liquid. If you are creating a module and wish for users to be able to organise, store and execute Liquid, this may be a viable method to make this possible safely.

Module Setup

Here we will outline the process involved in creating, building, submitting, and maintaining a Module in the following series of docs.

Prerequisites

Introduction

We will use the as an example of a pre-built Module which is available on the Marketplace which you can install on your own staging site to view.

With that in mind, anywhere in this documentation where you see text wrapped in <> are placeholders. For example <module_name> would be module_76 with the Siteglide Theme Demo module (more on this later!).

Create your Module in Siteglide

The first step to building your module is to create a listing for it in Siteglide. Within your Portal from the left-hand menu, select “Custom Modules” and then click the blue “+ Add New Module” button in the top right-hand corner.

Next, fill in the Module Details form. For now, you only need to provide the following basic information:

  • Name: The name of your module shown in the Marketplace and Siteglide Admin.

  • Version: A version number following the standard. Usually this would start as 1.0.0.

  • Description: Small text description of what the Module is - appears in ? icon in ‘name’ column on the Module installation page.

  • Type: Which category you would put your module into.

  • Show Menu Item: Does your module need to be within the sites menu under “Modules”? For example for a theme that has no items then this would be set to No.

We will come back later to enter information into the remaining fields, so for now click the blue “Save” button in the bottom right-hand corner to save your new module. Your module will only be visible to yourself at this point as it is not marked as “Public” and has not been approved for the Marketplace.

Vanity ID

Now that you have created your new module, when you view it you will see that it has a “Vanity ID” field. Make a note of this number for later as it will be used within the Module.

Data & UI Module Example

In this example we’re going walk through, step by step, how to create a Module with a Custom UI that could either be used as a private module for your agency or shared publicly in the marketplace to deliver turnkey site solutions to your clients and more.

It is recommended that you download our example so that you have all of the code and asset files available at each step of this guide and can easily follow along to and have your module end up looking exactly the same.

:::hint{type="info"} If developing a Custom UI then you will have to supply all elements of the Module, including but not limited to tables, GraphQL, data processing etc. :::

Custom UI

Custom UI modules open up the ability for complete flexibiltiy within Siteglide Admin and allows for truly unique experiences for your customers.

A module with a Custom UI will securely load a page within a frame in Siteglide Admin. This page can be developed in any language that can run on a Siteglide site.

Setting up your files

A Custom UI Modules will follow the same folder setup as other Modules, except when you come to build the UI itself to display in Siteglide Admin. The UI will be kept in the private folder of the module as we do not want it to be discoverable outside of the frame within Siteglide Admin.

Within our private folder we can store any files that Siteglide natively supports, such as GraphQL, Liquid, Assets etc. More information about folder structure can be found at .

Authentication

To ensure that the Custom UI is only being accessed by logging in users of Siteglide Admin, we require authentication to be added to each page that will be used within the Custom UI. This is stored in the YAML of the pages and is as follows:

The response_headers YML allows Siteglide Admin to view the content of the file when it is loaded. The authorization_policies YML will run a check to ensure that the user has an active session within Siteglide Admin, if not then it will display an error page.

As well as the YAML, any links within your Custom UI has to include a Liquid tag to apply the authencation string on the link. For example if you are linking from your Custom UI's index page to the add new item page, your link would be as follows:

This include will create a hash onto the end of the URL that is then used in the above authorization policy to decide whether to show the page. If the include is missing then following the link will throw an error within Siteglide Admin.

You can also control access to certain areas of a page with the module_is_in_admin function:

Module Details

Once your module is created, you will need to tell Siteglide Admin to load your Custom UI. You will need to take the slug of the Custom UI's homepage and enter that into your Module Detials within Portal. When editing the details about your module, there is a field called "Custom UI Path", entering in the slug there will change the menu item for your module to then load your Custom UI instead of the default Siteglide module UI. In our example the path for our Custom UI homepage is module_72/module_iframe/index

Quickstart: CLI

Get started with our CLI

If unsure please read more about CLI first:

Step 1: Install the Siteglide CLI

The Siteglide CLI can be installed from NPM using:

If you hit any issues please check our Troubleshooting guide:

Step 2: Connect to a Site

Setup a local folder and use the site specific command to connect directly to your site (you can find this on the site details page in Portal - ):

Need some help? Read our full Site Setup Guide:

Step 3: Start Building!

You should now be ready to start work, you'll likely want to pull down the files from the server:

Check out our Reference article for a full list of useful Commands:

If you're using Flowbite/Tailwind please ensure you have Tailwind setup to compile CSS:

Tutorials

This is a brief overview of the GraphQL tutorials we currently provide and the topics we plan to cover in future.

Introduction

This Article aims to give an overview of the topics currently covered in our GraphQL tutorials, and the topics we aim to provide in the future.

For a longer introduction to GraphQL and how it can be used with Siteglide, check out the following article:

How the Tutorials Work

These GraphQL tutorials are organised by topic, but we'd recommend you work through them one by one.

This is because each tutorial will build upon the skills covered in previous ones.

We also include challenges- because when learning something like GraphQL it always helps to get hands-on experience exploring and applying the skills you've picked up. Don't worry if you can't quite manage the challenges straight away. Give them your best shot and then check the answer pages.

Tutorial Contents

The following Tutorials are currently available:

Challenge Answers

Some topics will be covered in our challenges. We'll challenge you to build upon what you've learned, before revealing a solution. If the topics interest you, try the tutorial with the same name first.

Planned GraphQL Tutorials

In the future, we intend to cover the following topics:

  • Search for Results

  • Use other types of Query e.g. users

Please let us know if you'd like to see anything else included.

Submit Module for Approval

Preparing GitHub Repository

Before submitting your Module for approval, you should check that your GitHub Repository is ready.

See the following checklist to confirm it is ready for submission:

Sending your Module to Siteglide

Now that you have created your module, you will need to update the Module item that you made in Admin earlier.

You will need to edit the item with the following information:

  • Menu Name - If applicable, the name to show in the left hand menu when viewing a Site Admin: Modules > My Module.

  • GitHub Repo Account - The name of the GitHub account that your Module’s repository is in.

  • GitHub Repo Name - The name of the GitHub repository that your Module’s code is in

  • Installation Notes - Any notes on installation - appears in ! icon in ‘action’ column on the Module installation page.

  • Changelog- A link to your Module’s changelog documentation, this will display within the Marketplace and next to the “Install” button.

  • Public - Do you want this Module to be visible within the marketplace?

You can also find these notes in the tooltips on each field.

Once you have clicked "Save" with the above fields populated, your module will be submitted to the Siteglide team for review.

After Submitting your Module

Once you've submitted your Module for approval you'll need to give us access to see the Module. This is needed for the initial approval, but also for ongoing access to be able to install the latest version of the Module.

To provide us with access you need to invite Siteglide API () as a collaborator for the GitHub Repository. You can do this in your .

After accepting the invitation we will review your module. This includes reading the code, installing the module onto a site and testing some of its functionality. We will create a ticket within our area so that you are kept up to date with progress. Once your Module is approved by us then we will allow it to be shown within the so any user can see and install the Module on their sites

About Building Modules

Building Modules could be the perfect way to keep your workflow "DRY" (don't repeat yourself)! If you find a common requirement on more than one site, and no-one is currently offering a solution, or you think you can build a better one, building Modules might be for you.

This documentation section is a work in progress. If you have any questions in the meantime, please don't hesitate to get in touch and we'd be happy to help!

What Skills do I Need?

For some types of Module, anyone can have a go, for example building a Site Template could be done by a Designer with a little bit of HTML and CSS knowledge:

For other kinds of Module, we recommend getting hold of a developer. If you don't have one, check out Sitegurus who can provide this service.

Getting Started

To Publish or Not?

One way to start building Modules is to build them as an internal tool for your Agency. This doesn't require any approval from Siteglide.

Or, you can publish your Module on the marketplace. This requires Siteglide approval so we can check the module is transparent about the kind of code it runs. You can either:

  • Make the Module Open-Source

    • You can either all Siteglide users access, or require them to contact you before you decide whether to give their organisation access

    • You can optionally allow others to contribute to your Github repository

  • or Accept Payments

File Structure

In general there are two main types of folder structure for Modules, though within that, there will be variation on the types of file available, so check each individual module.

The form directory stores the form layout for Module create/ edit Forms.

Siteglide Published Modules

Most Siteglide published Modules will have a folder structure a little like this:

Newer Siteglide Published Modules & Marketplace Modules

The private folder will exist, but will not be possible to pull using CLI or view in the Code Editor UI. It is for storing functional code which will be used by the Module behind the scenes.

Read more about Module Directory Structure on platformOS:

About
Reference
Accessing Data in Liquid Variables - Tutorial 1 - Using Dot Notation
Accessing Data in Liquid Variables - Tutorial 2 - Iterating over Arrays and Objects
Using Collections with WebApps and Modules
Accessing Data from the Global Context Variable
Truthiness - Using Liquid to determine if a field is empty or blank
{% assign domain = context.location.host -%}
{% case domain %}
{% when 'www.domainone.com' or 'domainone.com'  -%}
  {% content_for siteglide_head_scripts -%}
    <title>Home - Domain One</title>
  {% endcontent_for -%}
  {%- include 'content_section', id: '17', name: 'Domain One Page' -%}
{% when 'www.domaintwo.com' or 'domaintwo.com' -%}
  {% content_for siteglide_head_scripts -%}
    <title>Home - Domain Two</title>
  {% endcontent_for -%}
  {%- include 'content_section', id: '15', name: 'Domain Two Page' -%}
{% else -%}
  {% content_for siteglide_head_scripts -%}
    <title>Home - Other Domain</title>
  {% endcontent_for -%}
  {%- include 'content_section', id: '16', name: 'Other Domain Page' -%}
{% endcase -%}
www
Siteglide Scripts
https://documentation.platformos.com/
https://documentation.platformos.com/api-reference/liquid/introduction
https://geekflare.com/json-online-tools/
https://getbootstrap.com/docs/4.5/getting-started/introduction/
https://stackoverflow.com/
https://codepen.io/
https://wesbos.com/courses
https://glidejs.com/
https://developers.google.com/maps/documentation
https://fullcalendar.io/
https://css-tricks.com/
query webapps_missing_meta_descriptions {
  records(
    page: 1
    per_page: 2000
    filter: { 
      table: {starts_with: "module_"}
      properties: [
        { name: "meta_title", exists: true }
        { name: "enabled", value_boolean: true}
        { name: "release_date", range: { lte: "1582108820" }}
        { name: "expiry_date", range: { gt: "1582108820" }}
        { name: "weighting", range: { gte: "1", lte: "3" } }
        { name: "meta_title", exists: true }
        { name: "category_array", value_in: ["158197"] }
        
      ]
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
head there first.
About GraphQL
query get_all_webapps {
  records(
    page: 1
    per_page: 20
    filter: {
      table: {
        starts_with: "webapp_"
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
head there first
About GraphQL
marketplace_builder/
    └── views/
        └── partials/
            └── layouts/
                └── modules/
                    └── module_3/
                        └── example_layout_name/
                        |   └── list/
                        |   |   |── wrapper.liquid
                        |   |   └── item.liquid
                        |   └── detail/
                        |       |── wrapper.liquid
                        |       └── item.liquid
                        └── form/
                            └── example_layout_name.liquid
modules/
    └── example_module_id/
        └── private/
        └── public/
            └── views/
                └── partials/
                    └── layouts/
                        └── modules/
                            └── module_3/
                                └── example_layout_name/
                                |   └── list/
                                |   |   |── wrapper.liquid
                                |   |   └── item.liquid
                                |   └── detail/
                                |       |── wrapper.liquid
                                |       └── item.liquid
                                └── form/
                                    └── example_layout_name.liquid
https://documentation.platformos.com/developer-guide/platformos-workflow/directory-structure#modules
Siteglide Admin
Siteglide CLI
Learn more at MDN
Learn more at MDN
Siteglide
platformOS
pOS documentation
An enterprise-grade infrastructure that delivers optimum security, reliability, flexibility, scalability and performance.
Sitegurus
https://discord.gg/BsmP3au6Am
Community Support and Resources
Community Support and Resources
https://discord.gg/BsmP3au6Am
https://www.siteglide.com/partners
Sitegurus
External Resources
Community Support and Resources
Installing Dynamic Layouts
Agency Whitelabelling
Billing Setup
Domains
Steps to Authenticating Sendgrid Emails on Live Sites
npm i -g @siteglide/siteglide-cli
siteglide-cli add --email [email protected] --url https://my_great_site.com
siteglide-cli pull <env>
more info
Set Up Tailwind CSS with the recommended CLI method
About
Troubleshooting
Site Setup
Reference
About GraphQL
Tutorial 1 - Your First Query
Tutorial 2 - Pagination
Tutorial 3 - Filtering the Results
Tutorial 4 - Advanced Filtering
Tutorial 5 - Using Liquid to run GraphQL queries on your Site
Tutorial 6 - Variables
Tutorial 7 - Sorting
Tutorial 8 - Building a Liquid API GET Endpoint Page powered by GraphQL queries
Tutorial 9 - Using Mutations to Create New Records
Tutorial 10 - Using Mutations to Edit a Record
Tutorial 11 - Using Mutations to Delete an Item
Tutorial 12 - Related Records and Datasources
Tutorial 3 - (Answers)
Tutorial 4 - (Answers)
Tutorial 6 - (Answers)
Site Template Modules - and How to Make Your Own
Quickstart: SiteBuilder
Dashboard
Quickstart: CLI
<p>{{this['Rich Text Field Example']}}</p>
{% if this['Show Form'] == "true" %} 
    {%- include 'form', id: this['Form ID'], layout: this['Form Layout'] -%}
{% endif %}

{% if this['Show Form'] == "true" and this['Form ID'] != "2" %}
    {%- include 'form', id: this['Form ID'], layout: this['Form Layout'] -%}
{% endif %}

---
response_headers: >
  {
    "X-Frame-Options": null
  }
authorization_policies:
  - modules/siteglide_system/module_is_in_admin
---
//Page content here
<a href="/add?{% include "modules/siteglide_system/modules/auth" -%}">Add new item</a>

">Add new item</a>
{%- function access_allowed = 'module_is_in_admin' -%}
{%- if access_allowed -%}
    <p>Content to show if loaded via Admin</p>
{%- endif -%}
Custom UI Demo Module Repository from GitHub
Top Level Folders
External DNS
Fully Delegated vs External DNS
Quickstart: CLI
GitHub
Siteglide Theme Demo
Semantic Versioning
here
[email protected]
repository settings
Support Tickets
Siteglide Marketplace

Subscriptions and Changes

Usage-based Billing

The Subscriptions tab shows you exactly what each site uses broken down into high-level types of infrastructure usage and then down to specific Siteglide features to help show more value to clients.

For example, you can see the total number of Units but the real value is seeing that this is made up of CRM Contacts, Form Cases, WebApp items, Module Items, Products and much more.

Note: External API calls are unlikely to be used for many sites if you're not working on platformOS directly via CLI.

When comparing the cost of the various different tools required to achieve the same result and then trying to somehow connect them all together you can show huge savings to clients both in terms of cost and time.

Plans and Extras

You can see the individual Site Plan pricing on the Plans page:

Extras are charged pro-rata based on the plan price. Simply divide the Plan Price by the Units included you get the Unit Rate that you will be charged if you exceed the limit.

We replaced the old, more costly Overages with Extras. If your site subscription tab shows a 'Legacy' plan please refer to old old Overages pricing or speak to the team about switching to one of our new plans!

How our usage-based billing works:

  1. Storage, Records, Domains and Client Admin Users do not reduce unless deleted.

  2. Emails, API Calls and Bandwidth renew each month.

  3. You are billed based on the peak usage each calendar month.

  4. Deleted items count towards peak usage for that month but do not count in subsequent months.

  5. Once you hit 70% usage of any metric it will be marked as orange as a warning.

  6. Once you hit 100% usage of any metric you will start incurring Extras.

  7. If you have Automatic Upgrades turned on and the cost of Extras exceeds the difference in cost to upgrade to the appropriate plan the site will automatically be upgraded on the 1st of the following month.

  8. You can manually upgrade or downgrade to any plan once per month but if the new plan doesn't cover the usage you will be charged overages.

  9. We do not downgrade automatically as we do not know what usage will be required in a given month and assume most sites increase in usage as the business grows and the site becomes more successful/popular.

Subscription Changes

To give you full use of the Siteglide platform we simply charge you based on your usage which can change over time and likely increase as you help your clients grow.

Please ensure you're aware of how subscription changes work before going live:

  1. We monitor usage and provide the Subscriptions tab for each site to show if you are close to exceeding or exceeding the usage available on the current plan. Find out more about the Subscriptions Area in Portal.

  2. We calculate usage at midnight on the 1st of each month for the whole of the previous month. If you have Automatic Upgrades turned on and exceed any usage limits we will apply the cheapest option at the time which will either be to charge Extras or Upgrade the plan. If you have Automatic Upgrades turned off we will only charge the Extras.

  3. Extras are charged in blocks as a one-off charge for excess usage in that billing period. Typically you could use double the allowance of one usage metric before it’s cheaper to upgrade to the next plan.

  4. For Monthly Upgrades the new plan will be applied immediately both for the new month and retrospectively for the previous month (to avoid paying more costly overages). We will charge for the new month and the increase between the new and previous plan. This will only be done to save higher overage costs, we will always apply the cheapest option.

  5. Annual Upgrades will start a new 12 month subscription crediting the balance of the previous subscription prior to payment (e.g. $250 - $63 (6 months remaining of Starter plan).

  6. For Sites on Connect Billing (deprecated) we will continue to pick the cheapest option based on our fees not what you charge your customer as this can vary (you could manually upgrade them to a higher plan if preferred).

  7. If your site usage decreases and you believe it will not increase again then you might wish to Downgrade to a lower plan. Simply press Downgrade on the Subscriptions page, pick your chosen plan and request the Downgrade. The Downgrade will not take effect until the end of the current billing period. Note: You may prefer not to do this on Connect Billing to avoid lots of changes.

  8. If you wish to cancel a site we recommend first discussing any issues with our Success team but alternatively you can delete the site via the portal. Please note deleting a site takes effect immediately and all data will be irretrievably lost. All payments are non-refundable so we recommend making full use of the current billing cycle before deleting the site (any remaining time will be forfeited).

  9. Please see our full Payment Terms and Payment Failure Process in our End User Licence Agreement (sections 7 and 8).

Troubleshooting

Is your website project folder inside a Cloud Virtual Drive?

There are known issues with using the Siteglide CLI on a directory inside a Google Drive or Microsoft One Drive or equivalent. While this may be solved with complex settings changes, we are not able to guide you through this and recommend instead that you store your project directories in a standard hard drive e.g. C. For backups we recommend https://www.sitegurus.io/protect or for more advanced version control https://github.com/.

Not Sure if You've Set Up Siteglide CLI Already?

In a Command Line, in any directory, type in:

siteglide-cli -v

If you already have siteglide-cli installed globally, this will tell you which version you have installed.

You can find out which is the latest version here: https://www.npmjs.com/package/@siteglide/siteglide-cli

Have You Installed the Dependencies?

The CLI is distributed via Node Package Manager (NPM) and so you will need NodeJS installed on your machine. The easiest way to get this is to visit https://nodejs.org/ and download the LTS version as this has better support.

Siteglide CLI requires a minimum of NodeJS version 10 but we recommend using the latest / LTS version

Open the download and follow the wizard with the default options to finish install.

Have you Installed the CLI Globally on your Machine?

You can use the following to install Siteglide CLI in any directory. The -g flag instructs your machine to make this command available in any directory (globally)- so you only need to do this on your machine once.

npm i -g @siteglide/siteglide-cli

When an update is released for the CLI, you can use the same command as above to install the updated version. Please note that the flags for commands below are not available on all versions of the CLI, updating to the latest version will allow you to use all of them.

Issues Installing on Windows?

The following video will give you a complete walkthrough guide for setting up Siteglide CLI on a Windows machine. Feel free to skip ahead to the parts you find the most useful.

Issues Installing on a Mac?

The following video will give you a complete walkthrough guide for setting up Siteglide CLI on a Mac. Feel free to skip ahead to the parts you find the most useful.

The steps followed in the video can be found here. If you are using zsh as your shell then the commands here will have to be run against the ~/.zshrc file, not ~/.profile. ZSH is the default for Macs since macOS Catalina, to find out which shell you are using you can run echo ${SHELL} in terminal. If the ~/.profile or ~/.zshrc files do not already exist then you may need to create them first before running the export PATH command

Struggling to Connect to a Site?

Read our in-depth guide:

Debug Mode

Sometimes Siteglide support may ask you to run the CLI in "debug mode". This provides more output into your terminal so that we can use it to aid in supporting you. To do this, you need to prefix the command you are running with some extra information. This prefix differs slightly on macOS, Linux and Windows, for example if you were to want to sync to a site with debug mode on:

DEBUG=true siteglide-cli sync production
DEBUG=true siteglide-cli sync production
set DEBUG=true && siteglide-cli sync production
$env:DEBUG='true'; siteglide-cli sync production

Note, for macOS and Linux, debugging will be turned on for that one command that you prefix. For Windows, debugging will be turned on for as long as Command Prompt or Powershell is open. Closing Command Prompt or Powershell and re-opening it will turn debug mode off.

Site Template Modules - and How to Make Your Own

About Site Templates

Templates are whole sites which have been saved so that you and other users can easily create copies of it moving forward. They are excellent for creating turnkey solutions for specific industries and client types, allowing you to sell a theme and then customise to each client on top.

Build out as much of a site as you’d like on a Trial site, then turn it into a template on the marketplace, making it publicly available to be installed during the creation of new sites.

How to Use a Template to Create a New Site of Your Own

First, find a template you’d like to use for a new site in the Marketplace. You can use the filter category at the top of the Marketplace page to filter ‘Templates’ only to find options more quickly.

Click on a template to view the popup for more information and images. If you would like to use this template, then continue to the next step below.

Once the template popup appears, you can create a new trial site using this template by selecting an owner in the “Site Owner” field, and then entering a name for your new site in the “Site Name” field.

Click the blue 'Create Site from Template' button, and then wait for the template copying process to complete.

Once you receive your email notification to tell you the site is ready, your new template site will appear in your sites list. From here you can edit the site just like any other, by clicking the 'Admin' button.

How to convert a Trial Site to a Template

Have you created a site that you’d like to turn into a reusable template?

Head over to Marketplace and click the blue “+add a new Module” button. Next, follow the usual Module creation process, but be sure to select the “Template” category from “Category” the dropdown field in the Module Details tab.

Select the trial site you want to convert to a Template Module using the “Site” dropdown field displayed below the Name, Category & Description fields.

Note: Only trial sites can be converted to a Template Module.

Once you have created your Template Module, you can either keep it for internal use within your team, or allow the template to be publicly viewable and installable.

If a template is both publicly viewable and installable, then the master trial site will be locked for editing (with a status of “Locked - Template”). The site is locked to prevent other users making copies of a site that has any ongoing work.

If the site were not locked, then the copy process could be triggered whilst there are unfinished changes from the managing development team. If you wish to make edits to the master trial site, at any time you can remove the template from the Marketplace by toggling either the 'Publicly viewable' or 'Publicly installable' values to 'No'.

The master trial site will then be editable again until you toggle both of those values back to 'Yes'.

Tutorial 2 - Pagination

Turning the Page! In tutorial 2, we'll control how many items Graph returns on each Page of results and retrieve specific Pages.

Prerequisites

  • You have completed the .

  • - optional- Read more about GraphQL and when it might be best used.

Introduction

When GraphQL returns results, it will organise them into pages.

This allows it to be more efficient, as it only returns the data that is needed straight away. At the same time, the rest of the pages are organised ready for the next request.

You'll always need to think about Pagination when using GraphQL, even you only want to retrieve the first Page of results.

Per Page

per page is an argument used to define the number of results that will be returned on each page.

It's now a mandatory argument on some types of query so we've already got the argument in our query from the last tutorial:

Experiment by changing the integer in the argument from 20 to another value, e.g. 1. Observe the difference. It's recommended to keep the per_page number as low as possible- for efficiency and performance.

Let's consider a few situations:

  • You want to display 3 items - Set per_page to 3- only the three items you need will be retrieved.

  • You want to display 20 items at a time, but allow the user to view the next 20 when they've finished reading - Set per_page to 20

  • If you want the user to be able to change per_page dynamically, we'll cover this when we look at variables in a future tutorial.

  • If you think you need to display more than 2000 items at a time (perhaps to provide data to a JavaScript plugin), consider using GET requests to a custom Liquid endpoint page to fetch each page of results asynchronously. You can learn more in but we recommend working through the other tutorials first.

Returning Pagination Metadata

You can return pagination metadata alongside your query results:

  • current_page - Returns the number of the current page.

  • per_page - Returns the number of items on every page.

  • has_previous_page - A boolean which is true if there is a page before the current page.

  • has_next_page - A boolean which is true if there is a page after the current page.

  • total_pages - Returns an integer representing the total number of pages in these results.

These optional properties can be requested alongside total_entries and your results object, like in the example below. Try them out. Code:

Notes:

  • Properties like has_next_page and has_previous_page are most useful for adding pagination buttons of your own.

  • You can see that by default, we get the first page of results- so page returns 1.

Explorer:

Changing the Page

You can view other pages by setting the page argument to the page of results you'd like to see.

Code:

Explorer:

Next time

In the next Article, we'll look at how to filter results.

For example, we'll learn how to return items from only a specific WebApp.

Let's go!

About GraphQL

Although it can feel like a big leap at first, using custom GraphQL gives you ultimate control over dynamic data. We'll guide you through.

Introduction

Firstly, let's go through a few frequently asked questions about GraphQL. In the next article, we'll then get started on our first Query on a new Starter Site.

What is it?

GraphQL is an open-source querying language which was originally developed by Facebook. The general idea was to create a language which allowed Developers to quickly develop flexible requests for data, while being efficient and only asking for exactly the data they needed. The structure of the query, including relationships between different tables, matches the structure of results- making it easy to iterate over the data after the query is complete. You can read about the open-source project on their website here:

How does Siteglide use GraphQL?

It's worth noting that you don't need to learn GraphQL to use Siteglide's core features. Most of the time, Siteglide does the querying for you. However, learning GraphQL will allow your Agency to take on more challenging projects- see the next question.

Although the language is open source, actual implementations of it can be quite different across platforms. This is because a GraphQL implementation has two parts:

  • The Schema - this defines different types of Query you can use, which parameters you can add, and what sort of output you can request. It reflects, in part, the database structure that the Platform uses, so it varies from Platform to Platform. Siteglide runs on platformOS and we use of GraphQL.

  • The Query Language - This is the syntax for writing queries- it is exactly the same across platforms, but can feel very different when the Schema is different.

If you've used GraphQL before with a different Schema, you will start to see lots of similarities.

How do Liquid and GraphQL work together?

Liquid is a templating language; GraphQL is a query language. To put it another way, think of GraphQL as a re-usable question.

Liquid will both ask the question- and listen to the answer, using it to build a dynamic website. The question itself is in a different language: GraphQL.

GraphQL files are stored inside the marketplace_builder folder. They can be called by Liquid and re-used as many times as you like.

How can I learn GraphQL?

GraphQL can be tricky to get started with, but most of our Developers report that at a certain stage, it just 'clicks' for them. We want to help any Agency who wants to learn to get there.

We'll provide a series of Tutorials, starting off simple and becoming progressively more challenging. As part of this, we'll aim to give you the skills you need to carry out further learning yourself- often this will mean learning to read the platformOS schema- finding the type of query you need, and experimenting with how to make it work.

Every time we add a new GraphQL Tutorial, you'll be able to access increased support on that topic via the Forum. You can see an overview of the topics we've covered so far

In the next Tutorial, we'll show you how to use the GraphQL sandbox to test out Queries.

I'm stuck. Can you help me build a Site with Custom GraphQL?

We want to give you all the tools you need to learn GraphQL and we'll give our general tips, tricks and links to help you solve problems. Unfortunately, Siteglide support can't build a custom Page for you or assist with custom GraphQL questions - we want to stick to providing first class support on the core product.

If you're stuck, here are some things you can try:

  • Help us improve our general documentation on GraphQL. We'll keep adding and improving on our articles. See the "did this page help you" buttons at the bottom of each developer docs page.

  • Ask the Community on . We have an ever-growing Community of developers from around the globe, join the discussion.

  • Siteglide's custom support partner, Sitegurus, can provide developers who are experts in using GraphQL with Siteglide.

  • Maybe this particular solution isn't suited to custom GraphQL, maybe it's better as an official feature? You can request features on the .

When should I use Custom GraphQL?

Writing GraphQL queries gives you ultimate control over your dynamic data. Now, for a small Site, it's probably faster to use Siteglide's pre-built features to cover this for you.

Here are some examples of where some custom GraphQL could open some doors for your Agency. We'll update this list with tutorials and suggestions in the future:

  • Get any data from your Siteglide instance

  • Reporting- Your Client wants several bespoke, complex queries of data, all organised into tables and charts? GraphQL can help you be this flexible and if implemented well, can maintain page speed.

  • GraphQL works inside workflow and autoresponder emails. With mutations, it can also send transactional notifications and API calls from anywhere in your Site.

  • Performance optimisation- We do our best to make sure all Siteglide features are delivered to you with the maximum flexibility and performance, but for very bespoke Sites we couldn't possibly make the Platform guess your priorities. Understanding GraphQL could help you optimise your most complex features for faster Page load time, while using ready-built features to save development time in other areas.

  • Front End Mutations- Use at your own risk! With mutations, you can change data in the database and trigger this with Liquid. This can make a lot of complex projects possible- see the platformOS GraphQL schema for a full list:

  • Update data without loading the Page- When changing Page in WebApp results, you currently have to wait for the Page to reload. Using Siteglide-CLI and GraphQL you can build an XHR endpoint to get updated data after the Page has loaded. (Note: you can do this with CLI, without GraphQL- GraphQL just gives you more flexibility.)

  • Jump ahead of the Roadmap- We're always updating our Roadmap with new functionality and Community requests. But what if you have that one Client that cannot wait? It'll take more time to Develop and may not be as re-usable as a fully tested official feature, but with GraphQL and Liquid understanding, you've got the power to build your own solutions.

Start Learning now!

Our first tutorial will get you set up on the GraphQL playground/ sandbox which we make available through Siteglide-CLI.

Reference

In this section, we'll add reference code which is relevant across Modules. Specific Modules may have further relevant reference material in their own sections of the docs.

Standard Module Fields

These Fields are available to all standard modules:

Output a Module Layout

List Layouts

Detail Layouts

Usually this is not outputted directly. Instead, a detail page is dynamically generated for you by the Siteglide Admin at the chosen slug, however, it can be useful in some situations to output manually!

Liquid Include Parameters

  • id - the Module's ID

  • item_ids - output one or more module items, comma separated

  • category_ids - output all items in one or more categories, comma separated

  • layout - default is /default/ - 'layout' values are relative to the folder: layouts/modules/Blog (module_3)/[layout name]

  • per_page - defines the number of items outputted on the page

  • show_pagination - default is true - defines if items should be paginated when the per_page is met.

  • sort_type - defines the type by which items are ordered

    • properties.name - name of the Module item (alphabetical)

    • created_at - date the Module item was created

    • updated_at - date the Module item was last edited

    • properties.weighting - weighting of the Module item

    • properties.release_date - date the item is set to be released

    • properties.module_field_3_1 - Sort by a core or custom field

  • sort_order - defines the order in which the type is sorted asc - sort items in ascending order desc - sort items in descending order

  • collection - default is 'false' - If you set it as collection: 'true' then any layout is suppressed.Data is accessible via {{context.exports.webapp_1.data}}. For Example, name would be: {{context.exports.webapp_1.data.result.items[0]['name']}}

  • use_search - Allows the Module to be searched by keyword parameter in URL

  • use_adv_search - Allows the Module to be filtered by core/custom field IDs in URL parameters

  • datasource - true/false - When outputting a "nested" module inside another, the inner module tag can be given this parameter to prevent it from inheriting parameters which would change its page etc.

If you nest any more Module or WebApp layouts inside this Module Layout, they will inherit the type parameter. This means if you want to input a nested list layout, you may need to reset the type parameter back to list with type: 'list'.

Output the Most Recent Item

To get the most recently "released" item, you can use the sort_type parameter to sort by properties.release_date. You can alternatively use created_at to sort by the Item you most recently added to the Admin.

Then just output one post- to get the first Item. To do this, set per_page to 1.

Use the sort_order to control whether you get the first or last item. To get the latest Item by date, the date will have a higher value, so use 'desc' (starts with highest number) rather then 'asc' .

Field Name

Liquid Tag

Description

Item Name

{{ this['name'] }}

name of the Blog Post

Item Slug

{{ this['slug'] }}

item URL

Weighting

{{ this['weighting'] }}

weight of item, used for sorting

Release Date

{{ this['release_date'] }}

release date of the item

Expiry Date

{{ this['expiry_date'] }}

expiry date of the item

Enabled

{{ this['enabled'] }}

enable/disable the item

{% include 'module', id: '3', layout: 'default' %}
{% include 'module', id: '3', layout: 'default', type: 'detail', item_ids: insert_item_id %}

{% include 'module', id: '3', layout: 'default', sort_type: 'properties.release_date', sort_order: 'desc', per_page: '1' %}
query get_all_records {
  records(
    per_page: 20
  ) {
    total_entries
    results {
      model_schema_name
      properties
    }
  }
}
query get_all_records {
  records(
    per_page: 20
  ) {
    current_page
    total_pages
    has_previous_page
    has_next_page
    per_page
    total_entries
    results {
      model_schema_name
      properties
    }
  }
}
query get_all_records {
  records(
    page: 2
    per_page: 20
  ) {
    current_page
    total_pages
    results {
      model_schema_name
      properties
    }
  }
}
first Learning GraphQL tutorial
About GraphQL
Tutorial 8 - Building a Liquid API GET Endpoint Page powered by GraphQL queries
https://graphql.org/
platformOS's implementation
here
Let's go
Discord
Create a task with SiteGurus
Roadmap
https://documentation.platformos.com/api-reference/graphql/glossary#mutation

Using Collections with WebApps and Modules

Take control over your WebApp Layouts by exposing the Data and making your own For Loop with Liquid

Prerequisites

In this Tutorial, we'll be using dot notation, so if you're not familiar with it, you may want to brush up here:

  • Getting Started with Dot Notation

  • Advanced Dot Notation - Arrays and Key Maps

You'll also need to be familiar with WebApps:

  • Creating and Editing WebApps

  • WebApp List Layouts

  • WebApp Detail Layouts

Introduction

By default in Siteglide, when you include a WebApp, we query the database and loop over the items for you. We take the data inside the loop and assign it to a variable called this which holds dynamic data about the current item. In certain situations, you may want to do something different, so we have provided the optional parameter collection.

Setting collection to 'true' makes the data from the behind-the-scenes query available to you directly, without a layout.

Using Collection

In the following example, we show the difference between a WebApp list which does use Collection and one which doesn't:

<div class="container">

  <h2>Here we have an ordinary WebApp</h2>
  {%- include 'webapp'
      id: '1'
      layout: 'portfolio_2'
      per_page: '20'
      show_pagination: 'true'
      sort_type: 'properties.name'
      sort_order: 'asc' 
  -%}
  
  <h2>Here we have a WebApp Collection</h2>
  {%- include 'webapp'
      id: '1'
      layout: 'portfolio_2'
      per_page: '20'
      show_pagination: 'true'
      sort_type: 'properties.name'
      sort_order: 'asc'
      collection: 'true' 
  -%}
  
  {{context.exports.webapp_1.data | json}}
  
</div>

This outputs:

To break it down further, setting collection to true exports the data to {{context.exports}} Under that, you can access it by the id of the WebApp in the original include tag. In this example, it's 1 so we can access {{context.exports.webapp_1.data | json}}.

You can then use dot notation to access the data as you wish.

A Possible Use Case

When would you use collection?

Well some people will prefer to always use collection, others will prefer to use layouts. One possible use-case where Collection works better though is if you want to display the same WebApp twice on the Page but differently each time.

For example, what if you wanted to display the first item largely at the top, then display other items in smaller cards below?

You could use the `

` tag twice to achieve this, with different Layouts each time, but this would have a negative effect on performance. This would slow the Page down, because we would be querying the database behind the scenes twice (once for every time you include the tag).

Alternatively, you could include the webapp just once as a collection, then use Liquid to display the items you want in the way you want:

<div class="container">
  {%- include 'webapp'
      id: '1'
      per_page: '6'
      sort_type: 'properties.name'
      sort_order: 'asc'
      collection: 'true' 
  -%}
  <h2>Featured Item</h2>
  
{% assign this = context.exports.webapp_1.data.result.items[0] %}
  {{this.Title}}
  <h2>Other Items</h2>
  {% for this in context.exports.webapp_1.data.result.items %}
    {{this.Title}}
  {% endfor %}

</div>

This outputs:

Great! Only one query needed behind the scenes and we've nearly met the objective, but there's one problem. The item "A Special Guest Appearance" has been included twice!

Advanced Looping

We can use the offset parameter on our loop tag to start the loop at a different index. Let's skip the first index when we loop, as this item has already been displayed.

<div class="container">

  {%- include 'webapp'
      id: '1'
      per_page: '6'
      sort_type: 'properties.name'
      sort_order: 'asc'
      collection: 'true' 
  -%}

  <h2>Featured Item</h2>
  
  
{% assign this = context.exports.webapp_1.data.result.items[0] %}
  {{this.Title}}
  
  <h2>Other Items</h2>
  
  {% for this in context.exports.webapp_1.data.result.items offset: 1 %}
    {{this.Title}}
  {% endfor %}


  
</div>

</div>

You can do a lot with loops. Offset is just one of your options. Head to the pOS documentation to learn more about loops in Liquid: https://documentation.platformos.com/api-reference/liquid/loops

Using Layouts with Collections

Hang on, wasn't the point of Collections to avoid Layouts? Not quite! The idea was to give you control over the loop- layouts are still possible. I can still include my portfolio_2 layout, but I need to work out its path from SITE MANAGER / Code Editor in Admin.

I can now include the Layout at this Path: `

`

The Layout is expecting an object called this containing the data, but as in the example above we already assigned variables called this containing the right data, the Layout works without further modification:

Accessing Data in Liquid Variables - Tutorial 2 - Iterating over Arrays and Objects

How to use Liquid For Loops and Indexing to handle arrays. Also, how to use a key to access key maps, using categories as an example.

Prerequisites:

This Article is the second in a series on using Dot Notation in Siteglide. We strongly recommend reading the Article below first:

  • Getting Started with Dot Notation

Introduction

If you're using dot notation, you'll probably come across arrays and maps. The syntax for dealing with them is slightly different, but this actually makes them more powerful when you're building complex Layouts.

The examples below focus mainly on Categories because they are a good example of arrays and maps.

Arrays

An array is a group or list of data. A key might have a single value, or an array of values.

In Siteglide for example, each WebApp item for example has a field called category_array which stores a list of the IDs of every Category assigned to that item. The more categories an item belongs to, the longer the array. E.g. { "category_array":["98479", "111111"] }

If you try to use standard dot notation as we practised in the previous Article, you will be able to output the array as it is using the key category_array and the liquid {{this.category_array}}:

["98479", "111111"]

However, there aren't many places where this would actually be helpful. It would probably be more useful to do one of the following:

  • Access one of the Array items by its Index

  • Loop over the array Items

  • Find the length of the array (how many items are there?)

Accessing an Array by its Index

Arrays have an index, which means they are numbered. In Siteglide (and in JavaScript) Arrays are zero-indexed, meaning the first item has the index of 0 while the second item has the index of 1.

We can access the first value in the category_array by its index using the following Liquid: {{this.category_array[0]}}outputs 98479

Note that straight after the key, we give the index number of the value we want in square brackets. If the array contained objects, you could go a step further and access one of their properties with a dot after the square brackets.

Looping the Array to Access all Values in it

We can access all values in the Array using a Liquid For Loop:

{% for item in this.category_array %}

  {{item}}<br><br>

{% endfor %}


This outputs:

98479
111111

Finding the Length of an Array

You can find the length of an array using a Liquid filter: {{this.category_array | size}} outputs: 2

Maps

As well as arrays, you might come across a map of data. Here is an example which can be tried on any Siteglide Starter Site using the Liquid: {{context.exports.categories}}

It outputs something like this (but I've shortened it here!):

{
  "items": {
    "98490": {
      "id":"98490",
      "external_id":"category_57703",
      "name":"Womens",
      "parent":"98487",
      "slug":"women",
      "image":null,
      "description":null,
      "meta_title":"Womens",
      "meta_desc":null,
      "og_title":null,
      "og_desc":null,
      "og_type":null,
      "twitter_type":null,
      "full_slug":"/our-products/merch/clothes/womens"
    },
    {
      "98489":{
        "id":"98489",
        "external_id":"category_57701",
        "name":"Men",
        "parent":"98487",
        "slug":"men",
        "image":null,
        "description":null,
        "meta_title":"Men",
        "meta_desc":null,
        "og_title":null,
        "og_desc":null,
        "og_type":null,
        "twitter_type":null,
        "full_slug":"/our-products/merch/clothes/men"
      }
    }
  }
}

A map is used to store data when you know the ID of an item and want to fetch it using it's ID as a key. In Siteglide we use them for performance reasons. You can tell this is a map, because the key items contains several comma-separated objects instead of a single value. Each of these has a key representing an ID, instead of the name of a property.

Let's say you have the ID of a category, but you want to display the Category's name: {{context.exports.categories.items["98490"].name}}

This outputs: Women

This sounds odd, but it's the name of the eCommerce Category we wanted! You can achieve the same if you have the ID stored as a variable:

{% assign my_example_category_id = 98490 %}

{{context.exports.categories.items[my_example_category_id].name}}

Looping over a Map

You can loop over all keys in a key map using a Liquid FOR loop. Inside the loop, each key-value pair is treated as an array with the key being the first item in the array and the value being the second and last value in the array.

In this example, we'll loop over all Categories in context.exports.categories.data and see what data is available to output:

{% for item in context.exports.categories.data %}
  {{item[0]}} <!-- Outputs the key, which in this case is the ID of the category -->
  {{item[1]}} <!-- Outputs the value, which in this case is an object containing this category's fields. You can see all available fields by outputting it. -->
  {{item[1].name}} <!-- Outputs the name of the Category in this iteration of the loop. -->
{% endfor %}

Using what you've learned so far

Here's a challenge you can try.

Can you create a WebApp with Categories and in your Layout output a list of the Category names that belong to the item?

Step 1) Create a WebApp and assign more than one Category to each of the Items. Step 2) In the Layout, access the this object. Step 3) Use your Understanding of arrays to loop through every category belonging to the item. Step 4) Use your understanding of maps to find the name of each category in the Loop and output it.

Next Article

Next, we'll look at how you can use dot-notation to work with WebApp collections: Using WebApp Collections

Tutorial 7 - Sorting

You can change the type and order of sorting. You can also sort by multiple properties at once.

Introduction

In this Article, we'll look at how you can instruct the query to return items in an order of your choice.

Using sort along with pagination means you can make sure the first pagen of results shows you the items you're interested in. Using sort along with variables allows you to let your end-User customise the sort order themselves.

Sorting by a Single Property

In this example, we'll start with a query which fetches the Categories.

Code:

query sorted_categories {
  records(per_page: 2000, filter: {table: {contains: "category"}}) {
    results {
      table
      id
      properties
    }
  }
}

Step 1) First, we'll add in a sort parameter

Code:

query sorted_categories {
  records(per_page: 2000, sort: {}) {
    results {
      table
      id
      properties
    }
  }
}

Notes:

  • As a parameter, sort is contained within the round brackets after the query type

Explorer:

Step 2) Next, you can add a sort method.

You can either use:

2) a) A field from the main options

Code:

query sorted_categories {
  records(per_page: 2000, sort: {created_at: {}}) {
    results {
      table
      id
      properties
    }
  }
}

Explorer:

2) b) a property from properties

Code:

query sorted_categories {

  records(per_page: 2000, sort: {properties: {name: "weighting"}}) {
    results {
      table
      id
      properties
    }
  }
}

Notes:

  • Remember, any field which is not a Platform default field will be a property. This means any field not listed in the other options, including several Siteglide fields such as "weighting". Default fields tend to be like created_at automatically filled in when an item is created.

Explorer:

Step 3) Select a sort order

You can choose between the keywords DESC (descending) and ASC (ascending).

Code:

query sorted_categories {
  records(per_page: 2000, sort: {properties: {name: "weighting", order: DESC}}) {
    results {
      table
      id
      properties
    }
  }
}

Explorer:

Sorting by Multiple Properties

Sometimes you may want to initially sort by one property, e.g. weighting, but then some items may have a blank weighting property. Those items where the sort field is blank will go to the end of the results list, but you can add additional sort conditions to sort these further.

Let's say we wish to firstly sort by "weighting", then sort those items without a weighting by the "release date".

Code:

query sorted_categories {
  records(per_page: 2000, sort: {
    properties: [
      {name: "weighting", order: DESC}, 
      {name: "release_date", order: DESC}
    ]
  }) {
    results {
      table
      id
      properties
    }
  }
}

Notes:

  • Here we use an array with square brackets [] to add multiple sorting objects with curly braces {}

  • Unfortunately, arrays are not yet supported by the Explorer, so you'll need to add this code manually.

  • You can use different sort orders for each condition

Next Time

If you want to challenge yourself, you could choose to try making a query which uses variables to change the order and field by which results are sorted.

Next time, we'll look at how you use what you've learned so far to start to build your own Application Programming Interface (API). This will use GraphQL to query the database, Liquid to display the results on an endpoint Page and JavaScript to GET those results on the client side.

Site Setup

Add an External Domain

We strongly recommend Fully Delegating your domain for best results or ensuring you use a domain control panel that allows CNAME records on the root/@ or ANAME records such as Cloudflare.

If you have a domain that is managed in GoDaddy or another registrar and would like to point it to a Siteglide website you can use our External Domain feature. This means that you can continue to manage your DNS through GoDaddy/other registrar.

Note: If using OCI (Oracle+Cloudflare)

If your site is on one of our new OCI (Oracle+Cloudflare) stacks the UI will look slightly different, you will be able to add both CNAMEs at the same time. The records are as follows (replace www.yourdomain.com with the actual domain ensuring www is included unless you're adding a subdomain):

Name
Type
Value

_acme-challenge.[www.yourdomain.com]

CNAME

[www.yourdomain.com].fb56597de0699182.dcv.cloudflare.com

www

CNAME

_fallback.uk-siteglide.com

root/@

CNAME

_fallback.uk-siteglide.com

If you cannot put a CNAME on the root we recommend switching either to our fully delegated option or using Cloudflare to manage your DNS who offer flattened CNAMEs on the root.


If using AWS please follow the steps below, contact us if you're unsure which stack you are on.

Step 1: Add a Domain

Navigate to the Domains tab on the Site and click Add Domain:

Step 2: Choose External

Step 3: Enter Domain Details

Type in the root domain without www and you'll likely want to set it as the Default Domain and Enable WWW Redirect. Then click Add Domain:

Important Note: Your domain can take up to a few minutes to be fully added to the system. Please check back and refresh the page shortly.

Step 4: Add SSL Verification CNAME

Once your domain has finished creating in the system, it's status will change to "Ownership Verification Pending" which means it is now ready.

Add the generated CNAME record to in your DNS control panel to verify ownership, generate an SSL certificate and automatically apply it to your site during the next step.

GoDaddy example:

Once you have saved the record in your registrar, allow some time for propagation, check back and refresh the page.

Important Note: The CNAME verification record must NOT be deleted at any point otherwise the SSL certificate will not renew.

Step 5: Add WWW CNAME Record

Once the verification CNAME record has propagated the WWW CNAME Record needs to be added in your DNS Control Panel (GoDaddy example):

The site will now be live via WWW (https://www.domain.com) but not via the root domain (https://domain.com). Complete Step 5 to fully setup the domain correctly. The domain should show as 'Live' under the Status column in Siteglide:

Step 5: Forward the Root/@ to the www (with SSL)

Most DNS Control Panels cannot correctly redirect the root traffic to the WWW over SSL so we recommend using an external service called Redirect.Pizza.

Just follow these steps to redirect your non-www to the www variant. The non-www is also sometimes called naked record or "apex" (screenshots below):

  1. Create a redirect.pizza account.

  2. Add your source. This should be the domain without www. For instance, enter example.com as your source.

  3. Set the destination to the variant with www. Example: www.example.com

  4. Press "Create redirect"

  5. The required DNS change pops up. Go to your domain registrar to make this DNS change for the A record. This record may already exists '@'. Press 'edit' to edit it to the required DNS setting for redirect.pizza. For more info, see What are these DNS changes?

  6. The DNS change is made! It may take up to 24 hours before the DNS is fully propagated.

These Steps can also be found here: https://redirect.pizza/support/redirecting-non-www-to-www

Tutorial 11 - Using Mutations to Delete an Item

Introduction

In this article we'll look at how to delete a record with a GraphQL mutation, but first of all, let's look at what deleting a record means (and how to undo it!).

What is Soft Deletion?

When we delete records with GraphQL, they are soft-deleted by default.

If you accidentally delete data, it may have been "soft deleted", and you may be able to recover it if you act quickly.

Think of soft-deletion as being a bit like a recycle bin:

  • Records are given a deleted_at property with a date in 30 days time in platformOS's datetime format (ISO 8601)

  • When the date is reached, the record will be permanently deleted.

  • They are removed from the results of all queries by default (this includes all Siteglide tags)

  • You can get ordinary recordsqueries to display soft-deleted records (only), see below:

query seeDeletedRecords {
  records(
    per_page: 20,
    filter: {
      deleted_at: {
        exists: true
      }
    },
    sort: {
      deleted_at: {
        order: ASC
      }
    }
  ) {
    results {
      id
      deleted_at
    }
  }
}

The results will be the same as in a normal query; it's helpful to add deleted_at to the results so you can see when they will be permanently deleted:

{
  "data": {
    "record_delete": {
      "id": "3",
      "deleted_at": "2024-01-02T15:15:55.666Z"
    }
  }
}

Recovering Soft Deleted Records

If a record has been soft-deleted by mistake, and has not yet been permanently deleted, you can recover it by setting the deleted_at property to null. Make sure to use the query above to find the ID of the record you want to recover and pass it in as a variable to the mutation below.

mutation recoverSoftDeletedRecord($id: ID!) {
  record_update(id: $id, record: {deleted_at: "null"}) {
    id
    deleted_at
  }
}

After changing deleted_at to null, the record becomes an ordinary record. It appears in queries and is no longer scheduled to be deleted. The mutation results should confirm that deleted_at is no longer set:

{
  "data": {
    "record_update": {
      "id": "3",
      "deleted_at": null
    }
  }
}

If you want to extend the amount of time the record stays in soft-deleted state, you can change the deleted_at property to set the deletion date to be further in the future (but this is an advanced method we won't cover here).

Deleting a Record

Building a record_delete mutation in explorer

Step 1) Find the ID of the Record you wish to Delete

Use another query to find the ID.

Step 2) Add the mutation keyword and a name

mutation deleteRecord {

}

Step 3) Add the records_delete mutation type

mutation deleteRecord {
  record_delete() {}
}

Step 4) Add the id of the record you wish to delete, with a variable if you like

mutation deleteRecord($id: ID!) {
  record_delete(
    id: $id
  ) {}
}

Step 5) Add a result to confirm the mutation's success

mutation deleteRecord($id: ID!) {
  record_delete(
    id: $id
  ) {
    id
    deleted_at
  }
}

A successful deletion may look like this:

{
  "data": {
    "record_delete": {
      "id": "3",
      "deleted_at": "2024-01-02T15:15:55.666Z"
    }
  }
}

Summary and Further Learning

You've now learned how to perform all four CRUD operations using GraphQL:

  1. Create

  2. Read (query)

  3. Update

  4. Delete

If you wish to programatically create, update or delete multiple records at once, it may be more efficient to use the following mutation types instead of looping:

  • records_create_all

  • records_update_all

  • records_delete_all

Be very careful with those and make sure you test with data which is backed up. We recommend using any_table: false as a precaution.

In these mutation types, you use a filter to determine which records should be affected instead of only passing id. You need to add count to the results.

Next Time

Next time we'll look at how you can use a query to access data from multiple relational database tables, or in other words, view data from Siteglide datasources.

Tutorial 10 - Using Mutations to Edit a Record

Introduction

Last time we looked at how you can use mutations to create a record.

This time, we will look at a mutation to update a single record.

The syntax is very similar to creating a record, but the main differences are:

1) using a different mutation type

2) We need to pass in an ID- to identify the record which needs to be changed.

Steps to Edit a Record

Step 1) Add the mutation keyword

All queries started with the query keyword; mutations start with the mutation keyword.

mutation editItem {

}

We've also named our mutation editItem.

Step 2) Add the recordUpdate mutation type

mutation editItem {
  record_update() {
  
  }
}
record_update in explorer

Step 3) Add the ID of the Record which should be updated

In this example, we'll add the ID as a variable:

mutation editItem($id: ID!) {
  record_update(id: $id, record: {})
}

Note, we don't need to add an object for the ID as we might when filtering a query.

Step 4) Define the new Properties in the Record Object

As with record_create mutations, the record object defines the properties the record will have after the mutation has run.

Any properties you set will be updated. Any properties you omit will stay in their current state.

mutation MyMutation($id: ID!) {
  record_update(
    id: $id
    record: {
      properties: [
        {
          name: "module_field_3_1",
          value: "Updated Blog Title"
        }
      ]
    }
  )
}

You do not need to specify a table. By default, the record will remain in its current table. Attempting to change the table may not be supported on Siteglide as the new table would not have compatible fields.

As in our last tutorial, it is possible to pass in properties as a single large JSON variable, if you see a convenience in doing so.

Step 5) Add Results

As in the last tutorial, it is required to add something to the object which returns with the results of the mutation. This can be useful to confirm results and show you what has changed. However, if you don't need many details, the best performing option is just to ask for id.

As with queries, explorer colours results in dark blue, and the mutation arguments in purple.
mutation MyMutation($id: ID!) {
  record_update(
    id: $id
    record: {
      properties: [
        {
          name: "module_field_3_1",
          value: "Updated Blog Title"
        }
      ]
    }
  ) {
    id
  }
}

A successful mutation result will look like this:

{
  "data": {
    "record_update": {
      "id": "96"
    }
  }
}

Handling Race Conditions

Imagine you have two different forms, each with an automation attached to them and you want to count how many times the automation runs.

You set up a WebApp with a custom integer field. Each time the automation runs, you:

  1. query the WebApp, get the current count

  2. add 1 to the value with the add Liquid filter and

  3. update the count with a record_update mutation.

9 times out of 10, this will behave as expected, but what if two people submit the form at the same time?

If both automations check the current count at the same time, they will both read the same value e.g. 5 and after adding 5, will save 6 as the updated count in the database. Even though the automation ran twice, the count would only increase by 1.

It is to combat this scenario that record_update contains the following special operations:

  1. array_append will push the provided value to the end of an existing array stored in a property.

  2. array_remove will remove the provided value from an existing array stored in a property, (wherever in the array it currently is)

  3. decrement will reduce an existing integer value by the provided integer value (could be 1 or some other integer)

  4. increment will increase an existing integer value by the provided integer value (could be 1 or some other integer)

In our example, we could do the following:

mutation MyMutation($id: ID!) {
  record_update(
    id: $id
    record: {
      properties: [
        {
          name: "webapp_field_1_1",
          increment: 1
        }
      ]
    }
  ) {
    id
  }
}

This safely increases the counter by one per mutation run, even if the two mutations are triggered by Liquid simultaneously. There is no need to query the existing value.

Summary

Nice work following through this tutorial.

If you want to try updating users, you can experiement with the user_update mutation.

Next Time

Next time, we'll look at how to delete records.

WebApps

Tutorial 3 - Filtering the Results

You shall not pass! This time, we'll look at how you can use filters to only return results based on specified rules.

Prerequisites

  • You have completed the Learning GraphQL tutorials 1 - 2. You can find the previous tutorial

  • - optional- Read more about GraphQL and when it might be best used.

Introduction

recordsin the database have a tablewhich tells us which WebApp or Module they belong to.

This time, we'll look at how you can use filters to only return results with a particular table, or with a table which matches a certain pattern.

Returning The Gallery WebApp

The Starter Site comes packaged with a ready-built WebApp with the id of 1 and the name webapp_1.

Step 1: Load the previous query

We'll return to our query from the previous tutorial, but this time, we'll rename it from get_all_records to get_webapp_1 to reflect the different purpose we intend for it. We'll also be wanting to look at page 1 again.

Code:

Explorer:

Step 2: Add the filter argument

Next, we'll add a filter argument:

Code:

Notes:

  • As an argument for the recordsquery, this goes inside the round brackets after records.

  • Like the other arguments, filter is followed by a colon :

  • We have more settings to choose next (filter is an object) so we add curly braces { }

Explorer:

Step 3: Filter by the table

In this tutorial we'll choose the tableto apply the filter to, because we're looking for items with the tableof webapp_1.

Code:

Notes:

  • We've chosen table as the only filter. In the next tutorial we'll explore using other fields, and filtering by more than one field at once.

  • This field also contains further options, so we use a colon : followed by curly braces { } to contain the next set of options.

Explorer:

Step 4: Define the filtering rule

We now have a choice about:

  1. How closely our value should match with the contents of a field before a match is returned. We'll use value (the exact value).

  2. The value we are matching against. We'll use webapp_1 .

Notes:

  • value is a key and is followed by a colon :

  • Our value "webapp_1" must be a String, so we wrap it in double quotes.

Documentation panel:

  • Selecting RecordsFilterInput gives you options for different filtering rules you can apply:

  • After the colons : you can see the type of value expected for each of these keys. They are mostly strings String or arrays of strings [String]. This topic will be covered in more detail in later tutorials. Keep an eye out for the different data types expected by GraphQL in the meantime.

Explorer: When implementing this using the Explorer, the wizard will help you get the type of value correct. In this case, it provides you with quotes so that you can enter the value as a String:

Returning Items from the Blog Module

You can adjust the filter to return items from a specific Module item only. In this example, we'll specify module_3 which is the Blog Module.

Code:

Explorer:

This should return Blog Posts from the Blog Module.

Returning Form Submissions

You can adjust the filter to return Form Submissions from a specific Form only. In this example, we'll specify form_1 which is the Newsletter Sign Up Form.

Code:

Explorer:

Challenge!

Introduction to GraphQL Challenges

In order to learn GraphQL, you'll need to start experimenting with what you've picked up from this tutorials.

To help you do this, we'll now start to set you some challenges. These will ask you to tweak the examples we've given you so far and see if you can achieve the desired results.

We'll always give you the answers to the challenge in the following Article, so don't worry if you get stuck.

Your Challenge is to Write a Query which returns Items from all WebApps but not Module items

To carry out this challenge, you will need to create a second WebApp and add a couple of items in the Admin. By experimenting with the options in the documentation panel, see if you can filter the results so that:

  • your query returns all items with the tableof webapp_1

  • your query returns all items with the tableof webapp_2

  • your query does not return items which start with module_

  • your query does not return Form submissions which start with form_

We'll go over the answer to this challenge in the next Article.

Next Time

We'll look at a possible solution to our challenge. After that, we'll continue to look at filtering queries in more detail, including:

  • filtering by different fields, or properties

  • filtering with different kinds of rules

  • using more than one filter at once

query get_webapp_1 {
  records(
    page: 1
    per_page: 20
  ) {
    current_page
    total_pages
    results {
      table
      properties
    }
  }
}
query get_webapp_1 {
  records(
    page: 1
    per_page: 20
    filter: {

    }
  ) {
    current_page
    total_pages
    results {
      table
      properties
    }
  }
}
query get_webapp_1 {
  records(
    page: 1
    per_page: 20
    filter: {
      table: {

      }
    }
  ) {
    current_page
    total_pages
    results {
      table
      properties
    }
  }
}
query get_webapp_1 {
  records(
    page: 1
    per_page: 20
    filter: {
      table: {
        value: "webapp_1"
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query get_blog_module {
  records(
    page: 1
    per_page: 20
    filter: {
      table: {
        value: "module_3"
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query get_newsletter_signups {
  records(
    page: 1
    per_page: 20
    filter: {
      table: {
        value: "form_1"
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
here
About GraphQL

Tutorial 6 - (Answers)

Last Time we challenged you to pull together everything you'd learned to create some Pagination Buttons powered by Graph. Answers here!

Challenge Objective

For this challenge, we'd asked you to set up your own simple Pagination controls. This combined several of the skills you've learned so far. As usual, don't worry if you need to check these answers before completing the challenge on your own.

On a new Page you create, the User should be able to see the first three Items in the Gallery WebApp.

Then, when they press the Page 2 button, the Page should refresh and they should see the second Page of Results.

Step 1) Writing the Graph Query

Firstly, here's the query without variables.

query gallery_by_page {
  records(per_page: 3, page: 1, filter: {table: {value: "webapp_1"}}) {
    page #optional
    results {
      properties
      id
    }
  }
}

Now let's add the variables:

query gallery_by_page($page: Int) {
  records(per_page: 3, page: $page, filter: {table: {value: "webapp_1"}}) {
    results {
      page #optional
      properties
      id
    }
  }
}

Notes:

  • The most difficult part here might have been working out the type. Here the query expects Page to be an integer. The documentation panel confirms this:

  • From the documentation panel, you can also see "= 1" after the type. This is a default, which means if no variable is passed through, the variable will be given a default value of 1. Not all variables will have defaults.

  • The type here does not have an exclamation mark ! so the query won't deliberately fail if no value is passed in.

  • We set per_page to 3

Step 2) Sync the Query to the Site using Siteglide-CLI

Refer back to Tutorial 5 to refresh these steps.

Step 3) Adding the HTML Controls

We gave you these in the tips. You'll have needed to add them on a Page of your choice.

<ul>
  <li><a href="{{context.headers.PATH_NAME}}?page=1">1</a></li>
  <li><a href="{{context.headers.PATH_NAME}}?page=2">2</a></li>
  <li><a href="{{context.headers.PATH_NAME}}?page=3">3</a></li>
</ul>

Pressing one of the buttons will redirect us to the same Page, but will be adding a page query parameter to the URL. This is the easiest way to pass variables to Liquid- because Liquid runs before the Page loads, and the URL is one of the only available sources of dynamic information at this point.

It's also possible to pass information to Liquid on another Page via an XHR request. This can lead to smoother UX, but is more challenging and will be covered in a future tutorial.

Step 4) Reading the URL Parameters

Let's say we pressed the second button, our relative URL will now be:/my-page-slug?page=2

As we showed you in the tips, Liquid can read this parameter at the following dot-notation path. This will output the value on the Page: {{context.params.page}}

Or you can store this in a variable, which we'll need to do here:

{% assign current_page = context.params.page %}

By the way, the keys in context.params are dynamically generated for any query parameter in the URL, so you can pass through any variable you like with this method.

Step 5) Feeding the Variables into the Query

a) First, let's set a default value, just in case a User arrives at the Page without setting the Parameter:

{% assign current_page = context.params.page | default: 1 %}

b) The query is expecting an integer, so let's apply an Integer filter that will change the type to an Int without changing the value:

{% assign current_page = context.params.page | default: 1 | plus: 0 %}

c) Let's add the graphql tag with the variable parameter.

{% assign current_page = context.params.page | default: 1 | plus: 0 %}
{% graphql my_result = "gallery_by_page",
page: current_page
%}

Step 6) Output the Results

We can output the results using the variable name we defined in the graphql tag.

{% assign current_page = context.params.page | default: 1 | plus: 0 %}
{% graphql my_result = "gallery_by_page",
page: current_page
%}
{{my_result}}

If pressing the HTML anchors changes the results by fetching different "pages" of JSON results, you've been successful. Congratulations.

If you're having difficulty with any of these steps still, please don't hesitate to ask for help on the Forum.

Full Code

Liquid

{% graphql my_result = "gallery_by_page",
page: current_page
%}
<pre>{{my_result}}</pre>
<ul>
  <li><a href="{{context.headers.PATH_NAME}}?page=1">1</a></li>
  <li><a href="{{context.headers.PATH_NAME}}?page=2">2</a></li>
  <li><a href="{{context.headers.PATH_NAME}}?page=3">3</a></li>
</ul>

GraphQL

query gallery_by_page($page: Int) {
  records(per_page: 2, page: $page, filter: {table: {value: "webapp_1"}}) {
    current_page #optional
    results {
      properties
      id
    }
  }
}

Optional- Taking this further

The next steps give you ideas for how to take this further. They are not part of the initial challenge!

Step 7) Output a Layout (Optional)

These JSON results don't look great. Remember, you can use Liquid to pass the results into a Layout- if you can find that Layout's relative path in Code Editor.

{% assign current_page = context.params.page | default: 1 | plus: 0 %}
{% graphql my_result = "gallery_by_page",
page: current_page
%}
{% assign result_array = my_result.records.results %}
{% for this in result_array %}
  {% include 'layouts/webapps/webapp_1/list/my_layout_name', this: this %}
{% endfor %}

However, you won't have access to the user-friendly names for fields in that Layout, because GraphQL will output the raw database IDs for fields.

You'll want to target fields in this Layout with syntax like:{{this.properties.webapp_field_1_2}}

You can view the database IDs for fields in the Admin using the toggle in the top-right of the screenshot, or you can look at the keys in the JSON results.

Step 8) Use the Results of the Query to only show buttons you need. (Optional)

By returning total_pages from the query, we'll know exactly how many Pagination buttons to display:

query gallery_by_page($page: Int) {
  records(per_page: 3, page: $page, filter: {table: {value: "webapp_1"}}) {
    current_page
    total_pages
    results {
      properties
      id
    }
  }
}

You can then use this to manipulate the HTML pagination controls:

<ul>
  {% for page in (1..my_result.records.total_pages) %}
    <li><a href="{{context.headers.PATH_NAME}}?page={{page}}">1</a></li>
  {% endfor %}
</ul>

Next Time

Next time, we'll learn how to sort the results of your Query.

Let's go!

Theme Module Example

In this example we're going walk through, step by step, how to create a Website Theme Module that could either be used as a private module for your agency or shared publicly in the marketplace to deliver turnkey site solutions to your clients and more.

It is recommended that you download our example Theme Demo Module Repository from GitHub so that you have all of the code and asset files available at each step of this guide and can easily follow along to and have your module end up looking exactly the same.

Create Your Module

The first step to building your module is to create a listing for it in Siteglide. Within your Portal from the left-hand menu, select "Custom Modules" and then click the blue "+ Add New Module" button in the top right-hand corner.

You only need to fill in basic information into the core fields. Checkout the Create your Module in Siteglide doc for more information.

Remember to note down your newly generated Vanity ID.

Create Your Folder Structure

First, create a fresh staging site for your module. Checkout Create Your Staging Site : for more information on this step.

Second, create a folder for your project on your local machine. Working within your new project folder, connect to your new staging site via CLI to pull the initial site files down onto your computer.

Next, create a top level folder called modules/<module_name>/public within your project folder. Because we are creating a basic theme Module, we don’t need to create the private top level folder. Checkout Top Level Folders for more info.

Assets

Within the public folder, we want to add in the core assets our Theme Module will use. There are three folders we want to copy in to our own Module project folder:

  • bootstrap5-plain-assets - Core Bootstrap 5 image assets which our Theme pages will display.

  • js - Core Bootstrap 5 JS files along with a custom JS file which our Theme pages will run using.

  • scss - Core Bootstrap 5 CSS files along with a custom CSS file which will Style our Theme pages.

Open the Module_Siteglide_ThemeDemo project folder you downloaded earlier and navigate into modules/<module_name>/public/assets. From here, drag and drop all three bootstrap5-plain-assets, js & scss folders into your own Module project assets folder.

Views

Next we’re going to add in various core elements such as Pages, a Page Template, Header & Footer.

First, within your modules/<module_name>/public folder, create a new folder called views. Inside the views folder, we'll be creating several new folder structures to house the various files we’d like to include. Below I’ll list each of these directories and give an explanation as to what they will be used for:

  • layouts/templates - A Page Template file used to wrap all of our theme pages.

  • pages - Several Pages we would like to include in our module and display on the site.

  • partials/includes/header - Page Header file used to store the Header including main navigation.

  • partials/includes/footer - Page Footer file used to store the Footer including secondary navigation.

Setting Pages up with appropriate Template, Header & Footer files allows us to only write the code once and apply it consistently across all of our theme pages. It also makes it easier to update later if we’d like to. For more information on page structure checkout: Templates - Getting Started.

Open the Module_Siteglide_ThemeDemo project folder you downloaded earlier and navigate into modules/<module_name>/public/views. From here, drag and drop all three layouts, pages & partials folders into your own Module project views folder.

Correcting Your Files

As we’ve been copying files from the Theme Demo Module, we'll need to update the module name in some paths written in the files. Open up your IDE and bulk find/replace module_76 with your <module_name>.

Module Setup Files

Next, we want to create one of the setup file options available: install-process.json.

Create a file called install-process.json on the root folder of your Module Project (alongside /modules/). Checkout Module Setup Files for more info.

Add the following Code Snippet to your newly created Install Process file:

{
  "1.0.0": "set_homepage"
}

Adding this line of code to the install-process.json file will ensure that when a user installs our module, the home/start page is automatically applied as part of the installation. Otherwise, the existing home/start page on a site will be honoured.

View Your Staging Module Site

Now that we have added all of the core assets and views to our module, let’s see it in action!

Using CLI run the deploy command to send all of the files within your project folder up to your staging site.

Open your staging site to view your work both front-end and back end.

Your site should now look and behave like this front-end: Module Siteglide Theme Demo.

If you’re happy everything looks as it should, continue to the final step. If not, check back along the steps above to make sure you've not missed anything.

Submit Module For Approval

First, delete the marketplace_builder folder from your project folder on your local machine. We don’t want to include this to avoid overwriting any pre-existing content on sites which install our module.

Preparing To Submit

Follow the checklist to confirm your Theme Module is ready for submission:

Here is what your project folder should now look like:

GitHub

Now that we have prepared our files, if you haven’t already create a new GitHub Repository and commit your project folder to it.

Please ensure:

Sending your Module to Siteglide

Next we will need to update the Module item that you made in Admin earlier to include some extra information. Checkout the Sending your Module to Siteglide doc for more information.

Once you’ve submitted your Module for approval you’ll need to give us access to see the Module. This is needed for the initial approval, but also for ongoing access to be able to install the latest version of the Module. To provide us with access you need to invite Siteglide API ( [email protected] ) as a collaborator for the GitHub Repository. Checkout the After Submitting your Module doc for more information.

Updating & Maintaining Your Module

There are a couple of key things you should be aware of when managing and updating your module moving forward. Checkout this doc for more info: Updating Existing Modules.

Clients

Organise your Sites into Client Folders

The Clients feature in Portal is only available to Agencies and gives you an extra level of management allowing you to group multiple Sites under one Client and control who can see it.

Create Client Companies

You can create unlimited Clients and Invite Users who would then only have access to that Client area and any Sites linked to it.

You can access this from the left hand side menu but also from within your Agency Account.

Invite & Manager Client Users

If you invite a User within a Client they only have access to that Client Company and any Sites within it. It's very useful when a customer has multiple Sites but it's also a good way to keep Siteglide organised and clutter free!

Create & Manage Client Sites

The Sites tab shows the Sites linked to this Client Company. Create a new Site here to ensure it can only be seen by the right Clients (your Agency users can see ALL sites):

Agencies

Siteglide is built for Digital Agencies by Agency owners that hit too many limitations with the platforms available. While anyone can use Siteglide it's designed with agencies in mind.

Approved Siteglide Agencies get various benefits and extra features to help them deliver the best results to end customers.

Explore the Agency Account features:

Whitelabel your Account:

Manage Client Accounts:

Make copies of Sites:

Field Types

Field Types available

A list of field types available on Siteglide and the corresponding platformOS data type applicable to them

Name
Siteglide Type
platformOS Type

Where are these field types available to use as Custom Fields?

  • Modules - All field types

  • WebApps - All field types

  • eCommerce Products - All field types

  • CRM Users - All field types

  • Forms - All field types except Custom Array

  • Custom Field Sets - All field types except Custom Array

Form Configuration metadata options

All field types have the following metadata options:

Name
Type
Notes

Some other metadata options are available to specific field types:

Name
Type
Available for
Notes
Agency Account
Agency Whitelabelling
Clients
Site Copies

Text (String)

input_text

string

Text (Multiline)

textarea

string

Checkbox

input_checkbox

array

Radio Button

input_radio

string

Dropdown (Single Item)

select

string

Dropdown (Multi Item)

select_multi

array

Datasource (Single Item)

datasource

integer

Datasource (Multi Item)

datasource_multi

array

Image (Single)

image

string

Image (Array)

image_array

array

File

file

string

Folder

folder

string

Date

date

integer

Number (Integer)

number_integer

integer

Number (Float)

number_float

float

Boolean

boolean

boolean

Custom Array

array_custom

array

name

string

The field name to be shown in Siteglide Admin UI

type

string

The field type in the 'Siteglide Type' column above

live

boolean

Determines whether or not the field is shown or used anywhere

hidden

boolean

Determines whether or not the field is shown in Siteglide Admin UI

order

integer

The position the field is shown in Siteglide Admin UI and Import CSVs

editable

boolean

Determines whether you can edit this metadata in the Siteglide Admin UI builder views

required

boolean

Determines whether it is required to fill in this field in Siteglide Admin UI and front-end forms

rich_text

boolean

textarea

Determines whether the Rich Text editor appears in Siteglide Admin UI for this field

options

string

input_checkbox input_radio select select_multi

A comma separated string of options to be displayed for this field in Siteglide Admin UI

datasource_id

string

datasource datasource_multi

The ID of the model you want options to show from for this field in Siteglide Admin UI. This includes the type, for example webapp_1.

num_min

string

number_integer number_float

The minimum value this number field will allow the user to select.

num_max

string

number_integer number_float

The maximum value this number field will allow the user to select.

num_step

string

number_integer number_float

The amount the number will change by using the number scroller in Siteglide Admin UI.

num_precision

string

number_integer number_float

The amount of decimal places shown in Siteglide Admin UI.

Accessing Data from the Global Context Variable

Want to use Liquid Dot Notation to find dynamic data on your Site? The context variable is most likely the place to look.

Prerequisites

In this Article, we'll be using dot notation, so if you're not familiar with it, you may want to brush up here:

  • Getting Started with Dot Notation

  • Advanced Dot Notation - Arrays and Key Maps - You might not need to fully understand this topic- but a first read of it will be very useful in helping you to recognise the more difficult to handle types of data structure- even if you're not ready to tackle them yet.

Introduction

Once you've got a good understanding of Liquid Dot Notation, the next step is branch out and discover what is possible.

Here are some of the most useful places to look for data:

  • {{context}} The platformOS object context contains a huge tree of information about your Site- often including variables you didn't know you wanted until you discovered them.

  • {{context.exports}} The object exports sits on the root level of context and its role is to allow a global storage location for any custom variables not already included in context. You'll mostly use it as a place to access Siteglide specific data, for example, it is the home of data relating to Siteglide categories and will contain WebApp or module data when you use the collection parameter in the include tag. exports will also contain any keys that you have entered within the Integrations area in Siteglide Admin. You can also add your own data to a namespace in exports - see the platformOS docs to see how. This is especially useful if you want to include some data in a Content Section, Code Snippet or Layout- but access it in on a higher level e.g. the Page Template.

  • {{this}} - This is a Siteglide variable containing data specific to a Layout- you'll only be able to use if within a Layout file. The data will completely change depending on the type of content, so it's a good place to use Dot Notation to explore each time you try a new feature.

In this article, we'll explore the context object in more detail.

The this object is used differently in different features, so the best place to learn about it is by reading the documentation on the specific feature you're using it with.

Context

The context object's role is to provide your Site with contextual information which might help it render HTML in a more flexible and personalised way.

Context should be available in any Liquid file, and this includes auto-responder and workflow notifications. However, bear in mind that context may work subtly differently in an email- for example- it's not possible to see information about the person reading an email- current_user would refer to the person submitting the Form which triggers the email to be queued.

We'll go through the top level of context's keys and explain each namespace's role. Beyond that, you will have to explore for yourself!

If a property is marked advanced only, it is because we haven't found a use-case for it yet on a Siteglide site, or they are not supported, or there is a newer version of this property which is easier to use. We may mark a feature as advanced only because it is possible for it to conflict with a Siteglide feature and it needs to be used with caution. You may be able to discover your own Use Cases for these- if so we'd be very interested to hear about them! To be clear, we can't offer support if these are used in your Site, but we're noting them here in order to be comprehensive.

authenticity_token (value)

Advanced only - You won't need to use this directly- it is a frequently changing token used as part of the security on form submissions.

session (object)

platformOS uses a single cookie to identify a visitor- whether signed in or not. The session object holds information in the database which relates to the visitor with that cookie. This is used in Siteglide eCommerce to allow us to identify someone who has added items to a Cart, without requiring them to Sign In. Learn more about how we use this essential cookie here.

Adding Session Fields: You can add fields to session and exports objects only.

The easiest way to add session fields is to use the


<div data-gb-custom-block data-tag="session"></div>

tag. https://documentation.platformos.com/api-reference/liquid/platformos-tags#session

Compatibility: To avoid conflicting with future Siteglide features, we recommend that if you use this feature, you prefix any new fields with the initials or name of your Agency.

e.g. Let's say a User has decided to opt-in to a particular non-essential feature or cookie. You could remember this:

{% session agency_optional_features = 'true' %}

You can then use logic to only show these features to Users who have opted-in.

{% if context.session.agency_optional_features == 'true' %}
<script>
  //My optional feature script
</script>
{% endif %}

***Removing Session Fields ***To continue our previous example, if the User chooses to change their preferences and opt out, you can forget the setting by setting it to an empty String:

{% session optional_features = '' %}

Ending a Session- Advanced only You can use custom GraphQL to completely forget a visitor and end their session. Use at your own risk as we cannot support Secure Zones with Users who have had their Session ended in this way: https://documentation.platformos.com/api-reference/graphql/deprecated/mutations/user-session-destroy

current_user (object)

Unlike session, this only holds information relating to a User if they are currently logged in to a Secure Zone. It is more sensitive than `session` and will include a name and email address. If there is no current_user, it will hold the value null.

headers (object)

Headers are information sent and received when a browser sends a request to a server for an HTML Page. This is a technical area which can be nevertheless useful on some every-day use cases.

For example, in this Article, we show you how to set up canonical URLs. We use the headers object to grab a useful version of the URL: Preventing Duplicate Content.

Another helpful request header is HTTP_REFERER. This tells you the URL of the Page that the User was visited before the current one- assuming this was not deliberately hidden. This can be really useful for redirecting a User back to a particular Page after carrying out an Action.

params (object)

This object helpfully takes the URL of the current Page and dissects it into chunks which you can use.

  • slugs refer to parts of a URL separated by forward slashes / - slug, slug2 and slug3 will be available to you only if they exist. This can be helpful if you're implementing breadcrumbs.

  • query parameters will become available to you if they exist and will be available under the same key they use in the URL. E.g. a parameter in the URL https://siteglide.com?password=test will become available here: {{context.params.password}}

Advanced only- When using CLI- params can also be used in a form_configuration to interrogate the form data being sent to the server, including virtual fields.

language (value)

Advanced only- You can ignore this, as Siteglide does not support this pOS feature.

environment (value)

Advanced only- This will currently only read the value of "Production" as currently all Siteglide Sites are Production ready- but watch this space!

is_xhr (value)

Advanced only - Returns true if the Page is being rendered by an XHR (sometimes referred to as Ajax) Request.

location (object)

This is analogous to "location" in JavaScript and can provide you with useful URL information.

  • {{context.location.url}} will get you the full absolute URL for the current Page

  • {{context.location.href}} will get you the relative URL for the current Page without query parameters- useful if you want to set new query parameters!

  • {{context.location.pathname}} will get you the relative URL for the current Page including query parameters.

This Object is often useful when you use it alongside params.

page (object)

This contains metadata for the Page.

layout (object)

This actually gives you metadata relating to the chosen Page Template, not about Layouts on the Page.

modules (object)

This contains objects for each module you have installed on your Site. It includes Siteglide Modules and any Modules you've installed via platformOS. It only contains metadata- if you want data relating to items in that Module, you'll have to access it in a Layout with {{this}} or through making a collection and then looking in {{context.exports}}.

visitor (object)

Advanced only- Contains the IP Address of the visitor.

constants

Advanced only-

useragent

Advanced only- Use device instead!

device

Device should give you information about your visitor's device, operating system and browser. However, use it with caution - you'll still need to make websites responsive! You could use this to suggest to certain Users that they will have a better experience if they update their browser to a modern one.

cookies

This lists the cookies currently used by the Site.

Siteglide only uses the session cookie directly which you can learn more about here

Any payment gateways you are using as part of Siteglide eCommerce e.g. Stripe may also have a cookie listed here.

You may also see cookies relating to browser extensions. Ignore these- as your Users will have different extensions to your Developers!

version

Advanced only

post_params

Advanced only

flash

Advanced only

exports

This is where all context data is kept which is custom- either to Siteglide or that you have added to your Site.

DNS Check and DNS Report Tools - Comprehensive DNS Tests - MxToolBox
DNS Propagation Checker - Global DNS Checker Tool

Site Setup

Connect to a site via CLI

You need CLI installed first, check out our Quickstart guide:

Once you've installed the CLI you should be ready to learn how to set up a new project folder and connect it to a specific Siteglide Site. You'll need to follow these steps every time you want to use the CLI to work on a new site, so over time these will become very familiar.

Step 1) Create a Project Folder on Your Machine

Your project folder will contain all of your Site's files.

Use Finder on Mac, or Windows Explorer to create this new empty folder, and give it a similar name to your site so you can find it again. We recommend creating this folder inside a sensible organisation structure, for example you may have a single folder which contains all of your project folders, or you might organise by customer.

Step 2) Open Your Project Folder in a Text Editor

In order for a CLI tool's ability to deploy and pull code from a Site to be useful, we need to have a good place to look at and change that code.

While you are free to use any kind of terminal or any kind of Text Editor, we'll use VSCode for these steps- one major benefit is that its integrated terminal allows you to easily manage your project in one place. It also comes with lots of useful extensions.

If you do not already have a text editor installed, you can browse for one or install VSCode here: https://code.visualstudio.com/download

You can then open your project folder in the Text Editor:

Step 3) Open the Integrated Terminal

If your IDE or Text Editor does not have an integrated terminal, you can open any terminal and change directory to your project folder.

The terminal will appear in its own panel, by default below the code. In VSCode this will automatically change directory to your project folder (if not, do this manually)!

If you need to run multiple commands at once, you will need two terminals. In VSCode you can "split" the terminal window if you want to run multiple commands at once and keep an eye on all of them.

Step 4) Make Sure Your Email Address is Listed in the Site Users

Open up the Siteglide Portal and find your Site in the Sites option in the left-hand-side menu.

In the Users tab, make sure your user's email address is listed. If not, invite yourself (as an existing user) to the Site.

Step 5) Copy the Env Command with Pre-Filled Variables

In the Details tab, near the bottom of the Site information, find the CLI Command and copy it.

Step 6) Run the Env Command to Set Up a Connection Between your Project Folder and a Site

Paste the command into your integrated terminal.

When prompted, enter the Siteglide Password associated with the email address in the command.

This won't work unless you follow step 5 first! If you forget your password, you can reset your password on the Siteglide Portal first.

This command creates what we call an envrionment. Think of it as a connection between a project and a Site. Sometimes you may maintain a staging Site and a live Site for the same project and you might create both a staging and a production environment.

You will need to remember the name of your environment in order to use any other Siteglide-CLI commands, but if you forget, there will be a .siteglide-config file in the root of your project folder which will list them:

Step 7) Run an Initial Pull of the Site to Pull Down the Existing Code Base

You're now set up with Siteglide-CLI for this Site! The obvious first move would be to pull the existing Site's files down to the Site, using the following command in the terminal:

siteglide-cli pull staging

This will create your directory structure and populate it with your site's files and assets (images will not be pulled due to their size - see export command).

If you use Github for backing up your code, debugging and undoing mistakes, it's a good idea to initiate it and make your initial commit now.

Next

You now know how to set up Siteglide CLI on a Site! You can follow these steps every time you want to work on a new Site.

Next, check the Reference for how to:

  • Deploy all your code to the site

  • Sync individual files to the site as you save them

  • And remember how to pull all the code down to your project folder

Tutorial 1 - Your First Query

Hello WebApp! In Tutorial 1, we'll use Siteglide CLI to open the GraphQL sandbox. There, you'll write your first query and retrieve details about some webapp items.

Prerequisites

  • Create a new Site from the Flowbite Portal Site Template. We'll use this in the examples

  • - You'll need to understand how to open terminal on your machine and navigate to your Project's root folder. You'll need to have installed Siteglide-CLI and its dependency Node.js. You can't use GraphQL without Siteglide-CLI.

  • - optional- Read more about GraphQL and when it might be best used.

Introduction

In this first of the GraphQL Tutorials, you'll need to either create a new Starter Site, or open up a Starter Site you've created previously.

We'll use Siteglide CLI to open the GraphQL sandbox. There, you'll write your first query- which will ask for all the data on the entire Starter Site!

Running the GraphQL sandbox

The GraphQl sandbox is a place for testing Graph Queries. It has the additional benefits of giving you suggestions and documentation alongside your code.

When you open the sandbox- you'll be opening it with your chosen environment. This means you're picking a Siteglide Site to give the sandbox access to- it will be able to find database items from this Site.

For these tutorials, we strongly suggest you use a Starter Site- because then you'll have exactly the same data and it will be ready to go.

Choose an Environment (Site) to Connect to

Change directory into your project folder (create a folder first if you don't have one).

Then add your Site as an environment - the quickest way to do this is to copy the CLI command from your site in the Portal- or you can replace the --url and --email flags with your own. Add your Siteglide Admin password when prompted:

Run the Sandbox

The command for running the sandbox is (replace the stagingplaceholder with the name you chose in the previous step):

siteglide-cli gui staging

Success should look like this:

Click, Ctrl+click (if your machine allows), or copy the "GraphQL Browser" link into a browser. This will open the sandbox.

Short Method: If you want to save time and skip the step of opening the link, add the flag -o to automatically open the link after you run the command:

siteglide-cli gui my_env -o

Your First Look at the GraphiQL Sandbox

To start with a few important features are hidden. Click and drag up the QUERY VARIABLES panel from the bottom left and click the < Docs button in the top right. We'll come back to these later.

Click the "Toggle Explorer" button to open the Explorer wizard. This is a new tool which will make writing (non-deprecated) queries even easier.

You've now set up your GraphiQL sandbox!

What does each panel in the GraphiQL Sandbox do?

The Central Panel

This is where you write your Query

The Results Panel This is where the data will be displayed in JSON format after your query fetches it.

Query Variables Panel This is where you'll be able to simulate dynamic variable inputs when testing variables in your Query.

We'll cover this in a later Article.

Explorer Panel *NEW* This displays the GraphQL schema in the form of a "wizard". Tick the options you want to include and they will automatically be added to your query. This is the most user-friendly way to write queries for beginners (and possibly for the experienced GraphQL developers as well!). Note the explorer user-interface can't handle arrays of query language very well e.g. when filtering by multiple properties, even though it's valid GraphQL; when you hit a limitation like this, it's time to leave the explorer behind and write the code manually.

The other limitation is that it doesn't currently support older deprecated query and mutation types, if you have a need to use those.

Documentation Explorer Panel

This displays the available GraphQL schema as documentation. It works with nested levels, so each time you choose an option you can "click through" to the next level of options you can include.

This is less user-friendly than the new "Explorer" panel above, but can still be useful:

  • especially if you want to use a deprecated query like "customisations" (The only reason for using deprecated queries is that some support features like keyword search that are not supported on the more modern queries yet)

  • If you want to search the schema for features and understand which types are allowed

A note on naming conventions

platformOS sometimes changes the names of elements of their schema for the sake of better communication or as part of a functionality update. For example, models have been now renamed to records.

We are in the process of upgrading these docs to refer to records instead of models but in the meantime there may be some screenshots which continue to show the older terminology.

Your First Query

For your first query, we'll fetch every recordon the Site. A recordis a catch-all term for WebApp and Module items that we'll use in these tutorials. The term records does not refer to users, as these contain more core fields like email which set them apart and therefore they use a different query type: users.

In these tutorials, we'll break the process of building a query into steps. For each step we'll show you:

  • The code you need

  • Notes explaining the syntax of the code example in detail

  • A screenshot from the Explorer Panel, showing the checkbox needed to quickly insert this code *NEW*

We'll sometimes also include a screenshot of the Documentation Explorer Panel to illustrate it's use, but this depends on whether it seems useful or not!

Step 1) Name your query

Code:

Notes:

  • The word query tells Graph we want to GET data, as opposed to modifying data.

  • getEverything is simply a name we have given the query. It is optional and has no functionality.

  • Curly braces { } have been added - these will contain an object containing the next level of instructions we will give the query. The next level from here is also known as RootQuery as it is the most fundamental level at which we choose which type of query we'll use.

Explorer: The "Explorer" wizard will add this for you if you type out a name here:

Step 2) Choose a Query type

The documentation panel on the right shows you the full range of query types available. Click RootQuery to see the options on this level.

You can also see a list of non-deprecated query types in the Explorer panel:

We'll be choosing recordsfor our first query. This will get us a list of records in the database.

Code:

Notes:

  • The new code goes within the first level of curly braces we opened in the previous step. Notice how our instructions are "nested" inside each other- like a "Russian doll", just as both the explorer panels show options nested inside each other.

  • We open another pair of curly braces inside records. We'll add further options here.

Explorer: Quickly implement this level in the Explorer Panel by selecting the records query type. This will also open the next level of options in the explorer:

Step 3) Ask for results

A GraphQL query doesn't give you results unless you ask for them. This helps it to stay as efficient as possible. We'll ask the query to pass back some key information about the records it finds, such as their table (this will help us identify which WebApp or Module the item belongs to) and then all of its properties (fields).

Next, we'll ask the query to return results and total_entries.

Code:

Notes:

  • total entries returns an integer, so we don't need to give it any more information.

  • results requires us to specify which fields we want to return with those results. We open a new pair of curly braces and choose fields from the docs. Siteglide doesn't use all of these fields, so some will return null . You can use properties to return all fields which have a value. table will return the name of the table the record belongs to (this was previously called model_schema_name).

Explorer:

Step 4) Add Arguments

You can press the "play" button to test your query:

Ours is not quite ready, and the error message in the middle-right Results Panel will tell us why:

Arguments are passed into a query to modify its behaviour. They always use round brackets ( ) syntax, just like JavaScript. Different types of queries will require some arguments, while allowing others as optional arguments.

This query requires that we specify how many records we would like to retrieve on each page of the results. This is to make sure the query isn't slowed down by retrieving too many records at once. We'll learn how to navigate multiple pages later, for now we'll ask for the first 20 records.

Code:

Notes:

  • per_page expects an integer, not a string, so we don't need quotes around 20

Explorer: You can set a value for per_page in Explorer.

Notice that the asterisk by the option in Explorer lets you know the property is mandatory.

In Explorer, there is a subtle colour scheme to help you differentiate between setting arguments and returning results:

  • Arguments are in purple

  • Results, and other returned properties are in dark blue

Step 5: Press play to fetch all results

If successful, you should see an object named "data" containing a tree of your results. Well done, you've written your first query! You've returned all records, or, in other words: WebApp and Module items.

You'll see there are actually more total_entries than the 20 results shown. That's because we're only viewing the first Page of the Results. We'll look at Pagination in the next Article.

CRM Users

If you want to get information on Siteglide CRM Users instead of WebApp or Module records, you can use the users query instead of the records query. The main difference is that users can return first_name, last_name, email etc. inside the results curly braces. See the explorer panel or the documentation panel to learn more.

Next Time

In the next Article we'll look at how GraphQL organises results into pages. We'll look at how to:

  • change page

  • change the number of items per page

  • output useful pagination metadata

Let's go!

Accessing Data in Liquid Variables - Tutorial 1 - Using Dot Notation

This Liquid is useful when you are accessing a WebApp 'collection', creating a Categories layout, using a custom GraphQL query and more.

Prerequisites

  • Choose your favourite third party tool for parsing and formatting JSON. Everyone has a different favourite tool, but you can see a third party comparison here to help you get started and find one: We'll look at this again below in "Using a Third Party Tool to Visualise Objects"

Introduction

Understanding dot notation is a really useful skill for Developers who are trying to get the most out of Siteglide and the platformOS technology it runs on. This Getting Started Article will cover:

  • What is dot notation?

  • How to find a property of an Object

  • How to chain dot-notation

  • The Data Tree

  • Visualising the Data Tree

You will come across the following terms which might be new:

  • Object

  • Array

  • Key-value Pairs

  • properties

  • Curly Bracket

This Article is intended to be the starting point for a Series of Articles involving dot notation.

What is dot notation?

Normally when you're accessing data on Siteglide, you simply want to access a single value. For example, on a Starter Site (using the WebApp installed by default, Gallery), you might need to output a Title on your Layout: {{this.Title}}

In this example, this outputs a String: The Latest Music

You could use this syntax from memory, or by referring to the Docs, but you're actually already using dot notation. The syntax above takes a variable this which returns an object. .Title is dot notation which specifies that the Developer wants to access the Title which is a property of this.

Note: Sometimes you may see something like this: {{this['Title']}} This is the exactly same thing, except this alternative syntax also allows you to add spaces in field names. We'll cover this in more detail in the next Article.

Key Value Pairs

Another important piece of terminology is the concept of a Key-Value Pair.

A key is a place where data is stored; a value is the data itself.

In the previous example, this and Title are both keys. The key Titlehas the value The Latest Music.

Chaining

You can chain dot notation. The following example will output the first name of the User currently signed in to a Secure Zone: {{context.current_user.first_name}}

This accesses the context object, then accesses its current_user property and finally accesses the id property of current_user. current_user can be considered an Object, because it has properties of its own, however, id has no properties and is stored as a String.

The Data "Tree"

You can think of objects in pOS and Siteglide as a Tree. The Object you start on like context or this is like the trunk of the tree. Each time you use a dot to access a property, you are going one level down the tree, until you reach the branch of data you needed.

Just outputting this on its own would show you the real JSON Object behind the first example in this Article and all of its properties: {{this}}

This outputs an object of data, which to start with is a little hard to read. That's because whitespace is removed for efficiency reasons. Adding the whitespace back in with a third party tool will allow us to read it more easily (see the next section).

Visualising the Object "Tree"

To make sense of the JSON that the Liquid outputs, you'll need a tool for automatically formatting the JSON data and adding whitespace. Some tools even help you visualise the data in other more advanced ways.

We don't have a favourite JSON parsing tool, but you can see a third-party comparison here: Many Code Editing environments like VSCode also have a useful extension for prettifying JSON data.

Important Note

We'd recommend when parsing JSON using third party tools that you do not use sensitive Client data. It's best to use test data and publically accessible data when testing and developing dot-notation. We cannot verify that any third party tool will handle your data safely.

Visualising the Data

Here is an example of a tree-view. It's the JSON data with extra whitespace and newlines added for readability (colours too).

From this view you can make the same observations we made when using the <pre> tag, but it is easier because each level of the tree is indented and all keys are coloured light blue:

  • The first and last character is a curly bracket- this represents the this object we are accessing.

  • The key id has no properties, it has a value of 98656 which is stored as a String. this.id will be enough to access it.

  • The key properties has properties which are accessible by dot notation. You can tell because the properties key is followed by more curly brackets indicating that it is an object. All the properties inside this set of curly brackets are properties of properties. `{{this.properties.name}}` will access the name property of properties.

Here is a chart view from the same tool. It shows more clearly how some objects are nested within others. You can use this to help you chain dot-notation to get the values you need.

A Special Case - Arrays

You may notice that category_array looks slightly different in the examples above. This is because its value is an array. You can notice an array in your data when square brackets are used around a list of values separated by commas e.g "category_array":["98479", "111111"]

We will cover arrays in the next Article: Advanced Dot Notation: Arrays and Key Maps

{
  "id":"98656",
  "properties": {
    "name":"The Latest Music",
    "slug":"the-latest-music"
  }
}
https://geekflare.com/json-online-tools/
Advanced Dot Notation- Arrays and Key Maps
Using WebApp Collections- Tutorial
https://geekflare.com/json-online-tools/
query getEverything {
  
}
query getEverything {
  records{
    
  }
}
query getEverything {
  records {
    total_entries
    results {
      table
      properties
    }
  }
}
{
  "errors": [
    {
      "message": "Field 'records' is missing required arguments: per_page",
query get_all_records {
  records(
    per_page: 20
  ) {
    total_entries
    results {
      table
      properties
    }
  }
}
Siteglide-CLI
About GraphQL
Finding the CLI add command in Portal
Quickstart: CLI
Logo

Tutorial 9 - Using Mutations to Create New Records

Tutorial 9 - Using Mutations to Create New Records

Introduction

So far, these tutorials have used GraphQL queries; in this article we're going to start looking at mutations.

The difference between queries and mutations is that:

  • Queries "read" data from the database

  • Mutations "create", "update" or "delete" data.

Keeping these types of operations separate is helpful, as it means you can be confident that running a query will not endanger the database on a live site.

"With Great Power Comes Great Responsibility"

  • When learning and testing GraphQL mutations, we strongly recommend you do so on a staging site.

  • If you need to test with real data, we recommend using the Siteglide Portal's Site Copy feature to create a staging site clone of a production site with a copy of the database.

  • Siteglide may not be able to recover the exact state of data as it was before running a mutation which modifies that data.

  • See for more tips on what to do if you accidentally delete data you didn't mean to. (This won't help if you accidentally change the properties!)

Steps for Creating a New Record

Step 1 - Start with the Mutation Keyword

All queries started with the query keyword; mutations start with the mutation keyword.

If using explorer, click the dropdown to select mutation instead of query, to unlock different options:

Step 2) Select the "record_create" mutation type

This will create a new record (or user_create to create a new CRM user.)

Step 3 - use the record object to define the new record

The required record object allows you to define both the properties of the new record and which table it should belong to.

Setting the table allows you to define which module or webapp it will belong to:

As with queries, the explorer UI cannot handle multiple properties in a mutation. You can use the explorer to figure out which individual property types are possible and then add the code manually. properties itself can be written as an array with each object inside the array defining a property to set.

Data Types

Each property must have a name which matches the ID of the field or custom field in Siteglide. When setting a value, you need to select a property value, value_array, value_boolean etc. depending on the platformOS type of data the field expects.

To figure out the correct one, you can reference this page which will show which platformOS types are used by each Siteglide type.

To find out which Siteglide types your table uses for each field, go to the corresponding WebApp or Module in the Siteglide Admin, or you can use this query to see each field's ID and Siteglide types together (you can also change the filter and properties to see which other tables are available):

When using boolean, float or integer, you don't need to use quotes.

Step 4 - add results

While results are generally less useful in a mutation than in a query, having at least one result is generally required and your mutation won't work properly without it. The idea is simply to give you information about the record in the database after the mutation to confirm that the mutation was successful - even if this is simply an ID.

Mutations which affect multiple records at once often have a count result which only returns the number of records created.

You can add the results you need in the curly braces after the round brackets which follow record_create

If successful, your results should return like this:

If you have an error, the error information will display instead of the results. In this case, value_int was used instead of value for the Blog Item's title:

Step 5 - Pass in variables

We covered variables in more detail in but they are just as useful in mutations!

In the following example, variables are added to pass in data programatically to create the new Blog post:

Advanced - Using variables to pass in entire objects in GraphQL rather than single properties

This is an advanced technique and you can skip it if you want to continue learning the basics of mutations.

If you like, you can use a variable to represent the entire properties object and pass in JSON which defines all the properties at once. Why? It might save time (or not), depending on the kind of data you have available. It may also make the mutation more re-usable. Learning advanced skills like this may give you more options as a developer and you may come across a situation where it's useful.

Note how the parse_json tag is used with literal square brackets to create a JSON array with nested objects defined by curly braces. The category_array property is an example of how you can pull in Liquid from different sources into these properies, though this.id would only contain a category ID in for example a category detail layout.

Note, the structure of the JSON we are passing in the previous example is similar to the structure of properties in the mutation, but it is necessary in JSON to use double quotes around the keys like name:, while the GraphQL syntax needs no quotes.

Using explorer to turn properties into a variable is a quick way to reference the required type for the complex variable [PropertyInputType]:

Conclusion

That's it! You should now be able to create Siteglide WebApp and Module items programatically using mutations.

You could experiment with the user_create mutation to create users. Instead of a record object this will have a user object which accepts a required email property.

Related Articles

  • - combining what you've learned in Tutorial 8 and 9, you should be able to create an API where the Liquid endpoint runs a mutation to create items. You could use this alongside a custom form to allow the user to enter properties which are then passed to the endpoint in the URL and then finally into the mutation via variables.

Next Time

In the next tutorial, we'll look at more examples of mutations, including mutations to update and delete records.

Creating WebApps via CLI

We are using 99 as an example WebApp ID in this doc. Depending on the WebApp ID that you'd like to use, replace 99 with the correct ID. Keep in mind that if you already have WebApps in the site you are working on then then some WebApp IDs may already be reserved.

WebApps in Siteglide rely on 3 different files to work correctly.

  1. JSON Structure - Used by Siteglide Admin and front-end to define structure of the WebApp with user-friendly names, and other UI metadata.

  2. Schema - Used to define the database structure itself

  3. Form Configuration - Used when submitting WebApp items front-end

There are 2 ways to create/edit a WebApp via CLI.

  1. Safest - Create/Edit the JSON structure file (marketplace_builder/views/partials/tables/webapps/99.liquid), and then in Siteglide Admin simply click 'Save' in the Table Builder view. This will generate a matching Form Configuration and Schema file.

  2. Most flexible - Create/Edit each of the 3 files manually. However, this relies on you keeping all 3 files in sync.

JSON Structure

This is used by Siteglide Admin to output WebApp field data with user-friendly names and other UI options (order, field type, etc.)

Location: marketplace_builder/views/partials/tables/webapps/99.liquid

Contents:

{
	"id": 99,
	"type": "webapp",
	"name": "My CLI WebApp",
	"slug": "my-cli-webapp",
	"detail": true,
	"detail_template": "templates/1",
	"detail_default_layout": "default",
	"sz": false,
	"sz_updated": true,
	"sz_display_type": "404",
	"system_fields": {
		"name": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"slug": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"weighting": {
			"type": "integer",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"release_date": {
			"type": "integer",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"expiry_date": {
			"type": "integer",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"enabled": {
			"type": "boolean",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"category_array": {
			"type": "array",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"meta_title": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"meta_desc": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"og_title": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"og_desc": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"og_type": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"twitter_type": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"secure_zone_array": {
			"type": "array",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"location": {
			"type": "geojson",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"address": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"og_image": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		},
		"og_url": {
			"type": "string",
			"editable": true,
			"hidden": false,
			"live": true,
			"order": 0,
			"required": false
		}
	},
	"custom_fields": {
		"webapp_field_99_1": {
			"name": "Custom Field",
			"type": "input_text",
			"live": true,
			"order": 1,
			"editable": true,
			"hidden": false,
			"required": false
		}
	},
	"automations": {},
	"install_to_site": true,
	"use_standard_fields": true
}

Please see the Field Types document for all the relevant types.

Schema File

This is what defines the database table structure, and how data will be stored.

Location: marketplace_builder/custom_model_types/webapps/webapp_99.yml

Contents:

---
name: webapp_99
properties:
- name: name
  type: string
- name: slug
  type: string
- name: weighting
  type: integer
- name: release_date
  type: integer
- name: expiry_date
  type: integer
- name: enabled
  type: boolean
- name: category_array
  type: array
- name: meta_title
  type: text
- name: meta_desc
  type: text
- name: og_title
  type: text
- name: og_desc
  type: text
- name: og_image
  type: text
- name: og_type
  type: string
- name: twitter_type
  type: string
- name: full_slug
  type: string
- name: secure_zone_array
  type: array
- name: location
  type: geojson
- name: address
  type: text
- name: webapp_field_99_1
  attribute_type: string
---

The above are all the default fields that are needed, the last field is an example of a standard text field. Please see the Field Types document for all the relevant types.

Form Configuration

This is used when submitting a WebApp item front-end. This file was also previously used to output WebApp structure data in Siteglide Admin, but that is now the job of the JSON structure file.

Location: marketplace_builder/form_configurations/webapps/webapp_99.liquid

Contents:

---
name: webapp_99
resource: webapp_99
resource_owner: anyone
configuration:
  properties:
    webapp_name:
      name: webapp_name
      value: 'My CLI WebApp'
      property_options:
        virtual: true
    webapp_slug:
      name: webapp_slug
      value: 'my-cli-webapp'
      property_options:
        virtual: true
    webapp_detail:
      name: webapp_detail
      value: true
      property_options:
        virtual: true
    webapp_detail_template:
      name: webapp_detail_template
      value: 'templates/1'
      property_options:
        virtual: true
    webapp_detail_default_layout:
      name: webapp_detail_default_layout
      value: 'default'
      property_options:
        virtual: true
    webapp_sz:
      name: webapp_sz
      value: false
      property_options:
        virtual: true
    webapp_sz_updated:
      name: webapp_sz_updated
      value: false
      property_options:
        virtual: true
    webapp_sz_display_type:
      name: webapp_sz_display_type
      value: '404'
      property_options:
        virtual: true
    name:
      name: name
      type: string
    slug:
      name: slug
      type: string
    weighting:
      name: weighting
      type: integer
    release_date:
      name: release_date
      type: integer
    expiry_date:
      name: expiry_date
      type: integer
    enabled:
      name: enabled
      type: boolean
    category_array:
      name: category_array
      type: array
    meta_title:
      name: meta_title
      type: string
    meta_desc:
      name: meta_desc
      type: string
    og_title:
      name: og_title
      type: string
    og_desc:
      name: og_desc
      type: string
    og_image:
      name: og_image
      type: string
    og_type:
      name: og_type
      type: string
    twitter_type:
      name: twitter_type
      type: string
    full_slug:
      name: full_slug
      type: string
    secure_zone_array:
      name: secure_zone_array
      type: array
    location:
      name: location
      type: geojson
    address:
      name: address
      type: string
    webapp_field_99_1:
      name: "Custom Field"
      type: input_text
      live: true
      hidden: false
      order: 1
      editable: true
      required: false
      validation: {}
---

{%- capture form_layout -%}layouts/webapps/webapp_99/form/{{_layout}}{%- endcapture -%}
{%- include form_layout -%}

After both the above files are synced you will then need to refresh Siteglide Admin and your WebApp will appear under WebApps in the sites left hand menu

Layouts

If you need layouts for your WebApp, these will be saved to:

marketplace_builder/views/partials/layouts/webapps/webapp_99/detail/default.liquid

marketplace_builder/views/partials/layouts/webapps/webapp_99/list/default.liquid

Both files contain the following layout by default: <p>{{this['name']}}</p>

mutation nameMyMutation {

}
mutation createWebAppItem{
  record_create(
  
  ) {
  
  }
}
mutation createWebAppItem{
  record_create(
    record: {
      table: "webapp_1",
      properties: [
        {
          name: "webapp_field_1_1",
          value: "foo"
        },
        {
          name: "webapp_field_1_2",
          value: "bar"
        }
      ]
    }
  ) {}
}
mutation createBlogItem{
  record_create(
    record: {
      table: "module_3",
      properties: [
        {
          name: "module_field_3_1", #title
          value: "Blog 1"
        },
        {
          name: "module_field_3_3", #description
          value: "Lorem Ipsum"
        },
        {
          name: "category_array",
          value_array: ["1"] # where each item in the array is a category ID
      ]
    }
  ) {}
}
query inspectBlogFieldsAndTypes {
  admin_forms(filter: {name: {value: "module_3"}}) {
    results {
      fields
    }
  }
}
value_booelan: true,

value_float: 3.456
mutation createBlogItem{
  record_create(
    record: {
      table: "module_3",
      properties: [
        {
          name: "module_field_3_1", 
          value: "Blog 1"
        },
        {
          name: "module_field_3_3", 
          value: "Lorem Ipsum"
        },
        {
          name: "category_array",
          value_array: ["1"]
      ]
    }
  ) {
    id #note there is no need to add a results object here like you would in a query. This returns the brand new item's automatically system-generated ID
    properties # returns an object containing the values of your properties which you passed in.
  }
}
{
  "data": {
    "record_create": {
      "id": "97",
      "properties": {
        "name": null,
        "slug": null,
        "og_url": null,
        "address": null,
        "enabled": null,
        "og_desc": null,
        "og_type": null,
        "location": null,
        "og_image": null,
        "og_title": null,
        "meta_desc": null,
        "weighting": null,
        "meta_title": null,
        "expiry_date": null,
        "release_date": null,
        "twitter_type": null,
        "category_array": [
          "1"
        ],
        "module_field_3_1": "Blog 1",
        "module_field_3_2": null,
        "module_field_3_3": "Lorem Ipsum",
        "module_field_3_4": null,
        "module_field_3_5": null,
        "module_field_3_6": null,
        "module_field_3_7": null,
        "module_field_3_8": null,
        "module_field_3_9": null,
        "secure_zone_array": []
      }
    }
  }
}
{
  "errors": [
    {
      "message": "Argument 'value_int' on InputObject 'PropertyInputType' has an invalid value (\"Blog 1\"). Expected type 'Int'.",
      "locations": [
        {
          "line": 6,
          "column": 9
        }
      ],
      "path": [
        "mutation createBlogItem",
        "record_create",
        "record",
        "properties",
        0,
        "value_int"
      ],
      "extensions": {
        "code": "argumentLiteralsIncompatible",
        "typeName": "InputObject",
        "argumentName": "value_int"
      }
    }
  ]
}
mutation createBlogItem($title: String!, $description: String!, $categories: [String]) {
  record_create(
    record: {
      table: "module_3",
      properties: [
        {
          name: "module_field_3_1", 
          value: $title
        },
        {
          name: "module_field_3_3", 
          value: $description
        },
        {
          name: "category_array",
          value_array: $categories
      ]
    }
  ) {
    id #note there is no need to add a results object here like you would in a query. This returns the brand new item's automatically system-generated ID
    properties # returns an object containing the values of your properties which you passed in.
  }
}
{% assign description = "Lorem Ipsum" %}
{% graphql createBlogItem = "create_blog_item", title: "The newest Blog Post", description: description %}
mutation createBlogItem($properties: [PropertyInputType!]!) {
  record_create(record: {table: "module_3", properties: $properties}) {
    id
    properties
  }
}
{% parse_json properties %}
[
  {
    "name": "webapp_field_3_1",
    "value": "Blog Title" 
  },
  {
    "name": "webapp_field_3_3",
    "value": "Lorem Ipsum"
  },
  {
    "name": "category_array",
    "value_array": [{{this.id}}]
  }
] 
{% endparse_json %}
{% graphql createBlogItem = "create_blog_item", properties: properties %}
Tutorial 11 - Using Mutations to Delete an Item
Field Types
Tutorial 6 - Variables
Tutorial 8 - Building a Liquid API GET Endpoint Page powered by GraphQL queries
record_create in explorer
Property types in explorer

Tutorial 4 - Advanced Filtering

Following on from the previous tutorial, we'll look at more advanced filtering options and show how you can filter with multiple rules.

Prerequisites

  • You have completed the Learning GraphQL tutorials 1 - 3

  • - optional- Read more about GraphQL and when it might be best used.

Introduction

In the last tutorial, we looked at how to filter your results so that only items from webapp_1 were returned. We also challenged you to see if you could adjust the query so that it returned all WebApp items.

This time, we'll look at:

  • filtering by different fields, or properties

  • filtering with different kinds of rules

  • using more than one filter at once

Filtering by Properties

Some fields in recordsare defined in the GraphQL schema, like table, this often means they have their own filter option in the documentation. Siteglide fields, and your own custom fields, are very likely to be custom "properties" which are not directly defined by the schema.

If you're not sure, check the schema for the field you're looking for. If you can't find it, it's a property.

For example, release_date is not a custom field in the Siteglide Admin, but it's not in the list of available fields to filter by in the schema- so we'll need to use properties.

A brief note on name . You'll see the term name available in the schema- but this is a deprecated way of referring to the tablee.g. webapp_1. To fetch the name of the item in the Siteglide Admin, you'll need properties.name.

In our next example query, we'll demonstrate this. Let's search for items with a properties.name that have the exact value of the string "music".

Code:

Notes:

  • See that properties uses a colon : and then curly braces { } as we have usually used so far. Properties can instead be used to store an array of filter objects, but we'll look at this later.

  • Note that we have to specify inside the curly braces both the name of the property we want to filter by (which happens here to also be called name, but it could also have been something else like release_date) and the method we'll be matching the value by, in this case contains: music.

Explorer: Adding a single filter to properties can be done with the Explorer wizard. However, if you want to be able to filter by an array of different properties, Explorer has no support for this yet, but it is possible by writing in the query manually.

This section of the docs used to have an example which used the "contains" comparison rather than "value". Good practice is to use "value" where possible for the best speed and performance. If you need "contains" functionality, consider whether using fulltext: {keyword: $search_keywords} search will meet your requirements instead?

Filtering Data Types Other than Strings

In the examples so far, we've only filtered by strings- or in other words, groups of letters or characters. Next we'll look at some other data types:

  • Booleans

  • Integers / Epoch Date stamps

  • Arrays

Filtering by Booleans

A good example of a Boolean property in Siteglide is enabled. This is a property which is stored as either true or false . Let's find all the items which are currently enabled:

Code:

Notes:

  • Again, we need to specify the name of the property we'll be filtering by, this time: enabled.

  • We'll use value_boolean as the most appropriate way to match an exact Boolean value. It's not the only method we could choose from the documentation, but it's more specialised to filtering Booleans, so is potentially faster.

Explorer:

Filtering by Integers and Epoch Timestamp Dates

When filtering by integers (which also includes Siteglide's release_date and expiry_date fields, as these are stored as Epoch Timestamps) you've got a choice whether to filter by values or a range of values.

Filtering for properties which match an exact integer

Code:

Notes:

  • value_int works the same as value_boolean but is designed to handle the different type of data more efficiently.

  • Be aware that running this query yourself on a Starter Site may produce no results. This is the correct result, because Starter Site does not at the time of writing ship with any weightings set. If you add a weighting of 1 in the Admin, you'll see it appear in the results.

Explorer:

Filtering for integer properties which fall inside a range

Most often, you'll want to use more complex comparisons for integers. We'll look at how to do this next- at the same time, we'll take a look at how Siteglide normally formats dates.

The dates are stored in Epoch Timestamp format, which is an integer storing the number of seconds which have elapsed since the Epoch. You can convert a date of your choice to this format here:

In this example, I'll use the time at the current time of writing: 1582108820. Let's query for all items which have already been released. In other words, the value stored in release_date should be less than, or equal to, the current timestamp. When you're thinking about dates, you can think of "less than" as meaning "before" and "greater than" as meaning "after". So here we're asking for "items with a release_date before now".

Code:

Notes:

  • The range filter requires you create a new object { } containing some logical comparison operators. You can choose between the operators in the documentation panel- see below.

Documentation Panel:

  • Available operators can be seen under RangeFilter. They are short for Greater Than , Greater Than or Equal , Less Than and Less Than or Equal.

Explorer:

Filtering by Arrays

An array is a list of data. One good example in Siteglide is category_array, which stores a list of unique IDs that refer to categories. We can now write a query to find items in a particular category.

Code:

Notes:

  • value_in is special to fields which have an array data type.

  • It takes an array of strings ["string_1", "string_2"] as its value. Here we are just using one category ID as an example. You could experiment with combinations of category_ids.

Explorer: Only partial support is currently available in Explorer for this. It's not so good at handling arrays. So you can select the property in Explorer, but you'll need to add the value manually into your query.

So firstly, implement with the wizard:

Secondly, change the value manually in the code from:

value_in: "158198"...to: value_in: ["158198"]

Filtering by Multiple Fields

For all the filters you've learned in this Article and the previous Article, you can apply more than one at once.

Notes:

  • In this example, tableand properties are both chosen as filter options.

  • properties takes an array (denoted by square brackets [ ] ) of objects (denoted by curly braces { }

  • Each pair of curly braces { } inside properties should contain the name of the property and an independent filtering method e.g. range or value which will be used.

  • Using a combination of filters in this way generally follows AND logic. Items must pass all of the filtering tests you choose before they are returned.

Explorer: Unfortunately, arrays are not currently supported in Explorer, so you'll have to enter this section of the query manually for now.

Filtering by whether a Property Exists - or doesn't exist!

You can also find items where a property does, or doesn't exist.

In this example, we're looking for WebApps where a meta_title was not added.

Code:

Notes:

  • exists accepts a Boolean, so you can use false or true. This shouldn't be wrapped in quotes, because Booleans don't require them.

Explorer:

*New* Filtering with OR logic

What if you are looking to filter records so you can find records which fit either one rule or another- but don't need to match both rules?

Here's an example from the pOS team of how you can use the "or" option when filtering. In this example we get records where either a webapp_field_1_1 exists OR a field parent exists.

Notes:

  • Inside filter we add an or property and an array with square brackets []

  • This array takes one or more objects with curly braces {} each Object is compared with OR logic. Within the object, you can use the same properties you might normally use inside filter.

You can add multiple filter properties inside each object, but these will be compared with AND logic. So, to filter records by those which have both ( webapp_field_1_1 AND webapp_field_1_2) OR "parent", you would do the following:

Challenge!

See if you can write one query which uses multiple filters. Try and return recordswhich meet these criteria:

  • They are an item from a recordwith a tablestarting with module_ .

  • They are enabled

  • They have already been released

  • They have not yet expired

  • They have a weighting between 1 and 3

  • They have a meta_title

  • They fall into the posters category

Hint: This search is so specific, that by default on Starter Site it will return no results. Try creating an eCommerce Product which meets these criteria before you start- that way, you'll know when you've succeeded.

It's unusual to run this many filters at once. Most of the time in a real use case, you'd have less to write than this! However, putting together everything you've done so far will be good practice.

We'll look at the answer to this Challenge in the next Article.

Next Time

We'll look at a possible solution to our latest challenge question.

After that, we'll look at how to run a query on a real Siteglide Site for the first time- and look at what you can do with the results.

Reference

The main commands you'll need when using Siteglide CLI

You need CLI installed first, check out our Quickstart guide:

CLI Commands

These commands should be run from within the project folder. Commands follow the format of siteglide-cli <command> <env> <flags> , so for example if you would like to view the logs of your production environment in "quiet mode", you would run:

siteglide-cli logs production -q

Please note that some commands may fail if you run them in your users home directory. We recommend a folder structure such as siteglide/ and then give each project/site it's own folder.

Command flags such as <command> <env> and <flags> should be replaced with the relevant data. For example, <env> should be replaced with the chosen name for the environment such as production.


Add

The first time you use the CLI with a project on your device, you will need to create an environment. This is essentially a config file that authorises your connection.

siteglide-cli add <env> --email [email protected] --url https://my_great_site.com

You must use the email of your Siteglide partner account to replace [email protected] and the development domain of the website you are connecting with to replace https://my_great_site.com

After your site goes live, you will only be able to add the development domain to an environment, not your main user-facing domain.

Replace with a chosen name for the environment, for example production. On larger projects you may have more than one environment to allow you to interact with both staging and production websites.


Sync

siteglide-cli sync <env>

This command will setup a watcher that will automatically sync file saves and deletions when that action happens on your machine. To stop the sync running, enter use the shortcut control + c in your terminal.

Flags:

-l: Turns on the live reload server. This enables automatic browser reloading of a page when a file is saved in your IDE.


Logs

siteglide-cli logs <env>

This command will output the last 20 logs and then a live list of logs from your site. Logs are written by using the log liquid code, for example: `

. To stop the logs running, enter use the shortcutcontrol + c` in your terminal

Flags: -f : Filter log types -q : Quiet mode


Version

siteglide-cli -v

Check the current version of Siteglide CLI you are running. See the to find the latest version number. If you need to update, run:

in a terminal window. Or use:

to target a specific version e.g. in this example 1.9.8

Help

siteglide-cli help

Generates a list of valid Siteglide CLI commands available to you.


GraphQL

Deprecated - Please see the GUI command below


GUI

siteglide-cli gui <env>

This command will open up the GraphiQL and Liquid Evaluator editors locally. This will let you run Graph queries and mutations to test them out before creating them within your site. Also good for quickly getting data out of the database to check.

The Liquid evaluator tool lets you quickly test Liquid from the CLI, without having to create and save a page in Siteglide Admin.

To stop the GUI command running, enter use the shortcut control + c in your terminal

Flags: -o: Automatically open GraphiQL in default browser


Pull

siteglide-cli pull <env>

This will pull down all files from the site in to a folder named marketplace_builder within your current directory. During this process it will also overwrite any local versions of files if they already exist. If you have made any changes locally that you have not synced they WILL be overwritten.

Flags:

-i: Ignore assets such as CSS and JS as part of the pull.

-m: Provide the name of a module to download. Note that this will only download that module and not the site as well. This will only download the public folder within the module, if the module only contains a private folder then nothing will be downloaded.

Note: Assets such as images and videos are not downloaded as part of CLI Pull


Init

siteglide-cli init

This will create a blank folder structure within the folder you run the command, which includes all folders that are automatically created for you when making a new website on Siteglide. If these folders already exist, you will receive an error and so it will not overwrite existing files.


Deploy

siteglide-cli deploy <env>

If you have made a lot of changes in your codebase, then you can use deploy to re-send all files to your site at once. Deploy is a single command that will create a .zip file of your site and then upload that to your website.

Flags:

-w : With assets, also deploys your sites assets folder, supports total assets of up to 5GB.


Export

siteglide-cli export <env>

Export is very similar to Pull, but it will also grab all data. This command will first grab all data from the site and export it into a data.json file, then it will run a pull to grab your code base.

Flags: -w : With assets, also download asset files from your instance such as images and PDFs.

--csv: Export the data files as CSV instead of JSON. Please note that these CSV's are currently not compatible with the ones that are exported/imported from Siteglide Admin

-a: Only export assets and not the sites codebase. This by default will only export code related assets (CSS/JS etc) within the /assets folder. It can be mixed with -w above to export all assets


Migrate

siteglide-cli migrate <env> --url <existing url>

Download and migrate an existing site to Siteglide using our site import tool. This tool will scrape the existing site, download all of the publicly accessible pages and assets, compress CSS/JS/images. The migrate command will also convert any existing forms (using the <form> tag) to a "form to email" in Siteglide. When filled in, this will email the user who triggered the CLI migration with an autoresponder. These forms can then optionally be updated at a later date within Siteglide Admin.

Note: If you are using a Mac, running migrate in your “home” folder may fail. Please move into a different folder in terminal and then try again.

Flags:

--url: Existing sites URL, usually the homepage. This flag is mandatory

-n: No Optimization, skips the CSS/JS and Image optimization

-m: A number to set the level of recursion that the scraper will search through the site. For example setting -m 1 will only follow 1 link deep from the entry page

-i: A string to send to the scraper that if found in the URL it will ignore downloading that page. For example if your blog posts are links such as site.com/post/post-name and you wanted to ignore them, you could use the flag -i /post/ This flag can be used multiple time such as -i /blog -i /posts/


List

siteglide-cli list

Output a list of environments you have previously added and their relative URLs for the current folder.


Modules

siteglide-cli modules <env>

Provides a list of modules installed onto the instance. These names can then be used in the pull command to download the contents of that module.

query get_items_with_musical_names {
  records(
    page: 1
    per_page: 20
    filter: {
      properties: {
        name: "name", value: "music"
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query get_enabled_items {
  records(
    page: 1
    per_page: 20
    filter: {
      properties: {
        name: "enabled", value_boolean: true
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query get_heavily_weighted_items {
  records(
    page: 1
    per_page: 20
    filter: {
      properties: {
        name: "weighting", value_int: 1
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query items_already_released {
  records(
    page: 1
    per_page: 20
    filter: {
      properties: {
        name: "release_date", range: {lte: "1582108820"}
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query mens_clothing {
  records(
    page: 1
    per_page: 2000
    filter: {
      properties: {
        name: "category_array", value_in: ["158198"]
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query released_mens_clothing_products {
  records(
    page: 1
    per_page: 2000
    filter: { 
      table: {value: "module_14/product"}
      properties: [
        { name: "category_array", value_in: ["158198"] },
        { name: "release_date", range: {lte: "1582108820"} }
      ]
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query webapps_missing_meta_descriptions {
  records(
    page: 1
    per_page: 2000
    filter: { 
      table: {starts_with: "webapp"}
      properties: [
        {
          name: "meta_title", exists: false
        }
      ]
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}
query {
  records(
    per_page: 2000,
    filter: {
      or: [
        { 
          properties: [
            { name: "webapp_field_1_1", exists: true } 
          ]
        },
        {
          properties: [
            {name: "parent", exists: true}
          ]
        } 
      ] 
    }
  ) { 
    results {
      id
      table
      properties
    }
  }
}
query { 
  records(per_page: 2000, filter: { 
    or: [ 
      {  
        properties: [ 
          { name: "webapp_field_1_1", exists: true },
          { name: "webapp_field_1_2", exists: true }  
      	] 
      }, 
      { 
        properties: [ 
          {name: "parent", exists: true} 
        ] 
      } 
    ] 
  }) { 
    results { 
      id 
      table
      properties 
    } 
  } 
} 
About GraphQL
https://www.epochconverter.com/
npm i @siteglide/siteglide-cli -g
npm i @siteglide/[email protected] -g
Siteglide CLI changelog
Quickstart: CLI
https://github.com/Siteglide/documentation/blob/master/developer-tools/cli/broken-reference/README.md
https://github.com/Siteglide/documentation/blob/master/developer-tools/cli/broken-reference/README.md
https://github.com/Siteglide/documentation/blob/master/developer-tools/cli/broken-reference/README.md
Logo

Tutorial 6 - Variables

Tutorial 6 - Variables

Adding variables to your query allows you to filter results based on User interaction - and re-use queries dynamically.

Introduction

So far, we've set up queries which return the same set of results each time.

Variables allow you to make queries which:

  • Can be re-used in different situations

  • Can search, sort, filter and change page based on User Interaction

  • Can show information which a particular User has permission to access

There are three main steps to setting up variables in a query:

  • Define the variables

  • Replace hard-coded values in the query with variables

  • Pass in values for the variables

There is no set order in which to follow these steps, as you'll need to follow all three before the query works as expected.

Our Example

In this Article, we'll use the following query as an example.

query find_items_with_category { 
  records(  
   per_page: 2000,
    filter: {
    deleted_at: {exists: false}
      properties: [
        { name: "category_array", value_in: "98486" }
      ]
    }
  ) {
    results {
      id
      properties
    }
  }
}

As you might have guessed, this query aims to find WebApp or Module items with a particular category assigned to them.

As it stands, the query is not very useful or repeatable. We want to be able to pass in the category we're interested in as a variable. This would allow different Pages to use the same query to view different categories - or allow the User to choose which category they wish to view.

Using Variables inside the Query

Normally in programming you would define the variables before you use them.

The benefit of adding the variable where you'll use it first is that the error message will tell you information about which type of data the variable will need to be.

In our example query, we'll remove the hardcoded category ID and replace it with the name of our new variable $category.

Code:

query find_items_with_category { 
  records(  
   per_page: 2000,
    filter: {
      deleted_at: {exists: false}
      properties: [
        { name: "category_array", value_in: $category }
      ]
    }
  ) {
    results {
      id
      properties
    }
  }
}

Notes:

  • All variables inside the query itself must be preceded with a dollar $ sign.

  • This query will currently fail, because we still need to define the variable.

Explorer: Sorry, explorer does not currently support arrays or variables, so no demo is available this time.

Defining Variables

Next, we'll define the variables we will use. These definitions will be entered as arguments on the top level of the query.

Each variable you define has two parts:

  • A name e.g. $category

  • A data type e.g. String

Here's what just the top level looks like with the newly added argument:

query find_items_with_category($category: String) { 

}

Here's the whole query:

query find_items_with_category($category: String) { 
  records(  
   per_page: 2000,
    filter: {
      deleted_at: {exists: false}
      properties: [
        { name: "category_array", value_in: $category }
      ]
    }
  ) {
    results {
      id
      properties
    }
  }
}

Notes:

  • The variable name is again preceded with a dollar sign $.

  • A colon : and an Title Case keyword are used to set the type. See below for more information on types.

  • We've deliberately made a mistake with the type of the variable- we'll discuss this below.

Explorer: Sorry, explorer does not currently support arrays or variables, so no demo is available this time.

Using the Correct Type

A variable's type is really important and an error will be thrown if you use the wrong one. For example, running our query now gives the following error:

{
  "errors": [
    {
      "message": "List dimension mismatch on variable $category and argument value_in (String / [String!])",

We can use the error message to find the type we need- in this case it's [String!] not String. This indicates that an array of Strings is expected, which makes sense because "value_in" is for filtering array fields. You can also check in the documentation panel:

We'll fix this in our query:

query find_items_with_category($category: [String!]) { 
  records(  
   per_page: 2000,
    filter: {
      deleted_at: {exists: false}
      properties: [
        { name: "category_array", value_in: $category }
      ]
    }
  ) {
    results {
      id
      properties
    }
  }
}

What's important is the type of data that a particular GraphQL property expects as its value, not the type of data you were intending to pass in. In fact, you may have to modify the type of your variable with Liquid beforehand, so that it is acceptable to GraphQL.

Mandatory Variable Types

An exclamation mark after a type e.g. String! means that this variable will be mandatory. The query will not run without it. This is useful when that variable is used to control sensitive information- terminating the query if you can't be sure which data you need to return. However, it's important to make sure ordinary user behaviour can't cause this to happen, because the error message can be bad for UX.

Array Variable Types

Square brackets around a value, indicate that it should be an array (which may have any length). E.g.

[String]

Examples of using Liquid to modify data types

If you require any other conversions, please request them on Intercom and we'll try and include them in the List.

*String to Int *Apply an integer filter:

{% assign original_string = "123" %}
{% assign new_int = original_string | plus: 0 %}




String to Float

{% assign original_string = "123" %}
{% assign new_float = original_string | plus: 0.00 %}




String to [String]

{% assign original_string = "123,456" %}
{% assign new_array = original_string | split: "," %}




String to Boolean

{% assign original_string = "true" %}
{% if original_string == "true" %}
  {% assign new_boolean == true %}
{% elsif original_string  == "false" %}
  {% assign new_boolean == false %}
{% endif %}




Boolean to String

{% assign original_boolean = true %}
{% assign new_string = original_boolean | downcase %}





Int to String

{% assign original_int = 123 %}
{% assign new_string = original_int | downcase %}

Float to String

{% assign original_float = 123 %}
{% assign new_string = original_float | downcase %}

Literal JSON object to HASH object (Needed for advanced variables only- like passing an array of properties objects into a filter). You can also use the parse_json tag to create any of the above types; if you can write the variable in a type that's supported by JSON, the tag will convert that to a variable in the hash format that can be passed into Graph as a variable value.

{% parse_json properties_object %}
  [
    { "name": "properties.webapp_field_1_1", exists: true },
    { "name": "properties.webapp_field_1_2", exists: true }
  ]
{% endparse_json %}




Passing in Variables using the Sandbox (Testing)

You can test by entering values in JSON format in the panel in the bottom left. The panel may be hidden, in which case you'll need to click and drag it up.

Firstly, open a new JSON object:

Next, define the key of your property:

{
  "category": 
}

And finally, define the value of your category. Make sure the data is in the type you defined earlier. Here are some examples:

  • String - `"Hello"`

  • Int - 3

  • Float - 3.2

  • Boolean - true

  • [String] (Array of Strings) - ["Hello", "Hi"]

In our example, we will need an array of Strings:

{
  "category": [ "98486" ]
}

Notes:

  • This time, you won't need to use a dollar sign $. That's because this panel represents the input and is not part of the GraphQL query- it doesn't use GraphQL syntax, instead it uses JSON syntax.

Our query is now finished:

Explorer: Sorry, explorer does not currently support arrays or variables, so no demo is available this time.

Passing in Variables using Liquid

Okay, so now you can use the GraphiQL Sandbox to test your queries with variables, but the really useful bit is to be able to use Liquid to pass variables in. This will unlock the ability to fetch data dynamically with GraphQL.

Step 1) Add parameters to the graphql tag for each variable you've defined in the Query. Here, we'll continue with our example and add category

{% graphql my_results = "find_items_with_category",
 category: 
 %}

Notes:

  • We will add the value of the category variable in the next step.

  • You don't need to use a dollar sign $ before the name of your variable.

  • You can add new lines to the inside of the tag to keep it tidy, or write the tag on one line, it's up to you.

Step 2) Next, add the value of the parameter.

You can either hardcode the value:

{% graphql my_results = "find_items_with_category",
 category: "98486"
 %}

Or, you can use a Liquid variable which you defined earlier:

<!-- Create an empty array -->
{% assign current_category_array = "" | split: "," %}
<!-- Add Strings to the Array -->
{% assign current_category_array = current_category_array | add_to_array: "98486" | add_to_array: "98487" %}

<!-- Feed the Liquid Array into the query by its variable name. No quotes are needed around a variable name when you set it as a parameter- it's dynamic data. -->

{% graphql my_results = "find_items_with_category",
 category: current_category_array
 %}

Or, you can use data from Siteglide that's already stored in a Liquid variable. In this case, let's say we're in an item.liquid file for a Product. We already have access to the Categories that this Product is assigned to - luckily, the data is already a Liquid Array. Let's use that data to find other records in the same Category.

{% graphql my_results = "find_items_with_category",
 category: this.category_array
 %}

Challenge - Using Page as a Variable

Challenge Objective

For this week's challenge, we'd like you to set up your own simple Pagination controls. This will combine everything you've learned so far. As usual, don't worry if you need to check the answers before completing it.

On a new Page you create, the User should be able to see the first two Items in the Gallery WebApp. Then, when they press the Page 2 button, the Page should refresh and they should see the second Page of Results. See the gif above for a demonstration.

In the Tips section, we'll give you the HTML and Liquid you need to get started, your challenge is to build the GraphQL query which will power the functionality.

Tips

Here are some snippets of code you can use to help you:

*User Interaction *You can use anchors to allow the User to refresh the Page and change the Page Parameter in the URL.

<ul>
  <li><a href="{{context.headers.PATH_NAME}}?page=1">1</a></li>
  <li><a href="{{context.headers.PATH_NAME}}?page=2">2</a></li>
  <li><a href="{{context.headers.PATH_NAME}}?page=3">3</a></li>
</ul>

*Reading URL parameters *You can use Liquid to read the Page parameter in the URL. You'll then need to work out how to feed this variable into your GraphQL query: `

`

By the way- we're using the filters | default and | plus: 0 to make sure the page defaults to 1 if no parameter exists, and that the data is converted to an integer.

*Remembering Pagination in GraphQL * You may need to refer to Tutorial 2, to refresh your understanding of Pagination.

Next Time

We'll look over the answers to our toughest challenge yet.

Let's go!

Tutorial 5 - Using Liquid to run GraphQL queries on your Site

You've now used the GraphQL playground to write queries which fetch data. Now you're probably itching to see how you can add one to a Site!

Prerequisites

  • You've completed the "Learning GraphQL" tutorials 1 - 4.

  • In order to use GraphQL results on your Page, you'll need to be familiar with Dot Notation Liquid syntax. You can get started with learning to use . You may find that you want to refer back and forth between articles on Dot Notation and GraphQL as you continue with your learning.

  • You'll need to be familiar with Siteglide-CLI. In this tutorial we'll be using Siteglide-CLI to add a GraphQL folder and sync our new queries up to the Site.

  • - optional- Read more about GraphQL and when it might be best used.

Introduction

Last time, we looked at how to use filter to refine your queries so that they only return results matching particular criteria.

So far we've been working in GraphiQL, the interative playground. This time, we'll run a GraphQL query on your Starter Site. We'll also take a quick look at how to handle the results- but you can learn more about this by following our Documentation on and .

Saving your GraphQL File

The best way to run your GraphQL query on a Site is to save the query inside a GraphQL file. This keeps it tidy and allows you to easily re-use the same query elsewhere on the Site should you need to.

Step 1) Find or Create a Project Folder on your Device

If you're following this tutorial with the same Site each time, you'll already have a project folder. After all, we have been using the .siteglide-config file in your Project Folder to interact with the GraphiQL interactive playground.

In this example, my project folder

Step 2) Use Siteglide-CLI to pull down the Site's main File Structure

We need to add a GraphQL query inside a specific folder in order to refer to it with Liquid. Before we do that- we need to see your Site's File Structure so we know where to add the folder.

In terminal, you'll need to change directory to the Project Folder.

If you've not already, run a pull command in terminal to pull down the current file structure: siteglide-cli pull my_environment_name

If you want to refresh your memory on using the Siteglide-CLI, you can learn more** here**.

Step 3) Load the Project Folder in a Text Editor/ Code Editor

I'll be using Microsoft Visual Studio Code in this example. Other Editors are available.

All the folders and files that can be synced with your Site are in the marketplace_builder folder.

Step 4) Add a new folder called graph_queries

Open up the marketplace_builder folder. If you've not already got a folder inside this called graph_queries , create one now.

Step 5) Create a new file in that folder to store your GraphQL Query inside

Create a new file to store your query inside. It's best to give the file the exact same name as your query. This will make it easier to find later.

The file should have the extension .graphql.

Step 6) Use Siteglide CLI to sync or deploy that file to your Site

So that file now exists on your device, but not yet on your Site. You'll need to either sync or deploy the file to your Site.

The choice of command is up to you. sync will watch for changes in the marketplace_builder folder, so you'll need to make a change in the file after running the command before your file is uploaded. The sync command will continue to watch and sync files until you cancel it. deploy will simply deploy all your local files to the Site as a one off command.

Here, I'll use sync:

This query file will not be visible in Code Editor, but the files will be on the server and Liquid will be able to access them. The reason for not displaying them here is to hide the most complex code from areas where Clients and non-developers can access it. This might change in the future.

If you wanted to check if the file has synced correctly, you can turn off sync, delete the file and run a pull command. In our experience, there's no need to run this check, as sync will tell you if a file is synced and "done" accurately.

Adding the GraphQL Liquid Tag

We'll use the graphql Liquid tag provided by platformOS. You may see other developers familiar with platformOS using tags like query_graph or execute_query, but graphql is the most up-to-date:

Notes:

  • The tag itself is graphql

  • The first parameter you give should be a new variable name. This will create a Liquid variable containing your results- you can choose your own name.

  • After the equals = sign, you should write the file name of your GraphQL file, without its file extension or any folders. For example, my filename is "get_items_with_musical_names.graphql" so I remove the extension: "get_items_with_musical_names" .

Accessing the Results

You can output all of your results on the Page, using Liquid output syntax {{ }} to output the variable we defined earlier.

Notes:

  • The Liquid output must be on any line below the graphql tag.

  • You can output this in any Liquid File; this includes Pages, Page Templates, Headers, Footers, Layouts, Code Snippets and Workflow/ Auto-Responders.

The results will output in Hash format. You can use dot notation to access specific results within the Object.

Looping over the Results

This example uses dot notation.

It is possible to use your Query to rename the keys in your Results- doing this would require different dot notation. We'll look at this later.

For now, we'll be using the query below, which does not rename any keys. I've set per_page to 2, in order to make the example data Object shorter and easier to read.

Code:

Step 1) Output the Results as shown above

Step 2) Find the key which holds the main array of results

My Page returns the following results- don't worry- there's no need to read them in this form:

Running these results through a 3rd-party JSON parser gives me the data in a format which is much easier to read. We don't recommend any particular JSON parser, but if you're using a text editor there will normally be an extension available which does this.

The structure of the results here matches the results we see in the GraphQL playground. We're looking for the key which returns an array of results- this is indicated by the square bracket [ ]:

The dot notation to reach the results is:

Alternatively, you can always run your query in the GraphiQL Playground and work out the dot notation needed from the results shown in the middle-right panel. You'll just need to ignore the very top key in the results data: and use the variable from your graphql tag instead e.g. my_results :

Step 3) Implement a Liquid For Loop to loop over the results

We loop over every item in the Results array. We create a variable called this with a scope which allows it to be accessed only inside each loop iteration. this contains all the data for that result.

Step 4) Output properties inside each Item's iteration

In this example, we'll output:

  • the table

  • An example property name

  • An example property webapp_field_1_2 . This is the second custom Field defined in the WebApp's structure on Starter Site.

We can now also bring in other Front End languages. I'll add some common HTML tags.

This gets me the following Results on the Page:

Outputting Layouts

You can use a Liquid include tag to output a Layout to display your results. The trick is to make sure that the data fits the same format as the layout is expecting.

You can learn more about this topic , as it works in the same way as displaying Collection Results in a Layout.

Congratulations

You've reached the end of the first collection of our Learning GraphQL tutorials.

By now, you should be able to:

  • Set up a development environment to test GraphQL queries

  • Run GraphQL queries on a Siteglide Site

  • Understand and use GraphQL Pagination

  • Use GraphQL filters

New Tutorials will be added soon!

Next Time

There's no official challenge for this tutorial, because you probably want to experiment with adding queries to your Site.

In the next Tutorial, we'll look at using variables in your Queries to make them more dynamic. We'll look at how you can use variables:

  • In the GraphiQL playground

  • Using Liquid in a Page

Let's go!


<div data-gb-custom-block data-tag="-"></div>

<div data-gb-custom-block data-tag="-" data-my_results='get_items_with_musical_names'></div>

<div data-gb-custom-block data-tag="-"></div>
{% graphql my_results = "get_items_with_musical_names" %}
{{my_results}}
query get_items_with_musical_names {
  records(
    page: 1
    per_page: 2
    filter: {
      table: { starts_with:  "webapp_"  }
      properties: {
        name: "name", contains: "music"
      }
    }
  ) {
    total_pages
    results {
      table
      properties
    }
  }
}  
{% graphql my_results = "get_items_with_musical_names" %}
{{my_results.records.results}}
{% graphql my_results = "get_items_with_musical_names" %}
{% for this in my_results.records.results %}
{% endfor %}
{% graphql my_results = "get_items_with_musical_names" %}
{% for this in my_results.records.results %}
  <h1>{{this.table}}</h1>
  <h2>{{this.properties.name}}</h2>
  <p>{{this.properties.webapp_field_1_2}}</p>
{% endfor %}
Dot Notation here
About GraphQL
Dot Notation
Collections
here

Tutorial 8 - Building a Liquid API GET Endpoint Page powered by GraphQL queries

This article shows a different use-case for the skills you've already learned- using a GET request to run a query.

Introduction

So far, we've written GraphQL queries which run on Page Load. This is powerful, but wouldn't it be even more useful if you could fetch data after Page load when a User interacts with a Site- e.g. changing page on a list without refreshing the Page? Using XHR (sometimes called Ajax) requests, you can run GraphQL at any time.

This approach can take a little bit of time to set up, but can allow you to create much faster interactive experiences for your Users and opens up a huge range of possible solutions you can build.

We cannot document every way in which you can build this kind of Page, but we will show you the basics and let your imagination do the rest! If you need additional support on this topic beyond the scope of what is documented here, we recommend you speak to one of our Experts or browse the resources we link to at the end of the Article.

Glossary

  • An API - (Application Program Interface) is a form of communication between two services on the internet. Communication takes place between 2 or more endpoints.

  • An endpoint in its simplest form is a URL which allows an API access to a server or application. On Siteglide, we provide you with API endpoints we've built with our public API, but now you've learned GraphQL, you also have the ability to build your own endpoints when you need them.

  • A method defines the role of the API + Endpoint e.g. a GET method is for "getting" or "fetching" data.

  • JSON (JavaScript Object Notation) is a common file format for exchanging data which is efficient and human-readable. We'll use it in some of our examples. (It's also the default format in which GraphQL results are outputted on the Page.)

Steps for Building your First Liquid Endpoint Page powered by GraphQL

Step 1) Create a Page to serve as an Endpoint

The first step is to create a Page on the Siteglide Admin which will become your API endpoint.

The slug you choose will be the URL via which you'll eventually access the data. It's worth naming the Page and writing a slug which reflect the fact that this Page is not a public-facing Page and should not be editable by Clients. e.g. /api/webapp_1

Step 2) Use CLI to apply advanced settings to the Liquid Page

In this step, we'll be changing the settings of the Page in CLI, as there are available settings here that are not yet editable from Admin. You can learn more about pages in platformOS here: https://documentation.platformos.com/developer-guide/pages/pages#content and more about the Siteglide-CLI here.

Using the Siteglide-CLI, pull down your Site's files to your machine and open them up in a Code Editor of your choice. Set up Siteglide-CLI sync so that changes you make will be pushed to the Site.

Open up the Page you've created in a Code Editor of your choice.

We'll be editing the yaml configuration at the top of the page. In my example, you can see the yaml settings between the triple dashes --- It's important when configuring yaml to use exactly 2 spaces instead of tabs for indenting. Your siteglide-cli sync command will alert you to any validation errors.

---
layout: templates/blank_page
max_deep_level: 2
metadata:
  name: API WebApp 1 GET endpoint
  enabled: true
  file_type: page
  last_edit: 1597241196376
redirect_to: ''
redirect_url: ''
---

2) a) Method

So far we've only learned to write GraphQL queries, so your endpoint will be handling GET requests. Set the method property of your page to get:

method: get

2) b) Format

Optionally, you can choose to change the format of your Page. If you like, instead of HTML, you can make your Page a different format, like JSON.

format: json

Note that if you set your Page to a different format now, you'll need to append the URL with the extension later. e.g. a Page with the slug my-slug and format JSON can be accessed at the URL /my-slug.json.

2) c) Remove the Page Template

As your endpoint Page will not be accessed by either humans or search-engine bots, it's much better to remove the Page Template, allowing you to only return the data you need.

layout: ""

2 d) Remove HTML

Unless you're using the HTML format from 2) b), you will need to remove any &nbsp; tags automatically added to the Page.

2) e) Remove from search engine results

Unlike the other steps, this is not a yaml property on the Page. However, it's included here as a quick sensible step you can take.

You may wish to specify in your robots.txt file that pages with this URL should be ignored by search engines.

2) f) Check yaml

My example looks like this:

---
layout: ""
max_deep_level: 2
metadata:
  name: API WebApp 1 GET endpoint
  enabled: true
  file_type: page
  last_edit: 1597241196376
redirect_to: ''
redirect_url: ''
method: get
format: json
---

Step 3) Add a GraphQL query with variables

Add a query of your choice using the Liquid graphql tag from tutorial 5 and variables from tutorial 6.

In this example, I'll use the following query to fetch data from webapp_1 and change page with the page variable:

query fetch_webapp_1_by_page($page: Int!, $per_page: Int!) {
  records(
    filter: {
      table: { value: "webapp_1" }
    }
    page: $page
    per_page: $per_page
  ) {
    results {
      id
      properties
    }
  }
} 

I'll use the following Liquid to run this query when the endpoint Page is accessed:

{%- graphql fetch_webapp_1_by_page = "fetch_webapp_1_by_page" -%}

Note- I'll be using - before and after my closing Liquid tags to remove unnecessary whitespace from the results- this is optional.

Step 4) Add Liquid logic to handle incoming URL parameters

In the example, we'll pass inputs into the endpoint Page using query parameters on the end of the URL, for example, I already have the URL for accessing the endpoint Page: /api/webapp-1.json

Remember

The ".json" extension should be replaced with the "format" you chose in step 2.

I'll be storing the page I want to request from the endpoint in query parameters like so: /api/webapp-1.json?page=2&per_page=1

You can now use context.params`to read the URL on the endpoint Page and dynamically access each query parameter. I'll store each in a variable before I feed these into the query, in case there is any type coercion to carry out first.

{%- assign page = context.params.page -%}
{%- assign per_page = context.params.per_page -%}

Step 5) Use Liquid to feed variables into the Query (and make sure they are the correct type)

Accessing these values via the above method tends to set them as String values in the variable. For this example we'll need to change the type to integer- as that's what the query expects. You can refresh your knowledge on changing the type of variable in Liquid in tutorial 6.

{%- assign page = context.params.page | add: 0 -%}
{%- assign per_page = context.params.per_page | add: 0 -%}

We can then add them to the query.

{%- graphql fetch_webapp_1_by_page = "fetch_webapp_1_by_page",
  page: page,
  per_page: per_page
-%}

If the query expects variables to be Strings you can actually add them straight to the query without assigning as variables first:

{%- graphql fetch_webapp_1_by_page = "fetch_webapp_1_by_page",
  page: context.params.page,
  per_page: context.params.per_page
-%}

Step 6) Output results on the Endpoint Page - These will be the response body

Results are accessible via the variable name you defined in the graphql tag, but at this point we can decide on the format in which we'll display them.

Option 6) a) HTML format

If you decided in step 2 that you didn't want to change the Page format, you should now build the required HTML structure you'd like to send back (this would probably be inserted as it is into a Page via JavaScript).

{%- graphql fetch_webapp_1_by_page = "fetch_webapp_1_by_page",
  page: context.params.page,
  per_page: context.params.per_page
-%}
<div class="row">
  {%- for item in fetch_webapp_1_by_page.records.results -%}
    <div class="col">
      <h2>{{item.properties.name}}</h2>
    </div>
  {%- endfor -%}
</div>

Option 6) b) JSON format

If you decided in step 2 to change the format of the Page, you'll need to use Liquid to output the content in this format.

As GraphQL already outputs in JSON format, this is easy:

{%- graphql fetch_webapp_1_by_page = "fetch_webapp_1_by_page",
  page: context.params.page,
  per_page: context.params.per_page
-%}
{{fetch_webapp_1_by_page}}

Option 6) c) CSV format

For something like CSV, you'll need to use logic to output the data in the correct format -this is just an example and you may need to alter it for your use-case.

We use {% rather than {%- in this example, because we want to preserve new lines to make sure each row of the CSV displays correctly.

{%- graphql fetch_webapp_1_by_page = "fetch_webapp_1_by_page",
  page: page,
  per_page: per_page
-%}
Name,ID,Description
{% for item in fetch_webapp_1_by_page.records.results %}{{item.properties.name}},{{item.id}},
  {{item.properties.webapp_field_1_1}}
{% endfor %}

Step 7) Test the endpoint Page

In your browser, visit the endpoint Page URL and see if the data displays as expected. Test changing the query parameters to see it change the returned data.

A successful JSON endpoint will return valid JSON in the body, as in the example here (other formats should also be checked for valid formats).

{
  "records":{
    "results":[
      {
        "id":"220",
        "properties": {
          "name":"We know guitar music"
          "slug":"we-know-guitar-music"
          "enabled":true
          "og_desc":null
          "og_type":null
          "og_title":null
          "meta_desc":null
          "weighting":null
          "meta_title":null
          "expiry_date":2145916800
          "release_date":1570797009
          "twitter_type":null
          "category_array":[]
          "webapp_field_1_1":"images/about/about-5.jpg"
          "webapp_field_1_2":"Man playing guitar"
          "webapp_field_1_3":"We know guitar music"
          "webapp_field_1_4":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
        }
      }
    ]
  }
}

Changing the URL parameters should allow you to return different responses

Common issues:

  • Your URL should include the relevant file extension e.g. .json for json format, .csv for CSV format. If you are using HTML format, no extension is needed. Below is an example of a 404 response from using the incorrect extension:

  • Your URL should contain any query parameters you need for your query. E.g. in my example, page and per_page are mandatory. Below is an invalid response body resulting from missing URL parameters:

Congratulations! You've built a working GET endpoint using GraphQL and Liquid!

The last two optional steps will expand upon your options for securing the Endpoint and using the data available.

Step 8) Optional - Security and Authorisation

Is your data sensitive and you want only logged in Users to access it? Is your data public and it doesn't need an additional authorisation step?

This step shows you some different ways to make sure your data is secure, but you can skip it if your data is not sensitive or private.

Option 8) a) Using Secure Zones

Adding a Secure Zone to your endpoint Page is a simple way to protect it from Users and bots who should not be accessing it.

A failed access will still generate a successful 2xx response code, because it will succeed in returning HTML body (the 404 message that displays to Users). This is only a problem if you're not using HTML format for your endpoint as it will cause any JavaScript which relies on parsing JSON to fail like so:

In step 9, you'll need to find a way to adapt your JavaScript logic to handle this kind of error which does not rely on the response code, but instead checks the response for the HTML tag <h1>401 - Unauthorised</h1>. See step 9) a) i) and 9) a) ii) for examples of this logic.

Option 8) b) Using Authorization Policies

You can use Siteglide-CLI and platformOS to build an authorization policy that checks the request either comes from a trusted Site or from a trusted User. The benefit of this is that it allows you to return HTTP response codes.

As this is custom platformOS code, it won't be covered by our support. Learn more about authorization policies on the platformOS docs: https://documentation.platformos.com/developer-guide/authorization-policy/authorization-policy

Authorization Policy tips

These tips are intended as inspiration and do not constitute complete examples. They do not require sending a query parameter over in your URL, making them easier to keep secure.

  • If the User has been logged in (to any Secure Zone), you can check this on the Endpoint Page Authorization policy.

{%- if context.current_user.id -%}
  true
{%- endif -%}
  • To check that the request comes from an authorized Page/ Site, you can check this with context:

{%- if context.headers.HTTP_REFERER contains "expected-URL" -%}
  true
{%- endif -%}

Step 9) Optional - Get the Data and use it

On any of your other Pages, you can now send requests to your new endpoint and fetch data. For this you could use JavaScript.

The JavaScript examples provided here are intended for inspiration only. Unfortunately, we cannot advise you on how to adapt these to suit individual projects. See the links at the bottom of this Article for more resources you can use to help plan and develop projects of this kind.

Example 9) a) i) Logging Json Responses

This basic example will request data from the example earlier and console log the response.

The if statement logic checks if a 2xx response code is received (meaning any authorization policies have passed) and that there is no HTML tag containing a 401 code from a Secure Zone check failure. See Step 8) for more details.

  var xReq = new XMLHttpRequest();
  xReq.onload = function () {
    const checkForSecureZone = /401 - Unauthorised/g;
    if (xReq.status >= 200 && xReq.status < 300 && xReq.response.search(checkForSecureZone) == -1 ) {   
      console.log(JSON.parse(xReq.responseText));
    } else {
      console.log('error', xReq.responseText);
    }
  };
  xReq.open('GET', '/api/webapp-1.json?page=1&per_page=1');
  xReq.send();

Example 9) a) ii) Parsing JSON and manipulating the DOM

In this expanded example, we'll fetch the data and then append it to the HTML DOM.

Add HTML and JavaScript

Consider using Live Updates The SiteBuilder Live Updates API, released since this doc was first written might be a quicker alternative here. You can put your GraphQL code in a Code Snippet and follow the docs to Live Update a Code Snippet.

  • An event listener targets the Form and watches for a click event

  • When the Form is submitted, the event triggers the function "getWebappOne".

  • The if statement logic checks if a 2xx response code is received (meaning any authorization policies have passed) and that there is no HTML tag containing a 401 code from a Secure Zone check failure. See Step 8) for more details.

  • The function requests the data from our new endpoint.

  • It then loops over the Items and appends each WebApp Name to the HTML DOM.

HTML

<section class="form form-01">
  <div class="container">
    <form>
      <h2>Get WebApp Names</h2>
      <div class="input-group">
        <label for="page">Page</label>
        <input id="page" type="number" step="1">
      </div>
      <div class="input-group">
        <label for="per_page">Per Page</label>
        <input id="per_page" type="number" step="1">
      </div>
      <button id="submit" class="btn btn-primary">Get WebApp 1</button>
    </form>
  </div>
</section>
<section class="form form-01">
  <div class="container">
    <h2>List of WebApps</h2>
    <div class="row" id="output"></div>
  </div>
</section>

JavaScript

var page = document.querySelector('#page');
var per_page = document.querySelector("#per_page");
var submit = document.querySelector("#submit");
var output = document.querySelector("#output");

function getWebappOne() {
  event.preventDefault();
  var xReq = new XMLHttpRequest();
  xReq.onload = function () {
    const checkForSecureZone = /401 - Unauthorised/g;
    if (xReq.status >= 200 && xReq.status < 300 && xReq.response.search(checkForSecureZone) == -1 ) {
      output.innerHTML = "";
      var jsonParsed = JSON.parse(xReq.response).models.results;
      for(i=0;i<jsonParsed.length;i++) {
        console.log(jsonParsed[i].properties.name)
        output.insertAdjacentHTML('beforeend', '<div class="col">'+jsonParsed[i].properties.name+'</div>');
      }
    } else {
      console.log('error', xReq.responseText);
    }
  }
  xReq.open('GET', '/api/webapp-1.json?page='+page.value+'&per_page='+per_page.value);
  xReq.send();
};
submit.addEventListener('click', getWebappOne);

Example 9) b) Requesting Data From an HTML Page

In the previous two examples, we've used an endpoint with a JSON format endpoint.

You may find it easier to build HTML on the endpoint and when it arrives in the destination Page, output it as it is.

Build HTML on the Endpoint Page

{%- graphql fetch_webapp_1_by_page = "fetch_webapp_1_by_page",
  page: page,
  per_page: per_page
-%}
<div class="row"> 
  {% for this in fetch_webapp_1_by_page.records.results %}
    <div class="col-4">
      <h3>{{this.properties.name}}</h3>
    </div>
  {% endfor %}
</div>

Fetch HTML on the Front End Page

<div class="webapp_1">
</div>

<script>
  var xReq = new XMLHttpRequest();
  xReq.onload = function () {
    if (xReq.status >= 200 && xReq.status < 300) {
      document.querySelector(".webapp_1").innerHTML = "";
      document.querySelector(".webapp_1").insertAdjacentHTML('beforeend', xReq.responseText);
    } else {
      console.log(xReq.responseText);
    }
  };
  xReq.open('GET', '/api/webapp-1?page=1&per_page=1');
  xReq.send();
</script>

A Footnote

Your Liquid endpoint Page will be acting as an extra Layer between your request and platformOS' own GraphQL endpoint. This extra layer is important because it allows you to run your own logic and security checks, before pOS deliver the data.

Related Articles

  • SiteGurus have created the Live Updates API as part of the SiteBuilder module- designed as an incredibly flexible API endpoint for refreshing almost any Siteglide Layout with different filters- this may save you time implementing your own API endpoint: Live Updates

  • The Siteglide Support Policy explains how you can get support with planning projects and writing custom code.

  • MDN have comprehensive documentation on the XML HTTP Request and how to use it in your Front End JavaScript Code: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest You can also use the modern https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API as an alternative.

  • Those developers who prefer to use jQuery when writing JavaScript can read more about Ajax Requests here: https://api.jquery.com/jquery.ajax/

  • platformOS's documentation on Pages includes lots of information about setting up Pages using the yaml configuration: https://documentation.platformos.com/developer-guide/pages/pages

Tutorial 12 - Related Records and Datasources

Introduction

In this tutorial we're going to show you how you can use a single query to get not only results in a single table, but at the same time return related results from another table.

For example:

  • Products may be related to categories- and each category may be related to some help articles.

  • Blog Articles may each be related to an Author, and that Author may have a relationship with some Secure Zones.

Using related_records is powerful because:

  • It allows you to build more complex applications - giving you complete control over getting the data you need.

  • It's flexible - if you've stored enough data, you can define relationships in your query without changing any settings.

  • Combining multiple queries into one will give you much better performance than any other way of getting this data, e.g. nesting Siteglide include tags inside each other.

Glossary of Terms

Here are a few of the terms you may come across in this topic:

Term
Definition

relational database

A relational database describes a database comprised of tables, where there may be predefined relationships between records in different tables. platformOS's database is a kind of relational database.

join

In many query languages e.g. SQL, a join defines how two tables are linked together. In platformOS, a join on property is used to define which field should be used to find other related_records.

tables

In platformOS, as in many other databases, a table is a set of data which contains records of a certain type, with a certain set of properties.

records

In platformOS, as in many other databases, a record is a single entry of data in a database.

related_records

In platformOS, records which share a relationship. It can be single:single, many:many or single:many. This is effectively the same concept as a datasource.

datasource

In Siteglide, a datasource is where there is a relationship between records in the same table or in a different table, and data can be "sourced" from that other table. It can be single:single, many:many or single:many. This is effectively the same concept as platformOS's related_records.

datasource and datasource_multi field types

In Siteglide, some fields can be given the datasource and datasource_multi field types: These are designed to store IDs of other records to make joining easy. However, you can also in GraphQL join other types of fields.

foreign

In platformOS, a foreign property refers to a property outside of the current record. The foreign property is matched with the join on property in order to fetch related records which share a relationship.

data graph

In computing a data graph describes the relationships between different nodes of data. Think of a node like a city and the relationships like roads. One of the reasons GraphQL is called GraphQL is that it is trying to give queries an intuitive graph-like structure- we define the relationships in the query itself, and the results return with the same relationship structure.

Building your first query using Related Records

The great thing about related records being as flexible as they are is that there are a lot of examples we could choose from. Our first example though will try to keep things simple by fetching Blog posts with their associated authors.

Step 1) Write your query to fetch Blog Posts

We've already covered how to write a records query and filter it so it contains only blog posts. Here's an example:

query blogsWithAuthors {
  records(
    per_page: 20,
    filter: {
      table: {
        value: "module_3"
      }
    }
  ) {
    results {
      id
      properties
    }
  }
}

Step 2) Find out which fields match across the two tables

Since these two modules - the Blog and Authors Module - are both created by Siteglide, they already store the information needed to join them inside their properties. We only need to look at the existing properties in detail to get the information we need to build our query.

There are several ways to do this. One of the easiest to do on an everyday basis is to use Introducing Siteglide CLI to pull your site, then to look in marketplace_builder/form_configurations e.g. 'marketplace_builder/form_configurations/forms/form_1.liquid' or 'marketplace_builder/form_configurations/modules/module_3.liquid':

For this exercise, we'd be looking at module_3 for blog and module_6 for authors.

In Blog, scroll down or use ctrl-f to search for author The form configuration will contain both the human-friendly name and the Siteglide ID for each field:

module_field_3_4:
      name: Author
      type: datasource
      live: true
      hidden: false
      order: 0
      editable: true
      datasource_id: module_6
      required: false
      validation: {}

Great! It's a datasource field, which is pefect, because it will already be set up to contain IDs of Author records related to each Blog record. We don't need to look at the Author record now to know that the module_field_3_4 property of the Blog and the id of the Author should match. Sometimes, you'll need to look at both.

Since this is a GraphQL tutorial- here's an alternative way to check the form-configurations- using a query (see how we use filter to look for only the Blog and Author configurations)!

query findBlogAndAuthorProperties {
  admin_forms(filter: {name: {value_in: ["module_3","module_6"]}}) {
    results {
      fields
    }
  }
}

In the Siteglide Admin, we can check that some of the Blogs already have authors set in the datasource field, in this case this Blog item has an Author ID of 100 in the field:

Step 3) Add related_records inside results

To start with, look inside results in explorer: related_record and related_records appear as possible results. By including these special objects inside the results object, we can define a branching tree of related results.

The two options are similar but have one key difference:

  • related_record - Use this to define a 1:1 or a many:1 relationship. It will return a single record as an object. This may give you better performance, if you know for example there's only one author.

  • related_records - Use this to define a 1:many or a many:many relationship. It will return multiple records as an array.

The main reason to pay attention to which you choose is that it will change the way you structure your Liquid to access the data- remember if there is an array in the data you may need to loop over it in order to access it.

For now, let's use related_record, as we have a datasource field to join with, and only one Author per Blog record:

query blogsWithAuthors {
  records(per_page: 20, filter: {table: {value: "module_3"}}) {
    results {
      id
      properties
      related_record() {
        
      }
    }
  }
}

Step 4) Rename the related_record

Remember, we use a string with no spaces and a colon to "rename" things in GraphQL. It's a good idea to rename this related_record to describe what it will return. In this case, it describes a single author.

query blogsWithAuthors {
  records(per_page: 20, filter: {table: {value: "module_3"}}) {
    results {
      id
      properties
      author: related_record() {
        
      }
    }
  }
}

This will make it easier to understand the results and allow you to add other related_records in future (it won't let you if names clash).

Step 5) Set the join_on_property

As mentioned in the glossary, the join_on_property is used to define the property in the current result record which will be used to match with a related record. In step 3 we worked out it was module_field_3_4.

  • We don't need to write the value of the module_field_3_4 field, just the Siteglide field ID.

  • you also don't need to add properties before the field ID, as you may when filtering a query; here, platformOS works this out automatically

  • this goes inside the curly brackets as an argument

query blogsWithAuthors {
  records(per_page: 20, filter: {table: {value: "module_3"}}) {
    results {
      id
      properties
      author: related_record(
        join_on_property: "module_field_3_4"
      ) {
        
      }
    }
  }
}

Step 6 - Set the Foreign Property

The foreign property is the counterpart to the join_on_property, but it refers to the property in the record we are looking for, in this case the Author record is the record, and the property is id.

query blogsWithAuthors {
  records(per_page: 20, filter: {table: {value: "module_3"}}) {
    results {
      id
      properties
      author: related_record(
        join_on_property: "module_field_3_4",
        foreign_property: "id"
      ) {
        
      }
    }
  }
}

Note the syntax is the same, even though module_field_3_4 is a custom property and id is a core property in platformOS.

Step 7 - Set the Table

table here is a filter, despite not being inside a filter object. It must be set to describe the table we should look in for these related records. In this case, the ID of the Authors module: module_6

query blogsWithAuthors {
  records(per_page: 20, filter: {table: {value: "module_3"}}) {
    results {
      id
      properties
      author: related_record(
        join_on_property: "module_field_3_4",
        foreign_property: "id",
        table: "module_6"
      ) {
        
      }
    }
  }
}

Step 8 - Results

We've already defined which results we want for Blog items, and that we want to return a related author per blog item, but we also need to define which authors properties should be returned.

We could return the whole properties object, but where possible it's worth being extra efficient when working with relationships. There is more work for the query to do, and it may run more slowly. Maybe we just need the author's name and an image (I use the information we looked at in step 2 to find the module_6 image field ID)?

The results go in the new object under related_record after the arguments; no results key is needed:

query blogsWithAuthors {
  records(per_page: 20, filter: {table: {value: "module_3"}}) {
    results {
      id
      properties
      author: related_record(
        join_on_property: "module_field_3_4",
        foreign_property: "id",
        table: "module_6"
      ) {
        name: property(name: "name")
        image: property(name: "module_field_6_4")
      }
    }
  }
}

That's the query itself done!

Step 9 - Working with the Results

The results in JSON may look like the below (we've minimised Blog properties which aren't useful to the example):

{ 
 "data": {
   "records": {
     "results": [
       {
         "id": "97",
         "properties": {...}, # note - this record 97 did NOT have a match with an author. Maybe the Author has been deleted or maybe the ID has not been stored in the field we're joining on.
         "author": null 
       },
       {
         "id": "8",
         "properties": {...}, # note this blog item with ID of 8 DID match with an author- the author's fields we asked for are below!
         "author": { 
           "name": "Jese Leos",
           "image": "https://cdn.staging.oregon.platform-os.com/instances/10093/assets/jese-leos.png"
         }
       },
       {
         "id": "10",
         "properties": {...},
         "author": {
           "name": "Karen Nelson",
           "image": "https://cdn.staging.oregon.platform-os.com/instances/10093/assets/karen-nelson.png"
         }
       }
     ]
   }
 }
}

As always, when outputting in Liquid, you can use dot notation (see Liquid Dot Notation or Tutorial 5 - Using Liquid to run GraphQL queries on your Site) to access the results, until you get to an array. Since we only asked for a single author, we can use dot notation inside the blog record to access the author. We still need to loop over the blog results as always:

{% graphql blogs_with_authors = "blogsWithAuthors" %}
{% for blog in blogs_with_authors.records.results %}
  <h2>{{blog.properties.module_field_3_1}}</h2>
  <div>
    By: {{blog.author.name}}
    <img src="{{blog.author.image | asset_url}}">
  </div>
{% endfor %}

A many:1 alternative

What if you need this to fetch information the other way around, e.g. you are on a page which displays information about an author and you wish to display a list of the Author's Blog Posts? An additional aspect to this is that this is a 1:many relationship- it's no problem- we'll use related_records plural instead of related_record to return an array instead of an object.

Starting with the query above, lets make some changes:

Step 1) Change the initial query to fetch Authors first and rename:

query AuthorsAndTheirArticles { #renamed
  records(per_page: 20, filter: {table: {value: "module_6"}}) { #change table
    results {
      id
      properties
      author: related_record(
        join_on_property: "module_field_3_4",
        foreign_property: "id",
        table: "module_6"
      ) {
        name: property(name: "name")
        image: property(name: "module_field_6_4")
      }
    }
  }
}

Step 2) Change join on property to Author's ID

query AuthorsAndTheirArticles { #renamed
  records(per_page: 20, filter: {table: {value: "module_6"}}) { #change table
    results {
      id
      properties
      author: related_record(
        join_on_property: "id", #edit join
        foreign_property: "id",
        table: "module_6"
      ) {
        name: property(name: "name")
        image: property(name: "module_field_6_4")
      }
    }
  }
}

Step 3) Change foreign property to Blog's property which contains Author's ID

query AuthorsAndTheirArticles { #renamed
  records(per_page: 20, filter: {table: {value: "module_6"}}) { #change table
    results {
      id
      properties
      author: related_record(
        join_on_property: "id",#edit join
        foreign_property: "module_field_3_4", # edit foreign
        table: "module_6"
      ) {
        name: property(name: "name")
        image: property(name: "module_field_6_4")
      }
    }
  }
}

Step 4) Change related table to Blog

query AuthorsAndTheirArticles { #renamed
  records(per_page: 20, filter: {table: {value: "module_6"}}) { #change table
    results {
      id
      properties
      author: related_record(
        join_on_property: "id",#edit join
        foreign_property: "module_field_3_4", # edit foreign
        table: "module_3" # edit table
      ) {
        name: property(name: "name")
        image: property(name: "module_field_6_4")
      }
    }
  }
}

Step 5) Change related_record to related_records and rename

query AuthorsAndTheirArticles { #renamed
  records(per_page: 20, filter: {table: {value: "module_6"}}) { #change table
    results {
      id
      properties
      articles: related_records(
        join_on_property: "id",#edit join
        foreign_property: "module_field_3_4", # edit foreign
        table: "module_3" # edit table
      ) {
        name: property(name: "name")
        image: property(name: "module_field_6_4")
      }
    }
  }
}

Step 6) Change Results

query AuthorsAndTheirArticles { #renamed
  records(per_page: 20, filter: {table: {value: "module_6"}}) { #change table
    results {
      id
      properties
      articles: related_records(
        join_on_property: "id",#edit join
        foreign_property: "module_field_3_4", # edit foreign
        table: "module_3" # edit table
      ) {
        title: property(name: "module_field_3_1")
        slug: property(name: "slug")
      }
    }
  }
}

Step 7) Liquid example

{% graphql authors_and_their_articles = "AuthorsAndTheirArticles" %}
{% for author in authors_and_their_articles.records.results %}
  <h2>{{author.properties.name}}</h2>
  <div>
    Articles Published:
    <ul>
      {% for article in author.articles %}
        <li>
          <a href="/siteglide-blogs/{{article.slug}}>{{article.title}}</a>
        </li>
      {% endfor %}
    </ul>
  </div>
{% endfor %}

Further Learning

Next, you could experiment with some of these options:

  • using filters to further filter related records e.g. Authors with Blog posts which are enabled

  • using related_users an alternative to related_records which fetches CRM users with a relationship to your record. You don't need to specify a table, but join and foreign properties work the same.

  • Additional nesting. Inside your related_records results, you can define related_records again. This allows you to build a complex results tree containing as many layers of related results as you like, for example adding to our example above- you could return the categories of each Blog the Author had published- and display a list of category names.

Create Folder Structure

A Module can contain any code that will run on Siteglide, but that code does need to be split into the relevant folders (notifications, views, etc.) and folder structures. Below, we'll take a look at how to do just that along with all the various options available to you, depending on what you would like to build.

<module_name>

The Module name is written in the following format: module_<vanity_id> . Replace vanity_id with the Vanity ID generated in your Module listing in Portal.

For example, with our Siteglide Theme Demo Module this would generate a name of module_76. Your module name (based on your generated Vanity ID) should be used anywhere the tag <module_name> is referenced in this documentation.

Create Your Staging Site

Create a new staging site within your agency to build your module on. Ideally you'll want to use a blank site, so pick the "Build a Custom Site" option on creation. Only you will see what your site is called, so you can call it anything you like.

Once you have successfully created your staging site, create a project folder on your machine to work within, connect to your site via CLI and then pull the site down onto your local machine so that you are ready to begin building your module.

Top Level Folders

When you begin building a Module, there are two top level folders that should be created alongside the marketplace_builder folder of your staging site. The top level module folders are:

  • modules/<module_name>/private

  • modules/<module_name>/public

Any information that is stored in the private folder will not be accessible to users within the Siteglide Site Admin, CLI and GraphQL. Here you should store any logic that you don’t want anyone to be able to see or edit. If you are following our example Siteglide Theme Demo Module, as it is a theme we do not have any private files and so this folder can be ignored.

Any information stored in the public folder will be accessible to users within the Siteglide Site Admin, CLI, and GraphQL. This folder should be used for any content that you would like to be visible and editable to the user.

Sub Level Folders

Under each top level folder you have the option to create any relevant folders for the information you are wanting to create and use, such as assets , views , notifications etc.

/my-project-folder
    /modules
        /<module_name>
            /assets
                /css
                /documents
                /js
                /scripts
            /authorization_policies
            /custom_module_types
                /forms
                /modules
                /webapps
            /form_configurations
                /forms
                /modules
                /webapps
            /graph_queries
            /notifications
                /api_call_notifications
                /email_notifications
            /pages
                /redirects
            /translations
            /user_profile_types
            /views
                /layouts
                    /templates
                /pages
                    /system_pages
                /partials
                    /layouts

You can view and download this full folder structure from the example directory-structure Git Repo. Alternatively, run the init command in CLI to automatically create the structure within the marketplace_builder folder and then move them into your module.

https://documentation.platformos.com/developer-guide/platformos-workflow/codebase

Directory/file

Explanation

** Learn more**

Assets

Used to store any assets (Images, Files, CSS etc) that you would like to ensure are accessible when your module is installed.

Views

Contains all layouts/partials/pages/templates visible from Admin & Front-end of a site the module is installed on.

Notifications

Email and API notifications

GraphQL

GraphQL Queries and other data handling

Module Setup Files

Beyond the standard site files that will be in your Module (GraphQL, Liquid, partials etc), the Module installation process will look for 3 other files in the root folder of your Module Project (alongside /modules/).

/my-project-folder
    /modules
       /<module_name>
            /views
setup.json
ignore-on-update.json
install-process.json

setup.json

The setup.json file contains information about your module, including any tables (data) you want to create when installing your Module to a site. You can view an example of this file here.

Files generated from this JSON file:

  • Model - <module_name>.yml stored at modules/<module_name>/public/custom_model_types/modules/<module_name>.yml

  • Form Configuration - <module_name>.liquid stored at modules/<module_name>/public/form_configurations/modules/<module_name>.liquid

  • Detail Page (if applicable) - <module_name>.liquid stored at modules/<module_name>/public/views/pages/modules/<module_name>.liquid

You can find details on field types in this document

ignore-on-update.json

This file should contain a list of files you don’t want to be overwritten on sites when updating to a new version of your Module. Typically these would be any layout or asset files in your Module that a user will have edited in the last version.

Each array’s key should be the version they are created at. For example, if you want a file to be ignored when updating to any version above 1.0.0, set the key as 1.0.0.

You can see an example structure here

In this example, an update above 1.0.1 will ignore all 6 files in the list.

install-process.json

The install process file will run a Siteglide created process while the module is installed on the site. A list of all of the scripts that are relevant for each module type can be found here ::create and link::

In this example we will need a page to be set as the homepage of the site after installing the module. This is done using the set_homepage process. We will create a file with the version number of our module and set_homepage as our process as seen here.

Field Types

Retrieve an overview list of all CRM users from your website

get
Authorizations
Query parameters
per_pageintegerOptional

Number of items to retireve, maximum of 500

Example: 20
sort_typestringOptional

Property by which to sort the results

Example: created_at
sort_orderstringOptional

Order of results (asc or desc)

Example: desc
pageintOptional

Page to retrieve number, to be used with per_page

Example: 1
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - The CRM does not contain any users
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/users HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all data about an individual CRM user

get
Authorizations
Path parameters
idstring · idRequired

The ID or Email Address of the user

Example: 1234 or [email protected]
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - CRM user does not exist
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/users/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Update a CRM user

put
Authorizations
Path parameters
idstring · idRequired

The ID of the user

Example: 1234
Body
namestringRequired
emailstringRequired
passwordstringOptional
secure_zonesstringOptional
cfs_field_1_1stringOptional
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - CRM user does not exist
422
Unprocessable Entity - Required data missing or data invalid
put
PUT /api/site/v2/users/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 93

{
  "name": "text",
  "email": "text",
  "password": "text",
  "secure_zones": "text",
  "cfs_field_1_1": "text"
}

No content

Create a CRM user

post
Authorizations
Body
namestringRequired
emailstringRequired
passwordstringOptional
secure_zonesstringOptional
cfs_field_1_1stringOptional
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - CRM user does not exist
422
Unprocessable Entity - Required data missing or data invalid
post
POST /api/site/v2/users/create HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 93

{
  "name": "text",
  "email": "text",
  "password": "text",
  "secure_zones": "text",
  "cfs_field_1_1": "text"
}

No content

Retrieve all Module Items from an individual Module

get
Authorizations
Path parameters
modulestringRequired

The Module ID

Example: 1
Query parameters
itemsid · arrayOptional

A comma seperated list of item IDs to retrieve, leave empty to get all items

propertiesstring · arrayOptional

A comma seperated list of properties that you would like to retrieve, leave empty to get all properties

Example: name,slug,enabled
per_pageintegerOptional

Number of items to retireve, maximum of 500

Example: 20
sort_typestringOptional

Property by which to sort the results

Example: created_at
sort_orderstringOptional

Order of results (asc or desc)

Example: desc
pageintOptional

Page to retrieve number, to be used with per_page

Example: 1
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - Module does not exist or contains no Items
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/modules/{module}/items HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all data about an individual Module Item

get
Authorizations
Path parameters
modulestringRequired

The Module ID

Example: 1
idstring · idRequired

The ID of the Module item

Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - Module Item does not exist
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/modules/{module}/items/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Update a Module Item within an individual Module

put
Authorizations
Path parameters
modulestringRequired

The Module ID

Example: 1
idstringRequired

The Module Item ID

Example: 1
Body
namestringRequired
slugstringOptional
enabledstringRequired
module_field_3_1stringOptional
module_field_custom_3_2stringOptional
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
422
Unprocessable Entity - Required data missing or data invalid
put
PUT /api/site/v2/modules/{module}/items/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 105

{
  "name": "text",
  "slug": "text",
  "enabled": "text",
  "module_field_3_1": "text",
  "module_field_custom_3_2": "text"
}

No content

Create a Module Items within an individual Module

post
Authorizations
Path parameters
modulestringRequired

The Module ID

Example: 1
Body
namestringRequired
slugstringOptional
enabledstringRequired
module_field_3_1stringOptional
module_field_custom_3_2stringOptional
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
422
Unprocessable Entity - Required data missing or data invalid
post
POST /api/site/v2/modules/{module}/create HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 105

{
  "name": "text",
  "slug": "text",
  "enabled": "text",
  "module_field_3_1": "text",
  "module_field_custom_3_2": "text"
}

No content

Retrieve an overview list of all form cases from your website

get
Authorizations
Path parameters
formstringRequired

The form ID which the cases are linked to.

Example: 1
Query parameters
per_pageintegerOptional

Number of items to retireve, maximum of 500

Example: 20
sort_typestringOptional

Property by which to sort the results

Example: created_at
sort_orderstringOptional

Order of results (asc or desc)

Example: desc
pageintOptional

Page to retrieve number, to be used with per_page

Example: 1
filterobject[]Optional

Filter cases on custom fields

Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - The form does not contain any cases
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/forms/{form}/cases HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all data about an individual form case

get
Authorizations
Path parameters
formstring · idRequired

The ID of your form

Example: 1
idstring · idRequired

The ID of the case

Example: 1234
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - Case does not exist
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/forms/{form}/cases/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Create a case against a specific user

post
Authorizations
Path parameters
formstring · idRequired

The ID of your form

Example: 1
Body
userIDstringRequired
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - Case does not exist
422
Unprocessable Entity - Required data missing or data invalid
post
POST /api/site/v2/forms/{form}/cases/create HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 17

{
  "userID": "text"
}

No content

Retrieve an overview list of all categories from your website

get
Authorizations
Query parameters
per_pageintegerOptional

Number of items to retireve, maximum of 500

Example: 20
sort_typestringOptional

Property by which to sort the results

Example: created_at
sort_orderstringOptional

Order of results (asc or desc)

Example: desc
pageintOptional

Page to retrieve number, to be used with per_page

Example: 1
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - The form does not contain any cases
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/categories HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all data about an individual category

get
Authorizations
Path parameters
idstring · idRequired

The ID of the category

Example: 1234
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - Case does not exist
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/categories/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Update a category

put
Authorizations
Path parameters
idstringRequired

The Category ID

Example: 1
Body
namestringRequired
slugstringOptional
enabledstringRequired
weightingstringOptional
meta_titlestringOptional
meta_descstringOptional
og_titlestringOptional
og_descstringOptional
og_imagestringOptional
og_typestringOptional
twitter_typestringOptional
image_altstringOptional
parentstringOptional
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
422
Unprocessable Entity - Required data missing or data invalid
put
PUT /api/site/v2/categories/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 231

{
  "name": "text",
  "slug": "text",
  "enabled": "text",
  "weighting": "text",
  "meta_title": "text",
  "meta_desc": "text",
  "og_title": "text",
  "og_desc": "text",
  "og_image": "text",
  "og_type": "text",
  "twitter_type": "text",
  "image_alt": "text",
  "parent": "text"
}

No content

Create a category

post
Authorizations
Body
namestringRequired
slugstringOptional
enabledstringRequired
weightingstringOptional
meta_titlestringOptional
meta_descstringOptional
og_titlestringOptional
og_descstringOptional
og_imagestringOptional
og_typestringOptional
twitter_typestringOptional
image_altstringOptional
parentstringOptional
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - Case does not exist
422
Unprocessable Entity - Required data missing or data invalid
post
POST /api/site/v2/categories/create HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 231

{
  "name": "text",
  "slug": "text",
  "enabled": "text",
  "weighting": "text",
  "meta_title": "text",
  "meta_desc": "text",
  "og_title": "text",
  "og_desc": "text",
  "og_image": "text",
  "og_type": "text",
  "twitter_type": "text",
  "image_alt": "text",
  "parent": "text"
}

No content

Retrieve an overview list of all eCommerce orders from your website

get
Authorizations
Query parameters
propertiesstring · arrayOptional

A comma seperated list of properties that you would like to retrieve, leave empty to get all properties

Example: name,slug,enabled
per_pageintegerOptional

Number of items to retireve, maximum of 500

Example: 20
sort_typestringOptional

Property by which to sort the results

Example: created_at
sort_orderstringOptional

Order of results (asc or desc)

Example: desc
pageintOptional

Page to retrieve number, to be used with per_page

Example: 1
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - The CRM does not contain any orders
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/ecommerce/orders HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all data about an individual eCommerce order

get
Authorizations
Path parameters
idstring · idRequired

The ID of the eCommerce order

Example: 1234
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - eCommerce order does not exist
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/ecommerce/orders/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all eCommerce order data about an individual CRM user

get
Authorizations
Path parameters
userIDstring · idRequired

The ID of the user

Example: 1234
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - eCommerce order does not exist
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/ecommerce/orders/user/{userID} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all products

get
Authorizations
Query parameters
propertiesstring · arrayOptional

A comma seperated list of properties that you would like to retrieve, leave empty to get all properties

Example: name,slug,enabled
per_pageintegerOptional

Number of items to retireve, maximum of 500

Example: 20
sort_typestringOptional

Property by which to sort the results

Example: created_at
sort_orderstringOptional

Order of results (asc or desc)

Example: desc
pageintOptional

Page to retrieve number, to be used with per_page

Example: 1
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - Product does not exist or this site has no products
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/ecommerce/products HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all data about an individual product

get
Authorizations
Path parameters
idstring · idRequired

The ID of the Product

Example: 1234
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - Product does not exist
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/ecommerce/products/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Update a Product

put
Authorizations
Path parameters
idstringRequired

The Product Item ID

Example: 1
Body
namestringRequired
slugstringOptional
enabledstringRequired
weightingstringOptional
expiry_datestringOptional
release_datestringOptional
category_arrayobject[]Optional
secure_zone_arrayobject[]Optional
module_field_14/product_1stringOptional
module_field_14/product_2stringOptional
module_field_14/product_3stringOptional
module_field_14/product_4stringOptional
module_field_14/product_5stringOptional
module_field_14/product/inventory/1stringOptional
module_field_14/product/inventory/2stringOptional
module_field_14/product/inventory/3stringOptional
module_field_14/product/price/aud/1stringOptional
module_field_14/product/price/aud/2stringOptional
module_field_14/product/price/cad/1stringOptional
module_field_14/product/price/cad/2stringOptional
module_field_14/product/price/eur/1stringOptional
module_field_14/product/price/eur/2stringOptional
module_field_14/product/price/gbp/1stringOptional
module_field_14/product/price/gbp/2stringOptional
module_field_14/product/price/nzd/1stringOptional
module_field_14/product/price/nzd/2stringOptional
module_field_14/product/price/usd/1stringOptional
module_field_14/product/price/usd/2stringOptional
Responses
200
OK
400
Bad Request - The API could not understand the request due to invalid syntax.
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
422
Unprocessable Entity - Required data missing or data invalid
put
PUT /api/site/v2/ecommerce/products/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 2117

{
  "name": "text",
  "slug": "text",
  "enabled": "text",
  "weighting": "text",
  "expiry_date": "text",
  "release_date": "text",
  "category_array": [
    {}
  ],
  "secure_zone_array": [
    {}
  ],
  "module_field_14/product_1": "text",
  "module_field_14/product_2": "text",
  "module_field_14/product_3": "text",
  "module_field_14/product_4": "text",
  "module_field_14/product_5": "text",
  "module_field_14/product/inventory/1": "text",
  "module_field_14/product/inventory/2": "text",
  "module_field_14/product/inventory/3": "text",
  "module_field_14/product/price/aud/1": "text",
  "module_field_14/product/price/aud/2": "text",
  "module_field_14/product/price/cad/1": "text",
  "module_field_14/product/price/cad/2": "text",
  "module_field_14/product/price/eur/1": "text",
  "module_field_14/product/price/eur/2": "text",
  "module_field_14/product/price/gbp/1": "text",
  "module_field_14/product/price/gbp/2": "text",
  "module_field_14/product/price/nzd/1": "text",
  "module_field_14/product/price/nzd/2": "text",
  "module_field_14/product/price/usd/1": "text",
  "module_field_14/product/price/usd/2": "text",
  "attributes": [
    {
      "id": "text",
      "name": "text",
      "items": [
        {
          "id": "text",
          "name": "text",
          "enabled": "text",
          "module_field_14/product_attribute_option_2": "text",
          "module_field_14/product_attribute_option_3": "text",
          "module_field_14/product_attribute_option_4": "text",
          "module_field_14/product_attribute_option_6": "text",
          "module_field_14/product_attribute_option/price/aud/1": "text",
          "module_field_14/product_attribute_option/price/aud/2": "text",
          "module_field_14/product_attribute_option/price/cad/1": "text",
          "module_field_14/product_attribute_option/price/cad/2": "text",
          "module_field_14/product_attribute_option/price/eur/1": "text",
          "module_field_14/product_attribute_option/price/eur/2": "text",
          "module_field_14/product_attribute_option/price/gbp/1": "text",
          "module_field_14/product_attribute_option/price/gbp/2": "text",
          "module_field_14/product_attribute_option/price/nzd/1": "text",
          "module_field_14/product_attribute_option/price/nzd/2": "text",
          "module_field_14/product_attribute_option/price/usd/1": "text",
          "module_field_14/product_attribute_option/price/usd/2": "text",
          "module_field_14/product_attribute_option/inventory/1": "text"
        }
      ]
    }
  ]
}

No content

Create a Product

post
Authorizations
Body
namestringRequired
slugstringOptional
enabledstringRequired
weightingstringOptional
expiry_datestringOptional
release_datestringOptional
category_arrayobject[]Optional
secure_zone_arrayobject[]Optional
module_field_14/product_1stringOptional
module_field_14/product_2stringOptional
module_field_14/product_3stringOptional
module_field_14/product_4stringOptional
module_field_14/product_5stringOptional
module_field_14/product/inventory/1stringOptional
module_field_14/product/inventory/2stringOptional
module_field_14/product/inventory/3stringOptional
module_field_14/product/price/aud/1stringOptional
module_field_14/product/price/aud/2stringOptional
module_field_14/product/price/aud/3stringOptional
module_field_14/product/price/aud/4stringOptional
module_field_14/product/price/cad/1stringOptional
module_field_14/product/price/cad/2stringOptional
module_field_14/product/price/cad/3stringOptional
module_field_14/product/price/cad/4stringOptional
module_field_14/product/price/eur/1stringOptional
module_field_14/product/price/eur/2stringOptional
module_field_14/product/price/eur/3stringOptional
module_field_14/product/price/eur/4stringOptional
module_field_14/product/price/gbp/1stringOptional
module_field_14/product/price/gbp/2stringOptional
module_field_14/product/price/gbp/3stringOptional
module_field_14/product/price/gbp/4stringOptional
module_field_14/product/price/nzd/1stringOptional
module_field_14/product/price/nzd/2stringOptional
module_field_14/product/price/nzd/3stringOptional
module_field_14/product/price/nzd/4stringOptional
module_field_14/product/price/usd/1stringOptional
module_field_14/product/price/usd/2stringOptional
module_field_14/product/price/usd/3stringOptional
module_field_14/product/price/usd/4stringOptional
Responses
200
OK
400
Bad Request - The API could not understand the request due to invalid syntax.
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
422
Unprocessable Entity - Required data missing or data invalid
post
POST /api/site/v2/ecommerce/products/create HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 2650

{
  "name": "text",
  "slug": "text",
  "enabled": "text",
  "weighting": "text",
  "expiry_date": "text",
  "release_date": "text",
  "category_array": [
    {}
  ],
  "secure_zone_array": [
    {}
  ],
  "module_field_14/product_1": "text",
  "module_field_14/product_2": "text",
  "module_field_14/product_3": "text",
  "module_field_14/product_4": "text",
  "module_field_14/product_5": "text",
  "module_field_14/product/inventory/1": "text",
  "module_field_14/product/inventory/2": "text",
  "module_field_14/product/inventory/3": "text",
  "module_field_14/product/price/aud/1": "text",
  "module_field_14/product/price/aud/2": "text",
  "module_field_14/product/price/aud/3": "text",
  "module_field_14/product/price/aud/4": "text",
  "module_field_14/product/price/cad/1": "text",
  "module_field_14/product/price/cad/2": "text",
  "module_field_14/product/price/cad/3": "text",
  "module_field_14/product/price/cad/4": "text",
  "module_field_14/product/price/eur/1": "text",
  "module_field_14/product/price/eur/2": "text",
  "module_field_14/product/price/eur/3": "text",
  "module_field_14/product/price/eur/4": "text",
  "module_field_14/product/price/gbp/1": "text",
  "module_field_14/product/price/gbp/2": "text",
  "module_field_14/product/price/gbp/3": "text",
  "module_field_14/product/price/gbp/4": "text",
  "module_field_14/product/price/nzd/1": "text",
  "module_field_14/product/price/nzd/2": "text",
  "module_field_14/product/price/nzd/3": "text",
  "module_field_14/product/price/nzd/4": "text",
  "module_field_14/product/price/usd/1": "text",
  "module_field_14/product/price/usd/2": "text",
  "module_field_14/product/price/usd/3": "text",
  "module_field_14/product/price/usd/4": "text",
  "attributes": [
    {
      "name": "text",
      "enabled": "text",
      "items": [
        {
          "name": "text",
          "enabled": "text",
          "module_field_14/product_attribute_option_2": "text",
          "module_field_14/product_attribute_option_3": "text",
          "module_field_14/product_attribute_option_4": "text",
          "module_field_14/product_attribute_option_6": "text",
          "module_field_14/product_attribute_option/price/aud/1": "text",
          "module_field_14/product_attribute_option/price/aud/2": "text",
          "module_field_14/product_attribute_option/price/cad/1": "text",
          "module_field_14/product_attribute_option/price/cad/2": "text",
          "module_field_14/product_attribute_option/price/eur/1": "text",
          "module_field_14/product_attribute_option/price/eur/2": "text",
          "module_field_14/product_attribute_option/price/gbp/1": "text",
          "module_field_14/product_attribute_option/price/gbp/2": "text",
          "module_field_14/product_attribute_option/price/nzd/1": "text",
          "module_field_14/product_attribute_option/price/nzd/2": "text",
          "module_field_14/product_attribute_option/price/usd/1": "text",
          "module_field_14/product_attribute_option/price/usd/2": "text",
          "module_field_14/product_attribute_option/inventory/1": "text"
        }
      ]
    }
  ]
}

No content

Retrieve all WebApp Items from an individual WebApp

get
Authorizations
Path parameters
webappstringRequired

The WebApp ID

Example: 1
Query parameters
itemsid · arrayOptional

A comma seperated list of item IDs to retrieve, leave empty to get all items

propertiesstring · arrayOptional

A comma seperated list of properties that you would like to retrieve, leave empty to get all properties

Example: name,slug,enabled
per_pageintegerOptional

Number of items to retireve, maximum of 500

Example: 20
sort_typestringOptional

Property by which to sort the results

Example: created_at
sort_orderstringOptional

Order of results (asc or desc)

Example: desc
pageintOptional

Page to retrieve number, to be used with per_page

Example: 1
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - WebApp does not exist or contains no Items
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/webapps/{webapp}/items HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Retrieve all data about an individual WebApp Item

get
Authorizations
Path parameters
webappstringRequired

The WebApp ID

Example: 1
idstring · idRequired

The ID of the WebApp item

Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
404
Not Found - WebApp Item does not exist
422
Unprocessable Entity - Required data missing or data invalid
get
GET /api/site/v2/webapps/{webapp}/items/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Accept: */*

No content

Update a WebApp Item within an individual WebApp

put
Authorizations
Path parameters
webappstringRequired

The WebApp ID

Example: 1
idstringRequired

The WebApp Item ID

Example: 1
Body
namestringRequired
slugstringOptional
enabledstringRequired
webapp_field_1_1stringOptional
webapp_field_1_2stringOptional
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
422
Unprocessable Entity - Required data missing or data invalid
put
PUT /api/site/v2/webapps/{webapp}/items/{id} HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 98

{
  "name": "text",
  "slug": "text",
  "enabled": "text",
  "webapp_field_1_1": "text",
  "webapp_field_1_2": "text"
}

No content

Create a WebApp Items within an individual WebApp

post
Authorizations
Path parameters
webappstringRequired

The WebApp ID

Example: 1
Body
namestringRequired
slugstringOptional
enabledstringRequired
webapp_field_1_1stringOptional
webapp_field_1_2stringOptional
Responses
200
OK
401
Unauthorized - Credentials Incorrect
403
Forbidden - You cannot access this site
422
Unprocessable Entity - Required data missing or data invalid
post
POST /api/site/v2/webapps/{webapp}/create HTTP/1.1
Host: {{your site}}
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 98

{
  "name": "text",
  "slug": "text",
  "enabled": "text",
  "webapp_field_1_1": "text",
  "webapp_field_1_2": "text"
}

No content

CLI Changelog

1.10.1 - 19th February 2025

  • Add support for new London server stack


1.9.8 - 20th August 2024

  • Add support for Node LTS (v20.16.0)


1.9.6 - 3rd August 2022

  • Allow .md and .htm file extensions


1.9.5 - 22nd March 2022

Bug Fixes

Logs

  • Fix a crash on logs when a specific deprecation warning is shown


1.9.4 - 15th March 2022

Bug Fixes

Export/Pull

  • Extend wait time to 2 minutes to lessen "Pull Failed" issue


1.9.3 - 25th January 2022

Bug Fixes

GraphiQL

  • Fix missing error/warning linting in editor


1.9.2 - 25th January 2022

Features

Sync

  • Show which file is failing if a sync error occurs

Misc

  • Update dependencies


1.9.1 - 21st January 2022

Bug Fixes

Pull

  • Improve pull support on Windows for page redirects with special characters

Misc

  • Improve log loading performance

  • Remove unused code

  • Update dependencies and remove unused dependencies


1.9.0 - 23rd September 2021

Features

Pull

  • You can now pull modules that are placed in the modules folder alongside marketplace_builder To do this pass in the name of the module to the pull command, for example siteglide-cli pull production --module my_great_module

Modules

  • A new command that will display a list of modules installed on the site. These names can then be used in the pull command to download the content of those modules. Example: siteglide-cli modules production

Bug Fixes

Migrate

  • Ignore BannerProcess.aspx pages when migration from Adobe BC


1.8.19 - 20th September 2021

Bug Fixes

Sync

  • Fix Studio sass not compiling when either custom.scss or custom_variables.scss was synced


1.8.18 - 10th September 2021

Bug Fixes

Migrate

  • Fix CSS not converting to asset urls for Muse sites

  • Fix muse data-hidpi-src not migrating correctly


1.8.17 - 2nd September 2021

Bug Fixes

Migrate

  • Ignore converting BC search forms when migrating


1.8.16 - 31st August 2021

Bug Fixes

Migrate

  • Fix migrate command failing to run


1.8.15 - 27th August 2021

Bug Fixes

Deploy

  • Fix a sites homepage not updating during a deploy

Misc

  • Improved messaging of some logs


1.8.14 - 17th June 2021

Features

Export

  • Allow export of .heic images

Sync

  • Add the following file types to the sync watcher:

    • heic

    • key

    • mov

    • mp3

    • numbers

    • ttf

    • pages

    • pptx


1.8.13 - 15th June 2021

Features

Migrate

  • The size of your assets and codebase will now be displayed when the migrate command has finished

Fixes

Migrate

  • Ignore OrderRetrieve pages from Business Catalyst


1.8.12 - 3rd June 2021

**Fixes **Deploy

  • Block deploy and show an error if assets are over 5GB

Migrate

  • Improve compatibility with images using Fill Proportional within Business Catalyst websites


1.8.11 - 29th April 2021

Fixes Migrate

  • Improved compatibility with Siteglide forms and CLI Migrated pages


1.8.10 - 21st April 2021

Fixes Migrate

  • Fixed a bug where migrate would fail if a site had no assets

  • Fixed a bug where migrate would fail if an asset did not have a valid link/href/src

Misc

  • Improved error messages throughout


1.8.9 - 13th April 2021

Features Migrate

  • Full Adobe Muse Support: When migrating Adobe Muse sites, CLI migrate will not correctly download all assets and configuration files. Please append the flag --muse to your migrate command when migrating a Muse site.


1.8.8 - 9th April 2021

Fixes Migrate

  • Add support for finding images within a data-orig-src attribute. These are commonly used with Muse websites

  • Improved the "Something went wrong" error during form conversionFixes


1.8.7 - 30th March 2021

Fixes Migrate

  • Fix ModuleStyleSheet.css missing from migrated Business Catalyst sites


1.8.6 - 29th March 2021

Fixes Migrate

  • Fix migrate command not running

GUI

  • Fix open command not working

  • Fix open command opening browser before GraphiQL was ready

Misc GraphQL

  • Removed the deprecated graphql command, please use gui instead

GraphQL - Removed the deprecated graphql command, please use `gui` instead


1.8.5 - 23rd March 2021

Fixes

Deploy

  • Fix for deploying with images reporting an internal server error


1.8.4 - 23rd March 2021

Fixes

Deploy

  • Fix deploy potentially not working correctly

Logs

  • Fix filter flag potentially not working correctly


1.8.3 - 22nd March 2021

Fixes Sync

  • Improvement to fix for syncing assets after sync had been running for more than 1 hour

  • Improved sync messaging


1.8.2 - 21st March 2021

Fixes Add

  • The Add command will now detect if you are trying to run the it within your computers home folder and stop you continuing

Sync

  • On macOS, sync will no longer report errors about .DS_Store files

  • Fixed an issue trying to sync assets after sync had been running for more than 1 hour General

  • Improved error messaging


1.8.1 - 12th March 2021

Fixes

Migrate

  • Improve image compression process

  • Fix deploy too large error when image compression fails


1.8.0 - 11th March 2021

Features

List

  • A new command of siteglide-cli list which will show an output the current environments setup for the site. These will display in the format of - [environment name] [url]

Migrate

  • New flag of -m or --max-recursive-depth that will allow you to define how many html links deep the scraper should follow. As an example: - Your homepage links to page /B - Page /B links to page /C Setting -m 1 will only download the homepage and page /B. Page /C will not be downloaded as it is more than 1 link deep away from the entry point of the migration. Any assets of the pages are downloaded as normal.

  • New flag of -i or --ignore to ignore certain URLs. This is a string that gets passed into a if(currentURL.includes(ignore)) function. For example if you do not want your blog to be migrated and the URL of your blog posts are site.com/post/post-name then you can pass in /post/ for them to be ignored. These can be stacked with multiple ignores, for example to ignore both your about and contact page you would do siteglide-cli migrate <env> --url <url> -i /about -i /contact.

Bug Fixes

Migrate

  • Fix migrate not completing on Windows due to invalid characters in folder paths and file names

Global

  • Fixed the progress spinner icon missing in certain environments


1.7.7 - 17th February 2021

Features

Migrate

  • Images within a data-src element are now found, migrated and the URL converted. For example the following image: <img data-src="/img/image.jpg" /> would be correctly migrated to: <img data-src="{{ 'img/image.jpg' | asset_url}}" />

Bug Fixes

Sync

  • Assets with an uppercase file extension can now be synced correctly

Migrate

  • Improved support for Adobe Muse sites

  • Asset file name casing is preserved instead of being automatically converted to lowercase

Misc

  • Show Changelog in update message

  • Updated minimum NodeJS version to 12


1.7.6 - 12th February 2021

Bug Fixes

Sync

  • Fix syncing assets

Deploy

  • Fix deploying assets

General

  • Fix incorrect message within update prompt


1.7.5 - 8th February 2021

Bug Fixes

Sync

  • Temporarily revert to the non-direct to S3 version


1.7.4 - 13th January 2021

Features

Sync

  • Syncing direct to S3 has now become the default. This removed the -d flag as it is no longer needed


1.7.3 - 27th November 2020

Features

Sync

  • New flag of -l on sync for live reload functionality. When you save an item within your IDE, your browser will automatically reload to show you the changes. Please make sure you have the relevant installed for this to work

Export

  • New flag of -a for asset export only. This will export code based assets within the sites /assets folder, but not the sites codebase (pages, headers, footers etc). You can mix this with -w to download only assets and include files like images, video etc

Bug Fixes

Deploy

  • Fixed a misleading error message if a deploy failed with a 5xx error


1.7.2 - 18th November 2020

Bug Fixes

Migrate

  • Fixed a bug that caused some Javascript files to have the contents `undefined` after being migrated


1.7.1 - 11th November 2020

Features

Pull

  • A new flag for ignoring assets that Pull would usually download such as JS, CSS and JSON files. The default is that Pull will download developer assets, but the new -i flag will ignore that folder completely. This is useful if you want to pull the updates to pages, but know that no developer files such as CSS or JS have changed


1.7.0 - 6th November 2020

Features

Liquid Evaluator - Alongside our GraphiQL editor, we now have a new Liquid Evaluator tool. This will let you quickly test Liquid from the CLI, without having to create and save a page in Siteglide Admin. To use this new tool, we have moved it and GraphiQL to siteglide-cli gui <env> command. The siteglide-cli graphql <env> command will continue to work, but is now deprecated and will be removed in a future update

Sync

  • Sync now watches for and syncs .map files which are commonly used alongside minified CSS or JS files.

Export

  • Export has a new optional flag of --csv, this will export the data files as a zip of multiple CSV files instead of JSON.

Migrate:

  • Migrate will now no longer automatically deploy the site when it has finished. This gives you time to remove and folders/pages that you may not want to be migrated. You can then use the siteglide-cli deploy <env> -w command as normal to deploy the site. You can use automatic deploy within a migration by including the -a flag

Bug Fixes

Deploy

  • Fixed an issue where the homepage would not update properly when the site is deployed

Pull

  • Pull will now automatically stop and exit if it fails

Migrate

  • Fixed an issue where migrate would not run under certain environments

  • Fixed an issue where after a migration the sites homepage would not save correctly in Siteglide Admin


1.6.10 - 22nd September 2020

Bug Fixes

Fixed a bug when running the add command with sites on the Sydney Datacenter


1.6.9 - 22nd September 2020

Bug Fixes

Fixed a bug where the add command would not correctly write the configuration file to disk. In previous versions this would case a “permission or site locked” error. To fix any sites with this issue, simply update and then re-run the add command for that site.


1.6.8 - 18th September 2020

Features

Pull now also downloads the following file types:

  • Less

  • txt

  • html

  • svg

  • map

  • json

  • htm

Bug Fixes

General

  • Improved logging throughout if a URL is invalid

  • CLI will let you know of an update straight away, not after you have finished running your command

Migrate:

  • Ignore Literature retrieve pages for Business Catalyst Sites


1.6.7 - 7th August 2020

Bug Fixes

General

  • Fix instructions within update notifier


1.6.6 - 6th August 2020

Bug Fixes

Pull/Export

  • Fix warnings related to modules folder for some instances


1.6.5 - 16th July 2020

Bug Fixes

Migrate

  • Fix “Last edited” date in Admin for pages that have been migrated via CLI

  • Improve performance by updating the core page conversion library

  • Fixed incorrect download of some Business Catalyst pages


1.6.4 - 29th June 2020

Bug Fixes

Migrate

  • Allow .aspx pages, fixes pagination within Business Catalyst WebApps

  • Slow down the concurrency of downloads to improve stability

  • Fix “type error” log that sometimes appeared

  • Added a user agent to help with any potential site blocks

  • Fixed "Undefined error" that sometimes appeared

Deploy

  • Fixed error logging when a deploy fails because of invalid syntax

Misc

GraphQL

  • Update dependencies


1.6.3 - 22nd June 2020

Features

Pull/Export/Sync/Deploy

  • Support for Modules folder

Bug Fixes

Migrate

  • Fix migrated forms not working correctly


1.6.2 - 15th June 2020

Bug Fixes

Migrate

  • Fixed an out of memory error when trying to migrate large sites

Deploy

  • Improved error messaging if the codebase of a site is larger than the allowed limit


1.6.1 - 4th June 2020

Features

Migrate

  • Add a certification command to migration

Help

  • The --help flag on all commands will now show the correct usage and a brief description of the command

Bug Fixes

Migrate

  • While downloading a site, the CLI will now ignore any files that return a response header outside of the 2xx range.

  • Links to asset files that had the word assets within the URL will now be linked correctly

  • Home page will be set visually within Siteglide Admin

  • Fix form detection/conversion for certain sites

  • Improved logging during migration

  • Upgraded core packages for image compression


1.6.0 - 27th May 2020

Features

Migrate

  • A brand new tool to migrate your existing websites from anywhere on the web. After adding your instance simply run siteglide-cli migrate <env> --url <existing site>. For more information see

Deploy

  • Deploy to S3: When deploying assets using the -w flag, assets will now get directly sent to S3, improving the performance of the deploy command. This also means that your assets can be up to 5gb in size, up from the existing 50mb limit.

Bug Fixes

Sync

  • The “Enabled Sync” message has been updated to be clearer

Pull

  • Fixed a warning related to modules that sometimes was shown

  • Improved speed and reliability of assets

Deploy

  • The deploying logs have been updated to be clearer

Misc

  • Support for upcoming version of NodeJS


1.5.3 - 13th May 2020

Features

Sync

  • Supports syncing of video files with extensions of .mp4, .ogg and .webm

Bug Fixes

Sync

  • Improve error messaging for files that are not allowed


1.5.2 - 9th May 2020

Misc

  • Support for upcoming v14 of NodeJS


1.5.1 - 23rd April 2020

Bug Fixes

Pull

  • Fix certain files not pulling correctly

Export

  • Fix certain files not exporting correctly

  • Improve error messaging


1.5.0 - 22nd April 2020

Features

GraphiQL

  • Add “Explorer”! This new functionality will allow you to explore and build your Graph Queries using a simple dropdown and checkbox method

  • Add an --open (shorthand: -o) flag that will automatically open a window to the GraphiQL editor in your default web browser

Sync

  • Sync delete: If you have sync running and delete a file locally on your computer, it will also remove that file from your website.

  • Sync Direct to S3: Add an optional flag of --direct-assets-upload (shorthand -d) that syncs asset files directly from your machine to your sites Asset URL which improves speed of syncing. This will become default in the future

Export

  • Remove the limit when downloading assets via export --with-assets

Logs

  • You can now filter the type of logs you would like to see with the flag --filter (shorthand: -f). This is useful if you would only like to see error message from your liquid logs

  • More context data will now log under each line showing information such as the path the error occurred on, the users email address if they were logged in and the Liquid partial the error originated from.

  • Add a --quiet (shorthand: -q) flag which will suppress the above extra context data

Bug Fixes

Sync

  • When using a pre-processor (e.g. Sass), there were cases where the CLI would sync the outputted CSS file before it had finished being compiled, resulting in an empty CSS file on your site. The Sync command will now wait for the compiled file to finish being written to your local disk before attempting to sync it.

Pull/Export

  • Pull/Export: Improved downloading of code files to improve reliability and also speed

  • Export: Allow shorthand flag of -w that acts the same as --with-assets

Init

  • Improved CLI Init download method


1.4.1 - 26th March 2020

Bug Fixes

  • Export - Excludes some automatically generated Siteglide files from export


1.4.0 - 25th March 2020

Features

  • Export - A new optional flag of --with-assets that will allow export to download non-developer assets (such as: pdfs, images, videos etc). Note: This is currently limited to sites with less than 1,000 total assets. A future update will remove this limit


1.3.0 - 4th February 2020

Features

  • Deploy - If you have made a lot of changes in your codebase, then you can use deploy to re-send all files to your site at once.

  • Export - Export is very similar to Pull, but it will also grab all data.


1.2.2 - 15th November 2019

Features

  • Pull will find and download and Sass (.scss, .sass) and LESS (.less) files


1.2.1 - 2nd October 2019

Bug Fixes

  • Fix a bug that caused unknown command error on new installs of CLI.


1.2.0 - 10th September 2019

Features

  • GraphiQL support! Now with the siteglide-cli graphql <env> command

  • Logs support! Now with the siteglide-cli logs <env> command

  • Version command has been moved to -v and not -V to better align with other tools

Bug Fixes

  • Better error handling when no API Key has been generated in Siteglide Admin Portal (instead of just silently failing)

Misc

  • Minimum version of NodeJS 10 now (up from NodeJS 8)


1.1.2 - 16th July 2019

Misc

  • Update dependencies


1.1.1 - 11th July 2019

Features

  • Error reporting within sync command for incorrect liquid

  • Allow syncing of zip files

Misc

  • Up to 4x increase in syncing speed


1.0.0 - 22nd May 2019

  • Initial release

browser extension
here

Invoices

You can view your previous and upcoming invoices on the Invoices page: