# Welcome to Squidex

Lets get started with Squidex.

## What is Squidex?

Squidex is a content management hub or content management system (CMS). A central place to manage all your content.

For example:

* Dynamic elements for your mobile Apps.
* Blog posts and articles for your website.
* Configuration data for your backend.
* Rich and structured data for your applications.

In contrast to a traditional CMS, Squidex is headless and does not provide the front end. This frees you up to choose a technology of your choice to create a thoroughly awesome user experience!

## How to Get Started?

### I want to get started immediately with Squidex Cloud

Read our guide for new users and learn how to publish your first content with Squidex.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

### I want to install Squidex on my own (virtual) machines

Follow our installation instructions to install Squidex on your own hardware.

{% content-ref url="/pages/-LrLTtjKKtbfCC3IMseD" %}
[Installation Instructions](/id-01-getting-started/installation)
{% endcontent-ref %}

### I want to contribute as a developer or understand the architecture

Check out our contributor guide with instructions how to build, run and extend Squidex.

{% content-ref url="/pages/-LrLUhBmP1DajXZSiFGo" %}
[Contributing and Developing](/id-01-getting-started/contributing-and-developing)
{% endcontent-ref %}


# Squidex Cloud

Quickly get started with Squidex by using Squidex Cloud hosted by us.

We provide an SaaS version hosted by us for anyone who wishes to quickly start using Squidex without the hassle of installation, configuration, maintenance etc. You can get started for free and move along as your needs increase.

To install Squidex yourself click [here](/id-01-getting-started/installation) for installation instructions available for various platforms.&#x20;

## Signing up for Squidex Cloud

1. Start by navigating to <https://cloud.squidex.io.&#x20>;
2. Click **Login to Squidex** (1) and then select **Signup** (2). Use one of the Identity providers such as GitHub, Google or Microsoft to signup to Squidex.&#x20;

   <figure><img src="/files/lJfKcoIy5Eq5Dze2NfVv" alt=""><figcaption><p>Signup to Squidex</p></figcaption></figure>
3. In the following steps you will be prompted to sign in and authorize Squidex with your identity provider. Upon successful signup, you are logged in to Squidex dashboard (you may have to login again, if required).

## Using Squidex Cloud

You can now start with an empty App (click [here](/id-02-documentation/concepts/apps) to read what an App in Squidex is) or select one of the starter Apps that we provide. Starter Apps come with sample schema(s) and some sample content, they help you to quickly get started using Squidex.

In the following example, we will use the starter sample blog to quickly create an App.

1. Click **Starter Sample Blog** (1) to get started.

   <div align="left"><figure><img src="/files/4r2ghs6mwiOQZrbE4GyE" alt=""><figcaption></figcaption></figure></div>
2. Enter a unique **Name** (2) (in Squidex Cloud if an App name is already taken it will ask you to enter a different name) and click **Create** (3).

   <div align="left"><figure><img src="/files/b5ADHfwjl5oQ76JtavO6" alt=""><figcaption></figcaption></figure></div>
3. Click on the newly create App (4) to access it.

   <figure><img src="/files/SCiZ0zH3toZmOZidJZjt" alt=""><figcaption></figcaption></figure>
4. You can now explore the pre-populated schemas and content and make modifications / additions to them. For a better understanding we highly recommend looking at the [documentation](/id-02-documentation/concepts).
5. At this point the App is ready to be used with a frontend of your choice. We have a few quick start guides that work with the blog sample which you can explore here:
   * [Vue.js blog with Squidex CMS](/id-01-getting-started/quick-start-guides/vue.js-blog-with-squidex)
   * [React.js blog with Squidex CMS](/id-01-getting-started/quick-start-guides/react.js-blog-with-squidex)
   * [Angular blog with Squidex CMS](/id-01-getting-started/quick-start-guides/angular-blog-with-squidex)


# Installation Instructions

## Choose your Target Platform

If you want to install Squidex on a bare metal server or virtual machine.

* [Install with IIS under Windows](/id-01-getting-started/installation/platforms/install-on-iis)
* [Install with Docker (Linux, OS X, Windows)](/id-01-getting-started/installation/platforms/install-on-docker)

## Choose your Cloud provider

If you want to install Squidex in the Cloud.

* [Install on Azure App Service](/id-01-getting-started/installation/platforms/install-on-azure)
* [Install as Azure Containers (ARM)](/id-01-getting-started/installation/platforms/install-on-azure-arm)
* [Install on AWS](/id-01-getting-started/installation/platforms/install-on-aws)
* [Install on Google Cloud Platform (GCP)](https://github.com/Squidex/squidex-docs2/blob/master/01-getting-started/installation/broken-reference/README.md)
* [Install on Heroku](/id-01-getting-started/installation/platforms/install-on-heroku)
* [Install on Render](/id-01-getting-started/installation/platforms/install-on-render)
* [Install on VULTR](/id-01-getting-started/installation/platforms/install-on-vultr)

## How to Configure Squidex

For all installations.

* [Configuration Options](/id-01-getting-started/installation/configuration)

## Optional: Configure Squidex.Identity

To provide user authentication for your end users.

* [How to use Squidex Identity](https://github.com/Squidex/squidex-docs2/tree/8d35c365b74878aa247c2d0b90867aceea77df1b/01-getting-started/identity/use-squidex-identity.md)


# Platforms


# Install on Kubernetes

Install Squidex on a Kubernetes cluster

## Supported Platforms

* Kubernetes 1.23+

## Prerequisites

* Kubernetes cluster
* An [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/) such as [NGINX](https://www.nginx.com/products/nginx-ingress-controller/) deployed in the cluster
* [cert-manager](https://cert-manager.io/v0.14-docs/installation/kubernetes/) for auto SSL of custom domain
* A custom domain for use during Squidex deployment

## Use the Helm Chart

We provide a Helm chart that deploys Squidex along with MongoDB.

The helm chart creates the following resources / objects:

* Deployments
  * A Squidex primary deployment
  * And a Squidex worker deployment for background jobs&#x20;
* Statefulsets
  * A MongoDB statefulset with 3 replicas&#x20;
* PVs & PVCs
  * Persistent volumes for MongoDB
* Services
* Ingress

The Github link below contains all the details:

> <https://github.com/Squidex/squidex/tree/master/helm>

### 1. Connect to the Kubernetes Cluster

Use *kubeconfig* to connect to your Kubernetes cluster and ensure you are able to run `kubectl` commands.

### 2. Add the Repository

Here *squidex* is the name used for the repository.&#x20;

```
helm repo add squidex https://squidex.github.io/squidex/helm/
```

### 3. Install the Chart

The below command installs version 7 of Squidex. &#x20;

```
helm install squidex squidex/squidex7 --set env.URLS__BASEURL=https://squidex.your.domain --set ingress.hostName=squidex.your.domain
```

* Here `squidex/squidex7` means we are installing version 7 of Squidex.
* Replace ~~squidex.your.domain~~ with your custom domain name.

### 4. Wait for the rollout

It may take a few minutes before the rollout is successful. Run the below command to check the status:

```
kubectl rollout status deployments
```

Below is a sample screenshot of a successful rollout

<figure><img src="/files/ftR3qNjnC0PCSg5onpJb" alt=""><figcaption><p>Successful rollout of Squidex deployments</p></figcaption></figure>

### 5. Verify resources/objects

You can verify / see all the objects created by running:

```
kubectl get all
```

<figure><img src="/files/myY7bpAr3RBGdgUTUAKa" alt=""><figcaption><p>Screenshot of all objects deployment by the helm chart</p></figcaption></figure>

### 6. Access Squidex

Open the custom URL address on a browser to continue with Squidex setup.

## Troubleshooting

To troubleshoot, check deployment logs of the respective deployment. You can also check the pod logs or use the tools that are provided by your hosting providers to view the log stream.

```bash
kubectl logs deployment/squidex-squidex7
```

### 404 Error on Accessing URL

If you receive a 404 Error, It's mostly an ingress issue. Check the ingress class name for your ingress controller deployment.

This helm chart uses the *ingressClassName* as **nginx**.

### Before v7.0.0: Warning for ServerGC

Don't worry if you see the following log entry:

> info: Orleans.Runtime.Silo\[100404]\
> Silo starting with GC settings: ServerGC=False GCLatencyMode=Interactive\
> warn: Orleans.Runtime.Silo\[100405]\
> Note: Silo not running with ServerGC turned on - recommend checking app config : --\
> warn: Orleans.Runtime.Silo\[100405]\
> Note: ServerGC only kicks in on multi-core systems (settings enabling ServerGC have no effect on single-core machines).

This is not a critical warning. ServerGC is a special Garbage Collector as it has no positive or negative impact when running with a single core. You can just ignore it. This log entry is generated by Microsoft Orleans, a framework which has been removed with v7.0.0 and is there not relevant anymore.

**Solution**: Request more than 1 CPU&#x20;

```
resources:
  requests:
    cpu: 2
```

### More Issues?

For other issues, it is likely that you have a configuration problem not related to hosting under Kubernetes. Checkout the following documentation:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}


# Install on AWS (Deprecated)

Install on AWS

{% hint style="danger" %}
The following AWS Marketplace Image is no longer available. We are working on updating the instructions.
{% endhint %}

## 1. Use the AWS Marketplace Image

Squidex is available in the AWS Marketplace as a listing. To begin, click the **Deploy to AWS** button below.

[![Deploy on AWS](https://img.shields.io/badge/-Deploy%20to%20AWS-232F3E?style=for-the-badge\&logo=amazon-aws\&logoColor=ffffff)](https://aws.amazon.com/marketplace/pp/prodview-zvohj6i2bye7w)

## 2. Subscribe

Squidex is free, but you still need to subscribe to the Amazon Marketplace listing.

In the listing page, click **Continue to Subscribe** (1).

<div align="left"><figure><img src="/files/L47Q5unLrOWYc2MSNHy8" alt=""><figcaption><p>Subscribe to AWS marketplace listing</p></figcaption></figure></div>

Read the EULA and accept the terms by clicking **Accept Terms** (2).

<div align="left"><figure><img src="/files/LkYXX3dihyUuubvyEX8M" alt=""><figcaption><p>Accept terms to subscribe to the listing</p></figcaption></figure></div>

Wait until AWS processes your subscription (3).

<div align="left"><figure><img src="/files/TCZK9TChdNHYF355xu6j" alt=""><figcaption><p>Subscription being processed by AWS</p></figcaption></figure></div>

Once that is ready, click **Continue to Configuration** (1).

<div align="left"><figure><img src="/files/3xwKwk7knqnSXF8YWBYp" alt=""><figcaption><p>Continue to Configuration</p></figcaption></figure></div>

## 3. Instance Configuration

Pick a **Delivery Method** (1), a **Software Version** (2) and a **Region** (3), then click **Continue to Launch** (4).

<div align="left"><figure><img src="/files/JRJV7fIsjSUZx8zcne6D" alt=""><figcaption><p>Choose a software configuration</p></figcaption></figure></div>

This will take you to the configuration of the EC2 instance to be launched.

Pick the **EC2 Instance Type** (5). We recommend at least a **m5.large**.

<div align="left"><figure><img src="/files/B8pxPAtX5Rk5qdmvmyrM" alt=""><figcaption><p>Choose an instance type</p></figcaption></figure></div>

Scroll down and select a **VPC** (6) and **Subnet** (7) from the pre-populated list. Next, select a **Security Group** (8) or click **Create New Based On Seller Settings** to automatically create one based on Squidex requirements.&#x20;

<figure><img src="/files/m27aRdmLAuppkwP5oqO4" alt=""><figcaption><p>Choose network configuration and security group</p></figcaption></figure>

Finally, select a **Key Pair** (9) and click **Launch** (10).&#x20;

{% hint style="info" %}
You must have an AWS Key Pair and selected here, otherwise you will not be able to login to the EC2 instance later.
{% endhint %}

<div align="left"><figure><img src="/files/hOjeya74nNKEfZWlBGwn" alt=""><figcaption><p>Select key pair and launch</p></figcaption></figure></div>

Once the instance is launched, go to the **EC2 Console** (11).

<figure><img src="/files/yXim4UQHNopWQqRJvCp3" alt=""><figcaption><p>EC2 instance successfully deployed</p></figcaption></figure>

## 4. Connect

Once the instance is ready, take its **Public IPv4** (1) and connect to it using SSH and the key pair you selected previously.

<figure><img src="/files/3tOfhQ7JgPQHBzcq6UVk" alt=""><figcaption><p>Retrieve Public IP</p></figcaption></figure>

```bash
ssh -i path/to/keypair.pem ubuntu@[PUBLIC IP]
```

Once you are logged in, run the setup command and follow the on-screen instructions.

```bash
/opt/squidex/setup-squidex.sh
```

You will be asked to enter the domain here. All other values are optional. The installation script will start *docker-compose,* download all images and start them in the right order.

After you make sure the domain you configured is pointing to the public IP of your instance and that the DNS changes propagated, go to <https://your_domain.com> and you should be ready to go.

For further help, go to [Configuration](https://docs.squidex.io/01-getting-started/installation/configuration).

## Troubleshooting

For other issues, it is likely that you have a configuration problem not related to hosting under Docker. Checkout the following documentation:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}


# Install on AWS EC2 using Docker

Learn how to deploy Squidex on an AWS EC2 instance using Docker containers

The instructions provided here help you get Squidex running on an AWS EC2 instance using Docker containers. It uses an EC2 instance running Docker and deploys Squidex along with NGINX for reverse proxy and MongoDB as the database. The data is stored outside of the containers for persistence but on the same instance (single volume).&#x20;

This tutorial does not cover the basics of AWS. You should be familiar with them before you begin with the instructions. We have attempted to keep them simple enough so they can be followed by anyone.

This tutorial runs all containers i.e. Squidex, MongoDB and NGINX in a single EC2 instance and may not be recommended for production environments.&#x20;

## Requirements

Before you start ensure you have:

1. An active AWS account.&#x20;
2. A domain name to be used with Squidex.

## 1. Launch an EC2 Instance

To launch an instance, login to AWS Console and select the desired **region** (1) from the drop down on the right. For this demonstration *us-east-1* has been used.

<figure><img src="/files/t2aEb6JHgWRzBtpFGEuh" alt=""><figcaption><p>Login to AWS and select a Region</p></figcaption></figure>

Navigate to the EC2 Dashboard at <https://us-east-1.console.aws.amazon.com/ec2/home>.

Click **Launch instance** (2) to begin.

{% hint style="info" %}
This demonstration uses a lot of the default values and is just a quick way to get an EC2 instance running. One can always customize the network, storage and other parameters which is beyond the scope of this guide.&#x20;
{% endhint %}

<figure><img src="/files/1KJUy9GLjFWtS7BoibBq" alt=""><figcaption><p>Launch an instance</p></figcaption></figure>

Start by giving a name for the **EC2 instance** (3).&#x20;

<figure><img src="/files/DWdbPXtpURPKM7bdyqb7" alt=""><figcaption><p>Name the EC2 instance</p></figcaption></figure>

Next, select **Ubuntu** (4) as the AMI.

<figure><img src="/files/xkIubHvCCh5jcgLEOkKV" alt=""><figcaption><p>Select an AMI</p></figcaption></figure>

Select an **Instance type** (5). We recommend at least the **m5.large**. But for sandbox/experimental requirements you can choose a lower configuration.&#x20;

<figure><img src="/files/QDpRI1pXzBjpkukq7muV" alt=""><figcaption><p>Select an Instance type</p></figcaption></figure>

Select a **Key pair** (6) to be able to connect to the EC2 instance later. &#x20;

{% hint style="info" %}
You must have an AWS Key Pair and select here, otherwise you will not be able to login to the EC2 instance later. If required, click **Create new key pair** to create one and then select it from the list.
{% endhint %}

<figure><img src="/files/HDBvadR2KXN85ts9afcV" alt=""><figcaption><p>Select a Key pair</p></figcaption></figure>

For Network settings ensure **Allow HTTP** and **Allow HTTPS** (7) traffic is checked. For this demonstration we will leave the rest to their defaults.

<figure><img src="/files/CKEavravaRCfvP6HtbjE" alt=""><figcaption><p>Select Network settings</p></figcaption></figure>

Similarly, for this guide we will not make any changes to storage but one can choose a larger disk size based on requirements. Click **Launch instance** (8) when ready.

<figure><img src="/files/8x1FykF9nRA5vNHWhJwu" alt=""><figcaption><p>Configure Storage and Launch</p></figcaption></figure>

This should initiate the launch the EC2 instance.  Click on the instance ID (9) to navigate to the instance details.

<figure><img src="/files/j2TOhukk3yr0k7LFtIpP" alt=""><figcaption><p>EC2 instance successfully initiated</p></figcaption></figure>

## 2. Configure DNS

Once the instance is ready, copy its **Public IPv4** (1) address and configure the DNS mapping i.e ensure that the domain you wish to use with Squidex is pointing to this IP. This typically involves creating an A record to the domain/subdomain you wish to use for Squidex. It is recommended to wait for the DNS changes to propagate before proceeding further.&#x20;

The mapping is necessary for successful deployment of Squidex and is also used to request SSL certificates.

<figure><img src="/files/1k5VTOGsKuWTx0JuZxqW" alt=""><figcaption><p>Retrieve Public IP</p></figcaption></figure>

## 3. Connect and Install Docker

Next, connect to the EC2 instance using SSH. Use the key pair selected during instance launch.

```bash
ssh -i path/to/keypair.pem ubuntu@[PUBLIC IPv4]
```

{% hint style="info" %}
*ubuntu* is the default username for Ubuntu AMI.
{% endhint %}

At this point the instance does not have Docker. We will go ahead and install it by running the following commands:&#x20;

```
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

# Add current user to docker group
sudo usermod -aG docker $USER
```

Next, logout and login back to the EC2 instance and verify that Docker commands run without the need for *sudo*.&#x20;

{% hint style="danger" %}
It is important to logout and login to apply the changes made earlier where the current user was added to docker group.
{% endhint %}

```
docker --version
```

## 4. Deploy Squidex

Once Docker is installed, the instructions to run Squidex using Docker containers is the same as [Install on Docker](/id-01-getting-started/installation/platforms/install-on-docker).

We provide multiple Docker Compose files to quickly get started with Squidex. They can be downloaded from <https://github.com/Squidex/squidex-hosting/tree/master/docker-compose>.

In the demonstration below we will run Squidex with NGINX and MongoDB using the `docker-compose-nginx.yml` file. Start by downloading the required files.

```bash
// Download the yml file
wget https://raw.githubusercontent.com/Squidex/squidex-hosting/master/docker-compose/docker-compose-nginx.yml

// Download the environment file
wget https://raw.githubusercontent.com/Squidex/squidex-hosting/master/docker-compose/.env
```

Edit the `.env` file and set the following variables:

<table><thead><tr><th width="294.5">Variable</th><th>Description</th></tr></thead><tbody><tr><td><code>SQUIDEX_DOMAIN</code></td><td>Your domain name, that you want to use for your installation. </td></tr><tr><td><code>SQUIDEX_ADMINEMAIL</code></td><td>The email address of the admin user. You can leave it empty to create a new user with the setup page when you visit your Squidex installation in the browser.</td></tr><tr><td><code>SQUIDEX_ADMINPASSWORD</code></td><td>The password of the admin user. Must contain a lowercase and uppercase letter, a number and a special character. You can leave it empty to create a new user with the setup page when you visit your Squid</td></tr></tbody></table>

The rest variables can be empty for now.

The data files, such as assets and the MongoDB database files will be stored, outside of the Docker container for persistence and to simplify backups. The default path `/etc/squidex` will be created by Docker automatically.

Run the docker-compose file with the following command:

```
docker compose -f docker-compose-nginx.yml up -d
```

{% hint style="info" %}
Using `-f docker-compose-nginx.yml` (or providing absolute path of the yaml file) is mandatory else the command will fail.&#x20;
{% endhint %}

Docker compose will download all images and start them in the right order.

To access Squidex, navigate to the domain name used in this installation, for example <https://your_domain.com> and you are ready to go.

For further help, go to [Configuration](https://docs.squidex.io/01-getting-started/installation/configuration).


# Install on Azure

Learn how to setup Squidex on Azure WebApp with MongoDB on Azure Container Instance.

The instructions provided help you to get your Squidex installation running on an Azure WebApp with the assets & MongoDB data hosted on Azure Storage and MongoDB running on an Azure Container Instance.

{% hint style="info" %}
Please note, that Azure also supports Docker Compose files so you can follow the Docker tutorial, especially if it is important for you to be independent from your Cloud provider (avoid vendor lock-in).
{% endhint %}

This tutorial does not cover the basics of Azure. You should be familiar with them before you begin with the installation instructions. We have attempted to keep the instructions simple enough so they can be followed by anyone.

This tutorial demonstrates running MongoDB as a single container and is recommended for non-production or trial environments. For production environments, it is best to use one of the MongoDB Atlas (or Enterprise) offerings from [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps?search=mongodb\&page=1) with at least 3 members for High Availability etc.

## Requirements

Before you start you have to setup a few things first:

1. An active Azure subscription
2. A Resource Group for all your Squidex resources.
3. An App Service plan (Linux) to host Squidex.
4. A *Standard (General Purpose v2)* Storage Account for assets and MongoDB.
5. An installation of the [Azure-CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) on your developer machine (optional).
6. An installation of a MongoDB tool like [MongoDB Compass](https://www.mongodb.com/try/download/compass2) on your developer machine (optional).
7. The [Microsoft.ContainerInstance](https://azure.microsoft.com/en-gb/services/container-instances/) provider registered in your Azure subscription.

## 1. Setup your Storage Account

Execute the following steps in the storage account.

{% hint style="info" %}
You must have a storage account before continuing. The steps do not cover the process of storage account creation.
{% endhint %}

1. Click **Containers** (1) under *Data Storage* and then click **+ Container** (2) to load the *New container blade*.<br>

   <figure><img src="/files/LpMTVTSu9F2AONlW85ce" alt=""><figcaption></figcaption></figure>
2. Create a container named `etc-squidex-assets`(3). Do not make any other modifications, click **Create** (4).<br>

   <div align="left"><figure><img src="/files/rNchFdQUR3uv92HZFSsm" alt=""><figcaption></figcaption></figure></div>
3. Similarly, select **File shares** (5), click **+ File share** (6) and create a file share named `etc-squidex-mongodb` (7). Click **Create** (8).<br>

   <figure><img src="/files/2JNf8AkmGINGtaF1S3KD" alt=""><figcaption></figcaption></figure>
4. Next, note down the storage account *Connection String* and one of the *Access Keys* for use in the subsequent steps.\
   \
   To do so, go to **Access Keys** (9) and click **Show** (10) next to *Key* in key1. Copy this Access Key safely somewhere. Similarly click **Show** (11) next to *Connection string* and copy it.<br>

   <figure><img src="/files/xBnO1ejDzpjSht4aBmeU" alt=""><figcaption></figcaption></figure>

## 2. Create the MongoDB Instance

To create the MongoDB Azure Container Instance we will use the Azure CLI. Using the browser based Azure Shell is the easiest and quickest way to run the command.&#x20;

<figure><img src="/files/zLmF5nsxovmEmmHFJyXk" alt=""><figcaption></figcaption></figure>

You can also execute them through an installation of Azure CLI on your developer machine. If you are using your own installation of Azure CLI, on the command prompt run `az login` first to login to Azure (this step is not required if you are using Azure Shell).

{% hint style="info" %}
The following creation of the container instance can only be done using the Azure CLI at the moment.
{% endhint %}

Run the following command to create the MongoDB container instance. You will need the following values noted down from earlier:

* \[YOUR\_RESOURCE\_GROUP]
* \[YOUR\_STORAGE\_ACCOUNT]
* \[YOUR\_STORAGE\_KEY]

```bash
az container create --resource-group [YOUR_RESOURCE_GROUP] --name mongodb --image mongo --azure-file-volume-account-name [YOUR_STORAGE_ACCOUNT] --azure-file-volume-account-key "[YOUR_STORAGE_KEY]" --azure-file-volume-share-name etc-squidex-mongodb --azure-file-volume-mount-path "/data/mongoaz" --ports 27017 --cpu 2 --ip-address public --memory 2 --os-type Linux --protocol TCP --command-line "mongod --dbpath=/data/mongoaz --bind_ip_all --auth"
```

This creates a single container instance running MongoDB.

### 2.1 Create an Admin User

At this point the MongoDB instance is running but requires authentication. But we do not have any users, so we will go ahead and create a user for use with the database.&#x20;

Connect to your MongoDB container console from Azure:

1. Go to **Container instances** (1) and select your container, usually **mongodb** (2).
2. Click **Containers** (3) and then select the **Connect** tab (4).
3. Select **/bin/bash** and click **Connect** (5) button.

<figure><img src="/files/WUn4kdT5iFf6Nzmu0Jlt" alt=""><figcaption></figcaption></figure>

Execute the following steps inside the container to create a MongoDB user.

{% hint style="info" %}
For security reasons provide your own choice of username and password
{% endhint %}

```
// Switch to Mongo shell
mongosh

// Switch to admin db
use admin

// Create user
db.createUser({ "user": "root", "pwd": "1q2w3e$R", "roles": ["root"] })
```

Now that the user is created, you can optionally connect to it from a tool like [MongoDB Compass](https://www.mongodb.com/try/download/compass2) and verify connectivity. To connect, you will need the Public IP address of the MongoDB instance. To do so, go to **Container instances** (1), select your **container** (2) and from **Overview** (3) copy the **Public IP address** (4). Make a note of it as it will be needed again in future steps.

<figure><img src="/files/Xyp2fFfPzVId6Bddd4FI" alt=""><figcaption></figcaption></figure>

To connect to it from MongoDB Compass, create a connection string that contains the username and password similar to the example below.

`mongodb://root:1q2w3e$R@[IP_ADDRESS]`

## 3. Create & Configure the Web App

### 3.1 Create Web App

1. Start by creating a new Web App with the following settings. The wizard also lets you create a new App Service Plan (think Hosting Plan) if there isn't any.​

   1. Select the existing **Resource Group** (1) from previous steps
   2. Enter a **Web App Name** for the FQDN provided by Azure Web App.

      <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>This Name should be globally unique.</p></div>
   3. Select **Docker Container** (3)
   4. Choose **Linux** (4) for the Operating System
   5. Set your preferred **Region** (5), preferably the same region as other resources
   6. The default *Sku and size* selected is ideal for production environments and costs more. If this is a sandbox, trial or a non-production environment it is best to change this to a lower Sku. Click **Change size** (6) to do so and change it to *F1* or *B1*.
   7. Click **Next: Docker** to continue with the next steps.<br>

   <div align="left"><figure><img src="/files/zT0QegGJmw8Lu89DgASr" alt=""><figcaption></figcaption></figure></div>

   <figure><img src="/files/uSsB5x0D27QWXEVCvYT5" alt=""><figcaption></figcaption></figure>
2. In the *Docker* settings tab, configure as following:

   1. Set *Options* as **Single Container** (8)
   2. Set *Image Source* as **Docker Hub** (9)
   3. Set *Access Type* to **Public** (10)
   4. Enter *Image and tag value* as `squidex/squidex:latest`
   5. Click **Review + create** (12).

   <figure><img src="/files/iyjtTApet6mQlZGG1XqD" alt=""><figcaption></figcaption></figure>
3. In the Review page, click **Create**.
4. Once deployment is complete, click **Go to resource** (14) and continue with the next section on configuring the environment variables.

   <figure><img src="/files/5TBX1ZUzJBiSXN0WqrMJ" alt=""><figcaption></figcaption></figure>

### 3.2 Configure Web App

During this step, we will add the configuration values to the Web App and restart the Web App. Before proceeding ensure you have the following info handy, you will need it:

<table><thead><tr><th width="306.3333333333333">Key</th><th>Description</th><th>Sample</th></tr></thead><tbody><tr><td><code>[AZURE_CONNECTIONSTRING]</code></td><td>The connection string to your storage account. See step 1.</td><td></td></tr><tr><td><code>[MONGO_USERNAME]</code></td><td>The username of the MongoDB user.</td><td>For example in this instruction: "root"</td></tr><tr><td><code>[MONGO_PASSWORD]</code></td><td>The password of the MongoDB user.</td><td>For example in this instruction: "1q2w3e$R"</td></tr><tr><td><code>[MONGO_IP]</code></td><td>The IP address to your MongoDB container.</td><td>See Step 2.1</td></tr><tr><td><code>[WEBAPP_NAME]</code></td><td>The name of your webapp. </td><td>For example in this instruction "squidexio"</td></tr></tbody></table>

1. From the previous step you should already be in the Web App page. Click **Configuration** (1) and then **Advanced edit** (2).

   <div align="left"><figure><img src="/files/oZYLKdYLUBtCGshzOlZP" alt=""><figcaption></figcaption></figure></div>
2. Copy and paste the following JSON into the code window (**DO NOT click OK.**)

   ```javascript
   [
     {
       "name": "ASSETSTORE__AZUREBLOB__CONNECTIONSTRING",
       "value": "[AZURE_CONNECTIONSTRING]",
       "slotSetting": false
     },
     {
       "name": "ASSETSTORE__AZUREBLOB__CONTAINERNAME",
       "value": "etc-squidex-assets",
       "slotSetting": false
     },
     {
       "name": "ASSETSTORE__TYPE",
       "value": "AzureBlob",
       "slotSetting": false
     },
     {
       "name": "DOCKER_REGISTRY_SERVER_PASSWORD",
       "value": "",
       "slotSetting": false
     },
     {
       "name": "DOCKER_REGISTRY_SERVER_URL",
       "value": "https://index.docker.io",
       "slotSetting": false
     },
     {
       "name": "DOCKER_REGISTRY_SERVER_USERNAME",
       "value": "",
       "slotSetting": false
     },
     {
       "name": "EVENTSTORE__MONGODB__CONFIGURATION",
       "value": "mongodb://[MONGO_USER]:[MONGO_PASSWORD]@[MONGO_IP]:27017",
       "slotSetting": false
     },
     {
       "name": "IDENTITY__GITHUBCLIENT",
       "value": "",
       "slotSetting": false
     },
     {
       "name": "IDENTITY__GITHUBSECRET",
       "value": "",
       "slotSetting": false
     },
     {
       "name": "IDENTITY__GOOGLECLIENT",
       "value": "",
       "slotSetting": false
     },
     {
       "name": "IDENTITY__GOOGLESECRET",
       "value": "",
       "slotSetting": false
     },
     {
       "name": "IDENTITY__MICROSOFTCLIENT",
       "value": "",
       "slotSetting": false
     },
     {
       "name": "IDENTITY__MICROSOFTSECRET",
       "value": "",
       "slotSetting": false
     },
     {
       "name": "STORE__MONGODB__CONFIGURATION",
       "value": "mongodb://[MONGO_USER]:[MONGO_PASSWORD]@[MONGO_IP]:27017",
       "slotSetting": false
     },
     {
       "name": "URLS__BASEURL",
       "value": "https://[WEBAPP NAME].azurewebsites.net/",
       "slotSetting": false
     },
     {
       "name": "VIRTUAL_HOST",
       "value": "[WEBAPP NAME].azurewebsites.net",
       "slotSetting": false
     },
     {
       "name": "WEBSITE_HTTPLOGGING_RETENTION_DAYS",
       "value": "10",
       "slotSetting": false
     }
   ]
   ```
3. Replace all placeholders with your values (without additional quotes). It should look similar to the screenshot below. Click **OK** (3) when done.\
   Configuration values for external authentication providers are empty to turn them off.

   <div align="left"><figure><img src="/files/0XUoc5BtwStMJ4YKy7ir" alt=""><figcaption></figcaption></figure></div>
4. Click **Save** (4).

   <div align="left"><figure><img src="/files/YcoQ3ntPJobiuAGZ08sP" alt=""><figcaption></figcaption></figure></div>
5. Click **Overview** (5) and then click **Restart** (6). Click **Yes** (7) when prompted.<br>

   <figure><img src="/files/al7COLyQp3E0iE647MFz" alt=""><figcaption></figcaption></figure>
6. You can now access Squidex at the App Service URL and continue with setting up Squidex.

{% hint style="info" %}
**IMPORTANT:** It may take a few minutes before Squidex welcome page loads due to background configurations and setup processes by Azure.&#x20;

If the restart in Step 5 above does not work, stop and start the Web App.
{% endhint %}

### 3.3 Turn on Logging

While this is an optional step, logging can make diagnostics easier.

{% hint style="info" %}
You can then use the *Log stream* to view all log entries
{% endhint %}

1. Next from the left menu scroll down (or search) and click on **App Service logs** (1). Enable Application logging by clicking on **File System** (2) and click **Save** (3).<br>

   <div align="left"><figure><img src="/files/PpLeujveQOaAIKZw0IzJ" alt=""><figcaption></figcaption></figure></div>

## More issues?

For other issues, it is likely that you have a configuration problem not related to hosting under Azure. Checkout the following documentation:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}


# Install on Azure using ARM

This version of the installation tutorial deploys Squidex along with MongoDB (for datastore) and Caddy (for reverse proxy along with SSL) as a [Container Group](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-container-groups) in Azure using an [Azure Resource Manager or ARM](https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/overview) template. The approach is called *Infrastructure as Code*.

The tutorial does not cover the basics of Azure. You should be familiar with them before you begin with the installation instructions.

This tutorial runs MongoDB as a single container and is recommended for non-production or trial environments. For production environments, it is best to use one of the MongoDB Atlas (or Enterprise) offerings from [Azure marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps?search=mongodb\&page=1), which provides capabilities such as High Availability.

Use this tutorial as a getting started guide in Azure to explore Squidex and use it as a Dev/Sandbox environment. This setup may not be recommended for a production environment.

## Prerequisites

Before you begin, ensure you meet the following requirements:

1. An active Azure subscription.
2. The following providers registered in your Azure subscription:
   * Microsoft.ContainerInstance
   * Microsoft.Storage

## Deployment Specifications & Other Details

The ARM template creates the following Azure resources:

* An Azure Storage Account
  * Azure Container
  * Azure Share
* Azure Container Instances (Group)
  * Squidex Web App Container (latest image)
  * MongoDB Container (latest image)
  * Caddy container (latest image)

**Important Details**

* Default resource allocations
  * Squidex container: **1 vCPU and 1.5 GiB memory**
  * MongoDB container: **2 vCPU and 2 GiB memory**
  * Caddy container: **1 vCPU and 1.5 GiB memory**
* Squidex and MongoDB containers remain private and are not exposed to public.
* Squidex talks to MongoDB without authentication (mongodb://localhost:27017). Since MongoDB is private it cannot be accessed from outside, hence secure to an extent. **With the current setup you cannot connect using an external client like MongoDB Compass. You can however connect from Azure Container Instance, click** [**here**](#connecting-to-mongodb-instance) **for instructions**
* Caddy reverse proxies to the Squidex Web App container.
* An FQDN is provided by Azure Container Instance, *Caddy* provides SSL for this as the FQDN does not come with SSL. This FQDN is set as the `URLS__BASEURL` and `VIRTUAL_HOST` for Squidex configuration.

{% hint style="info" %}
NOTE: If you wish to modify any of the variables such as vCPU, Memory etc., you can do so by editing the ARM template. Click \[here]\(#modifying-arm-variables) for basic instructions.
{% endhint %}

## 1. Launch the ARM Template

Simply click **Deploy to Azure** button to load the template in Azure Portal, fill in a few details (instructions below) and deploy Squidex.

[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fsangramrath%2Fsquidex-docs2%2Fmaster%2Fscripts%2Fsquidex-minimal-azure-arm.json)

## 2. Configure Details

On the Custom deployment page complete the following steps to submit the deployment.

1. Select your **Subscription** from the list.
2. Select an existing **Resource group** or click **Create new** to create a new Resource group.
   * If creating a new Resource group, enter a name and click Ok.
3. Select your preferred **Region** from the list.
4. Enter a name for the Container Group in **Container Name** field. (It uses the Resource Group name by default but you are free to change it)
5. If you wish to provide your own Domain/Sub-domain (e.g. example.com or squidex.example.com) enter it in the **Squidex Domain** field, otherwise leave it blank.
6. Click **Review + create**.

![Deploy ARM Template](/files/invioF66tOUlmcLtcF83)

1. Ensure that the *validation* is successful.
2. Click **Create** to submit the deployment.

![Review ARM Template](/files/lWIcgyzSXrBbeGtiIajl)

The deployment process will take a few minutes. (Preferably do not navigate away from this page)

## 3. Access Squidex

1. Once the deployment is complete, Click **Outputs** in the deployment page to retrieve the URL.
2. Copy the URL.

![Deployment Complete](/files/qoIFVBmK8CAMqcWGDZqE)

Alternatively, you can also retrieve the FQDN anytime from the Azure Container Instances blade.

{% hint style="info" %}
NOTE: This is only for deployments where custom domain is NOT provided
{% endhint %}

![ACI FQDN](/files/6xa3ys1jhg8j6FOBW2Ve)

## 4. Update DNS Record (For Own Domain Deployments Only)

1. Navigate to Azure Container Instances page, click on the Container instance (i.e. squidex) to access the details.
2. Under Overview, to the right you should find the **IP Address (Public)** value, copy it.

![ACI PublicIP](/files/oGlwXpTFJ1MjZllhc9yr)

1. Now access your domain control panel, and create an **A Record** pointing your domain to this IP address.
2. Continue with the next steps once the DNS propagation is done, this will depend on your domain provider.

{% hint style="info" %}
NOTE: The SSL certificates are not generated (by Caddy) until the DNS resolution works
{% endhint %}

## 5. Completing Squidex Installation

1. Open the URL in a browser to load the *Squidex Installation Page*.

**It may take a few minutes before the certificates are generated and the URL starts working. For own domain scenarios this process may be even take longer**

1. Create the Admin User to complete the installation and login to Squidex.

## Azure Troubleshooting

#### Validation Failed

* Ensure all the fields have been populated.
* If you are rerunning the ARM template (either another installation or due to a previous failure) and using the same names, change them (or delete previously created resources).

#### Deployment failed

In the Microsoft template deployment window click *Deployment failed. Click here for more details* to view the error details and look for clues. This tutorial does not go into the details of Azure troubleshooting.

{% hint style="info" %}
NOTE: Use \_Redeploy\_ to resubmit the deployment again (after fixing errors). This continues the operation instead of creating a fresh deployment.
{% endhint %}

#### Containers in *waiting*, *failed* or *terminated* status.

* Restart the Azure Container Instance

![Deployment Complete](/files/BvjFg95AGjyWqvX0ytno)

## Squidex Issues

It is very likely a configuration problem and not related to Azure. Please visit the following page:

[Configuration](https://github.com/Squidex/squidex-docs2/blob/master/01-getting-started/installation/platforms/configuration.md)

## Connecting to MongoDB Instance

1. On the Azure Portal, navigate to **Container Instances** page
2. Click on your Container Instance (e.g. `squidex-crm`)
3. Select *Containers* from the left navigation menu
4. Select the MongoDB container (i.e. `etc-squidex-assets-mongodb`) by clicking on it
5. Select the **Connect** tab
6. Ensure */bin/bash* is selected
7. Click **Connect** again

![Connect to MongoDB ACI](/files/jLuyLLwNsHc1ZD2oWlZU)

This will drop you into the Container Shell. Type `mongosh` to connect to MongoDB shell.

## Modifying ARM Variables

1. After clicking *Deploy to Azure* button, click **Edit template**.

![Edit Template](/files/mvOpIGpbdig20oGoRoCs)

1. Click **Variables** on left and this should show the variables section on the right.
2. You can now change any of the variables such as `mongoDBCPUCores` or `mongoDBMemory` etc.

![Modifying ARM Variables](/files/HZhDJGnz6zNiBb2M5KYB)


# Install on Google Cloud Platform (GCP)

Learn How to Install Squidex on the Google Cloud Platform

This tutorial deploys Squidex as a container on [Cloud Run](https://cloud.google.com/run/), which is a managed compute platform that lets you run containerized applications.

The tutorial does not cover the basics of GCP. You should be familiar with them before you begin with the installation instructions. This tutorial uses the following services from GCP:

* [Google Cloud Storage](https://cloud.google.com/storage)
* [Google Cloud Run](https://cloud.google.com/run/)
* [MongoDB Atlas](https://www.mongodb.com/cloud/atlas/mongodb-google-cloud) running on Google Cloud

This tutorial used MongoDB Atlas on Cloud running in shared (free) mode and is recommended for non-production, trial or sandboxed environments as it has limited resources and basic configuration. For production environments, it is best to go for one of the Pay-as-you-go models such as Dedicated. You can also deploy your own cluster.

Use this tutorial as a getting started guide in GCP to explore Squidex or use it as a Dev/Sandbox environment. This setup may not be recommended for a production environment as it does meet requirements such as scalability and high-availability.

## Prerequisites

Before you begin, ensure you meet the following requirements:

1. An active Google Cloud account.
2. A *Project* in GCP

## Deployment Specifications & Other Details

Over the course of this installation you will create the following resources:

* A Cloud Storage bucket
* A Cloud Run instance
* A free MongoDB Atlas cluster at <https://cloud.mongodb.com/>

**Important Details**

* Default resource allocations
  * Squidex instance: **1 vCPU and 1.5 GiB memory**

## 1. Create a MongoDB Atlas Cluster

Visit [MongoDB Atlas on Google Cloud](https://www.mongodb.com/cloud/atlas/mongodb-google-cloud) and click **Start Free** (1).

![Create MongoDB Step 1](/files/NZ9ISilWKSrUB9fFfA5K)

Register by clicking **Sign up with Google** (2) and sign-in using your existing Google Cloud credentials. This is the fastest way to register. On successful registration and login you will be redirected to the cloud.mongodb.com portal *Projects* page.

![Create MongoDB Step 2](/files/pdyq4Tx2OzA7Y6AJtK9N)

Start by creating a project. Click **New Project** (3).

![Create MongoDB Step 3](/files/kOgDKwcmXB5936mk9kuq)

Provide a project name such as *Squidex* (4) and click **Next** (5) to continue.

![Create MongoDB Step 4](/files/Cvu5GfmjfcL8JD6mfCMp)

Finish project creation by clicking **Create Project** (6).

![Create MongoDB Step 5](/files/qT3aNsOFdhdq1mSs2Vwv)

The next step is to create the MongoDB database. Click **Build a Database** (7) to start.

![Create MongoDB Step 6](/files/ctKTmWU8sI7QF60VxCr4)

Select **Create** under *Shared* which provides 1 free cluster.

![Create MongoDB Step 7](/files/v3v1gRKPAtBCFmny9lr2)

Ensure again that **Free Shared** (9) option is selected, click **Google Cloud** (10), select a region of choice (11) and click **Create Cluster** (12).

![Create MongoDB Step 8](/files/9zDW1Mdt2z3fM3i52cFV)

In the next step, set Security details. Enter a Username (13) and Password (14) of choice (or click Autogenerate Secure Password to get one generated). Click **Create User** (15).

![Create MongoDB Step 10](/files/ro6XeeeFNaYm2UlK3tZb)

Finally, add network access permissions so Squidex can talk to it. Ensure **My Local Environment** (16) is selected, enter 0.0.0.0/0 (17) for IP Address and any Description (18). Click **Add Entry** (19).

![Create MongoDB Step 11](/files/ILi7ExNSlgf3Oa2TAkVz)

Click **Finish and Close**. This will redirect you to the Database Deployments page similar to the screenshot below.

![Create MongoDB Step 12](/files/oLltGDSQ9Hb2IVvXD0LH)

You will need the connection information for this MongoDB for use in the app configuration later. To copy the connection string click **Connect** (20) and then select **Connect your application** (21).

![Create MongoDB Step 13](/files/M77evrlbxfeEV7Ej9vv1)

Select C#/.NET (22) & 2.13 or later (23), copy the connection string (24) and click **Close** (24)

![Create MongoDB Step 14](/files/J3hxcoQjximEImC8vQE3)

This completes creating the free MongoDB Atlas cluster.

## 2. Upload Squidex image to Artifact Registry

The following steps require Google cloud CLI, which can be installed on your development machine or you can use the Cloud Shell. The later is preferred for convenience. To launch Cloud Shell click  on the google cloud web console next to the search bar.

<figure><img src="/files/7RI8D45hIiH0WWYETzvd" alt=""><figcaption></figcaption></figure>

Run the following commands in sequence to create an Artifact Registry, download the latest Squidex from dockerhub and push it to Artifact Registry.

```
gcloud services enable artifactregistry.googleapis.com
```

```
export ARTIFACT_REGISTRY_NAME=squidex
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
```

```
gcloud artifacts repositories create $ARTIFACT_REGISTRY_NAME --repository-format=docker --location=$REGION
```

```
docker pull squidex/squidex:latest
```

```
gcloud auth configure-docker $REGION-docker.pkg.dev
```

```
docker tag squidex/squidex:latest $REGION-docker.pkg.dev/$PROJECT_ID/$ARTIFACT_REGISTRY_NAME/squidex:latest
```

```
docker push $REGION-docker.pkg.dev/$PROJECT_ID/$ARTIFACT_REGISTRY_NAME/squidex:latest
```

## 3. Create Storage Bucket

Follow the steps below to create a Cloud Storage bucket for Squidex assets.

Sign into Google Cloud Console and navigate to Cloud Storage page. Select **Buckets** (1) (should be already selected) and click **+ Create** (2).

![Create Bucket Step 2](/files/fwbrdBO64vmXyj10gWOf)

Enter a unique name (3) for the bucket and click **Create** (4). You can leave the rest of the settings as it is or feel free to modify them as per needs.

![Create Bucket Step 3](/files/NugPfX1REyZUQP09f2bJ)

You may be prompted with a message. Click **Confirm** (5).

![Create Bucket Step 4](/files/KmexAvV0RoscKej2Rzhj)

You will be taken to the bucket page. This completes the storage bucket creation step.

## 4. Create & Configure Cloud Run instance

Navigate to Cloud Run page in Google Cloud. Click **+ Create Service** (1) to begin.

![Create Cloud Run Step 1](/files/aOX1FRMTrW44pgwt7Ikd)

Click **Select** (2), choose **Artifact Registry** tab and select (3) the Squidex image added earlier. Click **Select** (4) to continue.

![Create Cloud Run Step 2](/files/HnQtT1HVvLaLMRelaHTB)

A Service name (5) is already assigned, modify this if needed. Set **Minimum number of instance** to *1* and **Maximum number of instance** to *1* (6).

{% hint style="info" %}
At this point Squidex has not been tested for autoscaling on Cloud Run, setting more than 1 instances here may have issues.
{% endhint %}

![Create Cloud Run Step 3](/files/gwcoZ2N3Q1zVTDTkAWJI)

Scroll down and select **Allow all traffic** (7) and **Allow unauthenticated invocations** (8). Then expand (9) the *Containers, Connections, Security* section.

![Create Cloud Run Step 4](/files/yyoCL5SFbLfh3rMqunsQ)

Set **Container port** to *5000* (10) and set Memory to *1* and CPU to *1* (11).

![Create Cloud Run Step 5](/files/u4oA0q7Hbmi5hKkX3Jxi)

Finally we set the environment variables. Click **+Add Variable** (12) and set the following environment variables (13) one by one replacing the values with your actual values where applicable, click **Create** (14) when done.

<table><thead><tr><th width="414.33333333333337">Key</th><th>Value</th><th>Notes</th></tr></thead><tbody><tr><td><code>[ASSETSTORE__TYPE]</code></td><td>GoogleCloud</td><td>Do not change this value</td></tr><tr><td><code>[ASSETSTORE__GOOGLECLOUD__BUCKET]</code></td><td><em>[STORAGE BUCKET NAME]</em></td><td>Provide your own value</td></tr><tr><td><code>[EVENTSTORE__MONGODB__CONFIGURATION]</code></td><td><em>[MONGODB CONNECTION STRING]</em></td><td>Provide your own value</td></tr><tr><td><code>[STORE__MONGODB__CONFIGURATION]</code></td><td><em>[MONGODB CONNECTION STRING]</em></td><td>Provide your own value</td></tr><tr><td><code>[ASPNETCORE_URLS]</code></td><td>http://+:5000</td><td>Do not change this value</td></tr></tbody></table>

![Create Cloud Run Step 6](/files/bcYHNCojWqIqFNxbJcs9)

Wait until the deployment is complete and you have sucessful notification with a green check. Copy the URL (15) and click **Edit & Deploy New Version** (16).

![Create Cloud Run Step 7](/files/wsYKtmW6Bb55g2Xfe1b8)

Add a new environment variable by clicking **+Add Variable** (17) and then adding the following (18). Click **Deploy** (19) when done.

| Key               | Value              | Notes                  |
| ----------------- | ------------------ | ---------------------- |
| `[URLS__BASEURL]` | *\[CLOUD RUN URL]* | Provide your own value |

{% hint style="info" %}
If you want to use your \*\*custom domain\*\*, enter the domain/sub-domain here instead of the Cloud Run URL.
{% endhint %}

![Create Cloud Run Step 8](/files/ANf4J0n7Uk0wG2UzkNzX)

This should deploy a new revision of the Cloud Run and update the Base URL value in the app.

![Create Cloud Run Step 9](/files/KqLprh03zaE3JmoSfeTu)

You can now open the URL in a browser and continue with Squidex setup.


# Install on Docker

Install Squidex on Linux machines with docker and docker-compose.

## Supported Platforms

* Linux with [Docker CE](https://docs.docker.com/install/linux/docker-ce/centos/)
* Windows 10 Pro, Enterprise or Education with [Docker for Windows](https://docs.docker.com/docker-for-windows/install/)
* Windows with [Docker Toolbox](https://docs.docker.com/toolbox/toolbox_install_windows/)
* Mac with [Docker for Mac](https://docs.docker.com/docker-for-mac/)

{% hint style="info" %}
Digital Ocean [Droplets](https://www.digitalocean.com/products/droplets) are not supported right now, because their DNS prevents that a container can make a request to itself, which is needed to get OIDC via Identity Server working properly. The issue has been discussed in the [support forum](https://support.squidex.io/t/non-standard-port-installation/1262).
{% endhint %}

## Use the Docker Compose Setup

We provide a Docker Compose configuration:

> <https://github.com/Squidex/squidex-hosting/tree/master/docker-compose>

There are 3 alternatives:

#### Squidex + Caddy

`docker-compose.yml` has the following containers:

* Squidex
* [Caddy ](https://caddyserver.com)as reverse proxy to support HTTPS. Also issues the certificate.
* [MongoDB](https://www.mongodb.com/de)

The caddy proxy uses a custom image to configure the Caddyfile.

{% hint style="info" %}
Recommended setup because of the performance of Caddy and the number of containers.
{% endhint %}

#### Squidex + NGINX

`docker-compose-nginx.yml` has the following containers:

* Squidex
* [NGINX ](https://www.nginx.com)as reverse proxy to support HTTPS
* NGINX sidecar to provision free and secure certificates with [LetsEncrypt](https://letsencrypt.org/de/).
* [MongoDB](https://www.mongodb.com/de)

The NGINX proxy uses a [custom image](https://github.com/Squidex/squidex-hosting/blob/master/docker-compose/proxy-nginx/Dockerfile) to increase the size of the http headers.

{% hint style="info" %}
Recommended setup when you are familiar with Nginx and have special requirements.
{% endhint %}

#### Squidex without Proxy

`docker-compose-noproxy.yml` has the following containers:

* Squidex
* [MongoDB](https://www.mongodb.com/de)

{% hint style="info" %}
Recommended setup if you already have a reverse proxy (e.g. Cloudflare).
{% endhint %}

### 1. Download the files

Download the following files to your server:

* `docker-compose.yml`
* `.env`

### 2. Configure Squidex

Open the `.env` file and set the following variables:

<table><thead><tr><th width="294.5">Variable</th><th>Description</th></tr></thead><tbody><tr><td><code>SQUIDEX_DOMAIN</code></td><td>Your domain name, that you want to use for your installation. For example the domain name for the Squidex cloud is <code>cloud.squidex.io</code>. If you run Squidex on your local machine it is <code>localhost</code>.</td></tr><tr><td><code>SQUIDEX_ADMINEMAIL</code></td><td>The email address of the admin user. You can leave it empty to create a new user with the setup page when you visit your Squidex installation in the browser.</td></tr><tr><td><code>SQUIDEX_ADMINPASSWORD</code></td><td>The password of the admin user. Must contain a lowercase and uppercase letter, a number and a special character. You can leave it empty to create a new user with the setup page when you visit your Squid</td></tr></tbody></table>

You can keep the other settings empty for now.

### 3. Data Folder

The data files, such as assets and the MongoDB database files will be stored, outside of the Docker container to simplify backups. The default path `/etc/squidex` will be created by Docker automatically.

### 4. Run the docker-compose file

```bash
docker-compose up -d
```

### 5. Visit your Installation

Squidex should be up and running now. You can visit your installation under the following URL:

[https://${SQUIDEX\_DOMAIN}](https://${squidex_domain}).

You should see the following screen:

![Setup Screen](/files/ng0LO9dnAseY3z2Xos2n)

The setup screen shows a checklist with hints and warnings. As long as there is no error (a red icon), everything is fine.

If no external authentication provider such as Google or Github is configured you will not see the red area.

Next, create a new administrator account with an email address and password and you are ready to go. We will not send you an email to this email address, so you can choose whatever email address you want.

## Troubleshooting

Please check the logs first using Docker.

```bash
docker ps # Get the container id first
docker logs <CONTAINER-ID> # Read the logs
```

### I Get NET::ERR\_CERT\_AUTHORITY\_INVALID from the Browser

You are very likely running under `localhost`. In which case the webserver (Caddy) cannot create a valid certificate and will create a self signed certificate. Usually, there\`is a button to continue to localhost:

![Accept self signed certificate with Chrome](/files/CqTQsdEWy9MkBlOYcqc5)

{% hint style="info" %}
This screenshot is taken from Chrome and might look different on other browsers.
{% endhint %}

### I Get a 502 Bad Gateway

It can take some time to issue the certificate, approximately around 10 minutes.

Do also ensure that your DNS server is configured correctly.

### I Get a IDX20803: Unable to obtain configuration from \<URL>

#### Problem 1: Firewall Issues

In some cases, especially on CentOS 7, the communication between Docker containers on the same host is blocked by a firewall. There is an open [issue on Github](https://github.com/moby/moby/issues/32138) for this problem.

The best solution is to add https as a service to the firewall:

CentOS:

```bash
sudo firewall-cmd --add-service=https --permanent --zone=trusted
sudo firewall-cmd --reload
sudo systemctl restart docker
```

Ubuntu:

```bash
sudo ufw allow 443
sudo ufw enable
sudo systemctl restart docker
```

#### Problem 2: Invalid Host name

This problem occurs because you are using a host name or IP address that is not reachable from the Docker itself. You can think about Squidex being two processes in one application. There is the OpenID Connect Token Server (that generates the access tokens and the API). When the API receives an access token it makes a request to the Token Server to validate the token (see the following diagram).

![Authentication Flow](/files/-M6PoQtZG15AeaUkx7ww)

When you use a local host name or IP address such as `localhost` or `127.0.0.1,` you're referring to the host name, but containers inside docker cannot resolve the network routes and therefore the authentication flow fails. The solution is to either use another local host name, that you have to configure in the host file of your operation system or to use a real host name, such as a public domain name.

### More Issues?

For other issues, it is likely that you have a configuration problem not related to hosting under Kubernetes. Checkout the following documentation:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}


# Install on Heroku

Install on Heroku

## 1. Use the 1-Click Deploy Button

[![Deploy to Heroku](https://img.shields.io/badge/-Deploy%20to%20Heroku-430098?style=for-the-badge\&logo=heroku\&logoColor=ffffff)](https://heroku.com/deploy?template=https://github.com/Squidex/squidex)

## 2. Fill in the details

Make sure that you fill in the **App name** (1) field with something unique, and then fill the **DOMAIN** (2) variable with the external domain you want your Squidex instance to be available on.

You can ignore *MONGO\_USERNAME* and *MONGO\_PASSWORD* for now.

![Fill Details](/files/-MdHzZ_ABBUYfQxPCHbB)

When you are done, click **Deploy App**.

## 3. Wait for the Deployment to Complete

![Deployment Started](/files/-MdHzZ_BfIe7FfqpI09P)

![Deployment Finished](/files/-MdHzZ_CFvIypUw5gCkI)

When it's finished, click **Manage app** (1).

## 4. Create MongoDB database and credentials

On the Dashboard, click **ObjectRocket for MongoDB** (1)**.**

![Open ObjectRocket Dashboard](/files/-MdHzZ_Dc4O591gSijGF)

On the ObjectRocket Dashboard, click **DATABASES** (1) and then **Add Database** (2).

![Open Databases](/files/-MdHzZ_EQnoKgMipUFvJ)

Enter the **database name** (1) (must be `squidex`), the **Username** and the **Password** (2). Keep note of those, as you will need them for the next step.

![Create Database](/files/-MdHzZ_FVh4ANuXwHZJw)

## 5. Fill In the Database Credentials

Back at the Heroku Dashboard, click **Settings** (1), then click **Reveal Config Vars** (2).

![Settings](/files/-MdHzZ_Gnhru1oVEpB6N)

Fill the fields `MONGO_PASSWORD` (1) and `MONGO_USERNAME` (2) with the same credentials you provided in the previous step.

![Config Vars](/files/-MdHzZ_Hk5rOauVaUn08)

You should then be ready to go!

## Troubleshooting

Please check the logs by clicking **More**, and then **View logs.**

![Logs](/files/-MdHzZ_IXUEpH_dunfh-)

### Restart Dynos

In case the deployment gets stuck, go to the top right of the dashboard, click **More** and then **Restart all dynos**.

### More issues?

For other issues, it is likely that you have a configuration problem not related to hosting under Heroku. Checkout the following documentation:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}


# Install on IIS

Install Squidex on Windows with IIS and a custom MongoDB server.

You can also read this useful guide from Microsoft, How to [Host ASP.NET Core on Windows with IIS](https://docs.microsoft.com/en-US/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2#install-the-net-core-hosting-bundle).

This document is just a shorter version of the official guide.

## Supported Operating Systems

The following operating systems are supported:

* Windows 7 or later
* Windows Server 2008 R2 or later

## Step 1. Install all Requirements

### 1.1. Install IIS

If you're reading this page and you're planning to setup Squidex under Windows, you are probably already familiar with IIS and have it installed on your machine. If not you can read the docs:

> [IIS configuration (Microsoft)](https://docs.microsoft.com/en-US/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2#iis-configuration)

### 1.2. Download and Install .NET Core Hosting Bundle:

Download the installer using the following link:

> [Current .NET Core Hosting Bundle installer (direct download)](https://dotnet.microsoft.com/download/thank-you/dotnet-runtime-2.2.0-windows-hosting-bundle-installer)

Ensure that the `ASPNETCORE_ENVIRONMENT` environment variable is set to `Production`

### 1.3. Download and Install MongoDB

Following the official setup instructions:

> [Install MongoDB Community Edition on Windows](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/)

## Step 2. Install Squidex

### 2.1. Download the binaries

Go to the release page and download the `binaries.zip` file from the latest release:

> [Squidex Releases](https://github.com/Squidex/squidex/releases)

Create a folder for your Squidex installation and extract the archive to this folder.

### 2.2. Create the IIS Site

1. In IIS Manager, open the server's node in the **Connections** panel. Right-click the **Sites** folder. Select **Add Website** from the contextual menu.
2. Provide a **SiteName** and set the **Physical path** to the Squidex folder that you created in the previous step. In this case we host Squidex at <https://localhost:80>
3. Ensure that you also create an https binding with a proper certificate or development certificate. Due to recent security changes in Chrome, you are no longer able to login to Squidex if you are using http instead of https. You can also use Cloudflare as https termination if you don't want to pay for a certificate.
4. Confirm the process model identity has the proper permissions.

   If the default identity of the App pool (**Process Model** > **Identity**) is changed from `ApplicationPoolIdentity` to another identity, verify that the new identity has the required permissions to access the App's folder, database, and other required resources. For example, the App pool requires read and write access to folders where the app reads and writes files.

![IIS Site configuration](/files/-LrLSHeBeDLbUnYeUtzY)

### 2.3 Configure the Application Pool

1. Under the server's node, select **Application Pools**.
2. Right-click the site's App pool and select **Basic Settings** from the contextual menu.
3. In the **Edit Application Pool** window, set the **.NET CLR version** to **No Managed Code**:

![IIS Application pool](/files/-LrLSHeFvumxfljoyb42)

### 2.4. Configure Squidex

1. Go to your Squidex installation folder.
2. Create a file called `appsettings.Production.json` which overrides the default settings. When you make an update and download a newer release your customized settings will not be overridden. You can also configure all settings with environment variables which is the recommended method for all other platforms, but in this case a file is easier.
3. Enter the following settings. The `baseUrl` is the most important setting as it must match the domain that you're using. All other settings are optional. If you use a version lower than 5.6 you must define the initial username and password in settings.

```javascript
{
  "urls": {
    /*
     * The url where your Squidex installation is available at.
     */
    "baseUrl": "http://localhost"
  },
  "identity": {
    /*
     * Disable authentication with google, github and microsoft login.
     */
    "googleClient": "",
    "googleSecret": "",
    "githubClient": "",
    "githubSecret": "",
    "microsoftClient": "",
    "microsoftSecret": "",
    /*
     * Initial username and password. Only needed before Squidex 5.6
     */
    "adminEmail": "admin@squidex.io",
    "adminPassword": "save1q2w3e$R"
  },
  "assetStore": {
    "folder": {
      /*
       * Define where to store the assets.
       */
      "path": "E:\\Sites\\squidex\\assets"
    }
  },
  "orleans": {
    /*
     * Disable clustering!
     */
    "clustering": "Development"
  },
}
```

{% hint style="info" %}
The password must contain a lowercase and uppercase letter, a number and a special character. If you use Squidex 5.6 or later you will see a simple setup screen when you open your site for the first time and no user is created. You can create the initial user on this screen.
{% endhint %}

Start the IIS site and go to <http://localhost>. Login and start editing.

## Troubleshooting

### Where Can I see the Logs?

Squidex logs to standard output.

To forward the logs to a file, open `web.config` and set `stdoutLogEnabled="true"`.

### I Get a HTTP Error 404.0 - Not Found

Ensure that the **Physical path** of your site points to the correct location.

### I Get a HTTP Error 502.5 - Process Failure

It is very important that you restart IIS after you have installed the .NET Core Windows Server Hosting package. Restart the server or execute `net stop was /y` followed by `net start w3svc` from a command prompt with elevated permissions to pick up a change to the system PATH.

### I Get a HTTP Error 504 - Method not allowed

This can happen when you try to make an API call with the *PUT* or *DELETE* verb. For example when you use the Management UI. The reason is that WebDAV might be installed on your server and it blocks these verbs. You have to add the following lines to the `web.config` file.

```markup
<system.webServer>
    <modules runAllManagedModulesForAllRequests="false">
        <remove name="WebDAVModule" />
    </modules>
</system.webServer>
```

### I only see a spinner on a white background

Ensure that the `ASPNETCORE_ENVIRONMENT` environment variable is set to `Production`.

Squidex logs all environment variables on the first start. Search for the following output in the logs:

```javascript
{
  "logLevel": "Information",
  "message": "Application started",
  "environment": {
    ...
    "aspnetcoreEnvironment": "Production",
  }
}
```

### I do not see referenced content items or assets when I add a lot of references

To fetch the referenced content items or assets the user interfaces makes an API call where all IDs are added to the query string. When you have too many items, the query string becomes too long and IIS will return a `404`status code. Newer versions of Squidex will fall back to POST requests but you can also increase the request limit.

```markup
<system.webServer>
    <security>
        <requestFiltering>
            <requestLimits maxQueryString="8192" />
        </requestFiltering>
    </security>
</system.webServer>
```

### More issues?

For other issues, it is likely that you have a configuration problem not related to hosting under IIS. Checkout the following documentation:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}


# Install on Render

Install on Render

## 1. Use the 1-Click Deploy button

[![Deploy to Render](https://img.shields.io/badge/-Deploy%20to%20Render-44E4B4?style=for-the-badge\&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAADyklEQVR4Xu1aT0gUURh/k5qV2y5pEbpqHiqqy4IIQUGghbNOK53WkwQFdekqRHXNDkHXIKhuXdZTZu6MRkbkJRFJKAKNUnQPEir+IXdr98XbdXbfzrznjLvvObM7s5dld7553/f93u/78/4IwOEfweH+AxcAlwEOR8ANAYcTwE2CfEIAAqEzpjSmQPLobhj2zi9N4fLiotxKet87vvZloKcnqT6jyW2sx2fGz1xb38kGJgCEv0b2h/yBvuu+0/0kZRAAAtX0/wqCkGcPhBAJ6T7SbNQXPSWtqQ9ocsFFpUNpDI5xBeBnYn2+pcrTtJuZpsnSANBCRQNAK8cVgM7F4W6lvmuQZRYxy4De2ajvlSkGvO1QGkPsGXB/aeJF/7G2myxmHR/DGIBMREizsnUhcO/3xMtHdW03WDuPxjMGIKPVshwgLshXZb84lO88OcUVApDtAYAwBUn5XHV2Kbn1tzc2FhptlkYKAcCKd0yXwbnExnxzVQ012z9emb5ztzbw1AonitFpCgBU5yPnwnGaouCCcl5pCn4uxhCr3jUFwLPVbw9v+84+0BkJAXiyMn2rry7w3CoHkN7u2JtDJP0HPv2J4x0jScYUALROaym5lTheebDaSueRbu6dIE2BOBe9PNIivS9vACAQIIApIn00vbtVQOgnKFOambTCV2KDzaP13XOlBUDGWiYAoKVmtEGcJCULbeNiHwYwBkBuECdLiwEMQwAxwO4AFMM8wzLoAuAyYDsECIs+uyRBx4dACkJIimVmZTAvCWJMsAsDuLbCpZAE9xYACADiG6KcAxmQ4b8aBQ4EIJNvHQ+AWnbswgDuZdDuiyHuAOTKYH435BgGuIuhklsO89wP2I4C9LXPjltiWJTyaYWxjGOXHMC3E/wlt8onSm1HiGcIZNsgG7XCKZjeu9aeWzoqBEhn1EwACMNIxcaypyZNqmUAQG0uCUTrcvd0imlGin23a2bYSxrDc7Jrc0AQspepCj4aK9ZAO79vuClqZ+NZ2OYCYISi2R0hdEQ9WB/aNHOAQqvb1Csyaobb/t7TO0Kirg/I5VvcYBUAUjY2e/fHSC49NgRA+kG5KIkrhwAEYwwuSuIM0DpHAqAQBtA2WEzfFE33AXrNTMpgFgCDcwGuIaDxbW9DwOTJkAsAzyRoJQMufn992OOpbsvZ8A8AUJn+id/EDkciFWsXvJdwW5EUktbe2BYX5HZSrtDLDbWrunB5r1/8iHd4tPHiNdVTH460r+5U6dw+wKgPKPfnLgPKfYaN/HMZYIRQuT93GVDuM2zk338nAalfI74e1QAAAABJRU5ErkJggg==\&logoColor=ffffff)](https://render.com/deploy?repo=https://github.com/Squidex/squidex)

## 2. Fill In the details

Fill the **Service Group Name** (1) and then fill the **DOMAIN** (2) variable with the external domain you wish your Squidex instance to be available on.

You can leave the **Branch** field at the default **master**.

![Fill Details](/files/-MdHzZof7hkzNLs2IOkn)

When you are done, click **Apply** (3).

## 3. Wait For the Deployment to complete

![Deployment Started](/files/-MdHzZohLBLLy5KFyrZ2)

![Deployment Finished](/files/-MdHzZoinaSxPlfhbP75)

When it's finished, click **managed resources** (1).

## 4. Get the external URL

Click **squidex\_squidex** (1).

![Open squidex\_squidex](/files/-MdHzZojQpwRumqWnZV5)

Your **URL** (1) will be just below the service name.

![Get URL](/files/-MdHzZokD8twGDGhb7rT)

You should then be ready to go!

## Troubleshooting

Please check the logs by clicking **Logs** (1) in the service page.

![Logs](/files/-MdHzZolCKo8hp9IOZmT)

### More issues?

For other issues, it is likely that you have a configuration problem not related to hosting under Render. Checkout the following documentation:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}


# Install on Vultr

Vultr is a cheap provider of virtual or physical machines all over the world and is great if you need fast servers for low costs.

## Why you Should Consider Vultr

### When to Use Vultr

* You want a server for low costs.
* You do not need scalability.
* You do not need integrated monitoring and logging.
* You have a custom domain.

### When Not to Use Vultr

* You are already customer of another Cloud provider.
* You need scalability for Squidex and the database.

### Requirements

* A custom domain or subdomain and the ability to create an A DNS record.

## What is Included

This installation configures a Ubuntu 20.04 x64 machine with

* Docker
* Docker-Compose

Based on this setup 3 containers are installed:

* Squidex
* MongoDB for storing your data.
* Caddy as reverse proxy and for https termination ([https://caddyserver.com/](https://caddyserver.com)).

To work properly you need a DNS A record entry pointing to your machine, because caddy will issue a certificate using lets-encrypt.

## Installation Guide

### Step 1: Go to the Marketplace

Just follow the link: <https://www.vultr.com/marketplace/apps/squidex>

### Step 2: Deploy a New Server

1. Select your target location close to your center of operation.
2. Select the size of your machine. At least 4GB or memory with 2 virtual cores are recommended.

![Recommended server size](/files/-MbHzgLJgLOv9kBtnw9j)

### Step 3: Wait For the Server to Spin Up

It can take a while until your server is ready to be used.

![Waiting for the server](/files/-MbI-9rKjZS4vdMCgOvl)

### Step 4: Create a DNS Record

Click your new server and wait until you get an IP address.

![IP address is ready](/files/-MbI-UdnMecAnR6dUVjw)

Create a DNS A record to this IP address.

### Step 5: Create the initial admin user

The server will pull all images now and start them in the right order. Furthermore caddy (the reverse proxy) will create a certificate for your domain. This can take a while, depending how fast your DNS record is propagated. If you use a domain that has not been used before, it takes only a few minutes.

Squidex is ready for use. If you visit your installation under <https://mydomain.com> you will see a simple setup guide where you can create the initial admin user. If you have configured an external authentication provider (for example Github or Google) you can just login and as the first user you will get admin permissions automatically.

<figure><img src="/files/lwxn7oDXWXkxsclgrcW4" alt=""><figcaption><p>Setup guide</p></figcaption></figure>

## Configuring Object Storage

Vultr Object Storage provides an option for cost-effective and scalable S3 compatible storage for Squidex assets. The following steps outline the process of configuring Squidex to use this Vultr product.

### Step 1: Add Object storage

Follow this link <https://my.vultr.com/objectstorage/add/> and proceed to add object storage to your account.

![Vultr object storage setup](/files/-MdzsQj168Isp4nypLs8)

### Step 2: Add a Bucket and a Folder

Navigate to the buckets tab and add a bucket.

![Vultr object storage bucket list](/files/-MdzsQj2QqVxyuLX8rYD)

Click on the bucket you've just created and add a folder.

![Vultr object storage bucket list](/files/-MdzsQj36wMFRpYNuP-k)

### Step 3: Configure Asset Storage to Use S3

Navigate to the overview tab and make note of your S3 credentials.

![Vultr object storage bucket list](/files/-MdzsQj4rIyEPkGotX7j)

Edit the `/home/.env` file using your editor of choice and add the following environment variables using the appropriate values for each (leave the S3\_REGION variable empty for now):

```
S3_SERVICEURL=https://ewr1.vultrobjects.com
S3_BUCKET=squidex
S3_FOLDER=assets
S3_REGION=
S3_ACCESSKEY=9I2XHCLL43LKD2WLKDEN
S3_SECRETKEY=PUT_YOUR_SECRET_KEY_HERE

# the following variable is used to compartmentalize 
# each app's assets in a separate folder
S3_FORCEPATHSTYLE=true
```

Next, edit the `/home/docker-compose.yml` file to pass these environment variables onto the container:

```
squidex_squidex:
    image: "squidex/squidex:5"
    environment:
      ... previous variables are here
      - ASSETSTORE__TYPE=AmazonS3
      - ASSETSTORE__AMAZONS3__SERVICEURL=${S3_SERVICEURL}
      - ASSETSTORE__AMAZONS3__BUCKET=${S3_BUCKET}
      - ASSETSTORE__AMAZONS3__BUCKETFOLDER=${S3_FOLDER}
      - ASSETSTORE__AMAZONS3__REGIONNAME=${S3_REGION}
      - ASSETSTORE__AMAZONS3__ACCESSKEY=${S3_ACCESSKEY}
      - ASSETSTORE__AMAZONS3__SECRETKEY=${S3_SECRETKEY}
      - ASSETSTORE__AMAZONS3__FORCEPATHSTYLE=${S3_FORCEPATHSTYLE}
```

In your command line shell, update your Squidex container to use the new asset storage settings:

```
docker-compose up -d
```

Enjoy and have fun.


# Configuration

## Configuration Model

We use the [ASP.NET Core Configuration](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration) model for all settings.

You can configure Squidex using the following methods:

1. The `appsettings.json` file.
2. The `appsettings.Production.json` file.
3. Environment variables.
4. Command line arguments.

The ordering is important. Command line arguments override other settings, environment variables override the JSON files and so on. Using a combination of all these options can be very helpful.

Read the comments of the `appsettings.json` file to understand all configuration settings.

In this example we want to override the following setting from the configuration file:

```javascript
{
  "assetStore": {
    "folder": {
      "path": "MyAssets"
    }
  }
}
```

If you combine all keys from the JSON root to the setting you get the full key of this setting.

Aggregate the keys by two underscores to get the name of the environment variable:

```
ASSETSTORE__FOLDER__PATH="MyAssets"
```

Aggregate the keys by using a colon and you get the name of the command line argument

```
assetstore:folder:path="MyAssets"
```

Casing does not matter.

## Important Settings

We assume that you use environment variables to store the settings. Therefore we use the notation with the two underscores.

#### Settings and Environment Variables

These are the most important settings:

| Setting                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `URLS__BASEURL`           | The base URL under which Squidex is running. It is used to generate hyperlinks and to make redirects with the correct host name. In some environments, Squidex is running behind several proxies, e.g. cloudflare, google load balancer and so on. In these cases the original host name might get lost. Therefore we introduced this configuration value.                                                                 |
| `IDENTITY__ADMINEMAIL`    | The email address of the admin user. You can also set the admin email with the initial setup screen.                                                                                                                                                                                                                                                                                                                       |
| `IDENTITY__ADMINPASSWORD` | The password of the admin user (Must contain lowercase, uppercase letter, number and special character). You can also set the admin password with the initial setup screen.                                                                                                                                                                                                                                                |
| `CLUSTERING__MODE`        | Until version 7, Squidex uses [Microsoft Orleans](https://dotnet.github.io/orleans/index.html) for clustering. It is technology, which was written for online games, such as Halo. With Orleans you develop small classes that are deployed automatically to a cluster of nodes. To enable clustering you have to set this setting to `Mongo`, which means that a MongoDB table is used to store the state of the cluster. |

Set

* `IDENTITY__GOOGLECLIENT`
* `IDENTITY__GITHUBCLIENT`
* `IDENTITY__MICROSOFTCLIENT`

to empty to disable authentication with third party providers.

#### Health Checks

Many systems support health checks to determinate the health of a service. For example load balancers periodically call an endpoint of the service to remove dead nodes from the list of available nodes and to stop serving HTTP requests to these nodes.

Squidex provides the following health checks:

* `/healthz` (e.g. <https://cloud.squidex.io/healthz>): An endpoint to check if Squidex can serve HTTP requests and has not consumed too much memory. This is a recommended health check for liveness, startup and readiness probes.
* `/cluster-healthz`(e.g. <https://cloud.squidex.io/cluster-healthz>): An endpoint to check the status of the cluster and if a node can connect to the cluster. It is not recommended to use this endpoint for liveness, startup and readiness probes, because the clustering system has its own health system.
* `/background-healthz` (e.g. <https://cloud.squidex.io/background-healthz>): An endpoint to check the status of background processes. It is not recommended to use this endpoint for liveness, startup and readiness probes, because a restart of a node would not help in most cases, but it is useful for monitoring.
* `/readiness` (e.g. <https://cloud.squidex.io/readiness>): Special endpoint for readiness and startup probes. Usually it is not needed to use this endpoint.

## Troubleshooting

Please check the logs to see detailed error messages.

### Login screen shows 'Operation failed' message.

Typically, the login fails because the `URLS__BASEURL` setting has an invalid value. Ensure that the public domain used by your users is configured here. Squidex might run behind several other servers such as Cloudflare, load balancers and reverse proxies and does not know the original domain. Therefore, you must configure the URL.

### I see the login screen but I cannot login.

Ensure that you have configured a strong password if you use `IDENTITY__ADMINPASSWORD`.

You will see the following entry in your logs:

```javascript
{
  "logLevel": "Error",
  "action": "createAdmin",
  "status": "failed",
  "exception": {
    ...
    "message": "Cannot create user:...",
    ...
  }
}`
```

The password requirements are:

1. Passwords must be at least 6 characters.
2. Passwords must have at least one non alphanumeric character.
3. Passwords must have at least one digit ('0'-'9').
4. Passwords must have at least one lowercase ('a'-'z').

If yoy have forgotten your admin password you can use the following environment variable to update the admin account with the password from the configuration whenever a Squidex instance is started:

`IDENTITY__ADMINRECREATE=true`


# Deploying the Image Resizer Service

This Page Describes How You Can Offload Image Resizing to a Separate Image Resizing Service

Image resizing could be an expensive task from a performance perspective as it could consume a lot of CPU resources depending on the size and number of images that are being resized. Since most images are compressed, for example a PNG image, in order to resize it has to be loaded to the server, decompressed, resized (in memory), compressed again and sent back.&#x20;

Squidex uses *Imagesharp* for this task which works for most formats in combination with *ImageMagick* for WebP and TGA images.

While these libraries are fast they can still be a bottleneck and a CPU intensive task when resizing images of large size and in large numbers.&#x20;

Hence, a small microservice was built to offload this task to another service. The aim was to create an external process that can handle the image resizing and the thread that accepted the request to resize the image can handle other HTTP requests while it is waiting for the result. This does not necessarily mean that resizing of images will become faster as this would depend on the resource allocated to the microservice.

## About The Microservice

This microservice is available as a docker image and is easy to install.

It does not have any dependencies or database requirements and hence can be simply installed on any server, Kubernetes cluster, or one can also use a managed services like Google Cloud Run (<https://cloud.google.com/run>) for infinite scalability.&#x20;

### Authentication

It is important to note that Squidex does NOT use any authentication to communicate with the Image Resizer microservice.

Hence it is recommended to host it in the same network, preferably keeping it private. Hosting in the same network also helps in keeping traffic costs low.

## Deploying the Microservice

### Docker Image&#x20;

The Docker image is available at <https://hub.docker.com/repository/docker/squidex/resizer>.

Running the docker image is as simple as running the following command:

{% hint style="info" %}
The image resizer runs on port 80 by default. You should map it to a different port if you have a reverse proxy like NGINX also running on port 80/443.

The following command maps image resizer to port 8001 on the host.
{% endhint %}

```
docker run -d -p 8001:80 squidex/resizer
```

### Kubernetes YAML

The following YAML file can be used in Kubernetes deployments:&#x20;

<https://github.com/Squidex/squidex-hosting/blob/master/kubernetes/resizer/resizer.yml>.

To deploy the resizer in Kubernetes, download/copy the `resizer.yml` from the link above and simply run the following command:

```
kubectl apply -f resizer.yml
```

## Configuring Squidex for the Image Resizer Service

Starting with Squidex 6.4.0, the following environment variable can be configured to point to the resizer microservice:

| Variable             | Description                                         |
| -------------------- | --------------------------------------------------- |
| `ASSETS__RESIZERURL` | The URL where the image resizer service is running. |

For example, if you were running the Kubernetes YAML file, then the URL would be [`http://resizer.default.svc.cluster.local`](http://resizer.default.svc.cluster.local).


# Troubleshooting and Support

How to Best Solve Issues and Leverage Squidex Support

## Before You Start

It is best to think about whether you want to install Squidex on your own machines or if it might be better for you to use the Cloud instead. In general, the Cloud is cheaper if you take into account the time spent for installation, maintenance and updates.

If you are sure that you want to install Squidex on your own machine, there are a few things that are essential:

1. You should understand how to use logging in your environment. Especially if you are running Squidex with multiple instances, it is crucial to aggregate all the logs into one stream that can be searched and analyzed. Many Cloud providers have a very good solution for this. There are also free products like the ELK (Elasticsearch, Logstash, and Kibana) stack (<https://www.elastic.co/log-monitoring>).
2. You must also understand how to monitor your installation. Very often, the logging infrastructure already provides a solution for problems, but you can also use alternatives like Statping (<https://github.com/statping/statping>). This is an awesome tool as a [public status page](https://status.squidex.io), but you should make sure you have internal monitoring solutions.
3. If you have a very complicated setup, it really is best to have an APM (Application Performance Monitoring) tool in place to analyze performance issues. Squidex provides an integration to Datadog APM (<https://www.datadoghq.com/product/apm/>) and Azure Application Insights.

The previous points are in prioritized order.

## I Need Help and Support!

If you do not have a paid support contract, the only option is the support forum: <https://support.squidex.io>. In the past, there were other support channels like GitHub, Email and Slack but it took too much time to answer all the support requests. The only option for good, free support, is to ask for help over a single, public channel (which helps others with the same problem to search the forum too). There's no way to support issues via private messages. If you would like to use another channel, you can always purchase a support contract.

### How to Ask for Help

The support forum provides a template for support requests with placeholders for information that is needed to give support. It is very important to fill in this information. Most of the time, the support team will ask for this information, so if it's there at the beginning, it's helpful and doesn't waste time! When all the information is in place, it usually only takes a few hours to solve an issue.&#x20;

#### What Questions Does Support Want to Know?

1. **Information about your environment**: Very often, problems are related to a specific environment, for example when the root cause is a networking issue in Docker. Therefore, environmental information is essential.
2. **Your browser**: This is very often needed when you have issues with the user interface or with authentication. It helps to reproduce the issue.
3. **Squidex versions**: Some issues are already widely known and have already been fixed . If you allow our team to organize Squidex installation from the beginning, issues can usually be resolved very quickly. If you don't know the Squidex version, it is helpful to give support the exact date and time when you installed Squidex. You can view your version in the Squidex dashboard, when you use Docker.

Furthermore, the support team will need logs from your installation, more on preparing server logs below:

### Preparing Server Logs

As we are the team who have developed Squidex, we know every line of code and have a lot of experience on how our product behaves in different environments! Of course, we haven't tested every environment or Cloud provider ourselves and we don't know every issue, otherwise we'd have fixed them!  Your Squidex installation is like a black box for us and we can only guess what's gone wrong.

This means that it is very important to gain insight into your installation via logs.

#### How to Get Logs

Squidex logs everything to the standard output stream (`stdout`). This is a recommended pattern for Cloud-ready applications and is described in the 12-factors--App manifest: <https://12factor.net/logs>.

Most Cloud providers redirect the standard output stream to storage and make the logs available in their Cloud portal. Please read the documentation from your own Cloud provider.

Here are a few hints on how to retrieve logs:

* If you installed Squidex under IIS in Windows you should read the following article from Microsoft: <https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-3.1#log-creation-and-redirection>.
* If you used Docker you should read the following article: <https://docs.docker.com/config/containers/logging/>. The document also describes how to setup a log driver that can be used to redirect all logs to a centralized logging service.

#### Search in Logs

Before you upload the logs, search for the `exception` keyword. You might find the solution to your problem in the logs.

Sometimes, the identity systems masks Personally Identifiable Information (PII) in the logs. If you see such a case in your log file and you think that relevant information is missing, you can turn off this behavior with the following setting: <https://github.com/Squidex/squidex/blob/master/backend/src/Squidex/appsettings.json#L580>

The environment variable for this setting is `IDENTITY__SHOWPII=true`.

Read more about how to configure Squidex here:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}

#### How to Provide Logs

Please provide your logs as an easy-to-read file. You can upload files to Dropbox, Google Drive or another file-sharing offering and provide the link in a private message through the support forum.

If the logs are less than 10 MB, you can just provide the full logs. Otherwise, you can provide a subset of the logs around the timestamp when you experienced the issue. The more logs you can provide, the better. It can also be helpful to restart Squidex and to reproduce your issue and then collect the logs from this test. Then, we'll get a full history from the time Squidex was started to the time you reproduced the issue.

If you only have a few lines, you can add them to your post, but please ensure that the logs are formatted properly with code blocks.

{% hint style="info" %}
Do not insert long logs as a plain text or formatted code block to your topic. The topic becomes hard to read and very often the logs are not complete.
{% endhint %}

### Prepare Browser Logs

Do also check your browser console for errors. It is very likely that you are a software developer and frontend engineer, so you probably know how to do that.

{% hint style="info" %}
Usually, browser logs contain only a handful of useful log lines. Therefore, you can post them directly in the support ticket as a code block.
{% endhint %}

### Prepare a Backup of Your Mongo Database

Sometimes, it's useful to have a backup of your database ready. Squidex provides its own backup tool but these backups are not useful for troubleshooting because the final result may differ from the state of your database.

Therefore, use `mongodump` to create a backup: <https://docs.mongodb.com/database-tools/mongodump/#mongodump-options>

1. Ensure that you have access to your Mongo database. It might be necessary to open ports temporarily.
2. Create a backup of your Mongo databases. Do not use the `archive` flag.
3. Create a ZIP file of the generated dump folder and upload it to an online storage like Dropbox.
4. Ensure that the ZIP archive can be downloaded as anonymous user.
5. Click the profile picture of the supporting developer in the support forum and send the link to the archive as private message.
6. Remember to provide the name of the App that's causes the problems.

### What Else?

Here are a few other things that are relevant:

1. If you have code examples or logs, use code blocks to format them properly.
2. If you have relevant screenshots for UI problems, you should also upload them but don't upload screenshots for error messages because we can't copy and paste these to search for the error message in our code (or somewhere else).
3. Provide as many details as possible.

Please bear in mind that you are getting free support. Respect everybody's time and prepare your support request properly. The more information you provide, the more likely it is that your issue will be resolved. By being prepared and providing lots of information, it's far speedier to get the response you want!&#x20;

{% hint style="info" %}
If it is very obvious that a topic creator has not read this guidelines, the support team will just delete the topic. The team just doesn't have the time to ask for logs over and over again!
{% endhint %}

## Known Tools and Solutions

### My Mongo Database is Corrupt

Squidex uses event sourcing (an architectural principle), where everything that happens is recorded as an event. An example for such an event is `ContentCreated` or `ContentDeleted`. Other state or collections are derived from events. Therefore, the events can be recreated if necessary.

You can have a look at the `appSettings.json` file for all the restore options: <https://github.com/Squidex/squidex/blob/master/backend/src/Squidex/appsettings.json#L683>

To start such a rebuild, you have to execute the following steps:

1. Enable rebuilding, for example, by setting the environment variable `REBUILD__APPS=true`.
2. Restart Squidex and wait until your instance has started.
3. Remove the environment variables or set the value to `false`.
4. Restart Squidex again and wait until your instance is available.

Read more about how to to use the configuration system here:

{% content-ref url="/pages/-LrLSGJjFug4kUmNpB9G" %}
[Configuration](/id-01-getting-started/installation/configuration)
{% endcontent-ref %}

### My Migration is Broken

In some cases, Squidex needs to run a migration script to convert the database structure to a new version. These cases are described in the changelog: <https://github.com/Squidex/squidex/blob/master/CHANGELOG.md>.

Sometimes a migration fails, for example if the application is restarted before the migration is complete or in the case of a bug. Very often, is is best to run the migration again. The state of the migration is stored in MongoDB. Use a tool of your choice and connect to your MongoDB database. Then, search for the `Migration` (1) collection. There is only one document. Decrement the version (2) and ensure that `IsLocked` is set to `false` (3). Then restart Squidex.

<div align="left"><figure><img src="/files/GtYZ7Cv9GXbrGCmgLfYM" alt=""><figcaption><p>Modifying the migration status</p></figcaption></figure></div>

### My Cluster Won't Start

Until Version 7 Squidex used a clustering technology to scale. With this setup, the memberships communicate through a database collection which members are part of the cluster. If your cluster crashes, or is not shutdown properly there will still be members in the collection, which are not marked as dead yet. When you restart your cluster, new Squidex instances try to connect to these members, which usually fails, because the new instances have new IP addresses. Depending on the setup you have, this will take too long for your load balancer. Therefore, it is easier to clear the membership collection using this method:

1. Ensure that your deployment has zero (0) instances.
2. Clear the `Orleans_OrleansMembershipSingle` collection.
3. Restart your cluster.

<div align="left"><figure><img src="/files/UlkHB9SQgwWRtouSg77k" alt=""><figcaption><p>Clearing the Orleans_OrleansMembershipSingle collection</p></figcaption></figure></div>


# Diagnose Runtime Issues

This Page Explains How to Diagnose Runtime Issues.  It is Helpful to Provide These Details to the Development Team

Squidex is built on .NET Core, which provides a tool to create dumps.

> A dump is a file that contains a snapshot of the process at the time it was created and can be useful for examining the state of your application. Dumps can be used to debug your .NET application when it is difficult to attach a debugger to it such as production or CI environments. Using dumps allows you to capture the state of the problematic process and examine it without having to stop the application.
>
> From: <https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dumps>

Dumps can be analyzed with tools such as:

* Visual Studio
* Visual Studio Code
* Memory Analyzers e.g. [dotmemory](https://www.jetbrains.com/dotmemory/?source=google\&medium=cpc\&campaign=12509621705\&gclid=Cj0KCQjw5oiMBhDtARIsAJi0qk2ZvCwqDMmKuDzjmibSonfQuJyZZW_jkhbizatYncy8ipncfEM05BIaAsaXEALw_wcB)

You have two options to create these dumps:

1. Directly with Squidex if you have version 6.8.0 or newer.
2. Manually after you have installed the necessary tools.

## Automated Process (v6.8.0 and Later)

With version 6.8.0, Squidex has been extended to create dumps from either the API or automatically.

### Configuration

{% hint style="info" %}
Skip this section if you use a container image, because everything will already be configured.
{% endhint %}

Squidex uses the same dump tools that are also used when you create the dumps manually, but they are not part of Squidex itself and therefore you have to tell Squidex where the binaries can be found. Use the following environment variables for that and change the path to point to your installation folder.

```
ENV DIAGNOSTICS__DUMPTOOL=/tools/dotnet-dump
ENV DIAGNOSTICS__GCDUMPTOOL=/tools/dotnet-gcdump
```

### Create a Dump Via API

To create a dump via the API you have to call the following endpoints using the normal authentication headers.

```
GET /api/diagnostics/dump
GET /api/diagnostics/gcdump
```

The dumps are stored in your asset storage under the following paths:

```
diagnostics/dump/yyyy-MM-dd-HH-mm-ss.dump
diagnostics/gcdump/yyyy-MM-dd-HH-mm-ss.gcdump
```

### Create a Dump via Configuration

You can also tell Squidex to create a dump automatically once a memory limit is reached. The dump will therefore only be created once for the whole instance of the Squidex instance.

```
# Triggers the dump tool when the process has consumed more than 4GB
DIAGNOSTICS__DUMPTRIGGERINMB=4096

# Triggers the gcdump tool when the process has consumed more than 4GB
DIAGNOSTICS__GCDUMPTRIGGERINMB=4096
```

## Manual Process

If you want to create a dump manually, you have to execute the following steps:

1. Connect to your production machine, container (Docker) or pod (Kubernetes).
2. Install the .NET SDK and the necessary tools.
3. Install the .NET tools.
4. Create the dump.
5. Download the dump to your local machine.
6. Optional: Upload the dump file to a network drive to make it available it others.

### 1. Connect to Your Production Machine

How you connect will depend on your hosting environment and the operation system:

#### Connect to a Linux Machine

Use SSH or [putty for Windows](https://www.putty.org).

#### Connect to a Windows Machine

Use Remote Desktop (RDP) connection.

#### Connect to a Docker Container

If you use the official Squidex container, you can run the following command to get a shell to the running container. If you have built a custom image, bash might not be installed.

```bash
command docker exec -it <container name> /bin/bash
```

#### Connect to a Kubernetes Pod

Use the following command to get a shell to the running container inside the pod:

```bash
kubectl exec -it <pod_name> -- /bin/bash
```

### 2. Install .NET SDK

Usually the .NET SDK is not installed on your server and only the runtime is installed. This is also true for the official Docker image. You can find detailed installation instructions for your environment from the official documentation here:

{% embed url="<https://docs.microsoft.com/en-us/dotnet/core/install>" %}
How to install .NET SDK
{% endembed %}

If you use the official Docker image, you can execute the following steps:

```
apk add wget

# Download DotnetSDK Installer
wget -O sdk_install.sh https://dot.net/v1/dotnet-install.sh

# Add permissions to file
chmod 777 sdk_install.sh

# Install sdk.
./sdk_install.sh -c 5.0
```

### 3. Install the .NET Tools

There are a wide range of tools that are helpful:

{% embed url="<https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters>" %}
.NET tools
{% endembed %}

We focus on the following tools:

1. **dotnet-dump:** Creates a full dump of the process. You can analyze the memory usage, stack traces and threads using this tool or third-party solutions. The resulting file is really big, usually it has around the same size as the process.
2. **dotnet-gcdump**: This global tool collects GC (Garbage Collector) dumps of live .NET processes. GC dumps are created by triggering a GC in the target process, turning on special events, and regenerating the graph of object roots from the event stream. This process allows for GC dumps to be collected while the process is running and with minimal overhead. These dumps are useful for several scenarios:
   * Comparing the number of objects on the heap at several points in time.
   * Analyzing roots of objects and memory leaks.
   * Collecting general statistics about the counts of objects on the heap.

In the following steps, we focus on **dotnet-gcdump.**

Run the following commands to install **dotnet-gcdump**:

```
# Go to the dotnet folder. In order to use SDK the process needs to be run directly
cd /root/.dotnet

# install dotnet gcdump or install dotnet-dump to get a full dump
./dotnet tool install --global dotnet-gcdump 

# Go to the tools folder
cd tools
```

### 4. Create the Dump

Run the following commands to create the dump using **dotnet-gcdump**:

```
# Go to the tools folder
cd /root/.dotnet/tools

# dotnet-gcdump collect -p 1
```

In Docker and Kubernetes, there is only one .NET process running with the Process ID (`pid`) 1. In a separate environment, you can use the following command to list the dotnet processes that GC dumps can be collected for:

```
dotnet-gcdump ps
```

### 5. Download the Dump to Your Local Machine <a href="#synopsis-2" id="synopsis-2"></a>

The process to copy the dump file to your local machine depends on your environment. We have examples for Docker and Kubernetes below.

#### How to Copy in Docker

```
docker cp <container_name>:<container_path> <local_path>
```

An example of what the command may look like is below:

```
docker cp squidex:/root/.dotnet/tools/core_20211101_160516 full.dump  
```

#### How to Copy in Kubernetes

```
kubectl cp <pod_name>:<pod_path> <local_path>
```

An example of what the command may look like is below:

```
kubectl cp squidex-123:/root/.dotnet/tools/core_20211101_160516 full.dump  
```


# Restoring Deleted Apps

This Guide Details How to Restore an App or Other Object That Might Have Been Accidentally Deleted

## When Can I Restore An App?

Squidex uses event sourcing to store the state of an object (an App, content, asset and so on). When an object is deleted, a new deletion event is added to the system and the object is marked as deleted. Therefore, most objects can be restored.

However, with Squidex 6.0 a new flag has been introduced: `apps:deletePermanent`. When this flag is set to `true`, a deletion process deletes all app data in the background. Therefore, it is not possible to restore the data.

## Use a Backup

If a backup of the data is available, it is best to restore that backup, but this process requires taking the system offline. If this is not possible, proceed with the following guide.

Read more about backups in the following article:

{% content-ref url="/pages/-MRfwCpJG6ltP-wz3xH4" %}
[Backups](/id-02-documentation/concepts/backups)
{% endcontent-ref %}

## Restore Process

Before continuing, you need a way to connect to the MongoDB instance. A graphical management tool, such as [https://studio3t.com/](https://studio3t.com), is recommended. If the database is not accessible from outside, login to the server directly, or use port forwarding.&#x20;

If MongoDB is running in Kubernetes, use `kubectl port-forward` command to forward the port.

```
kubectl port-forward mongo-0 27018:27017
```

Then the MongoDB server will be accessible under `mongodb://localhost:27018`.

### Step 0: Prepare a Backup

Before continuing, backup the database. If you don't have an existing process in place, create a backup using **studio3t**:

<https://studio3t.com/knowledge-base/articles/mongodb-export-csv-json-sql-bson/#export-mongodb-to-bson-or-mongodump>

### Step 1: Delete the `Deletion` Event

The main data source in Squidex is a list of events. Most other states are derived from that list, so you can easily restore the other states from the events.

1. Go to the **Events** (1) collection.
2. Find the deletion event with the following filter: \
   `{ "Events.Type": "AppDeletedEvent" }`(2).
3. Delete this document (3).

<div align="left"><figure><img src="/files/j2muEYXhMczFjPwY61gu" alt=""><figcaption><p>Deleting an AppDeletedEvent document from database</p></figcaption></figure></div>

### Step 2: Update the App Document

For this, there are two options. Variation 1 is faster, but does not work well if you have to undo a lot of changes.

#### Variation 1:

In this variation, changes are made in the database manually. It is faster, but there is the possibility of making mistakes. It is also time consuming if there are lots of changes.

To update the document for the App in the database, follow the procedure below:

1. Go to the **States\_Apps** (1) collection.
2. Set the `isDeleted` (2) and `_dl` (3) fields to `false`. Ensure that you update both fields.
3. Decrement the `version` (4) & `Version` (5) fields. There are two entries, ensure that you update both of them.
4. **Save** the document.

<figure><img src="/files/BzHKOYwxxx6IqfMaayvM" alt=""><figcaption><p>Updating the App document from database</p></figcaption></figure>

#### Variation 2

In the second variation you can use the rebuild process. This part of Squidex runs before startup and uses the events to rebuild all documents of a given type, for example, all Apps or all assets.

1. Stop your Squidex instance.
2. Turn on the rebuild process: <https://github.com/Squidex/squidex/blob/master/backend/src/Squidex/appsettings.json#L553>, for example set `REBUILDER__APPS=true` as environment variable (**IMPORTANT**: this it is a double underscore).
3. Start your Squidex instance and wait until the rebuild is over and your Squidex instance is available again. If you use multiple instances, set the instance count to 1 to ensure that the rebuild is not running multiple times or in parallel.
4. Stop the instance.
5. Turn off the rebuild process, for example, by removing the environment variable or by setting the value to `false`.
6. Restart your instance. If you use multiple instances, remember to set the instance count to your previous value to gain high availability once more.


# Install Identity (Deprecated)

Learn How to Install Squidex Identity

## About Squidex Identity

The Squidex Identity server is based on the Squidex Headless CMS. It implements the OpenID Connect and OAuth 2.0 protocols to act as a central, single sign-on server.

> <https://github.com/Squidex/squidex-identity>

{% hint style="info" %}
WARNING: Squidex Identity is no longer maintained.
{% endhint %}

## 1. Setup of Squidex Identity

### 1.1. Clone Squidex.Identity

Clone the Squidex identity repository with the following command:

```
git clone https://github.com/Squidex/squidex-identity.git
```

### 1.2. Create an Identity App

You can create the App with predefined schemas either in the Cloud or in your custom installation:

![Create Identity App](/files/-LsBt80nvHm2aydmTOxM)

### 1.3. Update the Identity Configuration

Update the configuration with the URL to your Squidex instance along with the Client ID and Secret of the default client.

![Copy Default Client](/files/-LsBt80p5AnaW5jPgWh1)

Update the configuration file at: `Squidex.Identity/appsettings.json`.

```javascript
"app": {
    // ...
    "url": "https://cloud.squidex.io",
    "clientId": "identity:default",
    "clientSecret": "xxx",
    // ...
}
```

Of course, you can also use environment variables, e.g.

* `APP__URL=https://cloud.squidex.io`
* `APP__CLIENTID=identity:default`
* `APP__CLIENTSECRET=xxx`

## 2. General Application Settings:

If you create an identity App in Squidex you will see a schema with the settings where you can upload a logo, footer text, privacy settings and so on.

Most settings are optional but you must setup credentials to an SMTP server.

![Site Setting](/files/-LsBt80rZNt36fjiMLJY)

Email Delivery Service:

* [https://www.mailjet.com/](https://www.mailjet.com)
* [https://www.sendgrid.com/](https://www.sendgrid.com)

## 3. External Authentication Providers

If you want to use external authentication providers, you can set them up in the authentication schemes section. Here is an example for Google:

You have to create an OAuth 2.0-Client-IDs in the Google Developer console. You must define the `redirect_uri` in this process and you must use `http://localhost:3500/signin-google`.

The redirect URLs for other authentication providers are:

* `http://localhost:3500/signin-twitter`
* `http://localhost:3500/signin-facebook`
* `http://localhost:3500/signin-github`

![Authentication Schemes](/files/-LsBt80ta2yUJa602S-9)

## 4. External Clients

When you want to connect an external application to Squidex identity, you have to configure a client. This is a little bit complicated, but you can find all settings here: <http://docs.identityserver.io/en/latest/reference/client.html>

### Squidex as an External Client

You can also setup Squidex as an external client, so that the same users can also login to manage content.

During first step, you have to create a new client:

![Self-Hosted](/files/-LsBt80vMScPzcyrsV3z)

![Self-Hosted](/files/-LsBt80x1U24hIxUro6v)

In the second step you have to update the Squidex configuration at `Squidex/appsettings.json`.

```javascript
"identity": {
    ...
    "oidcName": "selfHostedName",
    "oidcAuthority": "http://localhost:3500/",
    "oidcClient": "client:selfHosted",
    "oidcSecret": "xxx",
    ...
}
```

Then you can register at Squidex identity.

![Self-Hosted](/files/-LsBt80zXc1u5lA4HQMV)


# External Identity Providers

Instructions to using external Identity Providers with Squidex

This section provides methods to configure external identity providers with your installation of Squidex.

More options coming soon.

{% content-ref url="/pages/WiEElEQbVhvRppSEgBU8" %}
[Azure AD (OAuth) with Squidex](/id-01-getting-started/installation/external-identity-providers/azure-ad-oauth-with-squidex)
{% endcontent-ref %}


# Azure AD (OAuth) with Squidex

The steps in this article show how to use Azure AD (OAuth) as the Identity Provider for Squidex

[Azure AD](https://azure.microsoft.com/en-us/products/active-directory) is a fully-managed Cloud-based identity service from Microsoft providing SSO, MFA and conditional access, amongst many other features.&#x20;

Azure AD provides lots of authentication mechanisms such as OAuth, OIDC and Graph etc. This article uses the plain OAuth method. It shows you how to use Azure AD as an identity provider with a **self-hosted Squidex installation**. Azure AD provides authentication and authorization to users in the Azure AD tenant for your App.

{% hint style="info" %}
A basic understanding of Azure AD is highly recommended as the article does not explain what Azure AD is and how it works. Microsoft Learn has excellent [documentation](https://learn.microsoft.com/en-us/azure/active-directory/) for this.
{% endhint %}

## Prerequisites

These prerequisites should be in place to successfully follow this article:

* An Azure Active Directory tenant (FREE tier will work).
  * It is assumed that the Azure AD has a domain associated with it, e.g. *example.com*.
* An installation of Squidex. See [instructions](/id-01-getting-started/installation) available for various platforms.

{% hint style="info" %}
An Azure subscription is NOT required for this setup
{% endhint %}

## Azure AD Configuration

### Register an Azure AD App

During this step, you will register an application with Azure AD and gather a few details that will be required to configure Squidex.

1. Start by navigating to <https://portal.azure.com>.
2. In the top search box, type **Active Directory** (1) and select **Azure Active Directory** (2) from the list.

   <figure><img src="/files/dVToiZqkMvvMP2jiqmQA" alt=""><figcaption><p>Navigate to Azure Active Directory</p></figcaption></figure>
3. On the Azure AD page, select **App registrations** (3) from the left blade and then click **+ New registration** (4).

   <figure><img src="/files/0TUyJAsvtE0BSYLOjbGD" alt=""><figcaption><p>Start registration of a new app</p></figcaption></figure>
4. On the *Register an application* screen enter a **Name** (5) for the App, select **Accounts in this organizational directory only** (6).\
   Under the **Redirect URI (optional)** section, set the following values:

   1. Choose **Web** (7) from the drop down
   2. Then enter your Squidex domain URL followed by `/signin-microsoft`.\
      The URI must have `/signin-microsoft` else it will fail.

   Click **Register** (8) when done.

   <figure><img src="/files/Sgt4wuGMRjuXE074Xwhy" alt=""><figcaption><p>Register an app</p></figcaption></figure>
5. In the Azure AD app details page, copy the **Application (client) ID** (9) and **Directory (tenant) ID** (10) values as they will be needed later.

   <figure><img src="/files/qpUjfRvKjd1DQsuepqJs" alt=""><figcaption><p>Note down App ID and Tenant ID</p></figcaption></figure>
6. Select **Certificates & Secrets** (11) from the left, next click **+ New client secret** \
   (12). Give a **Description** (13) for the secret and click **Add** (14).

   <figure><img src="/files/fpiWFWzeLcgy4fo8OeZX" alt=""><figcaption><p>Generate client secret</p></figcaption></figure>
7. Copy the secret **Value** (15) shown in this step.

   <figure><img src="/files/qtLIK7dekL4Bzzk6URre" alt=""><figcaption><p>Note down client secret value</p></figcaption></figure>
8. Finally click on **Authentication** (16), check **ID tokens** (17) and click **Save** (18).

   <div align="left"><figure><img src="/files/0RsBxB43TpGqxxLkij5u" alt=""><figcaption><p>Enable ID token</p></figcaption></figure></div>

## Configure Squidex for Azure AD&#x20;

In the next set of steps you will configure Squidex to use Azure AD. Depending on how Squidex is installed / running the steps to add these values will be different and unfortunately we cannot cover everything but in a nutshell, you want the following variables configured with the values collected earlier.&#x20;

When the values are `null`, Microsoft authentication is disabled.

| Variable                    | Description             |
| --------------------------- | ----------------------- |
| `IDENTITY__MICROSOFTCLIENT` | Application (client) ID |
| `IDENTITY__MICROSOFTSECRET` | Client Secret Value     |
| `IDENTITY__MICROSOFTTENANT` | Directory (tenant) ID   |

### Kubernetes / Helm Example

For example, if using Kubernetes Helm charts, these variables should be updated with the values in the `values.yaml` file.

<div align="left"><figure><img src="/files/SF17b9eBxjTt5yZPouzI" alt=""><figcaption><p>Sample helm values.yaml configuration for enabling Microsoft authentication </p></figcaption></figure></div>

### Azure App Service Example

Similarly, if Squidex is running on an Azure App Service, they are added to the *Configuration* of the app service. An example is provided below.

<div align="left"><figure><img src="/files/7mcBiMZhr8k1K0mSxXXc" alt=""><figcaption><p>Sample app service configuration for enabling Microsoft authentication </p></figcaption></figure></div>

### Docker Example

When running it using Docker Compose (or Docker) they are updated in a `.env` file that is used by the `docker-compose.yml` file or you can update the values directly in the `docker-compose.yml` file itself (not recommended).&#x20;

The rest of the steps here refer to a Docker based containerized installation that uses the code/resources provided at <https://github.com/Squidex/squidex-hosting/tree/master/docker-compose>  and uses a `.env` file.

1. In the `.env` file set the values. Note that these variables are only used in the context of Docker, they are referenced by the actual variables in the `docker-compose.yml` file.\
   \
   A sample screenshot is provided below.

   <figure><img src="/files/lBkXCzO5DJsceApnOYRZ" alt=""><figcaption><p>Sample docker environment values for enabling Microsoft authentication </p></figcaption></figure>
2. Next update the Squidex container by recreating it. Run the following command to do so:\
   \
   `docker-compose up -d --force-recreate squidex_squidex`\
   \
   `squidex_squidex` is the default name of the container if you are running the `docker-compose.yml` file provided by us.&#x20;

## Test the Configuration

1. Reload the Squidex page and click **Login to Squidex**.
2. You should see a **Login with Microsoft** button. In the Signup section, you can also **Signup with Microsoft**.

   <div align="left"><figure><img src="/files/8H9LltLta61m7ZLKybhT" alt=""><figcaption><p>Signin with Microsoft</p></figcaption></figure> <figure><img src="/files/ign37Wj7MeFhQUIMT0id" alt=""><figcaption><p>Signup with Microsoft</p></figcaption></figure></div>
3. On the next screen, enter a username and password from the Azure AD tenant.
4. When prompted for permissions, click **Accept**.

   <div align="left"><figure><img src="/files/J4rNgaMujOGPhG0dR3Xj" alt=""><figcaption><p>Accept permissions</p></figcaption></figure></div>
5. You should be now be able to Signup or Login with a Microsoft account from the configured Azure AD tenant using OAuth.


# Contributing and Developing

Find Out More About Contributing and Developing


# Building

How to Build Squidex Using Docker or Manually

## 1. Build for docker

You can view provided Docker images on Docker hub: <https://hub.docker.com/r/squidex/squidex/>

* `squidex/squidex:latest` (the latest stable version).
* `squidex/squidex:vX.XX` (a specific stable version).
* `squidex/squidex:dev` (the latest dev version - master branch).
* `squidex/squidex:dev-XXXX` (a specific dev version - master branch).

To build a custom image, use the multistage `Dockerfile` and simply run the following command:

```bash
docker build . -t my/squidex
```

## 2. Build Squidex for Manual Deployment

When you want to deploy to IIS or NGINX you may prefer to build manually. Note that we also provide prebuilt binaries on GitHub, link below:

<https://github.com/Squidex/squidex/releases>

You will find the built files under `$SQUIDEX/publish`.

{% hint style="info" %}
`$SQUIDEX`is a placeholder for the path to your local copy of the Squidex source code.
{% endhint %}

### 2.1. Build Squidex with Docker

Run the following commands in *PowerShell* or *bash* to build Squidex with Docker:

```bash
# Build the image
docker build . -t squidex-build-image

# Create the container
docker create --name squidex-build-container squidex-build-image

# Copy the output to the host file system
docker cp squidex-build-container:/app/. ./publish

# Cleanup
docker rm squidex-build-container
docker rmi squidex-build-image
```

Alternatively, we also provide a script file (containing these commands) for both Windows and Linux.

For Windows, use the `build.ps1` script.

For Linux, use the `build.sh` script.

{% hint style="info" %}
We recommend giving the Docker machine at least 4GB of memory.
{% endhint %}

### 2.2. Build Squidex Manually

If you don't want to use Docker, you can also build manually.

#### 2.2.1. Build the current Version

The current structure differentiates between the frontend and the backend.

<div align="left"><figure><img src="/files/AcexV81rNEgxVpxwOMCH" alt=""><figcaption><p>Project structure</p></figcaption></figure></div>

The advantage is that the code is separated, so you can use multiple contains to build them independently, this makes better use of caching in Docker. After building frontend and backend,  copy the build artifacts to a common folder. You can use `$SQUIDEX/publish` for this step.

To build the backend, run the following commands.

```bash
cd backend
cd src/Squidex
dotnet publish --configuration Release --output "../../../publish"
```

To build the frontend, run the following commands.

```bash
cd frontend
npm i # Install npm packages
npm run build
copy build "../publish/wwwroot/build"
```

#### 2.2.2. Build the .NET Core 2.0 Version without Docker

Very old versions that still work with .NET Core 2.0 have a different structure. Therefore the build process is slightly different. For most people this version is not relevant anymore.

```bash
npm i
npm run build

dotnet restore
dotnet publish --configuration Release --output "../../publish"
```

Please note, on Windows you must run *PowerShell* or *CMD.exe* in elevated mode (**Run as Administrator**) to install the required build tools for `node-sass`.

```bash
npm install --global --production windows-build-tools
```

{% hint style="info" %}
We recommend building Squidex with Docker, because it ensures a clean environment. Due to Docker [layers](http://bitjudo.com/blog/2014/03/13/building-efficient-dockerfiles-node-dot-js/), the build isn't much slower and in some cases, it can actually be quicker.
{% endhint %}


# Developing

An Introduction on Setting Up Your Development Environment (this step is required if you want to contribute to Squidex or write customized extensions for it)

You can find the source code on GitHub: <https://github.com/squidex/squidex>

## Required Tools

To work with the source code you need the following tools.

### Docker

We recommend using Docker on your developer machine, it makes life much easier!

* [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
* [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/)
* [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/)

{% hint style="info" %}
Note, we weren't able to run newer versions of Docker on Windows 10 Home. If you don't have a Windows 10 PRO license, we recommend not wasting your money. Get a cheap build server for a few bucks per month instead, or just install MongoDB manually. It only takes around 15 minutes to install.
{% endhint %}

### For the Backend

* [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
* [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) (for version <= 6.1)
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) or [.NET Core 3.0 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.0) (for older versions of Squidex)
* [MongoDB](https://www.mongodb.com)
* Optionally: [RabbitMQ](https://www.rabbitmq.com/download.html)
* Optionally: [EventStore](https://eventstore.org)

#### Setup

You'll find ready to use Docker configurations for development at <https://github.com/squidex/squidex-docker>.

Execute the following commands to get a MongoDB installation for development.

```bash
git clone https://github.com/squidex/squidex-docker
cd squidex-docker/development
docker-compose up -d
```

### For the Frontend

* [NodeJS](https://nodejs.org/en/) (>= 10.0)

Usually, newer versions are better, but the newer versions of NodeJS are renowned for consuming a lot of memory during a build or when running *Webpack Dev Server*.

### IDEs and Editors

You can use any editor you want, but our recommendation is to use:

* [Visual Studio 2022 Community Edition](https://visualstudio.microsoft.com/de/vs/community/) OR [Visual Studio 2019 Community Edition ](https://visualstudio.microsoft.com/vs/?rr=https%3A%2F%2Fwww.google.com%2F)for the backend.
* [Visual Studio Code](https://code.visualstudio.com) for the frontend with the following plugins installed:
  * [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) (to run linting for typescript).
  * [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) (to run linting for scss / css).
  * [Angular Language Service](https://marketplace.visualstudio.com/items?itemName=Angular.ng-template).

## How to Run Squidex

To run Squidex, you must run both frontend and backend independently. This might feel redundant and annoying at first (we also had to use some code to run the the *Webpack Dev Server* automatically when the application started), but it only takes a minute for the *Webpack Dev Server* to begin. We have decoupled the commands so you can keep the *Webpack Dev Server* running, even when you have to restart the backend application.

{% hint style="info" %}
Before you start, ensure that the certificates for the test environment are installed. They can be found under `/dev` in the Squidex folder.
{% endhint %}

### Frontend

The frontend is written with [Angular](https://angular.io) and [webpack](https://webpack.js.org). Therefore you have to run the [webpack web dev server](https://webpack.js.org/configuration/dev-server/). It's a server application that builds the website and watches the file system. Whenever you make a change to a file, the server runs the build process and reloads the website automatically. It only tries to compile the files that have changed and in some cases, can even reload the style sheets without reloading the site.

#### How to run the Webpack Dev Server?

```
cd frontend # Go to the frontend
npm i # Install all dependencies, only needed the first time
npm start
```

{% hint style="info" %}
Installing the dependencies is only needed once or when the `project.json` file has changed.
{% endhint %}

Optionally:

1. `npm rebuild node-sass --force` (Only if you have issues with node-sass)
2. `npm test` (Runs the unit tests and listens for changes)
3. `npm run test:coverage` (Runs the unit tests and calculates the test coverage).

As the name *Webpack Dev Server* indicates, it's only used for development. For production we bundle and minimise all typescript, html and sass files and add the bundles to the deployment package. During development, the frontend downloads hundreds of files, this is a completely normal process.

### Backend

```bash
cd backend
cd src/Squidex
dotnet restore # Install all dependencies
dotnet run
```

Ensure that the `ASPNETCORE_ENVIRONMENT` environment variable is set to `Development`, either through the `launchSettings.json` file or through your IDE's settings.

Open <https://localhost:5001> to run Squidex.

You can also run and debug the backend with Visual Studio 2019. Here are some **recommended** steps you should undertake before starting your debug session:

1. Ensure that you run the `Squidex` project, which means that you use the integrated Kestrel web server (this begins faster than IIS Express).
2. Uncheck the `Launch browser` setting. You just want to keep Squidex open during development (and not constantly close and open the window) to make debugging the frontend with your browser easier.


# Extensions

How to Extend Squidex and the Different Extension Points for Customization.

## Introduction

This document describes how to write extensions for Squidex. We assume that you know the basic principles of C# and ASP.NET Core. Otherwise, it might be very hard to write custom extensions.

We use interfaces for all components, which can then be replaced with custom implementations. The default dependency injection system is used to register services. (see <https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection>).

Furthermore, you can extend Squidex with custom HTTP endpoints. Due to the static nature of the Management UI (which is built with Typescript and Angular and has a compilation and bundling process), you cannot extend the UI, except in a few cases where its been made possible.

Read the following article to understand how to write custom editors for the Management UI:

{% content-ref url="/pages/-LrLSGK56XXQ43fZlzx-" %}
[Custom Editors](/id-02-documentation/developer-guides/editors)
{% endcontent-ref %}

## How to Write a Custom Plugin

So far (as of October 2019), Squidex has a limited plugin architecture that can be used for most cases. In this section we will give you an introduction how to write this type of plugin, and how to register it in Squidex.

In this tutorial we use `Squidex.Extensions` project as an example, which also contains a sample plugin.

### 1. Clone the source code

Checkout the source code from GitHub:

```bash
git@github.com:Squidex/squidex.git
```

You only need the source code for development. There are other approaches on how to deploy the newest version of Squidex with your plugin.

### 2. Create a .NET Core Class Library

First, you need to create a new .NET class library to the backend solution:

<div align="left"><img src="/files/-LsNu8QIjUKNp8JB5lLL" alt="Squidex.Extensions Plugin"></div>

Ensure that you target `netcoreapp3.0`.

Then add references to the following other projects in the solution:

1. `Squidex.Domain.Apps.Entities`
2. `Squidex.Web`

This will implicitly create references to all other projects that might be needed.

{% hint style="info" %}
The reference projects contain interfaces as well as implementations - which is a problem, because it's hard to figure out which classes should be used in custom plugins. Implementations are subject to change and should not be used directly. Therefore we plan to restructure the content and create implicit contract projects, which don't contain implementation details.
{% endhint %}

### 3. Create a New Plugin Class.

A plugin class is used to register all implementations to Squidex. In this example we create a custom plugin which registers an asset store and keeps all files in memory. We register it only when it's activated via the configuration system, so either from the `appSettings.json` file, command line arguments or environment variables.

```csharp
namespace Squidex.Extensions.Samples.AssetStore
{
    public sealed class MemoryAssetStorePlugin : IPlugin
    {
        public void ConfigureServices(IServiceCollection services, IConfiguration config)
        {
            var storeType = config.GetValue<string>("assetStore:type");

            if (string.Equals(storeType, "Memory", StringComparison.OrdinalIgnoreCase))
            {
                services.AddSingletonAs<MemoryAssetStore>()
                    .As<IAssetStore>();
            }
        }
    }
}
```

You can also add multiple implementations to your class library if you have multiple extensions in one project and want to group them together.

### 3. Reference your plugin

Add a reference to your plugin in the `Squidex` project.

<div align="left"><img src="/files/-LsNwE9xa7FzA65GlXFc" alt="Add reference to your plugin"></div>

Furthermore, you need to add the path to your plugin to the configuration, for example to the `appSettings.json` file. As we reference the plugin, it will be automatically copied to the output folder when the build or packaging is executed.

<div align="left"><img src="/files/-LsNxFwqs9habJNJIl4f" alt="Reference your plugin in the configuration file"></div>

There is also a project on GitHub that demonstrates how to create a plugin for SendGrid with an older version of Squidex: <https://github.com/squidexcontrib/sendgrid>

## Extension Points

There are a few extension points that can be used for custom functionality.

### Rule Actions

Rule actions are used to integrate external systems to Squidex. Therefore, we have written a guide to show you how to write your first rule action here:

{% content-ref url="/pages/-LswZ5ko\_SJilyJM7\_\_p" %}
[Custom Rule Action](/id-01-getting-started/contributing-and-developing/extensions/how-to-write-custom-rule-actions)
{% endcontent-ref %}

### Controllers

You can write your own controllers, but you have to extract them from the `ApiController` class. Your controllers are available under the `/api` path, because all other requests will be forwarded to the Management UI. The `ApiController` enforces this rule and ensures the correct routing.

```csharp
public sealed class PluginController : ApiController
{
    public PluginController(ICommandBus commandBus)
        : base(commandBus)
    {
    }

    [Route("/plugins/sample")]
    public IActionResult Test()
    {
        return Ok(new { text = "I am Plugin" });
    }
}
```

### Infrastructure Extensions

#### IAssetStore

The `Squidex.Infrastructure.Assets.IAssetStore` interface is used to encapsulate storage solutions for assets. Currently there are the following implementations:

* `AzureBlobAssetStore`: Stores the assets in Azure Blob storage.
  * Read more: <https://azure.microsoft.com/en-us/services/storage/blobs/>.
* `GoogleCloudAssetStore`: Stores the assets in Google Cloud.
  * Read more: <https://cloud.google.com/storage/>.
* `MongoGridFsAssetStore`: Stores the assets in MongoDB using GridFS.
  * Read more: <https://docs.mongodb.com/manual/core/gridfs/>.
* `FolderAssetStore`: Stores the assets in the file system.

Recommended implementations:

* Amazon S3

#### IEventStore

The `Squidex.Infrastructure.CQRS.Events.IEventStore` is our abstraction for different event store implementations. You can append to events, query them or subscribe to events. Depending on your implementation, you might want to use the pub-sub system for subscriptions. The notification mechanism is provided by the `Squidex.Infrastructure.CQRS.Events.IEventNotifier` interface. Currently, there are these implementations as follows:

* `Squidex.Infrastructure.CQRS.Events.MongoEventStore`: Implementation for MongoDB.
  * Read more: <https://docs.mongodb.com/ecosystem/drivers/csharp/>.
* `Squidex.Infrastructure.CQRS.Events.GetEventStore`: Implementation for EventStore.
  * Read more: [https://geteventstore.com/](https://geteventstore.com).

Recommended implementations:

* SQL Databases

The `Squidex.Infrastructure.CQRS.Events.IEventConsumerInfoRepository` defines the contract for a system to store the current state of each event consumer.

#### IAssetThumbnailGenerator

The `Squidex.Infrastructure.Assets.IAssetThumbnailGenerator` interface encapsulates image transformations. We only have an implementation for ImageSharp: <https://github.com/SixLabors/ImageSharp>

### Repositories

You can provide other implementations for repositories, e.g. for Elastic Search or SQL:

* `Squidex.Domain.Apps.Entities.Assets.Repositories.IAssetRepository`: Stores metadata about assets such as names and sizes, but not the content itself. It can be challenging to implement the filtering.
* `Squidex.Domain.Apps.Entities.Contents.Repositoriess.IContentRepository`: Stores the content itself. It can be challenging to implement the filtering.
* `Squidex.Domain.Apps.Read.History.Repositories.IHistoryEventRepository`: Stores basic history events to show them in the UI.
* `Squidex.Domain.Apps.Entities.Rules.Repositories.IRuleEventRepository`: Stores the rule events, like an internal job queue.
* `Squidex.Infrastructure.States.IStore`: Key value store for JSON objects. It contains everything else like comments, apps, schemas, rules, custom indices and settings.
* `Squidex.Infrastructure.UsageTracking.IUsageTracking`: Stores historic usage information and performance metrics.

### Command Middleware

Command middlewares are used to handle commands, for example when a new content item is created. They run in a pipeline and can be used for a lot of different purposes. It's safe to implement the `ICustomCommandMiddleware` interface which ensures that your commands are running in the correct order.

```csharp
namespace Squidex.Infrastructure.CQRS.Commands
{
    public interface ICustomCommandMiddleware
    {
        Task HandleAsync(CommandContext context, Func<Task> next);
    }
}
```

They accept two parameters. The first is the command context that also includes a reference to the command. The next is a function to call the next command handler. Typical use cases are changes to one domain object, for example default fields for new schemas.

#### Example 1: Handle Command

If you can accept the command, handle it and call `Complete()`.

```csharp
class MyHandler : ICustomCommandMiddleware
{
    public async Task HandleAsync(CommandContext context, Func<Task> next) 
    {
        if (context.Command is MyCommand myCommand)
        {
            await Handle(myCommand);
            context.Complete();
        }
        else
        {
            await next();
        }
    }
}
```

#### Example 2: Measure Performance

```csharp
class MyHandler : ICustomCommandMiddleware
{
    public async Task HandleAsync(CommandContext context, Func<Task> next) 
    {
        var watch = Stopwatch.StartNew();

        try
        {
            await next();
        }
        finally
        {
            watch.Stop();

            Log(watch);
        }
    }
}
```

#### Example 3: Enrich Command

```csharp
class MyHandler : ICustomCommandMiddleware
{
    public async Task HandleAsync(CommandContext context, Func<Task> next) 
    {
        if (context.Command is UserCommand userCommand)
        {
            userCommand.UserId = await GetUserIdAsync();
        }

        await next();
    }
}
```

### Event Consumers

Event consumers are invoked when new events are created or when an event consumer is restarted and old events are replayed. You shouldn't raise new events, but of course you can create a new event.

```csharp
namespace Squidex.Infrastructure.CQRS.Events
{
    public interface IEventConsumer
    {
        // The name of the event consumer to display in the UI.
        string Name { get; }

        // Filter events by the stream name. Use regular expressions.
        string EventsFilter { get; }

        // Implement this method if you are interested to filter events before they are deserialized.
        bool Handles(StoredEvent @event);

        // Will be invoked when the event consumer is restarted.
        Task ClearAsync();

        // Will be called for each new or replayed event.
        Task On(Envelope<IEvent> @event);
    }
}
```


# Custom Rule Action

Rule Actions are Used to Integrate External Systems to Squidex, Learn How to Extend the Rule System Using Custom Actions

We will use the `WebhookAction` as an example to show you the basic principles.

## Step 1: Get Ready

To get started with your first rule action you might want to gain an understanding about the rule system first. Please read the following document before you continue:

{% content-ref url="/pages/-LrLSGJtMR37-NG2Rdd4" %}
[Rules](/id-02-documentation/concepts/rules)
{% endcontent-ref %}

## Step 2. Define Your Action Class

In the first step, let's define an action class.

The action class has several purposes:

1. It provides general metadata, such as the name of the action.
2. It holds all configuration values for your rule action.
3. It is used to automatically create the editor that is then used to create or edit an action.

Let's have a look at the `WebhookAction`:

```csharp
[RuleAction(
    Title = "Webhook",
    IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28'><path d='M5.95 27.125h-.262C1.75 26.425 0 23.187 0 20.3c0-2.713 1.575-5.688 5.075-6.563V9.712c0-.525.35-.875.875-.875s.875.35.875.875v4.725c0 .438-.35.787-.7.875-2.975.438-4.375 2.8-4.375 4.988s1.313 4.55 4.2 5.075h.175a.907.907 0 0 1 .7 1.05c-.088.438-.438.7-.875.7zM21.175 27.387c-2.8 0-5.775-1.662-6.65-5.075H9.712c-.525 0-.875-.35-.875-.875s.35-.875.875-.875h5.512c.438 0 .787.35.875.7.438 2.975 2.8 4.288 4.988 4.375 2.188 0 4.55-1.313 5.075-4.2v-.088a.908.908 0 0 1 1.05-.7.908.908 0 0 1 .7 1.05v.088c-.612 3.85-3.85 5.6-6.737 5.6zM21.525 18.55c-.525 0-.875-.35-.875-.875v-4.813c0-.438.35-.787.7-.875 2.975-.438 4.288-2.8 4.375-4.987 0-2.188-1.313-4.55-4.2-5.075h-.088c-.525-.175-.875-.613-.787-1.05s.525-.788 1.05-.7h.088c3.938.7 5.688 3.937 5.688 6.825 0 2.713-1.662 5.688-5.075 6.563v4.113c0 .438-.438.875-.875.875zM1.137 6.737H.962c-.438-.087-.788-.525-.7-.963v-.087c.7-3.938 3.85-5.688 6.737-5.688h.087c2.712 0 5.688 1.662 6.563 5.075h4.025c.525 0 .875.35.875.875s-.35.875-.875.875h-4.725c-.438 0-.788-.35-.875-.7-.438-2.975-2.8-4.288-4.988-4.375-2.188 0-4.55 1.313-5.075 4.2v.087c-.088.438-.438.7-.875.7z'/><path d='M7 10.588c-.875 0-1.837-.35-2.538-1.05a3.591 3.591 0 0 1 0-5.075C5.162 3.851 6.037 3.5 7 3.5s1.838.35 2.537 1.05c.7.7 1.05 1.575 1.05 2.537s-.35 1.837-1.05 2.538c-.7.612-1.575.963-2.537.963zM7 5.25c-.438 0-.875.175-1.225.525a1.795 1.795 0 0 0 2.538 2.538c.35-.35.525-.788.525-1.313s-.175-.875-.525-1.225S7.525 5.25 7 5.25zM21.088 23.887a3.65 3.65 0 0 1-2.537-1.05 3.591 3.591 0 0 1 0-5.075c.7-.7 1.575-1.05 2.537-1.05s1.838.35 2.537 1.05c.7.7 1.05 1.575 1.05 2.538s-.35 1.837-1.05 2.537c-.787.7-1.662 1.05-2.537 1.05zm0-5.337c-.525 0-.963.175-1.313.525a1.795 1.795 0 0 0 2.537 2.538c.35-.35.525-.788.525-1.313s-.175-.963-.525-1.313-.787-.438-1.225-.438zM20.387 10.588c-.875 0-1.837-.35-2.537-1.05S16.8 7.963 16.8 7.001s.35-1.837 1.05-2.538c.7-.612 1.662-.962 2.537-.962s1.838.35 2.538 1.05c1.4 1.4 1.4 3.675 0 5.075-.7.612-1.575.963-2.538.963zm0-5.338c-.525 0-.962.175-1.313.525s-.525.788-.525 1.313.175.962.525 1.313c.7.7 1.838.7 2.538 0s.7-1.838 0-2.538c-.263-.438-.7-.612-1.225-.612zM7.087 23.887c-.875 0-1.837-.35-2.538-1.05s-1.05-1.575-1.05-2.537.35-1.838 1.05-2.538c.7-.612 1.575-.962 2.538-.962s1.837.35 2.538 1.05c1.4 1.4 1.4 3.675 0 5.075-.7.612-1.575.962-2.538.962zm0-5.337c-.525 0-.962.175-1.313.525s-.525.788-.525 1.313.175.963.525 1.313a1.794 1.794 0 1 0 2.538-2.537c-.263-.438-.7-.612-1.225-.612z'/></svg>",
    IconColor = "#4bb958",
    Display = "Send webhook",
    Description = "Send events like ContentPublished to your webhook.",
    ReadMore = "https://en.wikipedia.org/wiki/Webhook")]
public sealed class WebhookAction : RuleAction
{
    [Required]
    [Display(Name = "Url", Description = "The url to the webhook.")]
    [DataType(DataType.Text)]
    [Formattable]
    public Uri Url { get; set; }

    [Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the signature.")]
    [DataType(DataType.Text)]
    public string SharedSecret { get; set; }

    [Display(Name = "Payload", Description = "The optional custom request payload.")]
    [DataType(DataType.MultilineText)]
    [Formattable]
    public string Payload { get; set; }
}
```

### Metadata

The metadata is provided with the `[RuleAction]` attribute and is mainly used in the Management UI.

You need to provide the following information:

| Metadata        | Description                                                                              |
| --------------- | ---------------------------------------------------------------------------------------- |
| (1) Icon        | The icon as an SVG document. It must be white only.                                      |
| (1) IconColor   | The background color for your icon.                                                      |
| (2) Display     | A display name that describes what the action does.                                      |
| (3) Title       | A title that describes the system that is integrated.                                    |
| (4) Description | A short description about the action                                                     |
| (5) ReadMore    | An optional link to additional information, e.g. the website of the integrated solution. |

<div align="left"><figure><img src="/files/WuefUyqP7GAvgjKQ7KVs" alt=""><figcaption><p>The metadata in the rule overview</p></figcaption></figure></div>

<div align="left"><figure><img src="/files/QdrnSWPHNASqdkLV9Xcx" alt=""><figcaption><p>The metadata when selecting an action.</p></figcaption></figure></div>

### Configuration Values and Editors

The properties of your action class hold the configuration values. You can only use primitives that can be serialized to JSON, such as `string`, `bool` or `int`.

Each property can also have a:

#### Name (1)

An optional name that is shown as label.

```csharp
[Display(Name = "My Name")]
```

#### Description (2)

An optional description that is rendered after the input field.

```csharp
[Display(Description = "My Description.")]
```

#### Formattable Hint (3)

A hint that describes whether the property supports formatting via scripting or placeholders. More about this later.

```csharp
[Formattable]
```

#### Required Hint (4)

A hint that the property is required. This will add validation to the API and the Management UI.

```csharp
[Required]
```

<div align="left"><figure><img src="/files/9IFXZGGHahYyQ9YnRni7" alt=""><figcaption><p>The formatting options in the UI</p></figcaption></figure></div>

#### Data Type

An optional data type to define the HTML control that is used:

| Control        | When                                                                                   |
| -------------- | -------------------------------------------------------------------------------------- |
| Checkbox       | Used when the type of the property is `bool`or `bool?`.                                |
| Number Input   | Used when the type of the property is `int` or `int?`.                                 |
| URL Input      | <p>Used with with the attribute</p><p><code>\[Editor(RuleFieldEditor.Url)]</code>.</p> |
| Password Input | <p>Used with the attribute</p><p><code>\[Editor(RuleFieldEditor.Password)]</code>.</p> |
| Email Input    | <p>Used with the attribute</p><p><code>\[Editor(RuleFieldEditor.Email)]</code>.</p>    |
| Text Area      | <p>Used with the attribute</p><p><code>\[Editor(RuleFieldEditor.TextArea)]</code>.</p> |
| Input          | For all other cases.                                                                   |

## Step 3: Develop Your Action Handler

As you know from the documentation concerning the rule system, the rules are executed in two steps:

1. The event is converted to a job that includes all formatted data.
2. The job is then executed.

We see this structure in the action handlers:

```csharp
public sealed class WebhookActionHandler :
    RuleActionHandler<WebhookAction, WebhookJob>
{
    public WebhookActionHandler(RuleEventFormatter formatter)
        : base(formatter)
    {
    }
    protected override (string Description, WebhookJob Data) 
        CreateJob(EnrichedEvent @event, WebhookAction action)
    {
       // Step 1: Create job.
    }
    protected override async Task<Result> 
        ExecuteJobAsync(WebhookJob job, CancellationToken ct = default)
    {
       // Step 2: Execute job
    }
}

public sealed class WebhookJob
{
    public string RequestUrl { get; set; }
    public string RequestBody { get; set; }
}
```

The `WebhookJob` contains all data that we want to store in the database and is a simplified version in this example.

### Create the Job

The first method we need to override is used to create the job:

```csharp
protected override (string Description, WebhookJob Data) 
     CreateJob(EnrichedEvent @event, WebhookAction action)
{
    var requestUrl = Format(action.Url, @event);
    var ruleDescription = $"Send event to webhook '{requestUrl}'";
    var ruleJob = new WebhookJob
    {
        RequestUrl = Format(action.Url.ToString(), @event),
        RequestBody = Format(action.Payload, @event)
    };
    return (ruleDescription, ruleJob);
}
```

As you can see, we create the job from the passed in action and also provide a short description about what we've done.\
\
We use the `Format` method to call the `RuleEventFormatter` that has been passed in via the constructor to apply formatting rules to our configuration values.

Whenever we do this, we must add the `[Formattable]` attribute to the properties to point out this behavior to the end users.

### Execute the Job

The second method is used to execute the job. We do not have access to our original configuration values anymore, therefore it is important to add all required information to the job:

```csharp
protected override async Task<Result>
    ExecuteJobAsync(WebhookJob job, CancellationToken ct = default)
{
    try
    {
        await HTTP(job.RequestUrl, job.RequestBody, ct);
		
	return Result.Success("My Request Dump");
    }
    catch (Exception ex)
    {
        return Result.Failed(ex, "My Request Dump");
    }
}
```

In this case, make an HTTP call with the provided request URL and body. We have to return a result object to indicate whether our job was successful or not.

Either way, exceptions are always handled but using the approach above we can provide an optional request dump with all necessary information to make debugging easy. This type of request dump should contain the request body and response or headers.

The passed in cancellation token should be used to handle timeouts correctly and the cancel long running requests when they have exceeded the allowed execution limit.

## Step 4: Register the Rule Action

We are almost done! Now, let's register the rule action, so for that, write a custom plugin:

```csharp
public sealed class WebhookPlugin : IPlugin
{
    public void ConfigureServices(IServiceCollection services, IConfiguration config)
    {
        services.AddRuleAction<WebhookAction, WebhookActionHandler>();
    }
}
```

That's it!

If you have written a custom rule action for a public system, like an SaaS solution, you can provide your implementation as a pull request.


# Contributing

Welcome to the Community! If Wou Want to Contribute to Squidex to Build the Most Awesome, Headless CMS, You're in the Right Place!

## Community

The support forum is the primary communication forum for the Squidex community. It's a great place to start, whether you have a question, you're a new contributor, a new user, or anything else. Make sure that you read the posting templates carefully before posting. The Squidex community is also governed by a [code of conduct](https://zulip.readthedocs.io/en/latest/code-of-conduct.html).

## Ways to Contribute

To create a code or documentation contribution, here's a small sample of the type of work that needs doing:

* Bug squashing and feature development on our .NET Core backend, or Angular framework.
* [Writing an integration](https://docs.squidex.io/01-getting-started/contributing-and-developing/extensions/how-to-write-custom-rule-actions).
* Improving our [documentation](https://github.com/squidex/squidex-docs2).
* [Reviewing code](https://github.com/squidex/squidex/pulls) and manually testing pull requests.

**Non-code Contributions**: Some of the most valuable ways to contribute don’t require touching the source code at all. Here are a few of them below:

* Reporting issues, including both feature requests and bug reports.
* Giving feedback if you're evaluating or using Squidex.
* Star us on GitHub, upvote us on product comparison sites, or write for [Squidex Blog](https://squidex.io/blog).

### Reporting Issues

If you find an easily reproducible bug and/or are experienced in reporting bugs, feel free to just open an issue in our support forum. We've decided to collect all issues in one single place, therefore the issues in Github are now closed.

**Reporting Security Issues**. Please do not report security issues publicly.

You can email issues to <hello@squidex.io>. We create a CVE for every security issue.

### User feedback

Nearly every feature we develop starts with a user request. If you are part of a group that is either using or considering using Squidex, we would love to hear about your experience with the product. If you’re not sure what to write, here are some questions we’re always very curious to know the answer to:

* **Evaluation**: What is the process by which your organization chose or will choose a group chat product?
* **Pros and Cons**: What are the pros and cons of Squidex for your organization, and the pros and cons of other products you are evaluating?
* **Features**: What are the features that are most important for your organization? In the best case scenario, what would your chat solution do for you?
* **Onboarding**: If you remember it, what was your impression during your first few minutes of using Squidex? What did you notice about it and how did you feel? Was there anything that stood out to you as confusing, or broken, or great?
* **Organization**: What does your organization do? How big is your organization? Can you provide a link to your organization’s website?
* **Cloud or Self-hosted**: What do your prefer, the Cloud or an on-premise solution? Can we convince you to move to the Cloud?

### Writing Samples

Our main programming language is C# and Typescript. However, Squidex speaks HTTP that can be used with any programming language. We cannot cover all programming languages that are out there in the wild! If you have developed a sample or extension, please share it with us. We would like to make it available for other users as well.


# Architecture

Short Description Concerning the Squidex Architecture.

## Concepts

Squidex is implemented based on the following concepts:

### CQRS

CQRS stands for *Command Query Responsibility Segregation*. It's a pattern that was first described by Greg Young. At its heart is the idea that you can use a different model to update information, rather than the model you use to read information. Typically, you use event sourcing as the primarily source of truth for your data and then you subscribe to the events to create read models that can be used to query and fetch information about your entities.

Read more from [Martin Fowler](https://martinfowler.com/bliki/CQRS.html)

### Event Sourcing

We can query an application's state to find out the current state of the world, and this answers many questions. However there are times when we don't just want to see where we are, we also want to know how we got there.

Event Sourcing ensures that all changes to an application state are stored as a sequence of events. Not only are we able to query these events, but we can also use the event log to reconstruct past states and as a foundation to automatically adjust the state to cope with retroactive changes.

A traditional representation of database state can be entirely recreated by reprocessing this event log. Event sourcing's benefits include strong auditing, creation of historic state, and replaying of events for debugging and analysis. Event sourcing has been around for a while, but we think it is used much less than it should be.

Although this approach adds a lot of complexity to the system it also has a lot of advantages:

1. There's a history of all changes.
2. It's possible to consume the events to create custom storage, e.g. you can use the Elastic Search Stack for full text search or statistics.
3. Data is never deleted. Even when there are bigger bugs, it's possible to provide fixes that restore all lost content.
4. There's no need to worry about data migration when changing the read models with a new version; just run the event consumers from the beginning to populate the read store with the updated data.

## Frameworks and Tools

Squidex is based on the following frameworks and tools:

* ASP.NET Core for the backend.
* Angular for the Management UI
* MongoDB for the event Store and persistence
* Orleans as the backend technology.

Of course there are also dozens of external dependencies (because there's no need to reinvent the wheel!), but it would just be too much information to list them all here.

See slides about the current architecture (June 2019): <https://www.slideshare.net/secret/yUk1EmYlw8pLZF>


# Translating

How to Provide a Translation for the Squidex API and Management UI.

## File Structure

There are several translation files in the repository but the files used by the API or the Management UI are automated using the translation tool. To use it, you only have to update the files under `backend/i18n/source`.

<div align="left"><figure><img src="/files/PHOwC04sYGrz33hDL2Bp" alt=""><figcaption><p>The file structure for source translation files</p></figcaption></figure></div>

There are separate files for frontend and backend, one for each language.

The master language is **English** (`backend_en.json`and `fronend_en.json`).

## How to Add a New Language

If you want to add a new language you have to complete a few manual steps:

1. Add the language to the backend: <https://github.com/Squidex/squidex/blob/de60af0bbb857cfb936edd3d1059d892e8f0bd1c/backend/src/Squidex/Config/Web/WebExtensions.cs#L35> (called culture in .NET).
2. Add the language to the frontend: <https://github.com/Squidex/squidex/blob/master/frontend/src/app/shared/state/ui-languages.ts>. Please use the native name in your language as the display name.
3. Add the language to the translator tool: <https://github.com/Squidex/squidex/blob/master/backend/i18n/translator/Squidex.Translator/Commands.cs#L143>.
4. Include the frontend translation file as an embedded resource: <https://github.com/Squidex/squidex/blob/4aa7f77df6945fcfe028e5ad092361f4e8e23aa4/backend/src/Squidex/Squidex.csproj#L144>
5. Create the backend translation file in the following folder: <https://github.com/Squidex/squidex/tree/master/backend/i18n/source>.
6. Create the frontend translation file in the following folder: <https://github.com/Squidex/squidex/tree/master/backend/i18n/source>.
7. Translate everything.
8. Run the translator.
9. Provide a PR.

## How to Update a Language

To update the translation for a language you need to complete the following steps:

1. Fork the source code.
2. Edit the corresponding JSON files with an editor.
3. Run the `translate.bat` or `translate.sh` file depending on the operation system.
4. Create a pull request.

{% hint style="info" %}
In the future, the `translate.bat` file will be executed by the build server but it is not quite ready yet.
{% endhint %}

## What Does the `translate.bat` File Do?

As you can see, the file executes the self-developed translation tool which consists of the following:

```bash
dotnet run translate check-backend ..\..
dotnet run translate check-frontend ..\..

dotnet run translate gen-frontend ..\..
dotnet run translate gen-backend ..\..
```

### Checking the Translations

The first step is to compare the translation files which do the following:

1. The translator tool will loop over all code files (`*.html, *.cshtml, *.cs, *.ts`) to analyze which strings are not translated yet. You can try to fix these errors but you'll need a lot of in-depth knowledge about the source code and architecture.
2. The translator tool will extract all used keys from the code files and compare the keys with the keys from the master language files. It prints all keys that don't exist in the master language (**missing keys**) or that exist in the master language file but not in the code files (**unused keys**). You can try to fix these errors, but you will need a lot of knowledge about user experience.
3. The translator tool compares all the translation files for all other languages with the master language and outputs all keys that do not exist in the language file (**missing keys**) or exist in the language file but not in the master language file (**unused keys**). Please try to fix all errors.

#### How to Fix Errors?

1. Key is missing: Get the English text from `backend_en.json` or `frontend_en.json` and translate the text into your language.
2. Key is unused: Just remove the label and the translation.

Sometimes, the keys are renamed, but it's easy to spot them when you see errors such as these:

```
Missing keys:
* new_KEY1
* new_KEY2

Unused keys:
* old_KEY1
* old_KEY2
```

You can just fix all these errors by replacing the prefix `old_` with `new_`.

### Generating the Final Files

The translator tool will also create the final files. It will take all translations for for all languages and add missing translations from the master language files.

The frontend files are also generated as JSON files under `backend/i18n`:

<div align="left"><figure><img src="/files/18vRq0hJka3hG3NSF4yB" alt=""><figcaption><p>Final frontend files</p></figcaption></figure></div>

The backend translations are generated as RESX files under `backend/src/Squidex.Shared/`:

<div align="left"><figure><img src="/files/uz5LicCY1ptFu0nyS8Sn" alt=""><figcaption><p>Final backend files</p></figcaption></figure></div>


# Squidex Docs Program

Earn Money by Improving Squidex Documentation.

### Programme Overview <a href="#program_overview" id="program_overview"></a>

Squidex Docs is our knowledge-base for the community. If you're a technical writer looking for assignments, you've come to the right place. We pay great rates for your articles!

Thank you for contributing to Squidex. We look forward to reading your articles!

### Programme Rules <a href="#program_rules" id="program_rules"></a>

* Articles must be in English, with proper spelling and grammar.
* You must submit original content. Do not plagiarize from other Squidex Docs or other sites.
* Articles that need extensive editing or have technical errors won't be accepted.
* Articles submitted to Squidex are not exclusive to Squidex Docs. You can republish your article elsewhere after submission.
* After payment, the content becomes the property of Squidex. We may reuse the content as needed or rewrite it to improve readability.
* Do not mention other providers that offer similar services to Squidex, except for feature comparisons.

### Payments and Rates <a href="#payments_and_rates" id="payments_and_rates"></a>

* We base payments on the amount of original writing, **excluding code blocks**.
* We may edit articles for clarity and the length may change significantly during the editing process.
* We review your account for payment eligibility.
* We pay using bank transfer.
* These payment rates are guidelines; we evaluate each article individually.
* If you want the highest payment for your article, we recommend these resources:

### New Articles <a href="#new_articles" id="new_articles"></a>

New articles that are ready to publish **without editing by Squidex** are eligible for the highest payments.

* New articles greater than 1,500 words are eligible for up to **$400**.
* New articles between 750 and 1,500 words are eligible for up to **$200**.
* New articles less than 750 words are eligible for up to **$100**.

### Article Updates <a href="#article_updates" id="article_updates"></a>

With pre-approval, we pay 50% of the new article rate for updates to existing articles. Updates should contain significant new information and should not plagiarize the original article.

### Technical Summaries <a href="#technical_summaries" id="technical_summaries"></a>

We may choose to accept technical summaries at 50% of the new article rate on a case-by-case basis. Technical summaries are incomplete articles that summarize a technical solution but do not comply with our style guide. Technical summaries require editing by our team before publishing. If you are a subject-matter expert on a particular topic, consider submitting a technical summary for us to review.

### Permitted Topics <a href="#permitted_topics" id="permitted_topics"></a>

Users reading your articles are primarily interested in server administration and applications. Therefore, our documentation library consists of these major categories:

* **Installation Guides** These are step-by-step installation and configuration instructions.
* **Quick-Start Guides** These are useful for users who want to use a tutorial on how to build a blog or similar application using Squidex.
* **Best Practices** These are short documents that explain the best way to complete a particular task, especially how to gain the best performance with Squidex.
* **Troubleshooting Guides** These are step-by-step instructions to resolve common problems.
* **Content Modelling Guides** These are instructions on how to model a use case, e.g. a hotel information portal or ticketing system with Squidex.
* **Frequently Asked Questions** These are common questions with short answers, rarely more than one paragraph and link to other documents as much as possible.

### Submission Process <a href="#submission_process" id="submission_process"></a>

There are six steps to the article submission process. Please save a local copy of your article before submitting it for review.

1. Please write your article in Markdown.
2. After you have edited and tested your article, send the article to <sebastian@squidex.io> to prove that you've written the article.
3. Create a pull request to the [Squidex Docs repository](https://github.com/squidex/squidex-docs2) at Github.
4. We perform an initial screening for spelling, formatting, grammar, plagiarism and topic suitability. This review may take several days. We will contact you through Github and ask for changes, if necessary.
5. We perform a full technical review of articles that pass the initial screening. We test all the steps exactly as written. For the article to be accepted, it must pass this test without errors. Depending on the number of pending submissions, the process may take a few weeks.
6. If we approve the article, we will send you a payment proposal.


# Roadmap

Planned Features and Changes

We have moved our roadmap to Trello to communicate our plans in one single place:

{% embed url="<https://trello.com/b/KakM4F3S/squidex-roadmap>" %}
Squidex roadmap
{% endembed %}

The roadmap is driven by the following sources and with the following priorities.

1. Requirements from our collaborations with Enterprises.
2. Community feedback and requests.
3. Strategic features decide by us.

In general, we try to implement smaller features as quickly as possible, sometimes even on the same day. If possible we try to build features incrementally.

Please post custom-feature requests on our support forum: [https://support.squidex.io/](https://support.squidex.io).


# Quick Start Guides

Learn How to Quickly Get Started With Squidex

If you would like to quickly get started with Squidex, we have a few useful articles that can help you. These articles consist of some sample codes for use cases such as how to create a blog.  They also include  Squidex Cloud in the instructions, but if you prefer, you can replace Squidex Cloud with your own hosted version.

{% content-ref url="/pages/bJlAaJqqdODf2K3peFcY" %}
[Vue.js Blog with Squidex](/id-01-getting-started/quick-start-guides/vue.js-blog-with-squidex)
{% endcontent-ref %}

{% content-ref url="/pages/bRugi9zdYntwTrUHcKbW" %}
[React.js Blog with Squidex](/id-01-getting-started/quick-start-guides/react.js-blog-with-squidex)
{% endcontent-ref %}

{% content-ref url="/pages/5POV08tjK44aoA9dY7Wz" %}
[Angular Blog with Squidex](/id-01-getting-started/quick-start-guides/angular-blog-with-squidex)
{% endcontent-ref %}


# Vue.js Blog with Squidex

Quickly Deploy a Simple Vue.js Blog with Squidex

This article provides you with instructions on how to quickly deploy a Vue.js blog with Squidex Cloud. The same approach can also be used for self-hosted Squidex. For installation instructions click [here](/id-01-getting-started/installation).

## Prerequisites

* Account at [cloud.squidex.io](https://cloud.squidex.io/) (or a self-hosted Squidex).
* Account at [app.netlify.com](https://app.netlify.com).
* Account at [github.com](https://github.com/).
* For local deployment / development:
  * nodejs 16 or above.
  * npm 8 or above.

## Creating the Squidex App

For this quick-start, you can create an App with pre-populated schemas directly in Squidex. To do so, follow the steps below:

1. Navigate to [https://cloud.squidex.io](https://cloud.squidex.io/) and login. (If you do not yet have an account, create one).
2. Click **Starter Sample Blog** (1), give it a unique **name** (2) such as `vuejs-blog-with-squidex-[your_initials]` and click **Create** (3). <br>

   <div align="left"><figure><img src="/files/q4kcLkgMSpZRLl2buwpx" alt=""><figcaption><p>Creating the Starter Sample Blog App on Squidex</p></figcaption></figure></div>
3. Click on the newly created App to navigate into it. You should also make a note of the *App Name*, as it will be required later in the instructions. <br>

   <div align="left"><figure><img src="/files/LiDv95y5toraVd8k5FR9" alt=""><figcaption><p>Accessing the Squidex App</p></figcaption></figure></div>

## Creating a Client in the Squidex App

In this step, we will create a client that will be used by the Vue.js code to talk to Squidex. \
When an App is created, there is a default client already present. However, it has an *Owner* role by default, so as a best practice, we don't recommend that it's used.

1. Navigate to **Settings** (4) > **Clients** (5) in the App. Next enter a **Name** for the client (6) and click **Add Client** (7).<br>

   <div align="left"><figure><img src="/files/tlPDMHbb7LFYnoW4jCba" alt=""><figcaption><p>Create a client for Vue.js</p></figcaption></figure></div>
2. This creates a new client with the role of *Editor*. **Copy** (8) the *Client Id* and *Client Secret*, as they will also be required along with the *App Name* later in the instructions.<br>

   <div align="left"><figure><img src="/files/n3jKKn3PdKFVBCEyf4jl" alt=""><figcaption><p>Copy the Client Id &#x26; Client Secret</p></figcaption></figure></div>

You are now ready to use this App with the code. &#x20;

If you prefer to run the sample code locally, proceed with these steps [here](#running-the-vue.js-blog-locally).

## Deploying the Vue.js Blog

You can deploy the Vue.js code using various methods such as self-hosting, as a Docker container or in Kubernetes etc. You can also deploy them to various modern, fast and developer-friendly serverless platforms such as *Netlify*. The instructions here cover the latter:

### Deploying to Netlify

*Netlify* is a platform that helps you to quickly build and deploy sites to a global network with a host of features and you can get stated for free!

When you click the **Deploy to Netlify** button below, the following steps will occur:

1. You will be redirected to *Netlify* and prompted to sign in. (If you don't yet have an account visit <https://app.netlify.com/signup> and create an account first).
2. Next, you will be asked to connect to your GitHub account. This step is required, as the deployment process will clone the code to a repository in your own account.&#x20;
3. Finally, you will be prompted to enter a name for the repository and the environment variables for the Squidex App. Enter the values noted down from before. \ <mark style="color:orange;background-color:orange;">Your values will be different than the values in the screenshot.</mark>&#x20;

   <figure><img src="/files/cJtgQZ07v8ZgyqIW8w5k" alt=""><figcaption><p>Configuring the Netlify site</p></figcaption></figure>
4. Next, *Netlify* will deploy the App and provide a URL to access the App. (*Netlify* runs the App in a starter plan, which is free).

Click [![Deploy to Netlify Button](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/sangramrath/squidex-vuejs-blog-starter) to get started.

### Running the Code Locally

1. Start by cloning the Vue.js sample blog code to your machine:\
   `git clone https://github.com/sangramrath/squidex-vuejs-blog-starter.git`<br>
2. Next, create the environment variables files using the example file available i.e. `.env.example`. To separate your variables from development and production you can create `.env.development` and `.env.production`.\
   \
   Add the *App name*, *Client ID* and *Client Secret* values noted down earlier. For the *URL*, use `https://cloud.squidex.io` if using Squidex Cloud or the URL to your Squidex instance, e.g. `http://localhost:5000` if you run it locally.\
   \
   The final file may look something like this:

   <figure><img src="/files/dOzXJn1qMZxC6WsQoFwA" alt=""><figcaption><p>Environment file example</p></figcaption></figure>
3. Run `npm install` to install the node modules and setup the project.
4. Run `npm run serve` to compile and run it in development mode. This will serve the App at `http://localhost:8080/`. (sample screenshot below):<br>

   <div align="left"><figure><img src="/files/ahu3LIbSp8bDrlz2pGny" alt=""><figcaption><p>Successfully running Vue.js App</p></figcaption></figure></div>

   \
   The development mode supports hot-reload for any live changes.
5. Access your App locally, it should display your content fetched from Squidex. The *Starter Sample Blog* App at Squidex has only one post.<br>

   <figure><img src="/files/HQhZDWFBv2gLhewaOxcE" alt=""><figcaption><p>Blog running locally</p></figcaption></figure>


# React.js Blog with Squidex

Quickly Deploy a Simple React.js Blog with Squidex

This articles provides you with instructions on how to quickly deploy a React.js blog with Squidex Cloud. The same approach can also be used for a self-hosted Squidex. For installation instructions click [here](/id-01-getting-started/installation).

## Prerequisites

* Account at [cloud.squidex.io](https://cloud.squidex.io/) (or a self-hosted Squidex).
* Account at [app.netlify.com](https://app.netlify.com).
* Account at [github.com](https://github.com/).
* For local deployment / development:
  * nodejs 16 or above.
  * npm 8 or above.

## Creating the Squidex App

For this quick-start you can create the App with pre-populated schemas directly in Squidex. To do this follow the steps below:

1. Navigate to <https://cloud.squidex.io> and login. (If you do not have an account, create one).
2. Click **Starter Sample Blog** (1), give it a unique **Name** (2) such as `reactjs-blog-with-squidex-[your_initials]` and click **Create** (3). <br>

   <div align="left"><figure><img src="/files/OQ4rE3FZVXYN8K4S7GbG" alt=""><figcaption><p>Creating the Starter Sample Blog App on Squidex</p></figcaption></figure></div>
3. Click on the newly-created App to navigate into it. You should make a note of the *App Name* as it will be required later in the instructions.<br>

   <div align="left"><figure><img src="/files/BzCq6lxWmhikSHDkJnmP" alt=""><figcaption><p>Accessing the Squidex App</p></figcaption></figure></div>

## Creating a Client in the Squidex App

In this step, let's create a client that will be used by the React.js code to talk to Squidex. \
When an App is created, there is a default client already present however it has an *Owner* role by default and so it's best not to use it as a best practice.

1. Navigate to **Settings** (4) > **Clients** (5) in the App. Next enter a **Name** for the client (6) and click **Add Client** (7).<br>

   <div align="left"><figure><img src="/files/dnUvQKaVgUNzyGqQLbrJ" alt=""><figcaption><p>Create a client for React.js</p></figcaption></figure></div>
2. This creates a new client with the role of *Editor*. **Copy** (8) the *Client Id* and *Client Secret* as they will also be required along with the *App Name* later in the instructions.<br>

   <div align="left"><figure><img src="/files/MtZ2HetqYVYFVxLV26qu" alt=""><figcaption><p>Copy the Client Id &#x26; Client Secret</p></figcaption></figure></div>

You are now ready to use this App with the code. &#x20;

If you wish to run the sample code locally, proceed with these steps [here](#running-the-vue.js-blog-locally), otherwise, continue to deploy the blog to an online platform.

## Deploying the React.js Blog

You can deploy the React.js code using various methods such as self-hosting and as a Docker container, in Kubernetes etc. You can also deploy to various modern, fast and developer-friendly serverless platforms such as *Netlify*. The instructions here cover the latter:&#x20;

### Deploying to Netlify

*Netlify* is a platform that helps you quickly build and deploy sites to a global network with a host of features and you can get stated for free!

When you click the **Deploy to Netlify** button below, the following steps will occur:

1. You will be redirected to *Netlify* and prompted to sign in. (If you do not have an account visit <https://app.netlify.com/signup> and create an account first).
2. Next, you will be asked to connect to your GitHub account. This step is required as the deployment process will clone the code to a repository in your own account.&#x20;
3. Finally, you will be prompted to enter a name for the repository and the environment variables for the Squidex App. Enter the values noted down from before. \ <mark style="color:orange;background-color:orange;">Your values will be different than the values in the screenshot.</mark>&#x20;

<figure><img src="/files/ZSVVx9JvmtYxD39zER75" alt=""><figcaption><p>Configuring the Netlify site</p></figcaption></figure>

4. Next, *Netlify* will deploy the App and provide a URL to access the App. (*Netlify* runs the App in a Starter plan which is free).

Click[![Deploy to Netlify Button](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/sangramrath/squidex-reactjs-blog-starter) to get started.

### Running the Code Locally

1. Start by cloning the React.js sample blog code to your machine.\
   `git clone https://github.com/sangramrath/squidex-reactjs-blog-starter.git`<br>
2. Next, create the environment variables files using the example file available i.e. `.env.example`. To separate your variables from development and production you can create `.env.development` and `.env.production`.\
   \
   Add the *App Name*, *Client ID* and *Client Secret* values noted down earlier. For the *URL* use `https://cloud.squidex.io` if using Squidex Cloud or the URL to your Squidex instance, e.g. `http://localhost:5000` if you run it locally.\
   \
   The final file may look something like this:

   <figure><img src="/files/r0uGqjoHMg6Tm3I034wx" alt=""><figcaption><p>Environment file example</p></figcaption></figure>
3. Run `npm install` to install the node modules and setup the project.
4. Run `npm start` to compile and run it in development mode. This will serve the App at `http://localhost:3000/`. (sample screenshot below):<br>

   <div align="left"><figure><img src="/files/afv0t3e2pX5olvrZTXpZ" alt=""><figcaption><p>Successfully running React.js App</p></figcaption></figure></div>

   \
   The development mode supports hot-reload for any live changes.
5. Access your App locally, it should display your content fetched from Squidex. The *Starter Sample Blog* App at Squidex has only one post.<br>

   <figure><img src="/files/nWbZ2YUOlBd9hVNmZFUF" alt=""><figcaption><p>Blog running locally</p></figcaption></figure>


# Angular Blog with Squidex

Quickly Deploy a Simple Angular Blog with Squidex

This article provides you with instructions on how to quickly deploy an Angular blog with Squidex Cloud. The same approach can also be used for a self-hosted Squidex. For installation instructions click [here](/id-01-getting-started/installation).

## Prerequisites

* Account at [cloud.squidex.io](https://cloud.squidex.io/) (or a self-hosted Squidex).
* Account at [app.netlify.com](https://app.netlify.com).
* Account at [github.com](https://github.com/).
* For local deployment / development:
  * Node.js 16 or above.
  * NPM 8 or above.

## Creating the Squidex App

For this quick-start, you can create the App with pre-populated schemas directly in Squidex. To do this follow the steps below:

1. Navigate to <https://cloud.squidex.io> and login. (If you do not have an account, create one).
2. Click **Starter Sample Blog** (1), give it a unique **Name** (2) such as `angular-blog-with-squidex-[your_initials]` and click **Create** (3). <br>

   <div align="left"><figure><img src="/files/meGLcJfAzzGJS40T40Vt" alt=""><figcaption><p>Creating the Starter Sample Blog App on Squidex</p></figcaption></figure></div>
3. Click on the newly created App to navigate into it. You should make a note of the *App Name* as it will be required later in the instructions. <br>

   <div align="left"><figure><img src="/files/rBieb9FKbIeGoVawtUiu" alt=""><figcaption><p>Accessing the Squidex App</p></figcaption></figure></div>

## Creating a Client in the Squidex App

In this step, let's create a client that will be used by the Angular code to talk to Squidex. \
When an App is created, there is a default client already present however, it has an *Owner* role by default and it's not recommended to use this as a best practice.

1. Navigate to **Settings** (4) > **Clients** (5) in the App. Next enter a **Name** for the client (6) and click **Add Client** (7).<br>

   <div align="left"><figure><img src="/files/3PPcpPOm2LFIlYFhRUGh" alt=""><figcaption><p>Create a client for the Angular App</p></figcaption></figure></div>
2. This creates a new client with the role of *Editor*. **Copy** (8) the *Client Id* and *Client Secret* as they will also be required along with the *App Name* later in the instructions.<br>

   <div align="left"><figure><img src="/files/RFEebXcUoGTmr5Qtu1za" alt=""><figcaption><p>Copy the Client Id &#x26; Client Secret</p></figcaption></figure></div>

You are now ready to use this App with the code. &#x20;

If you wish to run the sample code locally, proceed with these steps [here](#running-the-vue.js-blog-locally), otherwise continue to deploy the blog to an online platform.

## Deploying the Angular Blog

You can deploy the Angular code using various methods such as self-hosting and as a Docker container, in Kubernetes etc. You can also deploy them to various modern, fast and developer-friendly serverless platforms such as *Netlify*. The instructions here cover the latter:

### Deploying to Netlify

Netlify is a platform that helps you quickly build and deploy sites to a global network with a host of features and you can get stated for free!

When you click the **Deploy to Netlify** button below, the following steps will occur:

1. You will be redirected to *Netlify* and prompted to sign in. (If you do not have an account visit <https://app.netlify.com/signup> and create an account first).
2. Next, you will be asked to connect to your GitHub account. This step is required as the deployment process will clone the code to a repository in your own account.&#x20;
3. Finally, you will be prompted to enter a name for the repository and the environment variables for the Squidex App. Enter the values noted down from before. \ <mark style="color:orange;background-color:orange;">Your values will be different than the values in the screenshot.</mark>&#x20;

   <figure><img src="/files/X5MRc9eH58zQ4yX4eHV8" alt=""><figcaption><p>Configuring the Netlify site</p></figcaption></figure>
4. Next, *Netlify* will deploy the App and provide a URL to access the App. (*Netlify* runs the App in a Starter plan which is free).

Click [![Deploy to Netlify Button](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/sangramrath/squidex-angular-blog-starter)  to get started.

### Running the code locally

1. Start by cloning the Angular sample blog code to your machine \
   `git clone https://github.com/sangramrath/squidex-angular-blog-starter.git`<br>
2. Next, create the environment variables files using the example file available i.e. `.env.example`. To separate your variables from development and production you can create `.env.dev` and `.env.prod`.\
   \
   Update the `.env.dev` file with your values of *App Name*, *Client ID* and *Client Secret* noted down earlier. For the *URL* use `https://cloud.squidex.io` if using Squidex Cloud or the URL to your Squidex instance, e.g. `http://localhost:5000` if you run it locally.\
   \
   The final file may look something like this:

   <figure><img src="/files/KlrKbGAIcZZBT2Zroxvy" alt=""><figcaption><p>Environment file example</p></figcaption></figure>
3. Run `npm install` to install the node modules and setup the project.
4. Run `npm start` to compile and run it in development mode. This will serve the App at `http://localhost:4200/`. (sample screenshot below):<br>

   <div align="left"><figure><img src="/files/Qs8yhjqQPqgiEFrWOogz" alt=""><figcaption><p>Successfully running Angular App</p></figcaption></figure></div>

   \
   The development mode supports hot-reload for any live changes.
5. Access your App locally, it should display your content fetched from Squidex. The *Starter Sample Blog* App at Squidex has only one post.<br>

   <figure><img src="/files/razStLTwjGdXgfEg2JXn" alt=""><figcaption><p>Blog running locally</p></figcaption></figure>


# Introduction and Use Case

A Brief Introduction to Squidex and a Use Case

It's best to describe features and functionality with a use case. So, to make following our documentation easier and briefer, will use a single use case (which will be updated and extended over time). This page describes the details of this particular use case.

Our documentation is still in progress and only a few pages have been created/updated so far. As new information is published, we will update this page.  Do check back for our updates.

{% hint style="info" %}
We recommend opening this use case alongside the documentation, so you don't need to keep switching back and forth!
{% endhint %}

## The Company

The user is the Lead Developer and CTO of a mid-size news magazine called "*FoodCrunch*", a magazine for everything surrounding food startups. It has just over 100 employees speaking 14 different languages. The editors and content authors write articles, news and other types of editorial content. In addition to that, the magazine also maintains a database called "*FoodBase*" with information about all startups in the food industry and information on topics such as co-founders, investment rounds and more.

Therefore, for this use case, the team has created one project in Squidex for all content, assets and settings and invited all developers and content editors to the project to work together. In Squidex we call this an "App".

{% content-ref url="/pages/-LsBwGVIo5mfCrFORXVI" %}
[Apps](/id-02-documentation/concepts/apps)
{% endcontent-ref %}

## The Content Structure

The structure of the content is defined by schemas in Squidex. In this section, we describe the different content types for our use case. Read more about schemas here:

{% content-ref url="/pages/-LrLSGJqHJi51D2lBh5P" %}
[Schemas](/id-02-documentation/concepts/schemas)
{% endcontent-ref %}

### Editorial Content

The CTO has decided to have a single structure for all editorial content. After bringing everybody to the table, the developers and editors have confirmed what information they need for each type of content. Even though the *FoodCrunch* website is available in four different languages, the team writes editorial content in one language.

This schema is called `magazine` and has the following fields.

| Name           | Type      | Localizable | Description                                        |
| -------------- | --------- | ----------- | -------------------------------------------------- |
| `slug`         | String    | No          | A single slug for Google friendly URLs.            |
| `title`        | String    | No          | The title of the editorial content.                |
| `content`      | String    | No          | The actual content.                                |
| `content-type` | String    | No          | The type of the content, e.g. "Article" or "News". |
| `startup`      | Reference | No          | A reference to the startup in the database.        |
| `image`        | Assets    | No          | One or more teaser images.                         |

### Startup Database

The startup database is maintained in multiple languages, so that entries can be reference in the languages from all articles, independent from the language they are written in.

This schema is called `startups` and has the following fields.

| Name          | Type        | Localizable | Description                                 |
| ------------- | ----------- | ----------- | ------------------------------------------- |
| `slug`        | String      | No          | A single slug for Google friendly URLs.     |
| `name`        | String      | No          | The name of the startup.                    |
| `description` | String      | Yes         | The description of the startup.             |
| `funding`     | Number      | No          | The total funding in millions (USD).        |
| `founded`     | String      | No          | The year the startup has been founded.      |
| `founders`    | Array       | No          | The founders as list of name and position.  |
| `tags`        | Tags        | No          | A list of tags for search.                  |
| `location`    | Geolocation | No          | The geolocation of the headquarter.         |
| `metadata`    | JSON        | No          | Unstructured metadata.                      |
| `givenUp`     | Boolean     | No          | Indicates whether the startup has given up. |

### JSON Structure

If you are a content editor, you can skip this section.

We use JSON to represent content in the database and API. Each content item is one document and the values of all fields are just called "*content data*" or "*data*". As Squidex supports localized fields, we need a way to structure these localized fields as well as the non-localized fields. In Squidex we have decided to use a common structure for this purpose. Therefore, our content takes the following shape:

{% code overflow="wrap" %}

```javascript
// magazine
{
    // Additional metadata, such as content id.
    "data": {
        "slug": {
            "iv": "sustainable-eating-by-foodco"
        },
        "title": {
            "iv": "Sustainable eating by FoodCo"
        },
        "content": {
            "iv": "Introducing FoodCo, the latest player in the food industry. FoodCo aims to revolutionize the way we eat. Their innovative products and focus on sustainability have already garnered attention from foodies and investors alike. Stay tuned for what's cooking next!"
        },
        "content-type": {
            "iv": "Article"
        },
        "startup": {
            "iv": [
                "3a20690a-c40b-44bc-832e-0a6e3e708d93"
            ]
        },
        "image": {
            "iv": [
                "287a2948-8992-4e65-990f-3ee486c9a4b5"
            ]
        }
    }
}

// startups
{
    // Additional metadata, such as content id.
    "data": {
        "slug": {
            "iv": "foodco"
        },
        "name": {
            "iv": "FoodCo"
        },
        "description": {
            "en": "Sustainable eating products",
            "de": null
        },
        "stage": {
            "iv": "Early"
        },
        "founded": {
            "iv": 2019
        },
        "funding": {
            "iv": 234
        },
        "founders": {
            "iv": [
                {
                    "name": "John Doe",
                    "position": "CEO"
                }
            ]
        },
        "tags": {
            "iv": [
                "sustainability"
            ]
        },
        "location": {
            "iv": {
                "latitude": 32.0237703,
                "longitude": -92.0390231
            }
        },
        "metadata": {
            "iv": null
        },
        "givenUp": {
            "iv": false
        }
    }
}
```

{% endcode %}

As you can see, we need a JSON object for our localized fields. To use a generalized structure, all objects have an `iv` (which stands for invariant) key, used for localized fields. Read more about the reasoning in the section concerning localization:

{% content-ref url="/pages/-LrLSGJrSHOn33Bsb3G3" %}
[Localization](/id-02-documentation/concepts/localization)
{% endcontent-ref %}

## People and Roles

The following people work together with Squidex to bring content to a website.

* **Developers** work together to bring new features to a website. Their responsibilities are to define the schemas in Squidex and to implement the business roles with Workflows and Permissions. To make it easier for them to find and fix bugs on the website, they also have full control of the content itself.
* **Editors** are responsible for writing the articles in different languages and conducting research for preparation of articles. As false information is a big deal in the news industry, Editors are not allowed to publish the content itself.
* **Reviewers** check the content before it gets published for spelling, grammar and the accuracy of facts, as well as reviewing other information in the content.
* **Publishers** work together with marketing and social media to decide when content should go live. They can also publish reviewed content but don't create any content themselves.


# Concepts

Learn About the Different Concepts in Squidex.


# Apps

Apps are Used to Group Content for Projects, Teams or Applications.

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## What is an App?

An App in Squidex is the highest level of organization for your content. An App could represent a project, a team or an application in your organization or structure. This is where the schema, actual content, assets and settings etc., live.&#x20;

<figure><img src="/files/ZxA6Mx0l67RgPoddQRSb" alt=""><figcaption><p>Apps overview in Squidex (self-hosted) or Squidex Cloud</p></figcaption></figure>

An App is made up of the following:

* A display name, description and logo to make it discoverable in the overview.
* **Schemas** to define the structure of your content.
* **Content** itself.
* **Assets** such as images, that belong to your App and are linked to your content.
* **Rules** to model business workflows.
* **Custom Workflow** definitions.
* **Collaborators** who can work on your content with roles and permissions.
* A subscription and an associated plan in the Squidex Cloud.<br>

  <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>Subscriptions and plans are applicable only when using Squidex Cloud (https://cloud.squidex.io/)</p></div>

<figure><img src="/files/62nqcYRDHJzX82LFaV8A" alt=""><figcaption><p>Dashboard of an App</p></figcaption></figure>


# Teams

A Team is Used to Group Apps for Sharing a Subscription and for Easier Management

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

Teams are primarily beneficial for the Squidex Cloud where an organization has multiple Apps and wishes to share the subscription.

The *Teams* capability solves the following problems:

* Generally, subscriptions are associated with the single user who creates it. So, in a situation where this it must be transferred to another user, it can be inconvenient.
* Since subscriptions are bound to a single App, it cannot be shared among multiple Apps for scenarios such as grouping and distributing the cost amongst Apps.&#x20;

### Creating a Team

To create a team, click **Apps and Team** (1) and click **Create Team** (2).

<div align="left"><figure><img src="/files/bpVMWskOk9EjTrrPtPG2" alt=""><figcaption><p>Creating a Team - Step 1</p></figcaption></figure></div>

Next, enter a **Name** (3) for the team and click **Create** (4).

<div align="left"><figure><img src="/files/OeWskF0wX5ciXzWM78Fj" alt=""><figcaption><p>Creating a Team - Step 2</p></figcaption></figure></div>

### Managing Subscriptions

In order to manage subscriptions for a team, navigate to **Settings** (1) and then click **Subscription** (2).

<figure><img src="/files/aJTB9gOeyDyoFrfJqoAf" alt=""><figcaption><p>Managing subscription</p></figcaption></figure>

A subscription is shared among all Apps in the team.

### Transferring an App to a Team

Once a team is created, you can move an App to the team. To do so, navigate to the App, click **Settings** (1) and then more **Settings** (2). Then, select the team from the dropdown under **Transfer to team** (3) and click **Transfer** (4).

<div align="left"><figure><img src="/files/aRyjQrA2lVuGxzFDozOp" alt=""><figcaption><p>Transferring an app to a team</p></figcaption></figure></div>

{% hint style="info" %}
Only when a user has permissions for the App and the team, can they transfer the App to the team.
{% endhint %}

At this point, you won't see a confirmation or the screen won't change. A way to verify that the App has moved to a team is to check in your Welcome dashboard or on the Team dashboard.&#x20;

In this example, we moved the *FoodCrunch* App to the *FoodCrunch* team.

<div align="left"><figure><img src="/files/uUeu86sPnTtPIOLZujXu" alt=""><figcaption><p>App part of a team</p></figcaption></figure></div>

Once an App has moved to a team, the subscription for the App can only be managed by the team.

<figure><img src="/files/g2q4suarrtCyeN6MYoJH" alt=""><figcaption><p>Subscription management of app</p></figcaption></figure>

### Managing Quota (for Multiple Apps)

As a Subscription is shared among all Apps that are part of the team, the quota for a subscription is also shared.&#x20;

If you wish to limit API calls for certain Apps (e.g. to protect your API contingent for other Apps that are more important), you can set the Max API Calls parameter for each client in an App.

<div align="left"><figure><img src="/files/7lvDIsdTELciqn4Yi0Vn" alt=""><figcaption><p>Setting API limits for clients</p></figcaption></figure></div>

### Team Dashboard

Every team has a dashboard similar to an App and displays the same statistics, including the Apps associated with the team.

<div align="left"><figure><img src="/files/MBZGHSKMtNQnMK3Ct6x2" alt=""><figcaption><p>Team Dashboard</p></figcaption></figure></div>

### Deleting a Team

At the moment a team cannot be deleted.

### Other Important Information About Teams

* You can add contributors to a team but there is no provision for roles at the moment.&#x20;
* A team contributor can only manage the subscription of the team and does not have automatic access to associated Apps.


# Schemas

Schemas Define the Structure of Your Content.

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Introduction

Schemas define the structure of your content. Creating one or more schemas to organize your content is usually the first step after creating an App.&#x20;

There can be more than 1 schema in an App.&#x20;

To create a schema click **+** (1) button under Schemas, give it a **Name** (2) and select the **Type** (3). Click **Create** (4).

<figure><img src="/files/NjCyyVQZ409JoizfHMxf" alt=""><figcaption><p>Creating a schema</p></figcaption></figure>

A schema consists of multiple fields of various data types. Each field in a schema is identified by the following important properties:

| State            | Immutable | Description                                                                                                                                                                                                        |
| ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Name**         | No        | The name of the field in the API. It cannot be changed anymore, but you can add a optional label that is used in UI.                                                                                               |
| **Type**         | No        | The data type of this field.                                                                                                                                                                                       |
| **Editor**       | Yes       | Most fields have an editor, which depends on the type of the field. The editor can be changed, but it will not change the value. If you change a field editor from HTML to Markdown you will probably face issues. |
| **Partitioning** | No        | Defines whether the field content is localizable and managed in multiple languages or not.                                                                                                                         |
| **Validation**   | Yes       | A set of validation properties, which depend on the type of the field. For example they define the maximum length of a string or the maximum number of assets you can reference.                                   |

To create a field, click **+ Add Field** (1) and select the **Field Type** (2), give it a **Name** (3) and optionally select if it is a **Localizable** field. Click **Create and close** (4) when done.

{% hint style="info" %}
To add multiple fields simultaneously, click **Create and add field** or to immediately edit additional properties on the field click **Create and edit field**.
{% endhint %}

<figure><img src="/files/iuh8dwAkEKrGsYSpkYGd" alt=""><figcaption><p>Creating a field</p></figcaption></figure>

There are additional properties that can be set in the field after it is created.

<figure><img src="/files/rpXlSNJo9xNXCP9GG3gb" alt=""><figcaption><p>An example of a schema with fields of different data types</p></figcaption></figure>

The screenshot above shows the final schema of the `startups` database for *FoodCrunch*.

Furthermore, a schema has a published change. Only published schemas can have content. To publish a schema, click **Published**.

<figure><img src="/files/kgpS83F3wBoI8EHiTU8H" alt=""><figcaption><p>Publishing a schema</p></figcaption></figure>

## Field States

A field has multiple states:

1. **Enabled:** This is is the default state.
2. **Disabled**: The field cannot be manipulated in the Management UI. Do not use it together with the required validator, because by doing so, will not be able to update invalid content items.
3. **Hidden**: The field will not be returned by the API and is only visible in the Management UI.
4. **Locked**: The field cannot be updated or deleted anymore.

To change the state, click on the elipsis (three dots) next to the field and select the right option.

<figure><img src="/files/h7DhTwf1lXw2GOl3lXwz" alt=""><figcaption><p>Changing the state of a field</p></figcaption></figure>

## Field Types

Field types define how a field is structured in the API and in the processing pipeline. You can define the editor for each field, so a string field can either be a HTML, text, markdown or a list of allowed values with a dropdown editor.&#x20;

{% hint style="info" %}
If a field is not required it can also be `null` or **omitted**. A field is also `null` when it has been added or marked as required, after content items have already been added to the schema.
{% endhint %}

### String

<div align="left"><img src="/files/-LrLSJBPjmIH7khfqjxb" alt="String"></div>

A string is the most used field type and can be used for any type of text, such as product names, descriptions and additional information. It is also the most flexible field and the usage depends very much on the editor you are using:

1. **HTML**: With a WYSIWYG editor.
2. **Markdown**: With a Markdown editor.
3. **Simple Text**: With an input control for texts in one line.
4. **Multiline Text**: With a TextArea control.
5. **Selection of Predefined Values**: With a dropdown control or radio boxes.

#### API Representation

```javascript
"title": {
    "iv": "Sustainable eating by FoodCo"
},
// OR
"title": {
    "iv": null
}
```

### Number

<div align="left"><img src="/files/-LrLSJBRK6AHMyiMoRRN" alt="Number"></div>

A number can either be a point number or an integer. Typical examples of when to use numbers are quantities, IDs and prices.

#### API Representation

```javascript
"fundingInMn": {
    "iv": 234
}
// OR
"fundingInMn": {
    "iv": null
}
```

### Boolean

<div align="left"><img src="/files/-Lszf-Vrs_ElCnhVvpsG" alt="Boolean"></div>

Booleans have only 2 states: True or false, yes or no, 1 or 0.

#### API Representation

```javascript
"givenUp": {
    "iv": false
}
// OR
"givenUp": {
    "iv": true
}
// OR
"givenUp": {
    "iv": null
}
```

### DateTime

<div align="left"><img src="/files/-LrLSJBU00o97ArA_yim" alt="DateTime"></div>

Date and time in the ISO8601 standard. The format is: `YYYY-MM-DDTHH:mm:ssZ`.

#### API Representation

```javascript
"foundingDate": {
    "iv": 2021-01-10T00:00:00z"
},
// OR
"foundingDate": {
    "iv": null
}
```

### Assets

<div align="left"><img src="/files/-LrLSJBWCKT4o_hrpVYe" alt="Assets"></div>

Asset fields are used to maintain a list of asset IDs. You can also restrict the number of assets with a minimum and maximum limit, for example, when you want to have a single avatar or preview image for a content. You can use the IDs to load the asset. Read more [here](/id-02-documentation/concepts/schemas). When you delete an asset, a clean-up process will remove the asset id from your content. This process is executed in the background to improve performance and it can take several minutes to complete. Therefore, it is highly recommended to handle cases where a piece of content has an ID added to a deleted asset.

#### API Representation

```javascript
"image": {
    "iv": [
        "287a2948-8992-4e65-990f-3ee486c9a4b5"
    ]
},
// OR
"image": {
    "iv": null
}
```

### References

<div align="left"><img src="/files/-LrLSJBYNqBIzI6xPlfi" alt="References"></div>

References fields are used to model relationships to other content items. For example, you could have a schema for products and a schema for product categories. A product has a field with references to the categories it belongs to. Both products and categories can be created, updated and managed independently. Please think about the direction of the reference very carefully. For example, a typical product may only be in very few categories, but a product category could have thousand of products. Therefore, it is not recommended to reference the products from the categories. When you delete content, a clean-up process will remove the referenced ID from all content. This process is executed in the background to improve performance and it can take several minutes to complete. This is why it's highly recommended to handle cases where a piece of content has a reference to a deleted content.

#### API Representation

```javascript
"startup": {
    "iv": [
        "3a20690a-c40b-44bc-832e-0a6e3e708d93"
    ]
},
// OR
"startup": {
    "iv": null
}
```

### Array

<div align="left"><img src="/files/-LrLSJB_g63iOq9sz-ii" alt="Arrays"></div>

Some content items only exist as child content for another content item. For example, a product could consist of variations such as different sizes and prices. These content items can be represented with array fields, where each item in the field has a specified structure, this is called a **Nested Schema**.

#### API Representation

```javascript
"founders": {
    "iv": [{
        "name": "John Doe",
        "position": "CEO"
    }, {
        "name": "Jane Doe",
        "position": "CMO"
    }]
},
// OR
"founders": {
    "iv": null
}
```

### Component

<div align="left"><img src="/files/-MdRw_Qk37b8gLHil53k" alt="Component"></div>

A component is a content item (defined by another schema) that is embedded into the current content.

#### API Representation

```javascript
"component1": {
    "iv": {
        "schemaId": "3e6b3c9f-6de7-44a2-bdd0-4cc6ec255480",
        "title": "My Title",
        "text": "My Text"
    }
},
// OR
"component2": {
    "iv": null
}
```

### Components

<div align="left"><img src="/files/-MdRxSUJ8CY-6D6wAtpw" alt="Components"></div>

The components field is used to embed multiple content items (defined by other schemas) into the current item. The order is defined when creating or updating the content item and can be changed in the UI.

#### API Representation

```javascript
"components1": {
    "iv": [{
            "schemaId": "3e6b3c9f-6de7-44a2-bdd0-4cc6ec255480",
            "title": "My Title",
            "text": "My Text"
        }, {
            "schemaId": "410a07f2-a89e-4d77-9a43-46fff835ff8c",
            "image": "http://url/to/image",
            "alt": "My Image"
        }
    ]
},
// OR
"components2": {
    "iv": []
},
// OR
"components3": {
    "iv": null
}
```

### Geolocation

<div align="left"><img src="/files/-LrLSJBb088c_PYqXbE6" alt="Geolocation"></div>

The geolocation field represents a sequence of latitude and longitude and is designed to be used in combination with maps. It does not store additional data about the location, such as names, addresses or other information. You have to add additional fields for this purpose.

#### API Representation

```javascript
"location": {
    "iv": {
        "longitude": 32.0237703,
        "latitude": -92.0390231
    }
},
// OR
"location": {
    "iv": null
}
```

### Tags

<div align="left"><img src="/files/-LrLSJBdrjL5L494_i_v" alt="Tags"></div>

Tags are lists of strings that are use in the combination tag editor in the Management UI. They are especially useful if you enrich your content with external systems. At the moment, the tag editor does not support advanced tag management, such as global lists of tags, renaming and merging of tags.

#### API Representation

```javascript
"tags": {
    "iv": [
        "sustainability",
        "food",
        "healthy"
    ]
},
// OR
"tags": {
    "iv": null
}
```

### Json

<div align="left"><img src="/files/-LrLSJBfsxxa8wNApLXH" alt="Json"></div>

A json field is for developers. Whenever you have some structured or unstructured content, that you can't cover with the built-in field types or editors, you should use the json field. You should either write a [custom editor](/id-02-documentation/developer-guides/editors) when the content editors can edit the field or disable the field when the content for this field comes from an external source. Editing the json manually is delicate and can easily dismantle your processes.

#### API Representation

```javascript
"metadata": {
    "iv": {
        "createBy": "auto-importer"
    }
},
// OR
"location": {
    "iv": null
}
```

### UI

<div align="left"><figure><img src="/files/6l1brOuVycvYJf0QEYcT" alt=""><figcaption><p>UI</p></figcaption></figure></div>

Separator for editing UI.


# Field Rules

Learn About Field Rules in Squidex

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

A Field Rule is a method of implementing automated actions on schema fields when a certain condition is met. It is made up of three components as follows:

* **Action**: This component states what should be done when the condition(s) is/are met. It has the following options:
  * Disable
  * Hide
  * Require
* **Field**: This relates to when the schema field is selected dependent upon which action should be taken.
* **Condition**: Written in Javascript, this action is triggered when the condition is met.

For our *FoodCrunch* user case let's consider the following example to understand Field Rules. For all startups that are not in `Pre-Seed` stage, the funding value must be mentioned. It is therefore a required field.

For our use case, the rule is that anytime the value of the **Stage** field is anything other than `Pre-Seed`, then the **Funding** is a required field, otherwise it's optional.&#x20;

Let's see how to implement this:

* Ensure that the **Validation** (2) of the **Stage** (1) field is set to **Required** (3) in the settings. This is necessary to make sure that a value is always selected.&#x20;

<figure><img src="/files/uPuBEGiLkldGKHeMbZd5" alt=""><figcaption><p>Setting validation for a field</p></figcaption></figure>

* To create a Field Rule, go to the **Schema** (1) (i.e. in our case `startups`), select the **More** (2) tab, click **+** (3) under Field Rules and enter the selections for **Action, Field and Condition** (4). Click **Save** (5) when done.\
  \
  In our example the following values have been set:
  1. Action = `Require`
  2. Field = `funding`
  3. Condition (in Javascript) = `data.stage.iv != 'Pre-Seed'`

<figure><img src="/files/Dnj24Cd4BysmpbKRFCBA" alt=""><figcaption><p>Creating a Field Rule</p></figcaption></figure>

* This can now be validated by trying to add content. Go to **Content** and try adding content. Immediately you will find that the stage and funding fields have a red flag.

<figure><img src="/files/A2JwddDYmsT2WluD20mY" alt=""><figcaption><p>Fields with rules</p></figcaption></figure>

* If you set **stage** as `Pre-Seed`, the red flag denoting that the **funding** value is required will disappear, which means its optional.<br>

<figure><img src="/files/whYfjeWvlCMEykdGBmqu" alt=""><figcaption><p>Rule validation example 1</p></figcaption></figure>

* If you set **stage** as any other value, the red flag remains meaning that a value has to be entered in **funding**.<br>

<figure><img src="/files/Jy6PUQsGH5wXbQE6sLc4" alt=""><figcaption><p>Rule validation example 2</p></figcaption></figure>


# Content

Learn About Adding and Viewing Your Content on Squidex

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

**Content** is where you can add or view your actual (existing) content.&#x20;

{% hint style="info" %}
Content can only be added on a published schema.
{% endhint %}

Content could be in one of these 3 stages:

1. **Draft** - when you add content and click **Save**, the content is in draft mode. &#x20;
2. **Published** - when you add content and click **Save and Publish**, the content is published.
3. **Archived** - when content is archived.

To add content to your schema click on the **Schema** (1) i.e `startups` (in this example) and then click **+ New** (2).

<figure><img src="/files/kWIUjxAVLDYrlD2Jm0Dx" alt=""><figcaption><p>Add/view content</p></figcaption></figure>

Then add content depending on the schema. For our *FoodCrunch* use case, we will add information about a few emerging food startups.&#x20;

<figure><img src="/files/xC4jZoYgM9sNZhd8KIY4" alt=""><figcaption><p>Adding Content</p></figcaption></figure>

When complete, click **Save** (3) if you don't want to publish immediately or, click **Save and Publish** if you want to publish immediately.

<figure><img src="/files/IDxhjFXfDYJupOgg6sCA" alt=""><figcaption><p>Saving the content</p></figcaption></figure>

### Other operations on content

* Clone
* Delete


# Scheduled Publishing

Learn About Scheduling Content

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

When you add content through the Squidex portal, you can either *Save* or *Save and Publish*.

*Save and Publish* publishes your content immediately, however there may be instances where you would like to publish your content at a later date. You can do this by scheduling your content.

To schedule your content, select *Save* content. Once your content is saved, navigate back to the list of content. Select the content(s) that you would like to publish at a later date by using the **checkbox** (1), then click **Published** (2).

<figure><img src="/files/OfJU056dFuKr6k3ruX8K" alt=""><figcaption><p>Publishing at a later date</p></figcaption></figure>

On the pop-up prompt select **Set to Published at a later point date and time** (3) and click **Confirm** (4). Click on the time zone to change between *Local* & *UTC,* if required.

<div align="left"><figure><img src="/files/ICUBL9AywvQUupNxzzCg" alt=""><figcaption><p>Selecting a schedule</p></figcaption></figure></div>

You can now see your content publishing schedule under **Scheduled Contents**.

<figure><img src="/files/eXvDrrWAZkGvIUdTg87C" alt=""><figcaption><p>Scheduled contents view</p></figcaption></figure>

The Scheduled Contents area has a daily, weekly and monthly view that you can toggle between.


# Assets

Learn About Assets in Squidex and How to Use Them

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

An asset in Squidex is a static content such as a file, a document, an image, a video or any other media file. These assets may be used along with the data in the Content area.

An example for our *FoodCrunch* user case could be the **Logo** for the startups.

Assets can be uploaded to the root or in folders. For example, assets of type images could be uploaded to a folder called *images*.

To create a folder click![](/files/t2xbEewNPaVWkdf1IEKF)icon (2), enter a **Folder Name** (3) e.g. *images* and click **Create** (4).&#x20;

<figure><img src="/files/OQvDWnUwslQwkC01lfsz" alt=""><figcaption></figcaption></figure>

## Asset Properties

An asset has properties and some of these properties can be modified once you've uploaded the asset.&#x20;

Properties such as **Name**, **Slug**, **Tags** and **Metadata** can be added and/or modified. Assets can also be protected from anonymous download by checking the **Protected** box. This makes the asset private, a token must be used to download it directly.

{% hint style="info" %}
All assets uploaded are public by default.&#x20;
{% endhint %}

<figure><img src="/files/blbiANmKE0wf5DQAK5xI" alt=""><figcaption></figcaption></figure>


# Querying Assets

Learn How to Query Uploaded Assets

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

Squidex provides the capability of querying your uploaded assets. You can query (search) by **tags**, or **name**, or use a combination of both.

The following tag types are supported as of this writing:

* `image`
* `image/small`
* `image/large`
* `type/png`
* `type/jpg`

The following example shows how to search based on a tag `type/png`.

<div align="left"><figure><img src="/files/XJPrpJ64LE4DXvcMFbH0" alt=""><figcaption><p>Selecting a search tag</p></figcaption></figure></div>

<div align="left"><figure><img src="/files/QlBJ6OPUzPSHEQDnc8z6" alt=""><figcaption><p>Tag selection screenshot</p></figcaption></figure></div>

Additionally, you can also save the query and decide whether or not to share it with others. To save the query, simply click on the star button next to the search fields.

<div align="left"><figure><img src="/files/bqpxCe11TIRsDwj3V8JC" alt=""><figcaption><p>Saving the query - I</p></figcaption></figure></div>

On the pop-up window, enter a name under **Name your query** (1) and click **Create** (2).

<div align="left"><figure><img src="/files/vTWRGRoCHJIt8DHQF9y4" alt=""><figcaption><p>Saving the query - II</p></figcaption></figure></div>


# Localization

Localization Allows You to Define Content in Multiple Languages.

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Basic Concept

Localization is defined on a per field basis. We call this system "partitioning", more about that [here](#why-do-you-call-it-partitioning).&#x20;

Referring to our *FoodCrunch* use case, the **Description** field in the `startups` schema is a *Localizable* field.&#x20;

{% hint style="info" %}
Localization is set when adding a field and cannot be modified later.&#x20;
{% endhint %}

<figure><img src="/files/bsMxDkCfeEYPLzpQMiVW" alt=""><figcaption><p>Setting a field as Localizable</p></figcaption></figure>

### Languages

Before you can add localized content, you must add the additional languages you wish to support and their fallback settings. This is configured in the Management UI.

To add languages click **Settings** (1) and then select **Languages** (2). **Search** (3) for the desired language, select it and click **Add Language** (4).&#x20;

<figure><img src="/files/8TxIDUr5NGIpbKKTQarZ" alt=""><figcaption><p>Adding localization/ language support </p></figcaption></figure>

### Language Settings

There are three configurable parameters for the Languages used for localization.&#x20;

To modify or apply these settings, click on the **Gear** icon next to the respective language.

<figure><img src="/files/gBtqnXlakddNK8mEFTHJ" alt=""><figcaption><p>Modify language settings</p></figcaption></figure>

* **Fallback** - This setting states that whenever a value for a localizable field is not available, Squidex tries to resolve the value from the master language which is the default fallback language. You can set more than one fallback language.\
  To set a fallback, select a language from the **dropdown** (1) (list only includes enabled languages) and click **Add Language** (2).

  <figure><img src="/files/s0akmrcNdI3tQoZ4L95k" alt=""><figcaption><p>Adding a fallback language</p></figcaption></figure>

  The final screenshot for the Swedish language in our example looks something like below: <br>

  <figure><img src="/files/oNQMX4z47qBb66Xl8vD9" alt=""><figcaption><p>Set multiple fallback languages</p></figcaption></figure>

  You can reorder these to set your priority. To reorder, use the ![](/files/vn9KmBW0EvTx0XYb3Pck)  icon and drag it up or down.

{% hint style="info" %}
*Master Language* cannot have fallback languages and it cannot be Optional.
{% endhint %}

* **Is Master** - This is used to set if a language is the *Master Language*, meaning when another language has no value, it displays the content in this language. English is the *Master Language* by default.
* **Is Optional** - This means that required fields can be omitted. This is useful when you introduce a new language. You can save content with required fields even if the field value has not been entered for the *Optional Language*.

&#x20;Remember to click **Save** when done.

{% hint style="info" %}
A *Master Language* cannot be modified or deleted unless a different language is set as a the *Master Language*.
{% endhint %}

For our *FoodCrunch* use case, we have added the following languages along with their settings.&#x20;

<table><thead><tr><th width="123">Name</th><th width="77">Code</th><th width="153">Fallback</th><th width="100">Optional</th><th>Description</th></tr></thead><tbody><tr><td><strong>English</strong></td><td><code>en</code></td><td>-</td><td>N/A</td><td>Our master language. Whenever a fields is not available in a language it falls back to the master language.</td></tr><tr><td><strong>Swedish</strong></td><td><code>sv</code></td><td>Not configured</td><td>Yes</td><td>Swedish has no fallback language configured, therefore the fallback language is always English. This is an optional language.</td></tr><tr><td><strong>Finnish</strong></td><td><code>fi</code></td><td><code>sv,en</code></td><td>Yes</td><td>Finish has Swedish configured as a fallback language. This means that whenever a value for a localizable fields is not available Squidex tries to resolve the value from Swedish first and then from the master language (English). Finish is also marked as optional.</td></tr><tr><td><strong>Italian</strong></td><td><code>it</code></td><td><code>en</code></td><td>Yes</td><td>Italian is a new language and has been added after the content editors have created most of the content. Therefore most content items do not have a value available. Since no fallback language is configured, English will be the fallback language. Italian is optional too.</td></tr></tbody></table>

The final screenshot may look similar to the screenshot below.

<figure><img src="/files/2iDIrKDS1gEgQAXyFiVn" alt=""><figcaption><p>Languages settings for FoodCrunch app.</p></figcaption></figure>

It's easy to understand when you look at a content object from the API:

{% code overflow="wrap" %}

```javascript
{ 
    "id": "01",
    "created": "2017-02-25T19:56:35Z",
    "createdBy": "...",
    "lastModified": "2017-02-25T19:56:35Z",
    "lastModifiedBy": "...",
    "data": {
        "description": {
            "en": "FoodCo aims to revolutionize the way we eat. Their innovative products focus on sustainability.",
            "fi": "FoodCo pyrkii mullistamaan ruokailutottumuksiamme. Niiden innovatiiviset tuotteet keskittyvät kestävään kehitykseen.",
            "it": null,
            "sv": "FoodCo har som mål att revolutionera vårt sätt att äta. Deras innovativa produkter fokuserar på hållbarhet."
        },
        "founded": {
            "iv": 2019
        }
    }
}
```

{% endcode %}

Each field value is a set of values that are associated with keys. In JavaScript this is called an `Object`, other programming languages call it `HashMap` or `Dictionary`. The keys must be unique. Depending whether the field is localizable or not the API accepts different keys.

* The `founded` field is not localizable. Therefore the only allowed key is `iv`, which stands for "*invariant*".
* The `description` field is localizable. The allowed keys are the language codes for the languages you have configured.

## How to Use the API

### How to Retrieve the Correct Languages?

The rest endpoint provides two headers that can be used to query the correct language.

#### X-Languages Header

You can filter the languages with the `X-Languages` header. Other languages will be omitted. We do not use the `Accept-Language` header because we want to avoid compatibility issues. If you define a language that is not supported, this language will be ignored. For example, if you set `X-Languages: en,sv,it` for our example above you will only retrieve English (`en`) and Swedish (`sv`).

{% code overflow="wrap" %}

```javascript
X-Languages: en,sv,it
{ 
    ...,
    "data": {
        "description": {
            "en": "FoodCo aims to revolutionize the way we eat. Their innovative products focus on sustainability.",
            "sv": "FoodCo har som mål att revolutionera vårt sätt att äta. Deras innovativa produkter fokuserar på hållbarhet."
        },
        "founded": {
            "iv": 2019
        }
    }
}
```

{% endcode %}

If none of the specified languages are provided, you will only retrieve the *Master Language*.

{% code overflow="wrap" %}

```javascript
X-Languages: de
{ 
    ...,
    "data": {
        "description": {
            "en": "FoodCo aims to revolutionize the way we eat. Their innovative products focus on sustainability."
        },
        "founded": {
            "iv": 2019
        }
    }
}
```

{% endcode %}

#### X-Flatten Header

If you add this header, fields that only have a single value will be flattened. So the example above will be transformed to:

{% code overflow="wrap" %}

```javascript
X-Flatten: true
{ 
    ...,
    "data": {
        "description": {
            "en": "FoodCo aims to revolutionize the way we eat. Their innovative products focus on sustainability.",
            "fi": "FoodCo pyrkii mullistamaan ruokailutottumuksiamme. Niiden innovatiiviset tuotteet keskittyvät kestävään kehitykseen.",
            "it": null,
            "sv": "FoodCo har som mål att revolutionera vårt sätt att äta. Deras innovativa produkter fokuserar på hållbarhet."
        },
        "founded": 2019
        }
    }
}
```

{% endcode %}

Both headers can be combined. If you define a single language with the `X-Languages` header, the localizable fields will contain only one value each and therefore the fields can be flattened as well. If you provide an unsupported language you will just get the *Master Language*.

So, our example from above might look like this:

{% code overflow="wrap" %}

```javascript
X-Languages: de, X-Flatten: true
{ 
    ...,
    "data": {
        "description": "FoodCo aims to revolutionize the way we eat. Their innovative products focus on sustainability.",
        "founded": 2019
    }
}
```

{% endcode %}

This means that you can just forward the user language and Squidex will handle the rest.

{% hint style="info" %}
The headers above are not supported by the GraphQL endpoint, because in GraphQL the output should be defined as only the query.
{% endhint %}

### How to Disable Fallback Languages?

If you want to complete the fallback handling in your API, you can disable the behavior above. Add the following header to all your to all your requests: `X-NoResolveLanguages=1`

You will then receive the raw data and some fields might not have a value for a language, for example when this language is new and nobody has entered a value yet:

{% code overflow="wrap" %}

```javascript
X-NoResolveLanguages: 1
{ 
    ...,
    "data": {
        "description": {
            "en": "FoodCo aims to revolutionize the way we eat. Their innovative products focus on sustainability.",
            "fi": "FoodCo pyrkii mullistamaan ruokailutottumuksiamme. Niiden innovatiiviset tuotteet keskittyvät kestävään kehitykseen.",
            "sv": "FoodCo har som mål att revolutionera vårt sätt att äta. Deras innovativa produkter fokuserar på hållbarhet."
        },
        "founded": {
            "iv": 2019
        }
    }
}
```

{% endcode %}

## Why is this Called Partitioning?

This means that the a value is partitioned into multiple subvalues. When we implemented the localization feature we realized that it might be very helpful to extend this feature to other types of keys, for example you can:

* Define your prices for different currencies.
* Write your texts for different countries.
* Define customer groups.

So, we implemented the localization feature with the idea in mind that we might extend it in coming versions.


# Migrations

Migration is the Process that Takes Squidex From one Major Version to the Next Major Version.

## How Does it Work?

Some updates and new features require an update to the existing data structures and therefore to the database as well. To automate these updates, a migration process has been introduced.

### General Process

The migration process uses a version in the database to determine whether an update is needed.

This version is stored in the `Squidex` database (1) in the `Migration` (2) collection, where only a single document (3) exists.

The document has two relevant fields:

1. **Version:** The actual version of the database. This number has nothing to do with the production version. For example Squidex 6.6.0 has the database version 26.
2. **IsLocked**: This field indicates if a migration is already in process. This is useful when you have more than one Squidex server to synchronize the work between the servers.

![The migration document](/files/xvBEtpFRTTLvUnDvEtOd)

When a Squidex server starts up, the following steps are executed:

1. The current version is queried from the database and the "**IsLocked**" field is set to true, if it is not locked already. This is a single atomic operation, therefore it is in general, thread safe.
2. If the lock has been taken, the database version is compared with the new database version, that is part of the source code and incremented with each update of a data structure. If the two versions are different the necessary migration steps are executed. Then the lock is released.
3. If the lock has not been taken, the server waits for 5 seconds and continues with step 1. If another server is running the migration, the current server waits until the migration is complete and it can acquire a lock. Usually, the queried database version is then the same as the new version and the server will just release the lock for the next server.

{% hint style="info" %}
If a server crashes before it releases the lock, the database is essentially locked forever and all servers would have to wait for the lock to be released. In such cases, you have to release the lock manually to resolve the issue.
{% endhint %}

### Migration Steps

There is no documentation detailing how migration steps will run, but the following code file gives you some information:

{% embed url="<https://github.com/Squidex/squidex/blob/master/backend/src/Migrations/MigrationPath.cs>" %}
Migration steps
{% endembed %}

Usually, the migration will just rebuild a database from scratch. Squidex uses event sourcing, which means that each entity - for example an asset or content item - exists as a sequence of change events and as a snapshot document in the database for complex queries. As long as you do not lose the events, you can just rebuild the snapshot documents from the events. Therefore, events are never deleted or updated.

When a change is not backwards compatible anymore and when it is very likely that the target collection contains a lot of documents, the snapshots are rebuilt to a new collection. This makes it easier to roll back to an old version in cases where migration fails, or when the new version has a critical bug.

So, you will see collections that contain a number, for example **States\_Assets2** (1).

![New collections](/files/OAgKX8ajioJTE2xjxeJE)

## How to Plan a Migration

Usually, migration completes within a few minutes but there is no table or description depicting how long migration from your version will take to a new version. Migration time also depends on the number of content items and assets you have for example.  This is because these are typically the largest collections in the system.

### Migration Steps

In general it is not possible to initialize a migration without an interruption of your typical processes, but you can initialize migration without too much downtime.

These are the steps you should execute:

#### Step 0: Practice Your Migration With a Test Run

If you have a staging environment we recommend that you practice your migration first to assess how long it will take to complete. You can also clone your database and create a second deployment to try it out first with your production database.

#### Step 1: Switch Your Squidex Deployment to Read-Only Mode.

If you create a back-up or run a migration, you shouldn't allow updates, because while the migration runs, you will lose changes when these updates are executed. It's better to block updates. Therefore, Squidex provides a setting to switch your deployment to read-only mode:

{% embed url="<https://github.com/Squidex/squidex/blob/master/backend/src/Squidex/appsettings.json#L4>" %}
Read-only Mode
{% endembed %}

Just set the following environment variable: `MODE_READONLY=true` and restart your deployment.

#### Step 2: Create a Backup

Create a backup of your entire database with Mongodump: <https://www.mongodb.com/docs/database-tools/mongodump/>

It's not recommended to use the backup system of Squidex itself, because it is not a complete backup of your database and it's slower to restore.

#### Step 3: Clone Your Database

Use the backup you've created to clone your database.

#### Step 4: Deploy a New Squidex Version to Your Cloned Database

Do not change the existing database and deploy a new Squidex version to your new cloned database. You should also set this deployment to read-only. The new servers will automatically migrate your new database to the newest version. In case of an error, you can just delete the cloned database and the new Squidex deployment.

#### Step 5: Change Your Load Balancer or DNS to Point to the New Version

If the cloned database has been updated to the newer version you can change the DNS entry or your load balancer to point to the new version. It's recommended to change the load balancer or your Kubernetes service, because your update will be faster.

Please remember that at this stage, both versions will still be in read-only mode. So, they contain the same assets, content and settings. You can now test your new instance with your end users. In cases where there's an error, you can just change your load balancer or DNS to point to the old deployment.

Of course, your end users or content editors cannot change content items or upload assets while the migration is in process, but there is no downtime.

#### Step 6: Update Your New Deployment to Allow Updates.

You can now remove the read-only flag from your new deployment and migration is complete!

#### Step 7: Clean Up the Old Database and Deployment

You can now clean up old resources:

1. Delete your old deployment.
2. Delete old collections, e.g. if you see a collection such as `States_Contents_All3`, delete
   1. `States_Contents_All2`
   2. `States_Contents_All1`
   3. `States_Contents_All`

### FAQ

#### Is there a migration process for assets?

No, assets are never updated or deleted. Therefore, a migration processes is not necessary. You also do not have to backup your database.

#### Do I always need a second deployment for migration?

No, it just reduces the risk that something might go wrong. If a migration just takes a matter of a few minutes you can do it directly on the production system. Just increment the version (e.g. Docker tag) and update your deployment.

Whether a migration takes a few minutes or not can be answered by conducting a test run.

#### Is it always recommended to use the read-only flag?

Yes, because you will always lose updates when a user makes a change while a migration is running. The main problems are inconsistencies when a new event is added to the event stream but the snapshot still has the old version.


# Roles & Permissions

Roles and Permissions Provide Users the Ability to View, Change and Delete Content, Assets and Settings.

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Permissions in Squidex

Squidex uses a fine-grained permission system. Permissions are defined with a dot-notation as outlined below:

* `squidex`
* `squidex.apps.{app}.clients`
* `squidex.apps.{app}.clients.read`
* `squidex.apps.{app}.contents`
* `squidex.apps.{app}.contents.{schema}`
* `squidex.apps.{app}.contents.{schema}.read`
* `squidex.apps.{app}.contents.{schema}.create`

`{app}` and `{schema}` are placeholders that will be replaced with your current App name or schema name.

The full list of permissions can be found here: <https://github.com/Squidex/squidex/blob/master/backend/src/Squidex.Shared/PermissionIds.cs>

The system is expressed as a hierarchy. Visualizing it as a tree, its structure is as follows:

* squidex
  * apps
    * {app}
      * clients
        * reader
      * contents
        * {schema}
          * read
          * create

The meaning is as follows:

* `squidex` grants you all permissions and makes you an administrator.
* `squidex.apps.{app}` gives you all permissions for a specific App and makes you the App owner.

### Defining Permissions

The permission system also allows wildcards:

`squidex.apps.{app}.contents.*.read` gives you read access to all schemas in your App, but now write access.

Another example of a wildcard is below:

`squidex.apps.{app}.contents.magazine|startups.*` which gives you full access to two schemas, `magazine` and `startups`.

A third example of a wildcard might be:

`squidex.apps.{app}.contents.^settings.*` gives you full access to all schemas except to a schema called `settings`.

### Special Permission

In the list of [available permissions](https://github.com/Squidex/squidex/blob/master/backend/src/Squidex.Shared/PermissionIds.cs) there is one special permission i.e. `squidex.apps.{app}.common`.&#x20;

This gives you the minimum permission to access an App.&#x20;

If you create a role that can only view content, the role will need permission to query the configured languages and published schemas for an App. Therefore, all App roles implicity have this permission.

## Roles

Roles are used to assign one or more permissions to users. You can view the default roles and create custom roles under **Roles** in the **Settings** section of the App.&#x20;

<figure><img src="/files/GFace9xIZrIGpiYWQkzp" alt=""><figcaption><p>Roles</p></figcaption></figure>

### Default Roles

Squidex has four default roles that cannot be deleted.&#x20;

{% hint style="info" %}
`{app}` is your App name.
{% endhint %}

### Owner

An Owner is the owner of the app and can do everything, including delete the App.

|                      |                  |
| -------------------- | ---------------- |
| `squidex.apps.{app}` | All permissions. |

### Developer

Users with a Developer role can use the API view, edit assets, content, schemas, rules, workflows and settings.The Editor Role allows editing assets, editing content and viewing workflows.

| Permission                    | Description                                                     |
| ----------------------------- | --------------------------------------------------------------- |
| `squidex.apps.{app}.api`      | Can use the API section the Management UI.                      |
| `squidex.apps.{app}.assets`   | Can view and manage assets.                                     |
| `squidex.apps.{app}.contents` | Can view and manage contents.                                   |
| `squidex.apps.{app}.patterns` | Can view and manage patterns.                                   |
| `squidex.apps.{app}.rules`    | Can view and manage rules.                                      |
| `squidex.apps.{app}.schemas`  | Can manage schemas (viewing schemas is an implicit permission). |

### Editor

The editor role allows editing assets and contents and viewing workflows

| Permission                    | Description                  |
| ----------------------------- | ---------------------------- |
| `squidex.apps.{app}.assets`   | Can view and manage assets.  |
| `squidex.apps.{app}.contents` | Can view and manage content. |

### Reader

A user with a Reader Role can only read assets and content.

|                                      |                   |
| ------------------------------------ | ----------------- |
| `squidex.apps.{app}.contents.*.read` | Can view content. |

### Custom Roles

You can create custom roles in the Management UI. To create a Custom Role, navigate to **Settings** (1) and then to **Roles** (2) in your App. Enter a **Name** (3) for the role and then click **Add Role** (4).

For our *FoodCrunch* use case, we want to create a Custom Role for blog/magazine contributors. In this scenario, the role is named as **Contributor**.

<figure><img src="/files/py8SX9Q26HRxSN5V3ZwB" alt=""><figcaption><p>Creating a custom role</p></figcaption></figure>

Next, click on the **Gear** icon (5) ![](/files/K3S7HA6xJjjPujM1QoeQ) next to the Custom role to start assigning permissions.

<figure><img src="/files/4visWbXfN81gNCy1R97F" alt=""><figcaption><p>Custom role settings</p></figcaption></figure>

For our *FoodCrunch* magazine contributors, we want to provide *full access to assets and content* only in the App. The default Editor role has these permissions but it also has other permissions that allow reading roles and workflows, which we want to avoid for our use case. Hence, the need for a Custom role.&#x20;

The following permissions will be added:

* `assets`
* `contents.*`

To start adding permissions, begin by typing a few words for the permission that you want to assign and a drop down list will appear, select the desired permission and click + to add it.

So, for our use case we will search for the word **assets** (6) and select **assets** (7) from the list.  This is because we want to provide all permissions for assets. Click **+** (8) to add.

<figure><img src="/files/saFo1g5ePpGULnPF6wW9" alt=""><figcaption><p>Adding permissions to a customer role - 1</p></figcaption></figure>

Similarly, search for **contents** (9) and select **contents.\*** (10) from the list. Click **+** (11) to add the permission to the custom role and then **Save** (12) to save changes.

<figure><img src="/files/4sE3IlARBFpU9iWYxaMm" alt=""><figcaption><p>Adding permissions to a customer role - 2</p></figcaption></figure>

All permissions are automatically prefixed with `squidex.apps.{app}`, otherwise a user would be able to create Roles that grant permissions to another App.

## Administration

As an administrator you can also assign permissions to users individually:

![Custom permissions](/files/Ix6F9FRhFvfUl2M80Xh2)

There are some limitations to bear in mind:

* When you manually assign permissions to a user, the user has to logout and login again for the changes to take effect, as these permissions are stored as claims in a cookie.
* Even if a user has admin permission (`squidex`) or permissions for all Apps (`squidex.apps`) he or she will not see them in the Apps' overview in the administration UI. A Squidex instance can have thousands of Apps (like our Cloud) but the user interface is not designed for this. So, either assign them explicitly to an App, such as `squidex.apps.{app}`, or enter the URL manually.


# Rules

Rules are Used to Automate Workflows by Reacting to Events

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Concepts

### Event

Whenever you make a change in Squidex, such as creating content or updating settings, an event is created. An event describes what happened in the past and has a unique name, for example `ContentChanged`. You can react to events by defining rules.

A rule has two parts:

1. A **Trigger** that defines when to execute the rule. This is also called **If** in the UI.\
   The following triggers are currently available in rules:<br>

   <figure><img src="/files/ItRlLHjKR8EqR06jp4Tg" alt=""><figcaption><p>Triggers in rules</p></figcaption></figure>
2. An **Action** that defines what to do. This is otherwise called **then** in the UI.\
   The following actions are currently available:<br>

   <figure><img src="/files/8JfiIUwXMj4LPiwojyOY" alt=""><figcaption><p>Actions in rules</p></figcaption></figure>

To create a rule go to **Rules** (1) and click **+ Add Rule** (2) or **+ New Rule**.

<figure><img src="/files/wCz7w89tlhNvqW7iKcVd" alt=""><figcaption><p>Creating a rule</p></figcaption></figure>

The final list of rules would look something similar to the screenshot below:

![A rule that creates a twitter update when content has changed](/files/1jw9XRCs9WYKXmGLehad)

## Workflow

To execute a rule the following steps are performed:

1. **Enrichment**: The event is enriched with additional information.
2. **Matching**: The matching rules are determined by comparing the rule triggers with the enriched event.
3. **Formatting**: An rule job is created and stored. It contains all information to execute the rule for the current event.
4. **Execution**: The rule job is queried from the store and executed. If not succeeded it will be marked for a retry at a later point of time.

### 1. Enrichment

Events contain only the bare minimum of information. For the `ContentPublished` event only the if of the content that has been published is needed. All other information can be derived. In addition to that, metadata is only stored, such as the time stamp and the ID of the user who created the content. This is common for all events. However, for next steps additional information is needed. Therefore, these are retrieved from the system and enriched events are created.

The enriched events have the following structure:

#### Content Events

```javascript
{
    "id": "123...", // Id of the content.
    "actor": { "type": "subject", "id": "123..." }, // Id of the user
    "appId": { "name": "my-app", "id": "123..." }, // App name and id
    "created": "2018-01-01T12:00:00Z",
    "createdBy":  { "type": "subject", "id": "123..." },
    "data": { // Content data
        "city": {
            "en": "Munich",
            "de": "München"
        },
        "population": {
            "iv": 123000
        }
    },
    "lastModified": "2018-01-01T12:00:00Z",
    "lastModifiedBy": { "type": "subject", "id": "123..." },
    "schemaId": { "name": "my-schema", "id": "123..." }, // Schema id
    "status": "Draft", // Status of the content: Draft, Archived, Published
    "timestamp": "2018-01-01T12:00:00Z",
    "type": "Created", // The type of the event.
    "user": { // The user information.
        "id": "123...",
        "name": "John Doe",
        "email": "john@email.com"
    },
    "version": 1 // Version of the content, increased with any operation
}
```

The content event type can be one of the following values:

* `Created`: The content has been created.
* `Updated`: The content has been updated.
* `Published`: The status of the content has been changed to `Published`.
* `Unpublished`: The status of the content has been changed from `Published` to another status.
* `StatusChanged`: The status has been changed, e.g. `Draft` to `Archived`.
* `Deleted`: The content has been deleted.

#### Asset Events

```javascript
{
    "id": "123...", // Id of the asset
    "actor": { "type": "subject", "id": "123..." }, // Id of the user
    "appId": { "name": "my-app", "id": "123..." }, // App name and id
    "created": "2018-01-01T12:00:00Z",
    "createdBy": "subject:123",
    "fileName": "Avatar.png",
    "fileSize:": 512000,
    "fileVersion": 1,
    "isImage": true,
    "lastModified": "2018-01-01T12:00:00Z",
    "lastModifiedBy": { "type": "subject", "id": "123..." },
    "mimeType": "image/png",
    "pixelHeight": 600,
    "pixelWidth": 800,
    "timestamp": "2018-01-01T12:00:00Z",
    "type": "Created", // The type of the event.
    "user": { // User information
        "id": "123...",
        "name": "John Doe",
        "email": "john@email.com"
    },
    "version": 1 // Version of the asset, increased with any operation
}
```

The asset event type can be one of the following values:

* `Created`: The asset has been uploaded.
* `Updated`: The file of the asset has been replaced with a newer version.
* `Annotated`: The metadata or slug has been changed.
* `Deleted`: The asset has been deleted.

It is important to understand the structure because it is used in the matching step. Furthermore, some actions just pass over the enriched events to other systems. For example, the `webhook` action adds the event to the request body in (almost) the same format.

### 2. Matching

Let's have a look at a trigger definition first:

![Triggers when a blog post is published or restored](/files/-LrLSGxV9cgqRoJV-2N7)

In the matching process, it is checked whether the action should be executed. There are several conditions:

1. The event type must be correct:
   * A rule with a `AssetChanged` trigger can only handle asset events.
   * A rule with a `ContentChanged` trigger can only handle content events.
2. If a condition is defined it must evaluate to true.

A condition is a JavaScript expression that must return `true` to execute the rule.

Here are some examples to demonstrate this:

Specific asset events:

```
event.type == 'Created' || event.type == 'Updated'
```

Large assets only:

```
event.fileSize > 100000000
```

Images only:

```
event.isImage
```

Of course, it can be more complex if necessary.

### 3. Formatting

When configuring an action, several settings must be defined. In our example above we create a Twitter update and have to define the text we want to publish.

Almost all text settings for actions support placeholders that allow information from the enriched event to be integrated. Furthermore, JavaScript expressions are used for complex use cases.

More information can be found at:

{% content-ref url="/pages/-M2gcX8LcBWtI5NqkxxR" %}
[Rule Formatting](/id-02-documentation/developer-guides/rule-formatting)
{% endcontent-ref %}

### 4. Execution

Squidex will make several attempts to execute a job:

1. The first attempt occurs a few seconds after the event has happened.
2. The next attempt occurs after 5 minutes.
3. The next attempt occurs after 1 hour.
4. Another attempt occurs after 6 hours.
5. The next attempt occurs following 12 hours.

Jobs expire after 2 days and are deleted automatically.

## Webhooks

Webhooks are the most flexible rule actions. You have to provide an HTTP endpoint to Squidex and each event is sent to this endpoint as a `POST` request.

#### Request Headers

* `X-Application` and `User-Agent`

Used to identify the sender and has the static value: `Squidex Webhook`

* `X-Signature`

The signature can be used to verify that a request is from Squidex and not from a potential attacker. The signature is calculated in the following way:

```
ToBase64String(Sha256(RequestBody + Secret))
```

Do **not expose** the secret to the public, always keep it private.


# Publish an Event to Azure Queues using Rules

This Page Demonstrates How to Use Rules To Publish an Event to Azure Storage Queues

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## What are Azure Storage Queues?

Azure Storage Queues, part of the Azure Storage offering, is a service that provides scalable storage capabilities for messages. A single message in an Azure Storage Queue can be upto 64KB in size and there can be millions of messages in a queue.

To read more about Azure Storage Account and its services including Queues refer to the link below:

{% embed url="<https://learn.microsoft.com/en-us/azure/storage/common/storage-introduction>" %}
Azure Storage Documentation
{% endembed %}

## Prerequisites

{% hint style="info" %}
This tutorial does not cover the basics of Azure. It is highly recommended that one be familiar with Azure, Azure Storage, Azure Queues etc to understand this use case and the instructions better. We have attempted to keep the instructions simple enough so they can be followed by anyone.
{% endhint %}

In order to create this rule you will need the following:

* An Azure Subscription

### Create a Storage Account

For the purposes of this example, we will quickly create a Storage Account without getting into the details of configuring all the properties. Select an Azure **Subscription** (1), create or select a **Resource group** (2), provide a unique **Storage account name** (3), choose a **Region** (4) and finally click on **Review** (5).

<div align="left"><figure><img src="/files/Xd4SB2VXTOAuviQiH9xd" alt=""><figcaption><p>Create Storage Account - 1</p></figcaption></figure></div>

Click **Create** (6) to submit the form.

<div align="left"><figure><img src="/files/orfDRcTVBeSyhYgKBxyi" alt=""><figcaption><p>Create Storage Account - 2</p></figcaption></figure></div>

### Create a Queue

To create an *Azure Storage Queue*, select the **Azure Storage account** created earlier (1) and then choose **Queues** (2) from the list. Click **+ Queue** (3), provide a **Queue name** (4) and click **OK** (5) to create the Queue.

<div align="left"><figure><img src="/files/ua0bqAiCvXkRZ7vIwnaA" alt=""><figcaption><p>Create the Azure Storage Queue</p></figcaption></figure></div>

Make a note of this *Queue* name as it will be required later.

### Copy the Connection String

We will need the connection string of the Azure Storage Account as well, so Squidex can authenticate to it and push messages to the Queue.

To gather the connection string, within the **Storage Account** (1) select **Access keys** (2). There are two keys provided for each Storage Account. Click **Show** (3) next to any one of them and then use the **copy button** (4) to copy the connection string. Make a note of it for use in the next steps.

<div align="left"><figure><img src="/files/a41pxtjfjRSIuq7xJxb1" alt=""><figcaption><p>Copy connection string</p></figcaption></figure></div>

## Create the Rule

To demonstrate this example consider a scenario where you have a dashboard that shows the count of *assets created* and the count of *assets deleted*. So, every time an asset is created or deleted you want a message to be sent to an Azure Queue which will then be picked up by the dashboard application to update the counter.

Navigate to **Rules** (1) in the Squidex App and select **+ New Rule** (2).

<div align="left"><figure><img src="/files/q1Ag022DLA8Z2osHciBd" alt=""><figcaption><p>Create new Rule</p></figcaption></figure></div>

Select **Asset changed** (3) as the trigger for this event. This will load additional settings for the trigger that can be configured such as *Conditions*.

<figure><img src="/files/puA6pas7EJStHoZjTLkk" alt=""><figcaption><p>Select the Rule trigger event</p></figcaption></figure>

Add the following custom **condition** (4):

```
event.type == 'Created' || event.type == 'Deleted'
```

<div align="left"><figure><img src="/files/0kT32lh39sQabcgPlfTB" alt=""><figcaption><p>Type Rule condition</p></figcaption></figure></div>

Next, select **Azure Queue** (5) as the action.

<div align="left"><figure><img src="/files/4odFSJhWvcmkNMaY9ll3" alt=""><figcaption><p>Select Rule action</p></figcaption></figure></div>

In the following step, enter the **Connection String** (6) and the **Queue name** (7) noted down from earlier. Click **Save** (8) to save the rule.

<div align="left"><figure><img src="/files/ln87gUMC3Q5NZ1Ap75jq" alt=""><figcaption><p>Configure the Rule action</p></figcaption></figure></div>

### Simulate the Rule

After the rule is created, one can use the **Simulator** feature to simulate the rule using the last 100 events.

To simulate the rule, from the Rules page, click **Simulator** (1) next to the rule and click **Simulate** (2) to execute.&#x20;

This will display whether an event will be published to Azure Queues or not. An event that is will trigger the publish is shown as *Success*.

{% hint style="info" %}
You may not see any successful event if there are no relevant operations that meet the conditions of the rule. Or there may be events that are *skipped* because they are wrong events for this rule.&#x20;
{% endhint %}

<div align="left"><figure><img src="/files/7Bt1X2MIDJB3dXlkmpFB" alt=""><figcaption><p>Simulate a Rule</p></figcaption></figure></div>

## Test the Rule

To test the rule perform an asset create or asset delete operation. For this example, we can upload an image to Squidex that will register an event of type **AssetCreated**. (Steps for uploading assets is not demonstrated as this is a very fundamental operation)

On successful trigger, you will see a status message similar to the screenshot below.

<div align="left"><figure><img src="/files/sW0ACfSdFr1HjRY4in2J" alt=""><figcaption><p>Rule status </p></figcaption></figure></div>

## Verify Messages in the Queue

In a real world there will be applications that will consume the messages published to the Azure Storage Queue. For this demonstration, we can navigate to the Queue in Azure and see the messages.

To do so, navigate to the **Azure Storage Account** (1), click **Queues** (2) and then select the **Queue** (3) i.e. *squidexqueue* in this example, from the list.&#x20;

<div align="left"><figure><img src="/files/6BPONBCZcSWWBcgTTjBz" alt=""><figcaption><p>Navigate to Azure Queue</p></figcaption></figure></div>

You should now see the messages on the screen. The below screenshot is an example of an *AssetCreated* event and the message (without any customizations) that gets published to the Queue.

<div align="left"><figure><img src="/files/HsQFDfuWAsZ67NQxhu4h" alt=""><figcaption><p>Event Message in Azure Queue</p></figcaption></figure></div>

## Customizing the Payload

You can choose to customize the payload in order to customize the message that is pushed to the Queue. We support plain *Text*, *Script* and *Liquid* syntax.

Below is an example of custom payload that posts a message whether the asset is an **image or not**. It is written in the Liquid syntax. You must create a valid JSON string, therefore it is good to use the **escape** formatter.

Refer to the formatting documentation in the link below:

{% content-ref url="/pages/-M2gcX8LcBWtI5NqkxxR" %}
[Rule Formatting](/id-02-documentation/developer-guides/rule-formatting)
{% endcontent-ref %}

<div align="left"><figure><img src="/files/zNvIPCdY0Z4BV0KbRRt1" alt=""><figcaption><p>Custom Payload</p></figcaption></figure></div>

Below is an example of the custom payload message in Azure Queue.

<div align="left"><figure><img src="/files/m96fNKyedrc9HIEdP72s" alt=""><figcaption><p>Custom Payload</p></figcaption></figure></div>


# Populate Elasticsearch Index

This Page Shows How To Populate an Elasticsearch Index using Rules

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

If you are new to *Rules*, we highly recommend reading the following document before continuing:

{% content-ref url="/pages/-LrLSGJtMR37-NG2Rdd4" %}
[Rules](/id-02-documentation/concepts/rules)
{% endcontent-ref %}

The steps provided here are applicable to both self-hosted installations and Squidex Cloud.

## Prerequisites

In order to create this rule you will need a working installation of Elasticsearch. Elasticsearch can be installed either as a Docker container, or in Kubernetes. Refer to the following links for installation instructions:

* Docker installation: <https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html>
* Kubernetes Installation: <https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-installing-eck.html>

In this example, we are using the cloud version of ElasticSearch at <https://elastic.co>. You can subscribe to a 14 day trial of the Elasticsearch cloud [here](https://www.elastic.co/cloud/elasticsearch-service/signup).

{% hint style="info" %}
Squidex also provides an action for **OpenSearch**. The reason OpenSearch is also available is because Elasticsearch blocks all non-official drivers.&#x20;

OpenSearch action has the same logic and works similarly.
{% endhint %}

### Retrieving ElasticSearch Values for Squidex Rule

1. Select your Elastic deployment from the dashboard.&#x20;

<div align="left"><figure><img src="/files/tvOlMZ0TB8GAcjYE1yJO" alt=""><figcaption><p>Select Elastic cluster</p></figcaption></figure></div>

2. Copy the **endpoint** value next to Kibana (2). This is the *Server Url* value which will be required later.

<div align="left"><figure><img src="/files/yx4mpfsl9nNag4WGjy16" alt=""><figcaption><p>Copy Kibana endpoint</p></figcaption></figure></div>

3. The default user is **elastic**. The password is the same that has been set during Elastic cluster deployment. Make a note of them, these will also be required later in the steps.\
   \
   In case there is a need to reset the password, it can be done from security settings of the deployment. \
   Select **Security** from the deployment and then click **Reset password**.

<div align="left"><figure><img src="/files/loEtyx3iEjgRKQQoj8w2" alt=""><figcaption><p>Reset elastic user password (if needed)</p></figcaption></figure></div>

## Creating the Elasticsearch Rule

### Add a Rule

Navigate to **Rules** (1) in the Squidex App and select **+ New Rule** (2).

<div align="left"><figure><img src="/files/PUJTLQaYlQp8bfEm17Vp" alt=""><figcaption><p>Create a new rule</p></figcaption></figure></div>

Select **Content changed** (3) as the trigger for this event. This will load additional settings for the trigger that can be configured such as *Conditions*.

<div align="left"><figure><img src="/files/OggHeqj4naF9ZpyZBmq6" alt=""><figcaption><p>Select when to trigger the rule</p></figcaption></figure></div>

In this example, we are setting this rule for the `magazine` schema only and triggering it for all events in the schema where the event status is *Published*. Click **+** (4) to add a condition, select the **schema** (5) from the dropdown (i.e. `magazine` in this case) and use the following **condition** (6) in Javascript format:

```
event.status == 'Published'
```

<div align="left"><figure><img src="/files/IEDiMYtCFYqnHbg6MiuU" alt=""><figcaption><p>Set the condition for the trigger</p></figcaption></figure></div>

Next, select **Elasticsearch** (7) as the action.

<div align="left"><figure><img src="/files/dxav9nkjTX4aUeCJniYD" alt=""><figcaption><p>Select ElasticSearch as the connector</p></figcaption></figure></div>

Provide the ElasticSearch instance values here such as the Server Url, Index Name, Username, Password etc. Click Save when done.

<div align="left"><figure><img src="/files/NH9Kr7NeZKMTOM7kSBiJ" alt=""><figcaption><p>Configure ElasticSearch in the rule</p></figcaption></figure></div>

### Test the Rule

In order to test the rule we can either create a new article or update an existing article. The article / content must be in *Published* status for the event to trigger.

On successful trigger, you will see a status message similar to the screenshot below.

<div align="left"><figure><img src="/files/TtHgNcUxuqWS2Y88wJHQ" alt=""><figcaption><p>Verify that the rule ran</p></figcaption></figure></div>

### Verifying the Indexed Data

On successful *Rule* execution, one can connect to (or query) the ElasticSearch cluster to validate the creation of the documents in the Index. In this example we are using the managed cluster option through <https://cloud.eastic.co>, hence the screenshots and the navigation instructions refer their platform. If you are using a self-hosted ElasticSearch cluster the screenshots may be different.&#x20;

To view the documents, navigate and login to the **Kibana endpoint** (1) (you can find the same in your Elastic Cloud home page).&#x20;

<div align="left"><figure><img src="/files/7wOgmvTTXsj5RWmmXCYS" alt=""><figcaption><p>Copy Kibana endpoint</p></figcaption></figure></div>

Post login, select **Enterprise Search** (2).

<div align="left"><figure><img src="/files/1jrHBE8coF4hDFrrFMrB" alt=""><figcaption><p>Access Kibana Dashboard</p></figcaption></figure></div>

Next, click **Indices** (3) and then select the **Index** (4), which in this example is **search-squidex**.

<div align="left"><figure><img src="/files/gc8z6FGAj4ClWwaHJINK" alt=""><figcaption><p>Select the index</p></figcaption></figure></div>

Select the **Documents** (5) tab to view the documents created through the Rule.

<div align="left"><figure><img src="/files/9XAPZUI4kvSdC8Zkh8HY" alt=""><figcaption><p>Select documents tab to view documents</p></figcaption></figure></div>

A newly *Published* article appears as **MagazinePublished** and an *Updated* article will appear as **MagazineUpdated**.

## Customizing the Document

You can choose to customize the document that is populated in ElasticSearch. We support plain *Text*, *Script* and *Liquid* syntax.

Below is an example of custom document that sends only the **Title** of the article. It is written in the Liquid syntax. You must create a valid json string, therefore it is good to use the **escape** formatter.

<div align="left"><figure><img src="/files/8eARyLEOBsSr27YXwaPz" alt=""><figcaption><p>Custom Document</p></figcaption></figure></div>

## Additional Information

### Important Information about the Workflow

The content ID from Squidex is used as the Elasticsearch document ID.&#x20;

<div align="left"><figure><img src="/files/7ooIIUM44IyNJDNik6QU" alt=""><figcaption><p>Screenshot of ElasticSearch showing document ID</p></figcaption></figure></div>

Populating to ElasticSearch is an **asynchronous** operation. Hence, it may take a few seconds before the updated results appear on a search.

### Using ElasticSearch as the Full Text Store

You can configure Squidex to use ElasticSearch as the full text store and search directly using Squidex search API. To do this set the following values:

```
FULLTEXT__TYPE=elastic
FULLTEXT__ELASTIC__CONFIGURATION=[CONNECTION_STRING]
FULLTEXT__ELASTIC__INDEXNAME=[NAME_OF_THE_INDEX]
```

For more details and other full text parameters, refer to the `appsettings.json` file at: <https://github.com/Squidex/squidex/blob/master/backend/src/Squidex/appsettings.json#L30>


# Backups

Backups Store all your App Content and are Useful for a Few Scenarios

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## What is a Backup?

A backup is an ZIP archive file that stores all your data for one App. It contains several files of the following types:

1. One file per each event that has happened in your system, for example `AppCreated` or `ContentDeleted`events. These files are named in consecutive order by the order they have occurred in your application.
2. One file per asset in your App.
3. Metadata files, for example, all users in your system as a pair of user IDs and email addresses. When the backup is restored, these users are created in a target system if they do not yet exist.

## Creating Backups

### How to Create Backups

If you are the owner of the App, go to **Settings** (1) and then **Backups** (2). To create a new backup press the **Start Backup** (3) button.&#x20;

<figure><img src="/files/FBsUf8MlfRstrcHx2afN" alt=""><figcaption><p>Creating &#x26; managing backups</p></figcaption></figure>

The screen will not refresh immediately and it can take a few seconds until you see the status of your backup.

{% hint style="info" %}
Squidex only allows 10 backups per App. If you have reached this limit, you need to deleted an old backup to free up space.&#x20;
{% endhint %}

Each backup item has a **Download link** (5) that you need to restore the backup and also shows the **number of events and assets** in your backup (4). When you restore the backup, it will print the number of restored events and you can compare this with the total number of events in your backup to get an understanding of how long the backup operation might take. We do not show a progress indicator because it almost never works properly (see Microsoft Windows).

## Restoring Backups

### Restore a Backup (Self-Hosted)

In order to restore a backup, you will need access to the Administration page. If you are hosting Squidex, you are very likely the administrator and you will see a link to the Administration section when you click your profile.&#x20;

{% hint style="info" %}
This option is not available for you in the Cloud.
{% endhint %}

1. Click on your profile, select **Administration** (1) and click **Restore** (2).
2. Copy the URL from your backup and add it to the first input field called **Url to backup** (3). You can (as an option) provide an **App name** (4) in the second input field.\
   If an App with the same name already exists you have to either delete the App first or define a new name for your restored App.
3. Press the **Restore Backup** button (5) to restore your backup. If you have restored a backup before, you will still see the logs as shown in the following screenshot:

<div align="left"><figure><img src="/files/jrd8NvnAbh1fGU9gVllp" alt=""><figcaption><p>Restoring a backup</p></figcaption></figure></div>

### Restore a Backup (Squidex Cloud)

Backups are critical paths for Squidex and do not provide the same security mechanisms as normal API calls, therefore the backup must be validated first, to ensure that it won't cause any harm to the system. Create a backup of your local or Cloud App and send us the URL in a personal message through the [support forum](https://support.squidex.io).

If you want to restore a backup please complete the following steps:

1. Provide a download link directly to the backup. If you need to delete your App (see point 4) you have to upload your backup to an external storage location first. Please ensure that the backup can be downloaded anonymously (without any authentication). \ <mark style="color:orange;">Do not use Google Drive, because it causes issue when downloading a backup.</mark>
2. Provide the number of events and assets, so that the time it takes to restore the backup can be estimated.&#x20;
3. If you want to change the name of the App, provide a new name.
4. If you want to keep the name then you have to delete your App first. Once the App is deleted, the download link for the backup becomes invalid, which is why it is important to upload the backup to an external storage location first (see point 1).
5. Do not share your backup link in a public post, use personal messages for this purpose.

{% hint style="info" %}
Click the profile picture of the user you want to write a personal message to.
{% endhint %}

### How Does the Restore Process Work?

The restore process executes the following steps:

1. The name of the App is reserved.
2. All events from the backup are inserted into the system. If the event is an asset event, the corresponding asset is added to the system.
3. All indices are restored based on the inserted events.
4. Content and assets are created from the events and added to the database.
5. The App name reservation is either taken if the restore operation was successful or released if the operation has failed.

{% hint style="info" %}
The backup cannot use an existing App and creates a new App to avoid conflict.
{% endhint %}

## Using the Backup System

The backup feature has a few DOs and DON'Ts that are important to understand.

### When to Use the Backup System

1. The backup system is useful if you want to clone your App with its full history to either the same installation or another installation. You can define a new App name for your backup and create as many independent copies as you want.
2. The backup system can be used to migrate from the Cloud to a self-hosted installation or from self-hosting to the Cloud. In the first case, you can just restore the backup yourself. In the second case, you have to create a support ticket as described above.

### When Not to Use the Backup System

1. The backup system is not as fast as MongoDB backup and can only secure your App information. Therefore, using the backup system is not recommended for system backups. Have a look at the official documentation about [Back Up and Restore with MongoDB Tools](https://docs.mongodb.com/manual/tutorial/backup-and-restore-tools/#back-up-and-restore-with-mongodb-tools) to understand the different backup options for MongoDB. If you use a Cloud provider such as MongoDB Atlas, it is typically built in.
2. The backup system constantly creates new Apps and old Apps are not deleted from the system (they are only marked as deleted). Therefore, you should not use the backup system to create syncs between different environments. It is much more efficient to use the synchronization features of the CLI (link below) for this purpose. If you use the backup system for this use case, multiple zombie Apps will be created in your system.&#x20;

{% content-ref url="/pages/-LrLSGK-EWua3sKPXEnZ" %}
[Automation Tools (CLI)](/id-02-documentation/developer-guides/automation-tools)
{% endcontent-ref %}


# Subscriptions

Subscriptions are Used to Manage the Billing and Usage Information for Squidex Cloud.

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

In Squidex Cloud (i.e. cloud.squidex.io) each App has a subscription and each subscription has a quota, which is a defined number of the following:

* API Calls.
* API Traffic.
* Asset Storage Size.
* Number of Contributors.

Subscriptions can be managed in one of two ways:

* **Per App:** The user who creates the subscription owns the subscription and can make changes. The included resources are exclusive to the App.
* **Per Team:** The subscription applies to all Apps belonging to the team. All team *contributors* own the subscription and can make changes. The included resources are shared between all the team's Apps.

## Frequently Asked Questions

### How Can I Subscribe to a Paid Plan for an App?

To subscribe to a paid plan a credit card is required. Other payment methods are currently not supported.

Follow the steps below to subscribe to a paid plan:

1. Go to the App.
2. Click **Settings**.
3. Next, select **Subscription** from settings.
4. Click the **Change** button next to the plan that meets your usage requirements.

<div align="left"><figure><img src="/files/yaf2damCEX50WymVFdng" alt=""><figcaption><p>Subscribe to a paid plan for the App</p></figcaption></figure></div>

### How Can I Subscribe to a Paid Plan for a Team?

You can create a team to share a subscription between Apps and allow multiple users to change the subscription.

Follow the steps below to subscribe to a paid plan for a team:

1. Click the *Apps and Teams* drop down and select your team (1).
2. Go to **Settings**.
3. Select **Subscription**.
4. Click the **Change** button next to the plan that meets your usage requirements.

<div align="left"><figure><img src="/files/dnmgSpIGavwKlC9kK3DV" alt=""><figcaption><p>Subscribe to a paid plan for a team</p></figcaption></figure></div>

### Can Every Contributor Subscribe to a Paid Plan?

**Yes.** When you subscribe to a plan, a new customer is created in the billing system under your name and only the user who subscribed to the plan can cancel the subscription or move over to another plan.

### Why is an App Bound to a Single User?

Some companies manage multiple Apps. When the first version of the billing system was created, the teams' concept had not yet been introduced. Therefore, the billing system has been built in such a way that a single user is able to manage multiple subscriptions under one customer account.

### What Can I do if the Subscription Creator has Left the Company?

Only the subscription creator can delete or change a subscription. However, we can cancel the subscription for you in our Back Office. To do this, create a support ticket in the [support forum](https://support.squidex.io/) and we will cancel the subscription. **On cancelling the subscription, your App will fall back to the free plan but no data will be lost.** All contributors and assets are retained as they are, even if they exceed the limits of the free plan. However, it is possible that API might be blocked when you move beyond your monthly limit. Therefore, it is best to initiate this process at the beginning of the month.

### What Happens when I Exceed the API Limits?

The API limits include contingencies in each plan. If you exceed the API limit, you will be charged 0.2€ / 1000 API calls additionally at the end of the month, along with your last active plan.&#x20;

In addition to this, each paid plan has a maximum limit of three times the default API limit. The table below explains this:

<table><thead><tr><th width="145">Plan</th><th width="173">Included API Limit</th><th width="180">Blocking API Limit</th><th>Additional Cost</th></tr></thead><tbody><tr><td>Free</td><td>20,000</td><td>20,000</td><td>N/A</td></tr><tr><td>Basic</td><td>100,000</td><td>300,000</td><td>0.2€ / 1000 API calls after 100,000</td></tr><tr><td>Professional</td><td>500,000</td><td>1,500,000</td><td>0.2€ / 1000 API calls after 500,000</td></tr><tr><td>Business</td><td>1,500,000</td><td>4,500,000</td><td>0.2€ / 1000 API calls after 1,500,000</td></tr></tbody></table>

When this maximum limit is reached, API calls are blocked, but we will inform you if we foresee this happening.

### How Can I Change My Credit Card or Contact Information?

Squidex provides access to a billing portal for this purpose. Using this portal you can do the following:

1. View and manage all your subscriptions.
2. View and manage general account information.
3. View and change the billing address.
4. View and change payment methods.
5. View payment history and download invoices.

<div align="left"><figure><img src="/files/MdB2LrDv3wlQThYpzVBt" alt=""><figcaption><p>Billing portal</p></figcaption></figure></div>

To access the Billing Portal, navigate to **Subscription** in **Settings** and click the **Billing Portal** link at the bottom of the page.

<div align="left"><figure><img src="/files/BRTxHSnMNAVc0J6xBhjf" alt=""><figcaption><p>Link to the billing portal</p></figcaption></figure></div>


# App Subscriptions v/s Team Subscriptions

Common differences between app and team subscriptions, and frequently asked questions

{% hint style="info" %}
Subscriptions apply to Squidex Cloud only.
{% endhint %}

Teams were introduced to solve problems with sharing costs and changing subscription ownership. This article tries to provide differences between *App Subscriptions* and *Teams Subscriptions* and answer some of the frequently asked questions. &#x20;

Let's start with a table provides a difference between App Subscriptions and Team Subscriptions

<table data-full-width="false"><thead><tr><th width="375">(Single) App Subscriptions</th><th>Team Subscriptions</th></tr></thead><tbody><tr><td>Each App has its own subscription.</td><td>Multiple Apps that are part of a Team share the subscription.</td></tr><tr><td>Contributors in an App with the role "owner" are not subscription owners. Only the contributor that created the subscription is the owner.</td><td>All contributors of a team are subscription owners as well.</td></tr><tr><td>App subscription ownership cannot be transferred to another contributor</td><td>Subscription transfer issues do not apply to teams.</td></tr></tbody></table>

{% hint style="info" %}
Note that App owner and Subscription owner is not the same, especially in App settings where you can add more than one owner, these owners are App owners.&#x20;
{% endhint %}

## Frequently Asked Questions (FAQs) about *App Subscriptions*

### How can I transfer an *app subscription* ownership to another user?

Unfortunately, **subscription ownership transfer in Apps is not possible**. You can only cancel the subscription, which does not delete any data.

### What happens when the *app subscription* owner leaves the company?

Create a support request at <https://support.squidex.io/> to get it cancelled. **On cancelling the subscription, the App will fall back to the free plan but no data will be lost.** Please remember that you cannot add new members anymore, because the limits of the free plan apply.

### What happens if there is some money left in the *app subscription*?

Start with a subscription cancellation request first. You can then subscribe to the cheapest plan and create a support request to get promotional credit for your balance. When you change your new subscription to the target plan the promotional credit will be applied and deducted from your invoice.

### Can I apply usage restrictions to an app in a shared subscription?

There is no system in place to restrict the usage for a single app. But if you go the client settings of your app you can configure a limit for the number of API calls that a single client can make. Use this setting to ensure that one app does not dry out other apps.

### Can an app be moved to a team with a different subscription plan?&#x20;

(For example app subscription is *Professional*, where as teams subscription is *Basic*)\
Unfortunately **No**. The process is to cancel the current subscription for the App first and the move the App into the Team. Otherwise the applied limits would be inconsistent and confusing.


# Notifications

Learn How to Configure and Subscribe to Notifications in Squidex

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Introduction

Squidex provides a notification service that is power by *Notifo*. [*Notifo*](https://notifo.io) is an open source general purpose notification framework, initially developed for Squidex to enable users to subscribe to changes and receive notifications.

### Scenarios Where Notifications Can Be Triggered

* When there is a content item change.
* When there are changes made to the schema.
* When there are changes to contributors, such as adding contributors or changing their roles.&#x20;
* Notifications can also be triggered by rules.
* Notifications are also triggered by mentions and comments, e.g. if you add "*@user*" to a comment

{% hint style="info" %}
This notification service only works in Squidex Cloud (i.e. cloud.squidex.io) by default. To use this service in a self-hosted installation, you must separately install and run the Notifo service. Click here for installation instructions (coming soon).&#x20;
{% endhint %}

### How Notifications Work

* Notifications can be received through email or (web) push notifications.

{% hint style="info" %}
At the time of writing this article, email notifications are not operational.
{% endhint %}

* Those subscribing to a change won't see any notifications if they initiated the changes themselves. A notification is only created if changes are made by someone else.

The notification icon is situated on the top menu bar, next to the user icon. Clicking on this displays *Notifications*, *Archive* (archived notifications) and the notification *Profile*.

<div align="left"><figure><img src="/files/WGfky7jSgso62YAfJXdV" alt=""><figcaption><p>Notification icon</p></figcaption></figure></div>

## Subscribing to Changes

Follow the instructions below to subscribe to changes. For this purpose of this example we are subscribing to changes in **Settings** > **Contributors**.

1. Navigate to **Settings** (1) and then **Contributors** (2) for your App.<br>

   <div align="left"><figure><img src="/files/EnLeDg0GA0ByPxofZEWK" alt=""><figcaption><p>Contributor settings for the app</p></figcaption></figure></div>
2. Next, subscribe to the changes. To do so, click on the **bell icon** (3) and then toggle to the **notification mediums of choice** (4), Notifications can be received by Email and/or Push Notifications. Next, click **Save** (5).<br>

   <div align="left"><figure><img src="/files/BrdUBXDAskEJuuOjP6Xh" alt=""><figcaption><p>Subscribing to notifications</p></figcaption></figure></div>
3. At this point a new contributor can be added or invited. To test notifications, you can add a contributor. \
   \
   To do so, **enter the name or email** (1) address of the contributor (a drop down menu appears making it easy to select an existing user or enter an email address if you wish to add a new user). Next, select the role for example **Editor** (2) and click **Add Contributor** (3). \
   \ <mark style="color:orange;">NOTE: Subscribers will only receive a notification if the change is actioned by another user.</mark><br>

   <figure><img src="/files/7CoaISLxNENvCUt1DFD5" alt=""><figcaption><p>Adding a contributor</p></figcaption></figure>
4. Whenever a new contributor is added, the subscribed user will receive a notification through their chosen notification methods (currently, only Web Push Notifications work). An example of a (eWb) Push Notification can be viewed below.

## Setting the Notification Profile

To set the notification profile, click the **Notification icon** (1) on the top bar and then select the **Profile** tab (2). You can set your notification preferences, notification email and preferred time zone amongst other settings.

<div align="left"><figure><img src="/files/qXXjzZnSbHBemmHYziCZ" alt=""><figcaption><p>Setting notification profile</p></figcaption></figure></div>


# Dashboard

This Page Describes the Dashboard Page and How to Customize it

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

Apps in Squidex use a dashboard that displays various statistics concerning the App along with some quick links to operations, as well as some external links.

The default view of the dashboard shows the following statistics as cards:

* API Calls.
* API Performance (ms).
* API Calls Summary.
* Asset Uploads.
* Assets Size (MB).
* Traffic (MB).
* History.

<div align="left"><figure><img src="/files/Bj7z4wT6nQjy5635cPyr" alt=""><figcaption><p>Squidex App Dashboard</p></figcaption></figure></div>

{% hint style="info" %}
All assets are **anonymous**, this means you may see anonymous API Calls, Traffic or API performance statistics even though there may be no anonymous access.
{% endhint %}

### Customizing the Dashboard

#### Adding / Removing cards

In order to customize the dashboard, click on **Settings** (the gear icon) situated at the top-right of the page.

<div align="left"><figure><img src="/files/95AfRuNVrO2pZRXb76jo" alt=""><figcaption><p>Customizing the dashboard</p></figcaption></figure></div>

To remove a card or statistic, **uncheck** it from the list. To add a statistic, **check** it.

#### Resizing cards

The individual cards can also be resized to suit personal preference. To resize, move the mouse over to the bottom-right corner of the card to reveal the resize control.&#x20;

<div align="left"><figure><img src="/files/NrBuSj9SIu1gbSZHACE1" alt=""><figcaption><p>Resize a card</p></figcaption></figure></div>

Next, press and hold the pointer (mouse or touchpad) and drag it left or right to resize.

<div align="left"><figure><img src="/files/vORiMHsV4ZQbeErJx0SQ" alt=""><figcaption><p>Resized card</p></figcaption></figure></div>

#### Changing Graph View&#x20;

Some statistics can be viewed in both grouped mode (default) and in stacked mode. To toggle, click the **Stacked** check box on the card.&#x20;

<div><figure><img src="/files/V39JNBemBCnXn3DxUJWU" alt=""><figcaption><p>Grouped view</p></figcaption></figure> <figure><img src="/files/MVUIGjcYWi2Pa3vsx27d" alt=""><figcaption><p>Stacked view</p></figcaption></figure></div>

{% hint style="info" %}
Changing the view of any card to Stacked changes it for all cards.
{% endhint %}

#### Filtering

You can filter clients from a metric. This is done by clicking the name which strikes it off so it is no longer displayed on the card.&#x20;

For example, in the following screenshot default, reader and Squidex frontend clients are disabled and only anonymous traffic size is displayed.

<div align="left"><figure><img src="/files/YZU83tilxBFfR5AH8f97" alt=""><figcaption><p>Filter clients from a metric</p></figcaption></figure></div>

### Expert Mode

An **Expert Mode** is also available (choose from the Settings icon) if you wish to modify it through code.

<figure><img src="/files/KQIYlcTj9GoUVa3Wfn9K" alt=""><figcaption><p>Expert Mode</p></figcaption></figure>

### Downloading API Logs

API logs can be downloaded in a CSV format from the dashboard page too. To do so, click on the **Download Log** link on the **API Calls** card.&#x20;

<div align="left"><figure><img src="/files/ENwUvzbmyljxc4qhRqJ1" alt=""><figcaption><p>Download API Calls Logs</p></figcaption></figure></div>

Additional operations that can be completed from the dashboard include:

* Navigate to Create Schema page.&#x20;
* View the API documentation for the App.
* Navigate to [Support](https://support.squidex.io/) page for feedback and support.
* Navigate to the source code on GitHub.


# Workflows

When the Content Item is Available in the Public API, Workflows Define the Various Statuses of a Content Item, from Initial Status to Published Status,

## Introduction

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## What is a Workflow?

A workflow defines the various statuses of a content item from initial status to published status. Workflows are typically used when contributors work together to create and publish content items and when they have different roles, such as Writer, Reviewer and Publisher. Workflows are very flexible and you can have an unlimited number of workflows per App.

### Terms

A content workflow is represented as a [graph](https://en.wikipedia.org/wiki/Graph_\(discrete_mathematics\)) of statuses (nodes) that are connected with transitions (edges). The **Status** of a content item is defined by the workflow that is configured for the content schema.

* A **Status** is a unique name that represents the status of your content item and has a colour to identify it in the user interface.
* A **Transition** defines upcoming statuses of your content item. A transition defines the next status and can have an optional expression and role/s.

Workflows do not exist in older Squidex installations, but you can implement them using scripting.

{% content-ref url="/pages/-LrLSGK6PM\_UsZjAYfx2" %}
[Custom Workflows](/id-02-documentation/developer-guides/workflows)
{% endcontent-ref %}

## How to Create a Workflow

Complete the following steps to create a new workflow:

1. Go to your **App** (1).
2. Click **Settings** (2) and then select **Workflows** (3).
3. Enter a **Name** (4) for the workflow and click **Add Workflow** (5).\
   Note that this name is only used for this screen and can be changed later.

<div align="left"><figure><img src="/files/cB35Qdm5WyAXbCZYzrPB" alt=""><figcaption><p>Creating a workflow</p></figcaption></figure></div>

Typically, the list of workflows is empty here, which means that *default workflow* is applied to all the schemas. The default workflow has the same structure as the new workflow that's just been created. If a schema has no matching workflow, this default workflow will be applied.

#### The Default Workflow

To visualize the default workflow click on the **Gear icon** (1) ![](/files/K3S7HA6xJjjPujM1QoeQ) next to the workflow name, then click the **Visualize** (2) tab. You should see the graphical representation of your workflow here.

<div align="left"><figure><img src="/files/OlvFXI3RxoBbRzHErJjB" alt=""><figcaption><p>Visualizing the default workflow</p></figcaption></figure></div>

The default workflow has three statuses as follows:

* **Draft** (3) is the initial status for each content item. The initial status is visualized by an arrow. From Draft, you can either change a content item to "Published" or to "Archived".
* **Archived** (4) is a status that is used to mark deleted content items without actually deleting them. It is a soft delete. Archived content items cannot be updated. You cannot change from "Archived" to "Published" directly, you must change to "Draft" first.
* **Published** (5) is a special status that cannot be removed. Only content items with a status set to "Published" are available in the normal API. If you do not need a workflow at all, you can delete all other statuses.

## How to Edit a Workflow

The user interface is intuitive enough that it does not need detailed instructions, but there are some special cases that need explanation.

### Changing the Initial Status

The initial status is indicated with a small **arrow icon** (1). You can change this by moving the mouse over to another status (which displays the same arrow icon next to it) and clicking it.&#x20;

In the following screenshot, there are three statuses (this is default in a new workflow) and **Draft** is the initial status, marked with the arrow.

<div align="left"><figure><img src="/files/sC6rOq2VzvkyGOpbgytb" alt=""><figcaption><p>Initial status</p></figcaption></figure></div>

In the below screenshot, as an example, we are changing the initial status to **Ready**. To do this, we move the mouse over to Ready and click it.

<div align="left"><figure><img src="/files/qe4DKJe7ZZ4EB9EMs93l" alt=""><figcaption><p>Changing initial status</p></figcaption></figure></div>

### Changing the Color of a Status

The status colors can be changed. Just click the colored circle next to each step to change to the desired color.

<div align="left"><figure><img src="/files/F4LYt6COnQcCMU0jSfSN" alt=""><figcaption><p>Change the color of a status</p></figcaption></figure></div>

### Restricting Transitions with Roles

You can assign one or more roles to a transition. When a role is assigned, only contributors that are assigned to that role can move a content item to the target status as defined by this transition. When no role is assigned, all contributors can perform an action. However, the action is also restricted by Permissions, so when a user has no permission, he or she cannot update content items at all, and the workflow cannot perform these changes.

Let's have a look at the following example:

<div align="left"><img src="/files/-MSN5oCM94QZEsa0JORT" alt="A Use Case with a review status"></div>

{% hint style="info" %}
The screenshot above has been modified and some UI elements have been removed to make it more compact.
{% endhint %}

In our use case, we have described three user groups:

1. **Editors** write content. When they are finished, they move their content item to **Ready** status. They cannot review or publish content items.
2. **Reviewers** review content items, when they are ready for review. They either move the content item to **Reviewed**, when the review process is successful or back to "Draft" if the content item requires changes.
3. **Publishers** publish reviewed content items by moving these content items to "Published"**.**

This workflow can be extended further and multiple review steps can be implemented.

{% hint style="info" %}
Workflows can be combined with Permissions to restrict who can create content items.
{% endhint %}

### Restricting Transitions with Expressions

You can write a JavaScript expression. This expression is evaluated each time a piece of content is updated to determine whether a transition to another status is possible.

For example, if you have a schema for different types of editorial content items such as articles, blog posts or news, then this type of item is defined by a string field.

In this case, we extend the example from the previous section where we used roles.

<div align="left"><img src="/files/-MSNMFYSKpBPofVauAGE" alt="Workflow Expressions"></div>

A content item where the type field is set to **News** does not need a review and can be published directly. Articles that need the review process will have previous rules applied.

When you design the Expression you only have access to the content data which is directly part of the content. For example, when you have a relationship, only the IDs of the referenced items are stored in the content and not the content of the references itself.

The data has the same structure as it is also used for queries and for the API.

```javascript
{
    "data": {
        // More fields
        "type": {
            "iv": "News"
        }
    }
}
```

The structure is explained in detail here in this use case documentation:

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

### Assigning a Workflow to Schemas

Workflows can be assigned to schemas. If no schema is assigned, the workflow handles all schemas where no other workflow is assigned. Other schemas that have no assigned workflow, have an implicit default workflow.

<div align="left"><figure><img src="/files/loHNH5D4pvgoZXjEEmu4" alt=""><figcaption><p>Assigned schemas</p></figcaption></figure></div>

You must pay attention not to assign the same schema to multiple workflows. This can happen when you have multiple workflows that have no assigned schemas

<div align="left"><figure><img src="/files/zkPTyGYcJ9IzufloGSvp" alt=""><figcaption><p>Multiple workflows with no schemas assigned</p></figcaption></figure></div>

Or when multiple workflows have the same schema assigned to them.

<div align="left"><figure><img src="/files/DlFsX5jzgp84kbt4fEYK" alt=""><figcaption><p>Multiple workflows for the same schema</p></figcaption></figure></div>

In both cases, you will receive a warning which should be resolved. You can save workflows and create and update content items as normal but the workflow won't be defined when a content item is updated. It can even change over time and behavior is unpredictable.

### Prevent Updates

You can prevent updates when a content item has a certain status. In some cases, you can also define an optional expression or role to restrict the updates or for a specific subset of your contributors.

<div align="left"><img src="/files/-MSNPcF8i5FZPKiTK_ok" alt="Prevent updates"></div>

In the default workflow, this situation is used to turn **Archived** content items into read-only, but you can also use this feature to stop updates during the review process.


# Software Development Kits

We Provide Software Development Kits (SDKs) for .NET Standard and TypeScript


# TypeScript

Learn how to use the TypeScript SDK for Node and Browsers.

## Introduction

The SDK is mostly generated from the OpenAPI specification. You can read the API documentation here:[ https://cloud.squidex.io/api/docs](https://cloud.squidex.io/api/docs).

This means that the API is fully covered, including all endpoints used by the frontend. You can also use the SDK to create and configure Apps, schemas and rules.&#x20;

Use this SDK in your TypeScript or JavaScript application to integrate with Squidex CMS.

## Install the SDK

The SDK is available on [npm](https://www.npmjs.com/package/@squidex/squidex) registry. You can install it with:

```bash
npm install @squidex/squidex --save
```

or

```bash
yarn add @squidex/squidex
```

The SDK has no peer dependencies and will add/install all required packages.

## Instantiate the SDK

The `SquidexClient` is the main entry point for the SDK. It provides the properties for all endpoints.

You can instantiate the client using the following code snippet:

```typescript
import { SquidexClient } from "@squidex/squidex";

const sdk = new SquidexClient({
    appName: "your-app",
    clientId: "your-app:default",
    clientSecret: "xxx",
    environment: "https://cloud.squidex.io"
});
```

The `environment` parameter is optional if you are using Squidex Cloud.

### Additional Configuration

In this article, we will cover some of the important configuration values only. Have a look at the source code for all available options: <https://github.com/Squidex/sdk-node/blob/main/src/wrapper/SquidexClient.ts#L8>

#### Timeout

Configure a timeout in milliseconds to cancel unresponsive requests.

```typescript
const client = new SquidexClient({
    ...
    timeoutInMs: 5000
});
```

#### Token Store

The SDK uses the client ID and the client secret to acquire a bearer token, and handles invalidation and expiration automatically. By default the token is stored inside the client and therefore a new token is acquired when the client is cleaned by the garbage collector. However, one can define where the token is stored.&#x20;

You can store the token in the local store.

```typescript
const client = new SquidexClient({
    ...
    tokenStore: new SquidexClient.StorageTokenStore()
});
```

One can also store the token in the session store using the following configuration:

```typescript
const client = new SquidexClient({
    ...
    tokenStore: new SquidexClient.StorageTokenStore(sessionStore, 'MyTokenKey')
});
```

#### Interceptors

You can control the HTTP request flow with interceptors. They work similar to a middleware in NodeJS. Because the SDK has separate pipelines for normal requests and streaming requests (when downloading a file), you should ideally implement both interceptors.

For example you can add custom headers using the following code snippet:

```typescript
const client = new SquidexClient({
    ...
    fetcherInterceptor: next => {
        return args => {
            args.headers ??= {};
            args.headers['Key'] = 'Value';
            return next(args);
        }
    },
    streamingFetcherInterceptor: next => {
        return args => {
            args.headers ??= {};
            args.headers['Key'] = 'Value';
            return next(args);
        }
    },
});
```

Use the following code snippet example to implement retry requests:

```typescript
const client = new SquidexClient({
    ...
    fetcherInterceptor: next => {
        return args => {
            const NUM_ATTEMPTS = 5;
            for (let attempt = 1; attempt <= NUM_ATTEMPTS; attempt++) {
                try {
                    return next(args);
                } catch (ex) {
                    if (attempt === NUM_ATTEMPTS) {
                        throw ex;
                    }
                }
            }

            throw new Error('Max attempts reached without error.');
        }
    }
});
```

## Use the Client

To use the client you have to use the correct property.

For example, to create a schema use the `schemas` property.

```typescript
const createdSchema =
    await client.schemas.postSchema({
        name: "my-schema",
        fields: [{
            name: "field1",
            properties: {
                fieldType: "String",
            },
        }],
        isPublished: true,
    });
```

In order to query contents, use the `contents` property.

```typescript
const createdContent = 
    await client.contents.postContent("my-schema", {
        body: {
            field1: {
                iv: "My-Value"
            }
        }
    });
```

### Error Handling

The SDK uses exceptions for error handling.&#x20;

Refer to the code snippet below for an example:

```typescript
import { SquidexClient, SquidexError } from "@squidex/squidex";

const client = new SquidexClient({
    ...
});

try {
    await client.contents.postContent("my-schema", { ... });
} catch (ex) {
    if (ex instanceof SquidexError && ex.statusCode === 400) {
        console.log("Validation Error");
    }
}
```

## Limitations

As of this writing, the SDK has the following limitations:

1. Endpoints to download files (e.g. assets) return a stream only and not metadata information like the content type or the content length.
2. Endpoints to download files work for Node only and are not available in the browser. This is generally not an issue as assets are handled via direct links in the browser.
3. When logging an instance of the `SquidexError` class, only the message is written. But the instance also contains the error object from the API that has detailed information about the problem, especially for validation errors. Therefore, it is recommended to convert the error to a JSON string before logging it.
4. Deprecated methods and properties are not annotated yet.


# .NET

Read more on how to use the .NET Core and .NET Standard

## Introduction

The SDK is mostly generated from the OpenAPI specification. You can read the API documentation here:[ https://cloud.squidex.io/api/docs](https://cloud.squidex.io/api/docs).

This means that the API is fully covered, including all endpoints used by the frontend. You can also use the SDK to create and configure Apps, schemas and rules.

The downside is that some of the methods are not as user-friendly as they could be. Most methods have the App name as a required parameter, which is redundant because the SDK is designed to mainly connect to a single App and has the App name as a global configuration option.

This has been changed with **v15.0.0,** and the developer experience has been improved. Therefore, this document includes the difference between version 15 and previous versions.&#x20;

## Generating Content Classes

Because of localization, the OpenAPI specification and the generated classes are very difficult to use, especially if your fields are not localized. Therefore, it is best to manually create the mapping classes for your schemas. Of course, you can also use OpenAPI to generate them.

Our recommendation is to use [NSwag](https://github.com/RicoSuter/NSwag). The code generator is also available as a class library to automate the code generation in your CI pipeline.

## Install the SDK

The SDK is available on [nuget.org](https://www.nuget.org/packages/Squidex.ClientLibrary/). You can install it with:

```bash
dotnet add package Squidex.ClientLibrary
```

If you use [Dependency Injection](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0) (especially in ASP.NET Core) you can use the following package:

```bash
dotnet add package Squidex.ClientLibrary.ServiceExtensions
```

## Instantiate the SDK

As described above, the SDK has been improved with version 15. Therefore, it is best to read one of the following pages, depending on the installed version of the package.

{% content-ref url="/pages/VskFvl4kvXQQX0fr4MrX" %}
[Version v15 (and Later)](/id-02-documentation/software-development-kits/.net-standard/version-v15-and-later)
{% endcontent-ref %}

{% content-ref url="/pages/6RHpcVPdViF1yg9qfasR" %}
[Version v14 (and Earlier)](/id-02-documentation/software-development-kits/.net-standard/version-v14-and-earlier)
{% endcontent-ref %}

### Getting the Code from the Management UI

You can get the initialization code directly from the Management UI. To do so, follow the steps below:

* Go to **Settings** (1).
* Go to **Clients** (2).
* Click the **Connect** (3) button next to the client.

<div align="left"><figure><img src="/files/jAHsM4GsrFRDU2wdwmuQ" alt=""><figcaption><p>Client settings for the App</p></figcaption></figure></div>

Next, click on the third link titled **Connect to your App with SDK** (4) to view instructions.

<div align="left"><figure><img src="/files/UBA5Xt4UHxExD8JAoa9O" alt=""><figcaption><p>The connect wizard for the App client</p></figcaption></figure></div>

On the next screen, **copy** (5) and paste the sample code applicable for your version to the source file.

{% hint style="info" %}
As of this writing, SDK version 15 has not been deployed to Squidex Cloud. So Squidex Cloud will only show code for version 14.&#x20;
{% endhint %}

<div align="left"><figure><img src="/files/W3fkGUmhawHVOTH3IXnH" alt=""><figcaption><p>The sample code for the client manager</p></figcaption></figure></div>

## How to Work with Content Items?

Working with content requires more work!

### Why is there no Code Generation?

The reason there's no code generation is because of the JSON structure of the content data. Let's assume we have a `blog-post` schema with two fields: a localized `title` field and a normal (invariant) `slug` field. The resulting content response would look like the following example:

```javascript
{
   "title": {
      "en": "Hello Squidex",
      "de": "HALLO Squidex"
   },
   "slug": {
      "iv": "hello-squidex"
   }
}
```

When you map a structure to a C# class, every JSON object is mapped to either a dictionary or a class.

A code generator would then create the following class structure and would convert all JSON property names to a *PascalCase* naming to align the naming with the C# conventions. So, whenever you work with invariant fields you have to access the `Iv` property to get the value.

```csharp
public class BlogPostTitle {
    public string En { get; set; }
    public string De { get; set; }
}

public class BlogPostSlug {
    public string Iv { get; set; }
}

public class BlogPostData {
    public BlogPostTitle Title { get; set; }

    public BlogPostSlug Slug { get; set; }
}
```

This must be repeated for each field and would create a lot of code. Therefore, it is better to create the content classes manually and use custom converters to let the *Newtonsoft JSON serialiser* deal with this.

### 1. Create Your Class Model

For each schema two classes are needed:

The data object is the structure of your content data.

```csharp
using Newtonsoft.Json;
using Squidex.ClientLibrary;

namespace YourNamespace;

public sealed class BlogPostData
{
    // The invariant converter converts the object to a flat property.
    [JsonConverter(typeof(InvariantConverter))]
    public string Slug { get; set; }

    // For localizable fields you can use dictionaries.
    [JsonProperty("my_title")]
    public Dictionary<string, string> Title { get; set; }
}
```

Another class is created for the blog post itself, which holds the data and metadata.

```csharp
using Squidex.ClientLibrary;

namespace YourNamespace;

public sealed class BlogPost : Content<BlogPostData>
{
}
```

{% hint style="info" %}
Please note that the SDK still uses `Newtonsoft.JSON` and not `System.Text.JSON`. Some types, such as `JSONConverterAttribute` exist in both namespaces, so you have to ensure you add the user to the correct namespace. Otherwise you will get serialization exceptions, because the attributes have not been considered.
{% endhint %}

#### How to Map Fields to .NET Types

This depends on your field type i.e. which .NET type you use for a field.

The following is our recommendation:

<table><thead><tr><th width="197">Field Type</th><th>.NET Type</th></tr></thead><tbody><tr><td>Assets</td><td><code>System.Collections.Generic.List&#x3C;System.Guid></code></td></tr><tr><td>Boolean</td><td><code>bool</code></td></tr><tr><td>DateTime</td><td><code>System.DateTime</code> or <code>System.DateTimeOffset</code></td></tr><tr><td>Geolocation</td><td>A custom class.</td></tr><tr><td>Json</td><td><code>Newtonsoft.Json.Linq.JObject</code> or a custom class.</td></tr><tr><td>Number</td><td><code>double</code></td></tr><tr><td>References</td><td><code>System.Collections.Generic.List&#x3C;System.Guid></code></td></tr><tr><td>String</td><td><code>string</code></td></tr><tr><td>Tags</td><td><code>System.Collections.Generic.List&#x3C;Sstring></code></td></tr><tr><td>Array</td><td>A custom class.</td></tr></tbody></table>

#### Geolocation Classes

At the moment, the SDK does not provide a ready-to-use structure for geolocations, but you can use the following class:

```csharp
public class Geolocation 
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}
```

#### Arrays

When you have an array field, you need a class for your array items, for example:

```csharp
using Newtonsoft.Json;
using Squidex.ClientLibrary;

namespace YourNamespace;

public class Comment 
{
    public string Author { get; set; }

    public string Text { get; set; }
}

public sealed class BlogPostData
{
    // For invariant array fields.
    [JsonConverter(typeof(InvariantConverter))]
    public List<Comment> Comments { get; set; }

    // For localized array fields.
    [JsonProperty("my_comments")]
    public Dictionary<string, List<Comment>> Comments  { get; set; }
}
```

{% hint style="info" %}
Please note that the `InvariantConverter`is only needed for root fields.
{% endhint %}

### 2. Instantiate the Client

#### Version 15 and Above

Use the schema name and the created types as arguments.

```csharp
var blogPostsClient =
    client.Contents<BlogPost, BlogPostData>("blog-post");
```

The client is cached and therefore you can call this method as often as you want.

#### Version 14 and Below

Use the schema name and the created types as arguments.

```csharp
var blogPostsClient =
    clientManager.CreateContentsClient<BlogPost, BlogPostData>("blog-post");
```

Do not recreate the client for every request as it is not cached in the client manager.

### 3. Use the Client

Using the client is very easy, for example:

#### Get a Content Item by ID

```csharp
var post = await blogPostsClient.GetAsync("10cb16da-60d2-4ff7-bd2c-47d724a4798c");
```

#### Get a Content Item by ID and Version

```csharp
var post = await blogPostsClient.GetAsync("10cb16da-60d2-4ff7-bd2c-47d724a4798c", 4);
```

#### Create a New Content Item

```csharp
var data = new BlogPostData
{
    Slug = "hello-squidex",
    Title = new Dictionary<string, string>
    {
        ["en"] = "Hello Squidex",
        ["de"] = "Hallo Squidex"
    }
};

await blogPostsClient.CreateAsync(data);
```

#### Update a Content Item

```csharp
var data = new BlogPostData
{
    Slug = "hello-squidex",
    Title = new Dictionary<string, string>
    {
        ["en"] = "Hello Squidex",
        ["de"] = "Hallo Squidex"
    }
};

await blogPostsClient.UpdateAsync("10cb16da-60d2-4ff7-bd2c-47d724a4798c", data);
```

#### Query Items by Filter

```csharp
var posts = await blogPostsClient.GetAsync(new ContentQuery
{
    Filter = $"data/slug/iv eq '{slug}'"
});
```

### Control the Casing

The SDK uses camelCasing when serializing properties, but ignores the casing when deserializing properties. This is important, because Squidex is case sensitive and does not accept unknown properties. Therefore the following data class

```csharp
using Newtonsoft.Json;
using Squidex.ClientLibrary;

namespace YourNamespace;

public sealed class BlogPostData
{
    [JsonConverter(typeof(InvariantConverter))]
    public string Slug { get; set; }
}
```

is serialized to

```json
{
    "slug": "my-blog-post"
}
```

If you want to use PascalCase for your Squidex schema field names you have two options:

You can either disable the conversion to camel case with the `KeepCasingAttribute`:

```csharp
[KeepCasing]
public sealed class BlogPostData
{
    [JsonConverter(typeof(InvariantConverter))]
    public string Slug { get; set; }
}
```

or you control for each property how it will be serialized.

```csharp
[KeepCasing]
public sealed class BlogPostData
{
    [JsonConverter(typeof(InvariantConverter))]
    [JsonProperty("Slug")]
    public string Slug { get; set; }
}
```

## More Samples

We also use the .NET client for API tests. These tests cover almost all endpoints and especially edge cases and are therefore a good reference if you want to understand this class library.

<https://github.com/Squidex/squidex/tree/master/tools/TestSuite>


# Version v14 (and Earlier)

Learn How to Install, Initialize and Create Classes for Version 14 of the SDK

## Introduction

The basic concepts of the SDK are documented at the root page, linked below.  Please note, they are the same for all versions of this package.

{% content-ref url="/pages/-M0RwmaCINLQLaBnEs0\_" %}
[.NET](/id-02-documentation/software-development-kits/.net-standard)
{% endcontent-ref %}

This document focuses on the initialization of the SDK and how to use the concrete client classes.

## Install the SDK

The SDK is available on [nuget.org](https://www.nuget.org/packages/Squidex.ClientLibrary/). You can install it with:

```bash
dotnet add package Squidex.ClientLibrary
```

## Manually Creating Classes

The main entry class is `SquidexClientManager`, which handles authentication and creates the actual client classes, where each client is used for one endpoint such as assets, schemas and so on.

The client will create an access token using the App client credentials and cache this token in the memory for 30 days. When the token expires, it is recreated automatically. The cache is not shared between the instances of your application and not needed.

Read more about the authentication flow and best practices below:

{% content-ref url="/pages/-LtQiR7ByrSkKScDjX0O" %}
[Authentication](/id-02-documentation/developer-guides/api-overview/authentication)
{% endcontent-ref %}

To instantiate the client manager, you need the *App Name*, the *Client Id* and *Client Secret*. For self-hosted installations, the *URL* is also needed. For Squidex Cloud it is `https://cloud.squidex.io`.

<pre class="language-csharp"><code class="lang-csharp"><strong>ISquidexClientManager clientManager =
</strong>    new SquidexClientManager(
        new SquidexOptions
        {
            AppName = "app",
            ClientId = "id",
            ClientSecret = "secret",
            Url = "https://cloud.squidex.io"
        });
</code></pre>

### Configure Multiple Apps

The SDK supports multiple Apps using the normal options. When you create a request using an end point client you have to define the *App Name*, and the client manager picks the correct credentials. When you create a content client, you can also specify the *App Name*.

```csharp
ISquidexClientManager clientManager =
    new SquidexClientManager(
        new SquidexOptions
        {
            AppName = "app",
            ClientId = "id",
            ClientSecret = "secret",
            Url = "https://cloud.squidex.io",
            AppCredentials = new Dictionary<string, AppCredentials>
            {
               ["other-website"] = new AppCredentials
               {
                   ClientId = "...",
                   ClientSecret = "...",
               }
            }
        });
```

## Creating Classes with Dependency Injection

If you use [Dependency Injection](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0) (especially in ASP.NET Core) you can use the following package:

```bash
dotnet add package Squidex.ClientLibrary.ServiceExtensions
```

This package provides extension methods to register the client manager at the service collection.

<pre class="language-csharp"><code class="lang-csharp">services
    .AddSquidexClient(options =>
<strong>    {
</strong>        options.AppName = "app";
        options.ClientId = "id";
        options.ClientSecret = "secret";
        options.Url = "https://cloud.squidex.io";
    });
</code></pre>

You can inject `ISquidexClientManager` to your other classes.

This configuration uses the [Options Pattern](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-7.0), so it can also be configured the following way:

```csharp
services.AddSquidexClient()
    .Configure<SquidexServiceOptions>(options =>
    {
        options.AppName = "app";
        options.ClientId = "id";
        options.ClientSecret = "secret";
        options.Url = "https://cloud.squidex.io";
    });
```

Another option is to bind it to a configuration section as follows:

```csharp
services.AddSquidexClient();
services.Configure<SquidexServiceOptions>(
    configuration.GetSection("squidex"));
```

### Configure Multiple Apps

You can also configure multiple Apps using the normal options as follows:

```csharp
services.AddSquidexClient(options =>
{
    options.AppName = "app";
    options.ClientId = "id";
    options.ClientSecret = "secret";
    options.Url = "https://cloud.squidex.io";
    options.AppCredentials = new Dictionary<string, AppCredentials>
    {
       ["other-app"] = new AppCredentials
       {
           ClientId = "...",
           ClientSecret = "...",
       }
    };
})
```

### Configure the HTTP pipeline

The package also integrates the [HttpClientFactory](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests) to implement resilient HTTP requests. For example, this can be used to enable logging or to integrate [Polly](https://thepollyproject.azurewebsites.net/), a resilience and transient-fault-handling library.

You can make changes to the HTTP pipeline using the following method:&#x20;

```csharp
serviceCollection.AddSquidexHttpClient()
   .AddHttpMessageHandler(() =>
   {
       // YOUR CODE
   }).Services
```

## Use Concrete Clients

The classes for concrete endpoints can be created with the client manager. These instances are not cached and a new instance is returned for each call. Therefore, you should keep the instance as a local variable and field, and use them as often as possible.

<pre class="language-csharp"><code class="lang-csharp">var assetsClient = client.CreateAssetsClient();

<strong>var assets1 = await assetsClient.GetAssetsAsync();
</strong><strong>
</strong><strong>// Just use the client directly.
</strong><strong>var assets2 = await client.Assets.GetAssetsAsync();
</strong></code></pre>

The content clients are also not cached. They can be created using the following method:

```csharp
var blog1 = client.CreateContentsClient<BlogPost, BlogPostData>("blog1");
var blog1 = client.CreateContentsClient<BlogPost, BlogPostData>("blog1");

ReferenceEquals(blog1, blog2) == false;
```

### Using Dependency Injection

The endpoint clients are registered in the service locator. Therefore, you can also inject endpoint classes to your service class `MyService`.

```csharp
{
    public MyService(IAssetsClient assetsClient)
    {
    }
}
```

The content clients need parameters to be created. Therefore, you have to register them manually.

```csharp
services.AddSquidexClient(
    cm => cm.CreateContentsClient<BlogPost, BlogPost>("blog");

class MyService
{
    public MyService(IContentsClient<BlogPost, BlogPost> blog)
    {
    }
}
```


# Version v15 (and Later)

Learn How to Install, Initialize and Create Classes for Version 15 of the SDK

## Introduction

The basic concepts of the SDK are documented  on the root page, linked below (this is because they are the same for all versions of this package):

{% content-ref url="/pages/-M0RwmaCINLQLaBnEs0\_" %}
[.NET](/id-02-documentation/software-development-kits/.net-standard)
{% endcontent-ref %}

This document focuses on the initialization of the SDK and how to use the concrete client classes.

## Install the SDK

The SDK is available on [nuget.org](https://www.nuget.org/packages/Squidex.ClientLibrary/). You can install it with:

```bash
dotnet add package Squidex.ClientLibrary
```

## Creating Classes Manually

The main entry class is `ISquidexClient` which handles authentication and creates the actual client classes, where each client is used for one endpoint such as assets, schemas and so on.

The client creates an access token using the client credentials and caches this token in the memory for 30 days. When the token expires, it is recreated automatically. The cache is not shared between the instances of your application and it is not needed.

Read more about the authentication flow and best practices below:

{% content-ref url="/pages/-LtQiR7ByrSkKScDjX0O" %}
[Authentication](/id-02-documentation/developer-guides/api-overview/authentication)
{% endcontent-ref %}

To instantiate the client you need the *App Name*, the *Client Id* and *Client Secret*. For self-hosted installations the *URL* is also required. For Squidex Cloud it is `https://cloud.squidex.io`.

```csharp
ISquidexClient client =
    new SquidexClient(
        new SquidexOptions
        {
            AppName = "...",
            ClientId = "...",
            ClientSecret = "...",
            Url = "https://cloud.squidex.io"
        });
```

### Configuring Multiple Apps

The client no longer supports multiple Apps anymore. Use one client per App.

## Creation of Classes with Dependency Injection

If you use [Dependency Injection](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0) (especially in ASP.NET Core) you can use the following package:

```bash
dotnet add package Squidex.ClientLibrary.ServiceExtensions
```

This package provides extension methods to register the Squidex Client at the service collection.

<pre class="language-csharp"><code class="lang-csharp">services
    .AddSquidexClient(options =>
<strong>    {
</strong>        options.AppName = "app";
        options.ClientId = "id";
        options.ClientSecret = "secret";
        options.Url = "https://custom.squidex.io";
    });
</code></pre>

You can inject `ISquidexClient` to your other classes.

The configuration uses the [Options Pattern](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-7.0), so it can also be configured in the following way:

```csharp
services.AddSquidexClient()
    .Configure<SquidexServiceOptions>(options =>
    {
        options.AppName = "app";
        options.ClientId = "id";
        options.ClientSecret = "secret";
        options.Url = "https://custom.squidex.io";
    });
```

Another option is to bind it to a configuration section as follows:

```csharp
services.AddSquidexClient();
services.Configure<SquidexServiceOptions>(
    configuration.GetSection("squidex"));
```

### Configure Multiple Apps

Multiple Apps can be managed with the service provider by using named registrations as shown in the snippet below:

```csharp
services
    .AddSquidexClient(options =>
    {
        options.AppName = "app1";
        options.ClientId = "id1";
        options.ClientSecret = "secret1";
    })
    .AddSquidexClient("app2", options =>
    {
        options.AppName = "app2";
        options.ClientId = "id2";
        options.ClientSecret = "secret2";
    })
    .AddSquidexClient("app3", options =>
    {
        options.AppName = "app3";
        options.ClientId = "id3";
        options.ClientSecret = "secret3";
    });
```

Inject the `ISquidexClientProvider` instance to resolve a concrete client.

```csharp
class MyService
{
    public MyService(ISquidexClientProvider provider)
    {
        // Get the default client, that has been registered without a name.
        var client1 = provider.Get();
        
        // Get a named client.
        var client2 = provider.Get("app2");
        var client3 = provider.Get("app3");
    }
}
```

### Configure the HTTP Pipeline

The package also integrates the [HttpClientFactory](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests) to implement resilient HTTP requests. For example, this can be used to enable logging or to integrate [Polly](https://thepollyproject.azurewebsites.net/), a resilience and transient-fault-handling library.

You can implement changes to the HTTP pipeline using the following method:

```csharp
serviceCollection.AddSquidexHttpClient()
   .AddHttpMessageHandler(() =>
   {
       // YOUR CODE
   });
```

## Use Concrete Clients

The classes for concrete endpoints are properties of the client class. These instances are cached and a single instance is shared between all calls. Therefore, storing the instance in a separate variable is not required.

<pre class="language-csharp"><code class="lang-csharp">var assetsClient = client.Assets;

<strong>var assets1 = await assetsClient.GetAssetsAsync();
</strong><strong>
</strong><strong>// Just use the client directly.
</strong><strong>var assets2 = await client.Assets.GetAssetsAsync();
</strong></code></pre>

The content clients are also cached and can be resolved with the following method:

<pre class="language-csharp"><code class="lang-csharp"><strong>var blog1 = client.Contents&#x3C;BlogPost, BlogPostData>("blog1");
</strong>var blog1 = client.Contents&#x3C;BlogPost, BlogPostData>("blog1");

ReferenceEquals(blog1, blog2) == true;
</code></pre>

The content clients are cached internally using a thread safe dictionary.

### Using with Dependency Injection

The endpoint clients are not registered in the service locator. You have to register them manually if needed. It is best to use the root client class, especially if you work with multiple Apps. For performance reasons, this is not required.&#x20;

```csharp
services.AddSquidexClient(x => x.Assets);
```


# PHP

Software Development Kit for all PHP platforms

## Introduction

The SDK is mostly generated from the OpenAPI specification. You can read the API documentation here:[ https://cloud.squidex.io/api/docs](https://cloud.squidex.io/api/docs).

This means that the API is fully covered, including all endpoints used by the frontend. You can also use the SDK to create and configure Apps, schemas and rules.&#x20;

Use this SDK in your PHP application to integrate with Squidex CMS.

## Install the SDK

The SDK is available on [packagist (compose)](https://packagist.org/packages/squidex/squidex). You can install it with:

```sh
compose install @squidex/squidex
```

Composer is a dependency manager for PHP: <https://getcomposer.org/>

## Instantiate the SDK

The `SquidexClient` is the main entry point for the SDK. It provides the properties for all endpoints.

You can instantiate the client using the following code snippet:

```php
use Squidex\Client\Configuration;
use Squidex\Client\SquidexClient;

require_once __DIR__ . '/../vendor/autoload.php';

$config = new Configuration();
$config->setClientId('client-id');
$config->setClientSecret('client-secret');
$config->setAppName('my-app');
$config->setHost('https://cloud.squidex.io');

$client = new SquidexClient($config);

```

The `host` parameter is optional if you are using Squidex Cloud.

### Additional Configuration

In this article, we will cover some of the important configuration values only. Have a look at the source code for all available options:\
<https://github.com/Squidex/sdk-php/blob/main/lib/Configuration.php>

#### Timeout

Configure a timeout in seconds to cancel unresponsive requests.

```php
$config = new Configuration();
...
$config->setTimeout(60.0);
```

The default timeout is 30 seconds.

#### Token Store

The SDK uses the client ID and the client secret to acquire a bearer token, and handles invalidation and expiration automatically. By default the token is stored inside the client and therefore a new token is acquired when the client is cleaned by the garbage collector. However, one can define where the token is stored.&#x20;

```php
$config = new Configuration();
...
$config->setTokenStore(new MyTokenStore());
```

#### Ignore Certificates

By default the certificates are validated. But for test environments it might be necessary to connect to instances with self signed certificates only. Therefore we have introduced an option to ignore the certificate chain:

```php
$config = new Configuration();
...
$config->setIgnoreCertificates(true);
```

## Use the Client

To use the client you have to use the correct property.

For example, to create a schema use the `schemas` property.

```php
$id = uniqid();

$field = new UpsertSchemaFieldDto();
$field->setName('field1');
$field->setProperties(new StringFieldPropertiesDto());

$request = new CreateSchemaDto();
$request->setName("my-schema");
$request->setIsPublished(true);
$request->setFields([$field]);

$createdSchema = $this->client->schemas()->postSchema($request);
```

In order to work with contents, use the `contents` property.

```php
$data = [
    'field1' => [
        'iv' => 'My Value'
    ]
];
    
$createdContent = $this->client->contents()->postContent(static::$schema->getName(), $data);
```

### Error Handling

The SDK uses exceptions for error handling.&#x20;

Refer to the code snippet below for an example:

```php
try {
    $request = new CreateAppDto();
    $request->setName('my-app');

    $client->getClient()->apps()->postApp($request);
} catch (ApiException $e) {
    if ($e->getCode() == 400) {
        echo "App probably already exists.\n";
    } else {
        throw $e;
    }
}
```


# Java

Easy installation and setup of the Java SDK.

## Introduction

The SDK is mostly generated from the OpenAPI specification. You can read the API documentation here:[ https://cloud.squidex.io/api/docs](https://cloud.squidex.io/api/docs).

This means that the API is fully covered, including all endpoints used by the frontend. You can also use the SDK to create and configure Apps, schemas and rules.&#x20;

Use this SDK in your Java application to integrate with Squidex CMS.

## Install the SDK

The SDK is available on [Maven](https://mvnrepository.com/artifact/io.squidex/squidex). The installation depends on your build system.

### Maven

Add the dependency in your `build.gradle`:

```groovy
dependencies {
    implementation 'io.squidex:squidex:1.0.0'
}
```

### Maven

Add the dependency in your `pom.xml:`

```xml
<dependency>
    <groupId>io.squidex</groupId>
    <artifactId>squidex</artifactId>
    <version>1.0.0</version>
</dependency>// Some code
```

## Instantiate the SDK

The `SquidexClient` is the main entry point for the SDK. It provides the properties for all endpoints.

You can instantiate the client using the following code snippet:

```php
SquidexClient squidex = SquidexClient.builder()
    .appName("my-app")
    .clientId("your-app:default")
    .clientSecret("xxx")
    .url("https://your.squidex-deployment")
    .build();
```

The `url` parameter is optional if you are using Squidex Cloud.

### Additional Configuration

In this article, we will cover some of the important configuration values only. The client is based on the [OkHttp ](https://square.github.io/okhttp/)library to support all platforms including Android. Therefore many configuration options are available throug passing a custom client to Squidex. This client gets cloned to make customizations, for example for authentication.

#### Timeout

Configure a timeout in seconds to cancel unresponsive requests.

<pre class="language-php"><code class="lang-php">OkHttpClient client = new OkHttpClient.Builder()
<strong>    .connectTimeout(10, TimeUnit.SECONDS)
</strong>    .writeTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build();
        
SquidexClient squidex = SquidexClient.builder()
    ...
    .httpClient(client)
    .build();
</code></pre>

The default timeout is 30 seconds.

Read more: <https://square.github.io/okhttp/recipes/#timeouts-kt-java>

#### Token Store

The SDK uses the client ID and the client secret to acquire a bearer token, and handles invalidation and expiration automatically. By default the token is stored inside the client and therefore a new token is acquired when the client is cleaned by the garbage collector. However, one can define where the token is stored.&#x20;

```java
SquidexClient squidex = SquidexClient.builder()
    ...
    .tokenStore(new MyTokenStore())
    .build();
```

#### Ignore Certificates

By default the certificates are validated. But for test environments it might be necessary to connect to instances with self signed certificates only. Therefore we have introduced an option to ignore the certificate chain:

```java
SquidexClient squidex = SquidexClient.builder()
    ...
    .trustAllCerts()
    .build();
```

## Use the Client

To use the client you have to use the correct property.

For example, to create a schema use the `schemas` property.

<pre class="language-java"><code class="lang-java">CreateSchemaDto request = CreateSchemaDto.builder()
    .name("my-schema")
    .fields(Collections.singletonList(
        UpsertSchemaFieldDto.builder()
<strong>            .name("field1")
</strong>            .properties(
                FieldPropertiesDto.string(
                    StringFieldPropertiesDto.builder()
                    .build()))
<strong>                .build()))
</strong>    .isPublished(true)
    .build();

SchemaDto createdSchema = client.schemas().postSchema(request);
</code></pre>

In order to work with contents, use the `contents` property.

```php
Map<String, Map<String, Object>> dataItem = new HashMap<>();
Map<String, Object> dataField = new HashMap<>();
dataItem.put("field1", dataField);
dataField.put("iv", id);

ContentsPostContentRequest request = ContentsPostContentRequest.builder()
    .body(dataItem)
    .publish(true)
    .build();
    
client.contents().postContent(schema.getName(), request);
```

### Error Handling

The SDK uses exceptions for error handling.&#x20;

Refer to the code snippet below for an example:

<pre class="language-php"><code class="lang-php">try {
    client.client().apps().postApp(
        CreateAppDto.builder()
            .name("my-app")
            .build());
} catch (ApiError ex) {
<strong>    if (ex.statusCode() == 400) {
</strong>        System.out.println("App probably already exists.");
        return;
    } else {
        throw ex;
    }
}
</code></pre>

## Limitations

As of this writing, the SDK has the following limitations:

1. Endpoints to download files (e.g. assets) return a stream only and not metadata information like the content type or the content length.
2. Endpoints to download files work for Node only and are not available in the browser. This is generally not an issue as assets are handled via direct links in the browser.
3. The generated builders all follow the staged builder pattern. For untyped data, e.g. content items the builder is not available yet and you have to construct the data structure manually using hash maps and arrays.
4. Deprecated methods and properties are not annotated yet.


# Developer Guides


# API

An Overview of the Different APIs

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Introduction

With the API you can:

* Create, change or query your content.
* Upload, download or query assets.
* Create complex queries with the GraphQL endpoint.
* Execute all management operations such as creating Apps, schemas, rules and update settings.

The Management UI uses the same API endpoints. If you can perform an action manually you can also perform the same action with code.

The next paragraph will give you a short introduction about the different API endpoints. There are also some useful links to additional pages about specific aspects of the API.

## API Endpoints

Technically, the API has only a single endpoint, but the API is distributed in several parts, with different principles and generated documentations.

### Content API

Each App has its own content API. The documentation is generated when you change your schemas and is tailored for your content structure. The documentation is cached and it might take a few minutes to be able to use the newest version for your App and schemas.

You can find the link to your Content API in the API section of the Management UI.

<div align="left"><figure><img src="/files/qFR48RjsQ6lgjTHtE84j" alt=""><figcaption><p>Content API</p></figcaption></figure></div>

For example, this this is the Content API for the App that serves the content for the Squidex website:

<https://cloud.squidex.io/api/content/squidex-website/docs>

### Content GraphQL API

The [GraphQL ](https://graphql.org)endpoint is also generated per client. The documentation is provided by GraphQL, an integrated, interactive GraphQL query editor. This endpoint can only be used to query content items and assets. Mutations have not yet been implemented due to several restrictions with the underlying GraphQL framework. The GraphQL endpoint is also cached for each API, with a time to live (TTL) of 10 minutes. Therefore, it can take up to 10 minutes before you see your changes.

<div align="left"><figure><img src="/files/GXsPgKBk8Lv9IdMTzuok" alt=""><figcaption><p>GraphQL</p></figcaption></figure></div>

### General API

The rest of the API is the same for all your Apps. This includes endpoints to query and manipulate Apps, schemas, assets, rules and all settings.

For Squidex Cloud, the generated documentation can be found at:

<https://cloud.squidex.io/api/docs>

<div align="left"><figure><img src="/files/Bj3Ux9KZ9UYuzLrEnShN" alt=""><figcaption><p>General API</p></figcaption></figure></div>

## Cloud CDN (BETA)

The Cloud version provides CDN endpoints for professional and business plans.

The CDN is implemented using [https://www.fastly.com/](https://www.fastly.com).

### Endpoints

There are dedicated endpoints provided for different parts of the API. The CDN isn't that valuable for management end points, such as querying schemas and settings (where you always want to retrieve the current version). Furthermore, getting the caching behavior correct to provide up-to-date information is not an easy task, so the decision has been made to provide specialized endpoints.

#### Contents CDN

The content CDN endpoint only provides access to your content items, including the GraphQL endpoint.

The endpoint format is: `https://contents.squidex.io/<app>/<schema>` instead of `https://cloud.squidex.io/api/contents/<app>/<schema>`.

#### Assets CDN

The assets CDN endpoint only provides access to your content items and should be used for images.

The endpoint format is `https://contents.squidex.io/<app>/<asset-id>` instead of `https://cloud.squidex.io/api/assets/<app>/<asset-id>`.

### How Caching works

In general caching only works for GET requests.

Therefore, you will not leverage the caching system of the CDN provider when you query content items using GraphQL and POST requests. However, the GraphQL specification and our implementation supports [POST, as well as GET](https://graphql.org/learn/serving-over-http/#http-methods-headers-and-body).

The CDN provider uses the URL and the authentication states as cache keys. When you make requests with a user access token, the bearer token is used as an additional cache key. When the bearer token is created for an App client, the name of the client is used. This means that when you create a new access token for the client and then repeat the request again, you will get the cached version because the name of the client has not changed.

In addition to that, we make use of [surrogate keys](https://docs.fastly.com/en/guides/purging-api-cache-with-surrogate-keys). Surrogate keys are an HTTP response header that contain IDs that make up the HTTP response. For example, when you retrieve a single content item, the response is dependent upon the content item itself, the related schema and the App it belongs to. Therefore, we also add the App ID and schema ID together with the content ID as a response header. When the App or schema ID is changed we send a purge notification to the CDN provider to delete all cached entries that contains the changed ID. This means that changing App settings such as roles, contributors and clients, purges all cached content items. This is useful when you restrict a client's permissions.

{% hint style="info" %}
Surrogate keys header has a limitation of 16KB. Therefore, we can only serve around 400 keys.
{% endhint %}

### Pricing

The same pricing structures as described in the next paragraph are applied but the costs for cached requests are reduced by 50%.

## Cloud Costs

The pricing for the Cloud version mainly depends on the number of API calls but not all API endpoints have associated costs:

| Action                                     | Costs |
| ------------------------------------------ | ----- |
| Query apps / Modify apps                   | 0 / 1 |
| Query schemas / Modify schemas             | 0 / 1 |
| Query clients / Modify clients             | 0 / 1 |
| Query contributors / Modify contributors   | 0 / 1 |
| Query languages / Modify languages         | 0 / 1 |
| Query patterns / Modify patterns           | 0 / 1 |
| Query roles / Modify roles                 | 0 / 1 |
| Query histories                            | 0     |
| Query rules / Modify rules                 | 1 / 1 |
| Query rule events / Modify rule events     | 0 / 0 |
| Query workflows / Modify workflows         | 0 / 1 |
| Query assets / Modify assets               | 1 / 1 |
| Query asset folders / Modify asset folders | 1 / 1 |
| Query asset content                        | 0.5   |
| Query contents                             | 1     |
| Query contents with GraphQL                | 2     |
| Modify contents                            | 1     |

Modifications include creations, updates and deletions.

## Additional Reference

If you want to jump into the details of how to use the API, the following articles can be helpful.

### How to Get Started with Postman

{% content-ref url="/pages/-LrLSGK116IOqEAMIyTb" %}
[Postman](/id-02-documentation/developer-guides/api-overview/postman)
{% endcontent-ref %}

### How to Retrieve an Access Token

{% content-ref url="/pages/-LtQiR7ByrSkKScDjX0O" %}
[Authentication](/id-02-documentation/developer-guides/api-overview/authentication)
{% endcontent-ref %}

### How to Use the Query System to Filter Content

{% content-ref url="/pages/-LrLSGK0V6\_wMAcJGLSC" %}
[Queries](/id-02-documentation/developer-guides/api-overview/api)
{% endcontent-ref %}

### How to Download and Resize Assets

{% content-ref url="/pages/-LrLDrRidkEpiK2WjlS9" %}
[Assets](/id-02-documentation/developer-guides/api-overview/assets-api)
{% endcontent-ref %}

##


# Authentication

How to Retrieve Access Tokens to Gain Access to the API

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Introduction

Squidex uses OpenID Connect and OAuth2.0 as authentication protocols. Both are state-of-the-art specifications and adopted by a lot of internet services. You have already used these protocols before, when logging in with your Google account to a third-party website.

The implementation uses [IdentityServer4](https://identityserver.io), a certified access control solution.

## Basic Authentication Flow

First, let's talk about the general authentication flow.

### 1. Generate Clients

Before you gain an access token you must create a client first. A client is just another name for an application and could be a mobile App, a public website, single page application or a backend server.

If you create a new App, it will already have a default client.

<div align="left"><figure><img src="/files/62zr26EKy8PA6vdJt95D" alt=""><figcaption><p>Default Client</p></figcaption></figure></div>

Each client also has a role assigned to define which updates or queries can be performed with the client. This is particularly useful when your client is a public application that can easily be a reversed engineer, like a mobile App or single page application. You can store your client credentials (*Client ID* and *Client Secret*) in the application but you have to ensure that you only give your client the necessary permissions and not more.

Read more about permissions in the following link.

{% content-ref url="/pages/-LrLSGJsDVHGqt86sbQH" %}
[Roles & Permissions](/id-02-documentation/concepts/permissions)
{% endcontent-ref %}

### 2. Request a Token

The client ID and secret cannot be used directly in the API calls. You have to create an additional request to the identity-server first to gain an access token. This token is then valid for 30 days.

## Get access token

<mark style="color:green;">`POST`</mark> `https://cloud.squidex.io/identity-server/connect/token`

Get an access token from Squidex Identity.

#### Request Body

| Name           | Type   | Description                 |
| -------------- | ------ | --------------------------- |
| scope          | string | squidex-api                 |
| client\_secret | string | **\<YOUR\_CLIENT\_SECRET>** |
| client\_id     | string | **\<YOUR\_CLIENT\_ID>**     |
| grant\_type    | string | client\_credentials         |

{% tabs %}
{% tab title="200 The token response." %}

```
{
    "access_token":"<YOUR_ACCESS_TOKEN>,
    "expires_in":2592000,  // Expiration in seconds, 30 days
    "token_type":"Bearer",
    "scope":"squidex-api"
}
```

{% endtab %}
{% endtabs %}

Or simply create a request with curl:

```bash
curl
    -X POST 'https://cloud.squidex.io/identity-server/connect/token'
    -H 'Content-Type: application/x-www-form-urlencoded'
    -d 'grant_type=client_credentials&
        client_id=<YOUR_CLIENT_ID>&
        client_secret=<YOUR_CLIENT_SECRET>&
        scope=squidex-api
```

### 3. Use the Token

Add the returned token to all consecutive requests:

```bash
Authorization: Bearer <YOUR_ACCESS_TOKEN>
```

## How to Deal With the Access Token

A frequently asked question is how to deal with the access token, because there are a few challenges:

1. The token is only valid for 30 days.
2. The token might expire sooner, for example, when a certificate is replaced on the server.
3. You need an additional request to get the token.

Our recommendation is to use the following pattern (pseudo code):

```javascript
function makeRequest(url, body) {
    const token = getTokenFromCache();
    
    if (!token) {
        token = getToken(clientId, clientSecret);
        
        storeTokenInCache(token, days: 30);
    }
    
    const response = makeRequestToSquidex(url, body);
    
    // Token has probably expired. 
    if (response.status == 401) {
        // Request the token again.
        token = getToken(clientId, clientSecret);
        
        storeTokenInCache(token, days: 30);
        
        // Try the request again.
        response = makeRequestToSquidex(url, body);
    }
    
    // You can still have a 401 here, but this very likely not an epxired token then.
    return response;
}
```

As you can see, we use a simple memory cache to keep our token. We request a new token when it has expired in the cache or on the server and before the first request (after our application started).

You can also request multiple tokens in parallel, for example, when you have a cluster of servers. There is no need to sync the access tokens between your servers or to keep them in a centralized cache.


# Postman

A Short Tutorial On How to Use Squidex with Postman.

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

This is a short tutorial detailing how to create first requests using Postman.

## What is Postman?

Postman is a free collaboration platform for API development and a good way to get started.

{% embed url="<https://www.postman.com/downloads/>" %}

## Let's Get Started

### Pre-requisites

* Postman
* A Squidex App

If you do not yet have an App, you can refer to one of the quick start guides to create an App that has a pre-defined schema and sample content.

{% content-ref url="/pages/-LrLU4y1q7D2bxqm-j9w" %}
[Broken mention](broken://pages/-LrLU4y1q7D2bxqm-j9w)
{% endcontent-ref %}

Alternatively, if you wish to create an app from scratch and learn how to create a schema and add content then the following link is a good place to start.

{% content-ref url="/pages/ePh2uV0ky6XkFf4Vgwg7" %}
[Building a Blog with Squidex and Next.js](/id-02-documentation/developer-guides/tutorials/building-a-nextjs-blog-with-squidex)
{% endcontent-ref %}

### 1. Generate a Token for the Client

A client represents an application such as a mobile App or server application.

We have implemented the [OpenID client credentials flow](https://docs.axway.com/u/documentation/api_gateway/7.5.3/webhelp_portal_oauth/Content/OAuthGuideTopics/oauth_flows_client_credentials.). It is a secure and open standard to protect your APIs and to provide authentication for clients (aka applications) and users. It is also used by big brands such as Microsoft, GitHub and Google. You've already used it in the past, when you've logged into applications using third party logins.

{% content-ref url="/pages/-LtQiR7ByrSkKScDjX0O" %}
[Authentication](/id-02-documentation/developer-guides/api-overview/authentication)
{% endcontent-ref %}

By default, a *default* client is generated for your App. If this is not the case, you are probably running an older version of Squidex. If this is the case, you have to create a new client with a name of your choice.

To acquire a token you should perform the following steps:

Go to the **Settings** (1) section of your App, select **Clients** (2) under *Security* and then click the **Connect** (3) button.

<div align="left"><figure><img src="/files/CGWWsRPvWduBMAXSDfDA" alt=""><figcaption><p>Acquire a token - 1</p></figcaption></figure></div>

Next, we provide explanations on how to connect to your App. For now, we must connect manually with Postman. Click the first option i.e. **Connect manually** (4).

<div align="left"><figure><img src="/files/a3j1yC7JGLaZKy8CzgqG" alt=""><figcaption><p>Acquire a token - 2</p></figcaption></figure></div>

In the next step, you see the token that is generated for you and how to use it. Just copy the token by clicking the **copy-button** (5) as shown in the screenshot below:

<div align="left"><figure><img src="/files/q3tOXKdjSqvO5ipRLIwq" alt=""><figcaption><p>Acquire a token - 3</p></figcaption></figure></div>

This token is valid for 30 days, but can be renewed as often as you want.

You can also access the token with an HTTP request using the *Client ID* and *Client Secret* as below:

{% code overflow="wrap" %}

```bash
curl \
-X POST 'https://cloud.squidex.io/identity-server/connect/token/' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&client_id=[APP_NAME]:[CLIENT_ID]&client_secret=[CLIENT_SECRET]&scope=squidex-api'
```

{% endcode %}

### 2. Download the OpenAPI Specification

Squidex creates an OpenAPI documentation for your App. We are going to download it in order to import it to Postman:

Go to the **API** (1) section of your App and click the **Content API** (2) link to open the API docs in a new tab.

<div align="left"><figure><img src="/files/OwGl5GstBkMVDW4UY9kQ" alt=""><figcaption><p>Content API</p></figcaption></figure></div>

The documentation shows all the end points that are available for your content. **Download** (3) the OpenAPI specification file and save it in your file system to import it with Postman. The file is called `swagger.json`.

<figure><img src="/files/GdbGMnblwliOP0ghT6Wz" alt=""><figcaption><p>Download OpenAPI specification</p></figcaption></figure>

### 3. Import the OpenAPI into Postman

Next, you need to import the OpenAPI file to Postman. To do this, launch Postman and click the **Import** (1) button in your Workspace (or alternatively click File and then Import).&#x20;

<div align="left"><figure><img src="/files/z2tT7roZNLP5LJlzeXWV" alt=""><figcaption><p>Import OpenAPI file - 1 </p></figcaption></figure></div>

Next, select the downloaded **file** (2) that you have saved before.

<div align="left"><figure><img src="/files/jK0fbaRN8LOTQ8ewKVBe" alt=""><figcaption><p>Import OpenAPI file - 2</p></figcaption></figure></div>

Select **Postman Collection** (3) (this should be selected by default) and click **Import** (4).

<figure><img src="/files/sVprnX3xgk95Jb2K3jjY" alt=""><figcaption><p>Import OpenAPI file - 3</p></figcaption></figure>

You should be able to see the API now.&#x20;

### 4. Make a Request using Postman

We are now ready to make requests using Postman. To make a request:&#x20;

1. Go to the **Collections** (5).
2. Select the ContentAPI (6) for your App, its called *Squidex API* in this example
3. Select the endpoints for your **schema** (7). In our example there are two schemas and the *magazine* schema is selected.
4. Next, select the **query** endpoint (8) to query all content items.
5. **Uncheck** (9) all query parameters.

<figure><img src="/files/Y06FWZk6WtOY7vGCzvO4" alt=""><figcaption><p>GET request using Postman - 1</p></figcaption></figure>

The last step is to enter you access token:

1. Click the **Authorization** (10) tab.
2. Ensure **Type** (11) is `OAuth 2.0`.
3. Paste in the **Access Token** (12).
4. Send your request with the **Send** (13) button.

<figure><img src="/files/OfYULJnZ7pniiPJ1muf5" alt=""><figcaption><p>GET request using Postman - 2</p></figcaption></figure>

You should see a response body which is the result of the GET request.

You have just learnt how to retrieve the content items using Postman!


# Queries

How to Query Content with Filters, Sorting and Pagination

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Query Options

Squidex has a query engine that allows different query languages. So far, the following query languages are available:

1. **OData Queries** - this is the first system implemented using an existing solution that was easy to adapt, these are easier to write in URLs.
2. **JSON Queries** are newer and are mainly used for the UI, because they are faster and easier to determine. It is best to use JSON- queries for your client.

Both query languages support the same features:

1. Filters with complex comparison operators, conjunctions and negations.
2. Full text search.
3. Sorting by one or multiple fields.
4. Skipping items and restricting the size of the result set for pagination.

### OData Queries

OData is an open [protocol](https://en.wikipedia.org/wiki/Protocol_\(computing\)) which allows the creation and consumption of queryable and inoperable APIs in a simple, standardized way. It was designed and developed by Microsoft and provides ready-to use-solutions. We have decided to use the Query syntax because we wanted to leverage an existing system and parser and we find it easy to adapt to our needs.

The queries are provided over the URL and have a special syntax. OData query options start with a dollar character, e.g. `$filter`.

Here's an example:

```
https://.../api/content/the-foodcrunch-mag/startups?$top=30&$skip=10&$search=delivery
```

{% hint style="info" %}
Even though we use OData, we do not support the full capabilities and there are no plans to do so in future. Some features like select, expand or formatting can be handled better with GraphQL.
{% endhint %}

The full OData convention can be read at:

{% embed url="<https://www.odata.org/documentation/odata-version-2-0/uri-conventions/>" %}

### JSON Queries

JSON queries are passed in as **URL encoded JSON objects** with the `q` query parameter. They are much harder to read for humans, but easier and faster to parse. This was introduced when a new query editor was implemented for the Management UI.

Here's an example:

```
https://.../api/content/the-foodcrunch-mag/startups?q=%7B%22fullText%22%3A%22website%22%2C%22take%22%3A10%2C%22sort%22%3A%5B%5D%2C%22filter%22%3A%7B%22and%22%3A%5B%5D%7D%7
```

As you can see it is horrible to read, therefore we will just show normal JSON examples from now on.

## Content Structure

Let's demonstrate the API concepts based on our *FoodCrunch* App use case. The app has two schemas, we will consider the `startups` schema which contains a database of startups in the food space.

The schema has the following fields:

| Name          | Type        | Localizable | Description                                 |
| ------------- | ----------- | ----------- | ------------------------------------------- |
| `slug`        | String      | No          | A single slug for Google friendly URLs.     |
| `name`        | String      | No          | The name of the startup.                    |
| `description` | String      | Yes         | The description of the startup.             |
| `stage`       | String      | No          | Current startup stage                       |
| `funding`     | Number      | No          | The total funding in Millions (USD).        |
| `founded`     | Number      | No          | Year when the startup was founded.          |
| `founders`    | Array       | No          | The founders as list of name and position.  |
| `tags`        | Tags        | No          | A list of tags for search.                  |
| `location`    | Geolocation | No          | The geolocation of the headquarter.         |
| `metadata`    | JSON        | No          | Unstructured metadata.                      |
| `givenUp`     | Boolean     | No          | Indicates whether the startup has given up. |

Then, your content will have the following structure in the API:

{% code overflow="wrap" %}

```javascript
{
    "id": "7802056f",
    "created": "2022-12-31T08:00:52Z",
    "createdBy": ".....",
    "lastModified": "2023-01-09T19:29:05Z",
    "lastModifiedBy": ".....",
    "data": {
        "slug": {
            "iv": "foodco"
        },
        "name": {
            "iv": "FoodCo"
        },
        "description": {
            "en": "FoodCo aims to revolutionize the way we eat. Their innovative products focus on sustainability.",
            "de": "FoodCo zielt darauf ab, die Art und Weise, wie wir essen, zu revolutionieren. Ihre innovativen Produkte setzen auf Nachhaltigkeit.",
            "it": null,
            "sv": "FoodCo har som mål att revolutionera vårt sätt att äta. Deras innovativa produkter fokuserar på hållbarhet."
        },
        "stage": {
            "iv": "Early"
        },
        "founded": {
            "iv": 2019
        },
        "funding": {
            "iv": 234
        },
        "founders": {
            "iv": [
                {
                    "name": "John Doe",
                    "position": null
                }
            ]
        },
        "tags": {
            "iv": [
                "sustainability"
            ]
        },
        "location": {
            "iv": {
                "latitude": 32.0237703,
                "longitude": -92.0390231
            }
        },
        "metadata": {
            "iv": null
        },
        "givenUp": {
            "iv": null
        }
    }
}
```

{% endcode %}

Please note, that there is one object for each field because each field has a partitioning. This defines how the field is structured. The most simple partitioning is the invariant partition, which only allows a single key `iv`.

If the field is `localizable`, use the languages codes from the languages that you defined in your App settings as keys.

Read more about localization here:

{% content-ref url="/pages/-LrLSGJrSHOn33Bsb3G3" %}
[Localization](/id-02-documentation/concepts/localization)
{% endcontent-ref %}

### How to Identity Fields

To identify the field of our content item, use the full path to this field, separated by hashes.&#x20;

For example:

* `id`
* `createdBy`
* `data/name/iv`
* `data/description/en`
* `data/description/de`
* `data/founded/iv`

### Special Cases

#### Dot Notation in JSON Queries

When you use JSON queries, you can also use the dot-notation to create a syntax that is closer to Javascript and other programming languages. It is best to use this notation.&#x20;

For example:

* `data.name.iv`

#### OData Restrictions

In OData dash characters (-) are not allowed. Therefore, you should replace them with underscore in your queries.&#x20;

For example, if there was a field called `acquired-by` we would use:

* `data/acquired_by/iv`in OData
* `data.acquired-by.iv`in JSON

## Query Features

### Limiting the Number of Results

{% hint style="info" %}
The examples here used the `startups` schema of the *FoodCrunch* use case.
{% endhint %}

The `top` / `take` query option requests the number of items in the queried collection to be included in the result. The default value is 20 and the maximum allowed value is 200.

{% tabs %}
{% tab title="OData" %}

```markup
https://.../api/content/the-foodcrunch-mag/startups?$top=30
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "take": 30
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Because of an error the parameter is called **top** in OData and **take** in JSON.
{% endhint %}

### Skipping Items in the Result Set

The `skip` query option requests the number of items in the queried collection to be skipped and not included in the result. Use this together with `top` / `take` to read all your data page by page.

{% tabs %}
{% tab title="OData" %}

```markup
https://.../api/content/the-foodcrunch-mag/startups?$skip=20
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "skip": 20
}
```

{% endtab %}
{% endtabs %}

Example of `skip` combined with `top` / `take`:

{% tabs %}
{% tab title="OData" %}

```
https://.../api/content/the-foodcrunch-mag/startups?$skip=20&$top=30
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "skip": 20,
    "take": 30
}
```

{% endtab %}
{% endtabs %}

### Get Random Items

You can get random items using the `random`option:

{% tabs %}
{% tab title="OData" %}

```markup
https://.../api/content/the-foodcrunch-mag/startups?$random=5
```

{% endtab %}

{% tab title="JSON" %}

```json
{
    "random": 5
}
```

{% endtab %}
{% endtabs %}

The random operator picks elements from the result set (not from the entire database).&#x20;

For example, this query returns 5 random items from the first 200 elements with the default order:

{% tabs %}
{% tab title="OData" %}

```markup
https://.../api/content/the-foodcrunch-mag/startups?$random=5&$top=200
```

{% endtab %}

{% tab title="JSON" %}

```json
{
    "random": 5,
    "take": 200%%&% 
}
```

{% endtab %}
{% endtabs %}

### Full Text Searches

The `search` query option allows clients to request entities matching a free-text search expression. We add the data of all fields for all keys to a single field in the database and use this combined field to implement the full text search.

{% tabs %}
{% tab title="OData" %}

```
https://.../api/content/the-foodcrunch-mag/startups?$search=delivery
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "fullText": "delivery"
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
You can either use **search** or **filter** but not both.
{% endhint %}

### Filters

The `filter` system query option allows clients to filter a collection of resources that are addressed by a request URL.

For example, find all the startups in the *Seed* stage.

{% tabs %}
{% tab title="OData" %}

```
https://.../api/content/the-foodcrunch-mag/startups?$filter=data/stage/iv eq Seed
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
   "filter": {
      "path": "data.stage.iv",
      "op": "eq"
      "value": "Seed"
   }
}
```

{% endtab %}
{% endtabs %}

For example, find all the startups with a funding of more than 100 million USD.

{% tabs %}
{% tab title="OData" %}

```
https://.../api/content/the-foodcrunch-mag/startups?$filter=data/funding/iv gt 100
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
   "filter": {
      "path": "data.funding.iv",
      "op": "gt"
      "value": 100
   }
}
```

{% endtab %}
{% endtabs %}

For example, find all the startups with a funding of less than 10 million USD.

{% tabs %}
{% tab title="OData" %}

```
https://.../api/content/the-foodcrunch-mag/startups?$filter=data/funding/iv lt 10
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
   "filter": {
      "path": "data.funding.iv",
      "op": "lt"
      "value": 10
   }
}
```

{% endtab %}
{% endtabs %}

#### Array

If you have fields that have an array of values, for example, references that are represented as an array of content IDs, you can still use the equal operator. The API will return a content item if at least one item in the array is equal to the passed in value.

An example of filtering by tags:

{% tabs %}
{% tab title="OData" %}

```
https://.../api/content/the-foodcrunch-mag/startups?$filter=data/tags/iv eq 'Home Delivery'
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
   "filter": {
      "path": "data.tags.iv",
      "op": "eq"
      "value": "Home Delivery"
   }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
You can either use **search** or **filter** but not both.
{% endhint %}

#### More Examples

* Example demonstrating an array (components, array fields, references, assets, strings) cannot be empty:

{% tabs %}
{% tab title="OData" %}

```
$filter=empty(data/founders/iv)
$filter=empty(data/founders/iv) eq true
$filter=empty(data/founders/iv) eq false // Not empty
$filter=not empty(data/founders/iv)      // Not empty
```

{% endtab %}

{% tab title="JSON" %}

```json
{
    "filter": {
        "not": {
            "path": "data.founders.iv",
            "op": "empty"
        }
    }
}
    "filter": {
        "path": "data.founders.iv",
        "op": "empty"
    }
}
```

{% endtab %}
{% endtabs %}

* Example demonstrating a date must match value:

{% tabs %}
{% tab title="OData" %}

```
$filter=created eq 2023-01-19T12:00:00Z
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
   "filter": {
      "path": "created ",
      "op": "eq"
      "value": "2023-01-19T12:00:00Z"
   }
}
```

{% endtab %}
{% endtabs %}

* Example demonstrating a date must match one of many values:

{% tabs %}
{% tab title="OData" %}

```
$filter=created in ('2023-01-19T12:00:00Z', '2022-01-19T12:00:00Z')
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
   "filter": {
      "path": "created ",
      "op": "in"
      "value": [
         "2023-01-19T12:00:00Z",
         "2022-01-19T12:00:00Z"
      }
   }
}
```

{% endtab %}
{% endtabs %}

* Example demonstrating an ID must match value:

{% tabs %}
{% tab title="OData" %}

```
$filter=id eq B5FE25E3-...
---
$filter=id in (B5FE25E3-..., 311DD333-...)
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "path": "id",
        "op": "eq",
        "value": "B5FE25E3-..."
    }
}

---

{
    "filter": {
        "path": "id",
        "op": "eq",
        "value": [
            "B5FE25E3-...",
            "311DD333-..."
        }
    }
}
```

{% endtab %}
{% endtabs %}

* Example demonstrating a name must match string value:

{% tabs %}
{% tab title="OData" %}

```
$filter=data/name/iv eq 'Uber Eats'
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "path": "data.name.iv",
        "op": "eq",
        "value": "Uber Eats"
    }
}
```

{% endtab %}
{% endtabs %}

* Example demonstrating a boolean must match value:

{% tabs %}
{% tab title="OData" %}

```
$filter=data/givenUp/iv eq true
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "path": "data.givenUp.iv",
        "op": "eq",
        "value": true
    }
}
```

{% endtab %}
{% endtabs %}

* Example demonstrating a number must match a value:

{% tabs %}
{% tab title="OData" %}

```
$filter=data/funding/iv eq 500
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "path": "data.funding.iv",
        "op": "eq",
        "value": 500
    }
}
```

{% endtab %}
{% endtabs %}

* Examples of string property demonstrating `startswith`, `endswith` or `contains` :

{% tabs %}
{% tab title="OData" %}

```
$filter=startswith(data/founded/iv, '202')
$filter=startswith(data/founded/iv, '202') eq true // Aquivalent
---
$filter=endswith(data/founded/en, '022')
---
$filter=contains(data/description/en, 'catering')
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "path": "data.founded.iv",
        "op": "startsWith",
        "value": "202"
    }
}
---
{
    "filter": {
        "path": "data.founded.en",
        "op": "endsWith",
        "value": "022"
    }
}
---
{
    "filter": {
        "path": "data.description.en",
        "op": "contains",
        "value": "delivery"
    }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**contains**, **startsWith** and **endsWith** are always case insensitive.
{% endhint %}

* Examples of string property matching a regex pattern:

{% tabs %}
{% tab title="OData" %}

```
$filter=matchs(data/name/iv, 'a-z') // Case insensitive
---
$filter=matchs(data/name/iv, '/a-z/') // Case sensitive
---
$filter=matchs(data/name/iv, '/a-z/i') // Case insensitive
```

{% endtab %}

{% tab title="JSON" %}

```json
{
    "filter": {
        "path": "data.name.iv",
        "op": "matchs",
        "value": "a-z" // Case insensitive
    }
}
---
{
    "filter": {
        "path": "data.name.iv",
        "op": "matchs",
        "value": "/a-z/" // Case sensitive
    }
}
---
{
    "filter": {
        "path": "data.name.iv",
        "op": "matchs",
        "value": "/a-z/i" // Case insensitive
    }
}
```

{% endtab %}
{% endtabs %}

* Examples of using operators with false / negation\
  In OData these operators can also be compared with **false**. \
  In JSON queries you must use a **not** operation to negate your filter expression.

{% tabs %}
{% tab title="OData" %}

```
$filter=contains(data/name/iv, 'Uber Eats') eq false
---
not contains(data/name/iv, 'Uber Eats')
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "not": {
            "path": "data.name.iv",
            "op": "contains",
            "value": "Uber Eats"
        }
    }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
In **OData** single quotes (`'`) in text values must be replaced with double single quotes.
{% endhint %}

* Example of Geolocation within radius:

{% tabs %}
{% tab title="OData" %}

```javascript
// Point is defined as POINT(longitude latitude)
geo.distance(data/location/iv, geography'POINT(11.576124 48.137154)') lt 1000
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "path": {
            "data.location.iv",
            "op": "lt"
            // The radius is defined as Radius(Longitude, Latitude, Meters)
            "value": "Radius(11.576124, 48.137154, 1000)"
        }
    }
}
```

{% endtab %}
{% endtabs %}

* Examples of various conditions

{% tabs %}
{% tab title="OData" %}

```
$filter=data/funding/iv ne 100 // Not equals
---
$filter=data/funding/iv eq 100 // Equals
---
$filter=data/funding/iv lt 100 // Less than
---
$filter=data/funding/iv le 100 // Less or equals than
---
$filter=data/funding/iv gt 100 // Greater than
---
$filter=data/funding/iv ge 100 // Greater or equals than
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "path": "data.funding.iv",
        "op": "ne", // Not equals
        "value": 100
    }
}
---
{
    "filter": {
        "path": "data.funding.iv",
        "op": "eq", // Equals
        "value": 100
    }
}
---
{
    "path": "data.funding.iv",
    "op": "lt", // Less than
    "value": 100
}
---
{
    "filter": {
        "path": "data.funding.iv",
        "op": "le", // Less or equals than
        "value": 100
    }
}
---
{
    "filter": {
        "path": "data.funding.iv",
        "op": "gt", // Greater than
        "value": 100
    }
}
---
{
    "filter": {
        "path": "data.funding.iv",
        "op": "ge", // Greater or equals than
        "value": 100
    }
}
```

{% endtab %}
{% endtabs %}

* Examples of combining conditions:

{% tabs %}
{% tab title="OData" %}

```
// AND: Both condition must be true
$filter=data/funding/iv eq 100 and data/givenUp/iv eq true 

// OR: One condition must be true
$filter=data/funding/iv eq 100 or data/givenUp/iv eq true 
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "and": [{ // AND: Both condition must be true
            "path": "data.funding.iv",
            "op": "eq",
            "value": 100
        }, {
            "path": "data.givenUp.iv",
            "op": "eq",
            "value": true
        }]
    }
}
---
{
    "filter": {
        "or": [{ // OR: One condition must be true
            "path": "data.funding.iv",
            "op": "eq",
            "value": 100
        }, {
            "path": "data.givenUp.iv",
            "op": "eq",
            "value": true
        }]
    }
}
```

{% endtab %}
{% endtabs %}

* Examples of negations

{% tabs %}
{% tab title="OData" %}

```
not endswith(data/name/iv, 'Food')
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "filter": {
        "not": {
            "path": "data.name.iv",
            "op": "endswith",
            "value": "Food"
        }
    }
}
```

{% endtab %}
{% endtabs %}

### Sorting

The `orderby` or `sorting` query option allows clients to request resources in a particular order.

For example, find the top 20 most funded startups:

{% tabs %}
{% tab title="OData" %}

```
https://.../api/content/the-foodcrunch-mag/startups?$orderby=data/funding/iv desc$top=20
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "sort": [{
        "path": "data.funding.iv",
        "order": "descending"
    }],
    "take": 20
}
```

{% endtab %}
{% endtabs %}

You can also sort by multiple fields.

{% tabs %}
{% tab title="OData" %}

```
https://.../api/content/the-foodcrunch-mag/startups?$orderby=data/funding/iv desc,data/name/iv asc$top=20
```

{% endtab %}

{% tab title="JSON" %}

```javascript
{
    "sort": [{
        "path": "data.funding.iv",
        "order": "descending"
    }, {
        "path": "data.name.iv",
        "order": "ascending"
    }],
    "take": 20
}
```

{% endtab %}
{% endtabs %}

## Published Items

By default, the content API only returns published content. You can also use the `X-Unpublished` header to return draft content.

### Versioning

The API tracks the version of each content element and provides this information in the `ETag` content header if you create an update (POST, PUT, PATCH) or if you request a single resource. If you request multiple resources, the version is provided as a field to each entry.

You can use this header for two use cases:

1. When you create an update, you get the new version. This information can be used to find out if your change has already been written to the read store when you receive the same resource following your update.
2. When you create an update, you can use the `If-Match` header to pass the expected version to the API. If the version does not match the version in the database, this means that another user or client has changed the same resource. In which case, the `412 (Precondition Failed)` status code is returned. You should provide this information to the user and ask if the user wants to reload the data or if the resource should be overwritten (but don't use the `If-Match` header for the second request).

Read more about the `If-Match` header at:

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match>" %}


# Assets

How to Use the Assets API to Resize Images

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## How to Retrieve Assets and Content

All assets are publicly available and not secured. However, the IDs are randomly generated as 128 bit numbers ([GUID](https://en.wikipedia.org/wiki/Universally_unique_identifier)) and almost impossible to find out just by using trial and error. Consider your assets safe!

You can download and link assets using the following URL format:

```
http://<YOUR-DOMAIN>/api/assets/{app}/{idOrSlug}/{*more}
```

So, if you are using the Squidex Cloud you have to use:

```
https://cloud.squidex.io/api/assets/{app}/{idOrSlug}/{*more}
```

Typically, an asset URL will look like this:

```
https://cloud.squidex.io/api/assets/the-foodcrunch-mag/182999c2-e266-46bc-9586-6ad9e49bc5b8/
```

If your assets have a slug, you can also use that slug for your URL:

```
https://cloud.squidex.io/api/assets/the-foodcrunch-mag/foodco_logo
```

If multiple assets with the same slug exist, the first asset with this value will be returned.&#x20;

The `more` parameter can be used to add additional text to the URL, to improve your link quality. Here's an example:

```
https://cloud.squidex.io/api/assets/the-foodcrunch-mag/foodco_logo/grayscale
```

The API reference with all parameters is provided here: <https://cloud.squidex.io/api/docs#operation/AssetContent_GetAssetContent>

## How to Resize Images?

The API has several parameters to provide and manipulate images. Resized images are cached permanently to improve performance for the following requests.

Use the following query string parameters:

<table><thead><tr><th width="191">Parameter</th><th width="130">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>width</code></td><td>number</td><td>The target width of the asset, if it is an image.</td></tr><tr><td><code>height</code></td><td>number</td><td>The target height of the asset, if it is an image.</td></tr><tr><td><code>mode</code></td><td>string</td><td>The resize mode when the width and height is defined. Default: <code>Max</code></td></tr><tr><td><code>download</code></td><td>number</td><td>By default, Squidex provides the file name of the asset in the response. Therefore most browsers will download the file, which might be not desired for images. By setting this parameter to <code>0</code> you can prevent the download for images.</td></tr><tr><td><code>version</code></td><td>number</td><td>Use this value to get a concrete version. If omitted, the last version is returned. The initial version is <code>0</code> and it is updated with each update of the asset, not only when a new file version is uploaded.</td></tr><tr><td><code>cache</code></td><td>number</td><td>The client cache duration in seconds. Only affects the response header.</td></tr><tr><td><code>quality</code></td><td>number</td><td>Define the quality of the resized image.</td></tr><tr><td><code>bg</code></td><td>string</td><td>Background color, if the image has a transparent background.</td></tr><tr><td><code>focusX</code></td><td>number</td><td>Overrides the X value of focus point as a relative value from 0 (left) to 1 (right). Can also be configured via the Management UI. The dimensions are changed during resize, the focus point will be always visible.</td></tr><tr><td><code>focusY</code></td><td>number</td><td>Overrides the Y value of focus point as a relative value from 0 (top) to 1 (bottom). Can also be configured via the Management UI. The dimensions are changed during resize, the focus point will be always visible.</td></tr><tr><td><code>nofocus</code></td><td>boolean</td><td>Ignores the focus point when resizing.</td></tr><tr><td><code>force</code></td><td>boolean</td><td>Resizes the image, even it is cached on the server side. Can be used to invalidate broken cache entries.</td></tr><tr><td><code>auto</code></td><td>boolean</td><td>Delivers the image in the best format, depending on the <code>Accept</code> header. This is usually <code>webp</code> or <code>avif</code>.</td></tr></tbody></table>

You must fill in either the `width` or `height` parameter. If the asset is not an image, these parameters have no effect.

Squidex supports the following resize modes:

<table><thead><tr><th width="193">Mode</th><th>Description</th></tr></thead><tbody><tr><td><code>Crop</code></td><td>Crops the resized image to fit the bounds of its container. If the desired width and height is greater than the image dimensions it behaves like <code>BoxPad</code>.</td></tr><tr><td><code>CropUpsize</code></td><td>Crops the resized image to fit the bounds of its container, also desired width and height is greater than the image dimensions.</td></tr><tr><td><code>Pad</code></td><td>Pads the resized image to fit the bounds of its container. If only one dimension is passed, will maintain the original aspect ratio.</td></tr><tr><td><code>BoxPad</code></td><td>Pads the image to fit the bound of the container without resizing the original source. When down-scaling this mode performs the same functionality as <code>Pad</code>.</td></tr><tr><td><code>Max</code></td><td>Constrains the resized image to fit the bounds of its container maintaining the original aspect ratio.</td></tr><tr><td><code>Min</code></td><td>Resizes the image until the shortest side reaches the set given dimension. Up-scaling is disabled in this mode and the original image will be returned if attempted.</td></tr></tbody></table>

### How to Change the Image Quality

You can also add the `quality` parameter to the asset URL to define the quality from 0 (lowest) to 100 (best). If you define the quality, the image will always be returned in jpeg format and you might loose transparency information.

## How caching works

Squidex provides the `ETag` \*\*header for each asset. The browser caches the asset, along with its ETag. If the user wants to retrieve the same URL resource again, it will first determine whether the locally cached version of the URL has expired (through the `Cache-Control` and the `Expire` \*\* headers). If the URL has not expired, it will retrieve the locally cached resource. If it is determined that the URL has expired (is stale), the client will contact the server and send its previously-saved copy of the ETag along with the request in an `If-None-Match` field.<br>

Squidex now compares the passed ETag with the ETag of the current version of the asset. If the ETag values match (meaning that the asset has not changed), the server sends back an empty response with an [HTTP 304 Not Modified](https://en.wikipedia.org/wiki/HTTP_304) status and the browser will show the cached version.

More about the ETag header can be found on [Wikipedia](https://en.wikipedia.org/wiki/HTTP_ETag).&#x20;

You can add the `cache` query parameter as a number of seconds to cache the image in the browser. Then the following header is added to the response:

```
Cache-Control: public,max-age=<cache>
```

### Resolving References Assets

When you have content items with asset fields, the assets are added as IDs to the content item. Therefore, by default you will only retrieve the IDs when you query content items and you have to create additional requests to get asset information.

You have two options to solve this:

### Use GraphQL

With GraphQL you can get the exact information you are looking for. You have to define the fields you want to retrieve, from referenced assets or referenced content. In the following example you will find a query built to retrieve testimonials using the associated company logo as the field:

This example is NOT from the *FoodCrunch* use case

<figure><img src="/files/id1D4fYC5y2q7vJ0BR8l" alt=""><figcaption><p>Use GraphQL to get asset information</p></figcaption></figure>

### Using Headers in the REST Endpoint

When using the normal REST endpoint, you can use the `X-Resolve-Urls` to define the fields that should be resolved. The value of this header must be a comma-separated list of field names:

![Resolve the image URL](/files/-Lto9mh9ZmDr9o9Ub-Qh)


# Automation Tools (CLI)

The Squidex CLI (Command Line Interface) Tool Helps Automate Administration Processes

The Squidex CLI (Command Line Interface) is a terminal application available for Windows, Linux and macOS (OS X).

The CLI has two main advantages:

1. It is easy to automate things in your build and release processes, for example, you can trigger nightly updates and schema migrations from one App to another or export content.
2. It is easier to integrate complex features, such as export to CSV because it takes more time to write a good user interface than the export routine itself.

## Installation

The CLI can be installed by downloading the binaries from the GitHub release page here: <https://github.com/Squidex/squidex-samples/releases>

If you are a .NET developer you can also install the CLI from [Nuget](https://www.nuget.org/packages/Squidex.CLI):

```bash
dotnet tool install --global Squidex.CLI
```

## How to Use the CLI?

Hopefully, the CLI itself is good enough for you and you can use the integrated help to navigate through all the features.

The general structure of each command is as follows:

```
.\sq.exe [FEATURE] [COMMAND] [ARGS] [OPTIONAL_PARAMETERS]
```

Depending on your use cases you will need a client with the **Developer** or **Owner** role.

{% hint style="info" %}
This page demonstrates examples using Windows, but the CLI is also available for Linux and macOS (OS X).
{% endhint %}

## How to Manage Configurations

A configuration contains all information to connect the CLI to a specific App in your Squidex installation and contains the *App Name*, *Client ID*, *Client Secret* (and optionally the *URL* to your installation if you do not use the Squidex Cloud).

The CLI can manage multiple configurations, so you don't have to define the App, client and secret for each command. Configurations are stored in a file that is located just next to the CLI, so you need to write permissions to this directory.

The `config` feature of the utility is used to manage configurations.

### STEP 1: Add a Configuration

```
.\sq.exe config add [APP_NAME] [CLIENT_ID] [CLIENT_SECRET]
```

<figure><img src="/files/cTWtowpVqcWkIy2m66TB" alt=""><figcaption><p>Adding an app configuration</p></figcaption></figure>

### STEP 2: Show all Configurations

```
.\sq.exe config list
```

<figure><img src="/files/r9IhaNVLFWr0RGdTaUIM" alt=""><figcaption><p>List all added configurations</p></figcaption></figure>

To view the configurations as a table use `-t` or `--table`.

```
.\sq.exe config list -t
.\sq.exe config list --table
```

<figure><img src="/files/vGDY1ofIcQ3EhK4TzZei" alt=""><figcaption><p>List all added configurations in a table</p></figcaption></figure>

### STEP 3: Switch to Another Config

When working with multiple Apps, the `config use` command can be used to switch between Apps. It is used to select an App (configuration) from a list of configurations.

```
.\sq.exe config use [APP_NAME]
```

## Use Cases

The following section describes the most common use cases and how to execute them with the CLI.

### Synchronize all App Settings

{% hint style="info" %}
You need a client with the **Owner** role for this use case.
{% endhint %}

A common use case of the CLI is that you can synchronize your Squidex app settings using the `sync` command.

The syntax for it is:

```
.\sq.exe sync [COMMAND] [OPTIONS] [ARGS]
```

The `sync` command can be used to export or import your App settings to a set of JSON files. This command is able to synchronize the following settings:

* Schemas
* Contents
* Contributors (this setting can be imported only)
* Clients
* Roles
* Rules
* Workflows

#### Use Case - Create Sample Configuration files

The `sync` command can be used to create a new folder with sample files that can then be imported to the App.

The following command does the same (replace `<folder>` with a folder name of your choice):

```bash
.\sq.exe sync new <folder>
```

The CLI creates a set of sample configuration files that begin with \_\_ (see screenshot below). The files starting with a double underscore are ignored during the synchronization. Therefore, you have to rename or copy the samples files in order to sync them.

The screenshot below shows a typical folder structure with sample configuration files:

<div align="left"><figure><img src="/files/D6i9HvbZjm0smO4W00el" alt=""><figcaption></figcaption></figure></div>

The CLI also generates JSON schema files that are referenced by the configuration files and provide basic intelligence features in editors such as [Visual Studio Code](https://code.visualstudio.com). You will see error messages when you do not follow the JSON schema. Do also look out for additional errors that might occur when synchronizing your configuration.

#### Use Case - Import (Synchronize Configuration From Folder to App)

To synchronize a configuration (created through the CLI) with your App, use the following command replacing `<folder>` with the folder name where the configuration is:

```bash
.\sq.exe sync in <folder>
```

{% hint style="info" %}
When synchronizing your configuration, it is very important that the client used by the CLI configuration to establish the connection to your App is the same in the App.JSON file. If you don't do this, the client will be deleted during the synchronization stage and also, consecutive commands will fail.
{% endhint %}

The `sync in` command also provides flags to control the synchronization process. You can view a list of supported flags/options by running the following command:

```bash
.\sq.exe sync in --help
```

<table><thead><tr><th width="374">Flag</th><th>Description</th></tr></thead><tbody><tr><td><code>app</code></td><td>The name of the app. If not provided then app configured in <code>currentApp</code> gets created.</td></tr><tr><td><code>targets</code></td><td>This flag can be used to only import certain parts of your app. You can use multiple parameters. E.g. 'sync in -t contents -t schemas'. Use <code>sync targets</code> to view all targets.</td></tr><tr><td><code>language</code></td><td>The content language to synchronize.</td></tr><tr><td><code>content-action</code></td><td><p>Defines how to handle content. </p><p>Allowed values: <strong>Upsert, UpsertPatch, Create, Update, Patch</strong>.</p></td></tr><tr><td><code>delete</code></td><td>Use this flag to also delete entities.</td></tr><tr><td><code>patch-content</code></td><td>Make content updates as patch.</td></tr><tr><td><code>recreate</code></td><td>Use this flag to also recreate entities.</td></tr><tr><td><code>skip-assets</code></td><td>Use this flag to sync asset folders but not assets.</td></tr><tr><td><code>update-current-client</code></td><td>Also update the client that is used during the sync process.</td></tr><tr><td><code>emulate</code></td><td>Use this flag to only emulate the changes, like a dry run.</td></tr></tbody></table>

#### Use Case - Export (Synchronize Configuration from App to Folder)

The `sync` command also lets you export your App configuration to a folder. If you do not want to start from scratch, you can also export. Use the following command replacing `<folder>` with an actual folder name.

```bash
.\sq.exe sync out <folder>
```

{% hint style="info" %}
Make note of the following when synchronizing out or exporting.

1. A client with the Owner role can export all configurations.
2. A client with the Developer role will not be able to export clients (in Step 3) and will throw a `403 Forbidden` error message. This will complete export of the rest of the configurations.&#x20;
3. A client with the Editor role will completely fail exporting with the `403 Forbidden` message.
   {% endhint %}

For example, in the following screenshot we are exporting the configuration of an App called *blog-with-squidex* to our local machine where CLI is installed. The client has the Owner role.

```
.\sq.exe sync out blog-with-squidex
```

<div align="left"><figure><img src="/files/Ya7J6E9TagxSdvqL6EAZ" alt=""><figcaption><p>Synchronize out / Export configuration from app to folder</p></figcaption></figure></div>

The `sync out` command also provide flags to control the synchronization process.

```
.\sq.exe sync out --help
```

<table><thead><tr><th width="374">Flag</th><th>Description</th></tr></thead><tbody><tr><td><code>app</code></td><td>The name of the app. If not provided then app configured in currentApp gets created.</td></tr><tr><td><code>targets</code></td><td>This flag can be used to only export certain parts of your app. You can use multiple parameters. E.g. 'sync out -t contents -t schemas'. Use '<code>sync targets</code>' to view all targets.</td></tr><tr><td><code>describe</code></td><td>Create a README.md file.</td></tr></tbody></table>

#### Restrictions

The synchronization feature has a few restrictions:

1. **Contributors Cannot be Exported**. We use the email address of the users to add new contributors to your App. However, because Squidex protects the PII (Personally Identifiable Information) of our users, we do not expose email addresses via the API and therefore cannot export the contributors.
2. **Content Cannot be Deleted.** It is just too complicated!

### Synchronizing Schemas

{% hint style="info" %}
You need a client with either **Developer** or **Owner** role for this use case.
{% endhint %}

The CLI provides a mechanism to synchronize schemas using a JSON file. The `schemas` command helps you to achieve this. The syntax for this is:

```
.\sq.exe schemas [command] [options]
```

STEP 1: Use the `config` command to select the source App and then save the schema to a JSON file.

```bash
.\sq.exe config use app1
.\sq.exe schemas get schema1 > schema.json
```

STEP 2: Use the `config` command again to switch to the destination App and sync the schema from the saved file.

```bash
.\sq.exe config use app2
.\sq.exe schemas sync schema.json
```

Or you can also sync it to another schema name.

```bash
.\sq.exe schemas sync schema.json --name <schema-name>
```

### Start a Backup

You can also create a backup using the CLI. This is achieved by using the `backup` command.

{% hint style="info" %}
You need a client with the **Owner** role for this use case.
{% endhint %}

```bash
.\sq.exe backup create backup.zip
```

### Export Content to CSV

```bash
.\sq.exe content export <schema-name> --fields=id,version
```

You have to define the fields you want to export. The general syntax is:

```bash
(<CSV_COLUMN>=)?<JSON_PATH>
```

The CSV column is optional and can be skipped. If no column name is specified, the path string will be used.

To get a good understanding of paths, it is helpful to have a look to the API documentation of your schemas, e.g.

<https://cloud.squidex.io/api/content/squidex-website/docs#operation/GetTestimonialsContent>

Some sample paths

* `id`
* `version`
* `data.personName.iv`
* `data.personName` (`iv` is added by default for non-localized fields)
* `personName=data.personName` (Column name for non-localized field).

More examples (not from the example operation above):

* `data.text.en` (Localized field)
* `data.hobbies.iv.0.name`(For array of objects)
* `data.hobbies.iv`(To serialize the whole array to a string)
* `data.json.iv.property` (For a nested object)
* `data.json.iv` (To serialize the whole object to a string)

If the extract value is a JSON array of an object, it will be serialized to a string.

### Export Content to JSON

```bash
.\sq.exe content export <schema-name> --format=JSON
```

Take a look at the help section of the `content export` command for a list of all options.

```
.\sq.exe content export --help
```

### Import Content from CSV

```bash
.\sq.exe content import <schema-name> File.csv --fields=text
```

You have to define the fields you want to import. The general syntax is:

```bash
<JSON_PATH>(=<CSV_COLUM>)?
```

The CSV column is optional and can be skipped. If no column name is specified, the path string will be used.

To get a good understanding of the paths, it is helpful to have a look to the API documentation of your schemas, e.g.

<https://cloud.squidex.io/api/content/squidex-website/docs#operation/GetTestimonialsContent>

Some sample paths:

* `personName.iv=personName`
* `personName` (`iv` is added by default for non-localized fields)

More examples (not from the example operation above):

* `text.en=text` (Localized field)
* `hobbies.iv.0.name=firstHobby`(For array of objects)
* `hobbies.iv=hobbies`(To serialize the whole array to a string)
* `json.iv.property=jsonProperty` (For a nested object)
* `json.iv=json` (To serialize the whole object to a string)

If the extract value is a JSON array of an object it will be serialized to a string.

### Import Content from JSON

```bash
.\sq.exe content import <schema-name> File.json --format=JSON
```

Take a look at the help section of the `content import` command for a list of all options.

```
.\sq.exe content import --help
```

### Use Case: Generate Test Data

Sometimes, it is useful to generate test data for a field, e.g. if you need several hundred of items to test a user interface.

This can be done with the CLI as well:

```bash
.\sq.exe content test-data <schema-name> --count 100
```

Before you generate the data you can also test it by dumping the data to a file first:

```bash
.\sq.exe content test-data <schema-name> --count 100 --file Test-Data
```

The CLI does not support all field types, it has the following restrictions:

* No support for references.
* No support for assets.
* No support for string fields with a pattern validator.

It is also unable to support custom validations via scripts or custom extensions.

## How to Use Help Feature Within the CLI

The CLI utility has an inbuilt help feature which can be accessed by using the `--help` parameter.

For example `--help` at the utility level will show all features the CLI supports.

`.\sq.exe --help`

<div align="left"><figure><img src="/files/S6eGLSV7kFJ4uZ4TGUJd" alt=""><figcaption><p>Help Example 1</p></figcaption></figure></div>

Similarly a `--help` parameter after a feature gives information about all the commands the feature supports. For example:

`.\sq.exe config --help`

<div align="left"><figure><img src="/files/ax9syzqYGW2QFGUflBN5" alt=""><figcaption><p>Help Example 2</p></figcaption></figure></div>


# Scripting

Learn How to Use Scripting to Implement More Validation and Security Solutions

## Introduction

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Why Scripting?

Some business rules around security and validation are hard to solve with a generic feature that works for everybody and is easy to use. The workflow system has limitations as well and you cannot write permissions that depend on the data of the content.

In general, scripting can be used to handle gaps in the Squidex feature set.

### Scripting for Content

You can create scripts that run whenever a content item is created, updated, deleted, queried or when the status changes (e.g. from Draft to Published).

Scripts can be defined in the schema editor:

1. Go to your **App** (1).
2. Go to the **Schema** (2) settings.
3. Select the schema (3) you want to write a script for, i.e `startups` in this example.
4. Select the **Scripts** (4) tab
5. Select the tab (5), depending on when you want the script to run, to work with the editor.

<figure><img src="/files/XumIwSuMveWvsub0P81a" alt=""><figcaption><p>Creating a script</p></figcaption></figure>

In the editor, you can define scripts for the following actions:

* **Query** script is executed whenever a content item is queried with the API, but not when queried by the Management UI.
* **Prepare Query** is called once for all content items of the current query. It can be used to precompute or prefetch data.
* **Create** script is executed before a content item is created.
* **Change** script is executed before the status of a content item is changed. When you use scheduling to change the status of a content item in the future, the script is called just before the status is changed and not when you schedule it. This can also stop your scheduling, when the script fails or it rejects the change.
* **Delete** script is executed before a content item is deleted.
* **Update** script is executed before a content item is updated.

Content creation and updates happen in the following order:

1. The content item is loaded. If it does not exist, the API responds with 404 (NotFound).
2. The data from the request is validated. The API responds with 400 (BadRequest) for invalid data.
3. **The script is executed.**
4. The data from the request is enriched with configured default values.
5. The constraints, such as unique fields are checked.

This means that you have the guarantee in your scripts, that the data is always valid and that you cannot violate constraints such as unique fields when you auto-generate or change content data.

{% hint style="info" %}
Scripts are executed for the REST endpoint as well as for the GraphQL endpoint.
{% endhint %}

### Scripting for Assets

Asset scripts can be defined in the settings:

1. Go to your App (1).
2. Go to the **Settings** (2).
3. Select the **Asset Scripts** (3) menu item.
4. Select the script type (4) you want to edit.

<figure><img src="/files/VDikLMHRpl4HbgQ5eKYI" alt=""><figcaption></figcaption></figure>

In the editor you can define scripts for the following actions:

* **Annotate** script is executed before the metadata of an asset is changed.
* **Create** script is executed before an asset is created.
* **Moved** script is executed before an asset is moved to another folder.
* **Delete** script is executed before an asset is deleted.
* **Update** script is executed before an asset is replaced with a new file.

## Execution and Variables

The scripts are executed in a Sandbox. You do not have access to the file system and only to allowed functions. The latest ECMAScript 2024 (ES15) JavaScript syntax is implemented, which means you can use lambda expressions, Promises, or classes. For more details on supported features, refer to the [Jint ECMAScript Compatibility](https://github.com/sebastienros/jint/tree/v4.1.0?tab=readme-ov-file#supported-features)

### Variables

All variables are accessible over the `ctx` (Context) variable.

The following fields can be used for all scripts:

```javascript
var ctx = {
    // The ID of the current app.
    appId: string,
    
    // The name of the current app.
    appName: string,
    
    // The name of the operation, as it is also used in the UI.
    // 
    // For assets:
    //  * Query
    //  * PrepareQuery    Once for all assets
    //  * Update
    //  * Delete
    //  * Move
    //
    // For content
    //  * Query
    //  * PrepareQuery    Once for all contents
    //  * Create
    //  * Update
    //  * Delete
    //  * Change
    //  * Published
    //  * Unpublished
    operation: string,
    
    user: {
        // The ID of the user or the name of the client,
        // if the update or query is invoked from a client.
        id: string,
        
        // The email address of the user, 
        // if the user is not a client.
        email: string,
        
        // True, if the current user is a client, false otherwise.
        isClient: boolean,
        
        // Each user has a list of claims.
        // Claim are just key-value-pairs. 
        // Such a claim could be ... 
        // * The display name of the user or
        // * The link to the profile picture.
        // 
        // Most of them are not interesting for scripting,
        // but you can also go to your profile and
        // add custom properties as claims to your account 
        // and use them in the scripts or rules.
        claims: {
            key: string
        },
    }
}
```

#### Content Script Variables

The following fields can be used for content scripts:

```javascript
var ctx = {
    ... ALL FROM ABOVE,
    
    // The ID of the content item.
    contentId: string,
    
    // The data for the content item.
    data: {
        field: {
            iv: any,
        },
        localizedField: {
            en: any,
            de: any,
        }
    },
    
    // The old data of the content item for 'Update' scripts.
    dataOld: {
        field: {
            iv: any,
        },
        localizedField: {
            en: any,
            de: any,
        }
    },
    
    // Same as dataOld,
    oldData: ...
    
    // The status of the content.
    status: string,
    
    // The old status of the content item.
    // For 'Change' scripts only.
    statusOld: string,
    
    // True when the content should be deleted permanently.
    // For 'Delete' scripts only.
    permanent: boolean,
}
```

#### Asset Script Variables

The following fields can be used for Asset scripts:

```javascript
var ctx = {
    ... ALL FROM ABOVE,
    
    // The ID of the asset.
    assetId: string,
    
    // The asset.
    asset: {
        // The SHA256 hash of the file. Can be null for old files.
        fileHash: string,
        
        // The file name of the asset, e.g. 'My File.png'
        fileName: string,
        
        // The size of the file in bytes.
        fileSize: nummber,
        
        // The URL slug of the asset, e.g. 'my-file.png'
        fileSlug: string;
        
        // True, when the asset is not public.
        isProtected: boolean,
        
        // The asset metadata.
        metadata: {
            // Example for images:
            pixelWidth: number,
            pixelHeight: number,
            
            // In general:
            key: string,
        },
        
        // The content type, e.g. 'image/png'.
        mimeType: string,
        
        // The ID of the folder.
        parentId: string,
        
        // All parent folders from the root to the current folder.
        parentPath: [{
            id: string,
            folderName: string,
        }],
        
        // True when the asset should be deleted permanently.
        // For delete operations only. 
        permanent: boolean,
        
        // The tags assigned to the asset.
        tags: [
            string,
            string
        ]
    },
};

// Contains new values, when something has been changed.
var command = {
    // SEE: ctx.asset.    
};
```

### Methods

#### Control Methods

These methods are used to make changes to the content item or to reject changes.

```javascript
/**
 * - Content scripts only.
 * - Tells Squidex that you have made modifications to the ctx.data object.
 * - This change should be applied to the content.
 */
function replace() {}

/**
 * - Tells Squidex that this operation is not allowed.
 * - A 403 (Forbidden) status code should be returned.
 * - The user will see an alert in the Management UI.
 */
function disallow() {}

/**
 * - Tells Squidex that this operation is not valid.
 * - A 400 (BadRequest) status code should be returned.
 * - The user will see an alert in the Management UI.
 */
function reject(reason) {}

/**
 * - Tells Squidex that the script should complete successfully.
 */
function complete() {}
```

#### Helper Methods

Squidex provides a set of general helper functions for scripting and rule formatting.

{% content-ref url="/pages/-MgPIF\_xXTjGDwutI\_x7" %}
[Scripting Helper Methods](/id-02-documentation/developer-guides/scripting/scripting-helper-methods)
{% endcontent-ref %}

In addition to that, there are also methods that are only available for scripting.

```javascript
/**
 * - Makes a request to the defined URL.
 * - If the request succeeds with a HTTP response status code (2XX) and a valid JSON response is returned
 *   the callback is invoked and the JSON response is passed to the callback as a JSON object.
 * - The script fails otherwise.
 * - You can also pass in an object with headers.
 * - If ignoreError is true, non-2XX responses return an object with the schema instead of throwing.
 *   { statusCode: number, headers: { [key: string]: string }, body: string }
 */
function getJSON(url, callback, headers?, ignoreError?) {}
function postJSON(url, body, callback, headers?, ignoreError?) {}
function putJSON(url, body, callback, headers?, ignoreError?) {}
function patchJSON(url, body, callback, headers?, ignoreError?) {}
function deleteJSON(url, callback, headers?, ignoreError?) {}

/**
 * - Queries the content items with the specified IDs.
 * - Invokes the callback with the resulting content items when the request has been completed.
 * - If the current user does not have permissions to read the content items, the callback is invoked with an empty array.
 */
function getReferences(ids, callback) {}

/**
 * - Queries the content item with the specified ID.
 * - Invokes the callback with an array that includes the resulting content item when the request has been completed.
 * - If the current user does not have permissions to read the content item, the callback is invoked with an empty array.
 */
function getReference(id, callback) {}

/**
 * - Queries the assets with the specified IDs.
 * - Invokes the callback with the resulting assets when the request has been completed.
 * - If the current user does not have permissions to read assets, the script will fail.
 */
function getAssets(ids, callback) {}

/**
 * - Queries the asset with the specified ID.
 * - Invokes the callback with an array that includes the resolved asset when the request has been completed.
 * - If the current user does not have permissions to read assets, the script will fail.
 */
function getAsset(id, callback) {}

/**
 * - Queries the asset with the specified ID.
 * - Invokes the callback with a single asset when the request has been completed.
 * - If the current user does not have permissions to read assets, the script will fail.
 */
function getAssetV2(id, callback) {}

/**
 * - Takes the specified asset and computes the text.
 * - The asset can not be larger than 4 MB, otherwise an error is returned.
 */
function getAssetText(asset, callback) {}

/**
 * - Takes the specified asset and computes the blur hash.
 * - Read more: https://blurha.sh/
 */
function getAssetBlurHash(asset, callback) {}

/**
 * - Translates a given text to the target language.
 * - Invokes the callback with the translated text when completed.
 * - The source language is usually detected automatically, but can be passed in and usually provides better results.
 */
function translate(text, targetLanguage, callback, sourceLanguage?) {}

/**
 * - Generates content described by the prompt using OpenAI or other services.
 * - Invokes the callback with the generated text.
 */
function generate(prompt, callback) {}
```

## Use Cases

### Write the Context to a Field

If you want to understand your data structure and the context object, you can just write it to a string field.

```javascript
ctx.data.debug.iv = JSON.stringify(ctx, null, 2);
// Tell Squidex that the content should be replaced.
replace(); 
```

### Do Not Return Sensitive Information When Queried by a Client

```javascript
if (ctx.isClient) { // ctx Variable contains all Context information
    ctx.data.password.iv = '********';
    // Tell Squidex that the content should be replaced.
    replace(); 
}
```

### Do Not Allow the Client to Set Fields

```javascript
if (ctx.isClient && ctx.data.password.iv) {
    // Tell Squidex to return a 403 (Forbidden)
    disallow();
}
```

### Ensure that Two Fields Have the Same Value

```javascript
if (data.password.iv !== data.passwordConfirm.iv) {
    // Tell Squidex to return a 400 (Bad Request)
    reject('Passwords must be the same');
}
```

### Ensure that Only a Specific User can Publish Content

```javascript
if (ctx.operation === 'Published' && ctx.user.email !== 'content@master.com') {
    // Reject the call if the publisher has another email address.
    reject('You are not allowed to publish the content');
}
```

### Compute Field From Other Values

Store in a separate field if another field has a valid value:

```javascript
ctx.data.hasPassword = { iv: !!ctx.data.password.iv };
// Tell Squidex that the content should be replaced.
replace();
```

Calculate the slug for a content title automatically:

```javascript
ctx.data.slug.iv = slugify(ctx.data.title.iv);
// Tell Squidex that the content should be replaced.
replace();
```

Calculate the number of words in a Markdown field:

```javascript
ctx.data.wordCount.iv = wordCount(markdown2Text(ctx.data.html.iv)));
// Tell Squidex that the content should be replaced.
replace();
```

Calculate the number of characters in a HTML field:

```javascript
ctx.data.characterCount.iv = characterCount(html2Text(ctx.data.html.iv)));
// Tell Squidex that the content should be replaced.
replace();
```

### Enrich Your Content with Data from External Services

You can use the `getJSON` function to enrich the content with data from external services. This example is a little bit more complicated that the other examples above, but let's jump into the code first:

```javascript
var url = 'https://jsonplaceholder.typicode.com/todos/1';

var headers = {
    ApiKey: 'secret'
};

getJSON(url, function(result) {
    data.title.iv = result.title;
    // Tell Squidex that the content should be replaced.
    replace();
}, headers);

// I am done
```

When you make an asynchronous call to another service or content, the script engine cannot stop the script automatically. Therefore, it is very important to finish the script with a call to `replace()`, even if you do not make a change to the content data.

### Advanced Use Case with Modern ECMAScript

Using async/await with an Immediately Invoked Function Expression (IIFE) eliminates the complexity of callback-based patterns and enables clean asynchronous code. This approach is ideal for handling multiple external service calls or avoiding callback hell.

```javascript
const data = ctx.data;
const url = 'https://jsonplaceholder.typicode.com/todos/1';

function todoRequest(url) {
    return new Promise((resolve, reject) => {
        getJSON(url, (result) => {
            result.id === 1 ? resolve(result) : reject(new Error('failure'));
        });
    });
}

(async () => {
    try {
        const todo = await todoRequest(url);
        data.title.iv = todo.title;

        // Tell Squidex that the content should be replaced.
        replace();
    } catch(ex) {
        reject(ex.message);
    }
})()
```

## Restrictions

There are some existing restrictions:

1. You cannot include external libraries.
2. You cannot make calls to external services, except `getJSON`.
3. Scripts will timeout after 200ms of CPU execution.
4. Scripts will timeout after 5 seconds of total execution, e.g. waiting for external services with `getJSON`.


# Scripting Helper Methods

Helper Methods for Scripting

The following helper methods are not part of the ES5 JavaScript standard, but they are very helpful for a lot of use cases.

<table><thead><tr><th width="299">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>html2Text(text)</code></td><td>Converts a HTML string to plain text.</td></tr><tr><td><code>markdown2Text(text)</code></td><td>Converts a markdown string to plain text.</td></tr><tr><td><code>formatDate(date,pattern)</code></td><td>Formats a JavaScript date object using the specified pattern.</td></tr><tr><td><code>formatTime(date,pattern)</code></td><td>Same as <code>formatDate</code>.</td></tr><tr><td><code>wordCount(text)</code></td><td>Counts the number of words in a text. Useful in combination with <code>html2Text</code> or <code>markdown2Text</code>.</td></tr><tr><td><code>characterCount(text)</code></td><td>Counts the number of characters in a text. Useful in combination with <code>html2Text</code> or <code>markdown2Text</code>.</td></tr><tr><td><code>toCamelCase(text)</code></td><td>Converts a text to camelCase.</td></tr><tr><td><code>toPascalCase(text)</code></td><td>Converts a text to PascalCase.</td></tr><tr><td><code>sha256(text)</code></td><td>Calculate the SHA256 hash from a given string. Use this method for hashing passwords.</td></tr><tr><td><code>md5(text)</code></td><td>Calculate the MD5 hash from a given string. Use this method for hashing passwords, when backwards compatibility is important.</td></tr><tr><td><code>slugify(text)</code></td><td>Calculates the slug of a text by removing all special characters and whitespaces to create a friendly term that can be used for SEO-friendly URLs.</td></tr></tbody></table>


# Embed Content

Learn to Embed Content to Unstructured Text Such as Markdown or Rich-Text to Close the Gap Between Unstructured Content and Structured Content

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

## Use Case

When a content author writes an article about new food startups or a review for a product sold by a startup, they might want to add startup information to the article. As the article is unstructured and just Markdown or rich-text, there are limited options mentioned below and none of them really work:

1. The author can copy and paste the startup information to the article. When the information about the startup is updated at a later date, the article will contain outdated information.
2. It's possible to use a special placeholder in the Markdown to reference the startup and ask the developers to resolve this reference in the UI.
3. The developers can build a complex schema, for example with arrays to structure the article.

As none of the options are practical and convenient, this feature has been added.

## How to Use Embedded Contents

To use this feature follow the steps below:

### 1. Define Which Schemas Can be Embedded

By editing the string field, you can decide which schemas can be embedded. Set **Markdown** (1) as the editor as it provides easy options to insert contents. Next, check **Is embedding contents and assets** (2) and select the schema.&#x20;

In this example, we only allow embedding of contents from the `startups` schema.

<div align="left"><figure><img src="/files/naelLIpnQKBgEQKlwW6V" alt=""><figcaption><p>Enabling embedding on a string field</p></figcaption></figure></div>

### 2. Add Links to Your String Field

We can now use the Markdown editor to add links to other content items. To do so, click the **Insert Contents** (1) button in the editor.&#x20;

{% hint style="info" %}
The string field must be set to Markdown editor to see the insert contents button.
{% endhint %}

<div align="left"><figure><img src="/files/TfbEa4rAWegyYI8aTwD2" alt=""><figcaption><p>Adding linked contents from another schema</p></figcaption></figure></div>

On the popup window, select the entries you wish to link by **checking the box** (2) next to them and click **Link selected contents** (3). Refer to the example screenshot below, here we are selecting a couple of startups:

<div align="left"><figure><img src="/files/TTE12Qvw8NmVbInoxpno" alt=""><figcaption><p>Selecting linked contents from another schema</p></figcaption></figure></div>

The result should be two links appearing in the Markdown editor. An example screenshot is provided below for reference:

<div align="left"><figure><img src="/files/ax0CAnK1xFzBGzyxoxTC" alt=""><figcaption><p>Linked content from another schema</p></figcaption></figure></div>

### 3. Use the GraphQL to Fetch References

Use the new GraphQL structure to fetch the text and references. When you allow embedding, the structure of the GraphQL response changes and you can fetch the text and the references with a single request:

<div align="left"><figure><img src="/files/w6I4p9X9YT8qFR8mcNLR" alt=""><figcaption><p>Get the references with GraphQL</p></figcaption></figure></div>

### 4. Use the References to Render the Embedded Content

In the frontend both of the pieces of information can be used together to render the embedded content. In this sample code we've used react and react-markdown for this purpose. We can hook into the rendering process and render custom components for links.

At this point, it's important to check if the link is referencing  a content item and if this content item is part of the references. Then it's possible to render the startup.

{% code overflow="wrap" %}

```javascript
const startupsRegex = new RegExp(`${CONFIG.url}\\/api/content\\/${CONFIG.appName}\\/startups/(?<id>[a-z0-9\\-]+)`);

export const Markdown = ({ markdown, references }) => {
    return (
        <ReactMarkdown children={markdown} components={{
            a({ href, children }) {
                const match = startupsRegex.exec(href);

                if (match && match.groups) {
                    const referenceId = match.groups.id;
                    const reference = references?.find(x => x.id === referenceId);

                    if (reference) {
                        return <EmbeddableStartup startup={reference} />;
                    }
                } 
                
                return <a href={href} target='_blank' rel='noopener noreferrer'>{children}</a>;
            }
        }} />
    )
}s
```

{% endcode %}

As Markdown is unstructured  a regular expression must be used for this. The result is an article with embedded startup information.

This feature gives the content authors a lot more flexibility and simplifies the schemas in question.

### Examples

A sample for this feature is available in GitHub: <https://github.com/Squidex/squidex-samples/tree/master/jscript/react/sample-hotels>.&#x20;

{% hint style="info" %}
This sample does not use the *FoodCrunch* use case but can be used as a reference to understand how it is implemented.
{% endhint %}

The template for the schemas and sample content is also available under: <https://github.com/Squidex/templates/tree/main/sample-hotels>


# Custom Workflows

Write Advanced Workflows with Scripting

This documentation is based on the *FoodCrunch* use case. Please open the link below alongside this page to understand the examples.

{% content-ref url="/pages/-MSTfiNBwo8C1m-UB3WL" %}
[Introduction and Use Case](/id-02-documentation/introduction-and-use-case)
{% endcontent-ref %}

{% hint style="info" %}
Custom workflows were implemented in 2019 and is mainly for older versions of Squidex. This guide will teach you how to implement custom workflows with permissions and scripting.

If you are up-to-date with your Squidex version, simply use the newer *Workflows* feature. For more information visit the documentation for *Workflows* linked below:

[Workflows](/id-02-documentation/concepts/workflows)
{% endhint %}

## Our Requirements

In our *FoodCrunch* use case, we publish magazine articles and have a schema for it called `magazine`. In order to manage these articles we have three roles:

1. **Creators**: Write articles and when they are complete, they mark an article as `Ready`.
2. **Reviewers**: Review articles and either mark an article as `Approved` or `Rejected`.
3. **Publisher**: Decide when to publish an article and should only publish approved articles.

Therefore we have the following statuses for content:

1. `Draft`
2. `Ready`
3. `Approved`
4. `Rejected`
5. `Published`

The following diagram visualizes our workflow:

<div align="left"><img src="/files/-LrLSJH1q0Q2bP_eoUud" alt="Workflow"></div>

## Step 1: Modify the Schema

In the first step we will modify the `magazine` schema to add a field called **Status** which displays the status of the article as a string field with a dropdown editor.

To do so, navigate to **Schemas** (1) in the App, select the **schema** i.e. `magazine` (2) and click **+ Add Field** (3).

<div align="left"><figure><img src="/files/38oVwvSPf2QDy4Cgbp7b" alt=""><figcaption><p>Modify the magazine schema</p></figcaption></figure></div>

Select **String** (4) as the field type, enter **Name** (5) as `status` and click **Create and edit field** (6).

<div align="left"><figure><img src="/files/L406Rr8C07PauJmsfkpL" alt=""><figcaption><p>Create status field</p></figcaption></figure></div>

Select **Editing** (7), choose **Dropdown** (8) as the Editor type and add the *statuses* in **Allowed Values** (9). Click **Save and close** (10) to finish editing the schema.

<div align="left"><figure><img src="/files/0B4jhCSBuHREKXYD30FW" alt=""><figcaption><p>Change editor type and add allowed values.</p></figcaption></figure></div>

In the list view, the `magazine` schema will look something like this:

<div align="left"><figure><img src="/files/fERJuA2yFRBKxqXgL2f5" alt=""><figcaption><p>Magazine articles in list view with Status</p></figcaption></figure></div>

Now it's possible to easily see all the articles and their status. However, the reviewer will only be interested in the articles `Ready` to be reviewed. &#x20;

It's possible to search for these articles using the following query: `$filter=data/status/iv eq 'Ready'`

<div align="left"><figure><img src="/files/TF7tklEva9wEyAFKcClh" alt=""><figcaption><p>Query articles with status 'Ready' </p></figcaption></figure></div>

It looks a little technical, but reviewers shouldn't worry about this! The query can be **saved** (1) and given a friendly **name** (2), so it can be reused later on.&#x20;

<div align="left"><figure><img src="/files/IsrKDjPgMMaXhZVQfNX7" alt=""><figcaption><p>Save a query</p></figcaption></figure></div>

To retrieve the query, click **Filters** from the side bar.

<div align="left"><figure><img src="/files/StsaCwLIoQBs2DvsPE6p" alt=""><figcaption><p>View saved queries</p></figcaption></figure></div>

The basic setup is already complete and might work well enough in a small team. It requires a little  discipline and co-operation from all team members. However, if there is critical content such as product texts or there's a large team where it's difficult to rely on other people, it's a good idea to use some rules to ensure that the workflow is utilized correctly. &#x20;

It's also possible to use the new comments feature to share information between reviewer and writer:

<div align="left"><figure><img src="/files/IOZsew0PYhk6XYMvQj5R" alt=""><figcaption><p>Using Comments feature</p></figcaption></figure></div>

## Step 2: Enforcing the Workflow

Start by creating the Creator and Reviewer roles in Squidex:

<div align="left"><figure><img src="/files/Z1aysj0tYwJ0uxpfrEHj" alt=""><figcaption><p>Creator role permissions</p></figcaption></figure></div>

<figure><img src="/files/BGsqTfAHvI7bA7gQrmvu" alt=""><figcaption><p>Reviewer role permissions</p></figcaption></figure>

As you can see in the screenshot above, the **Creator** can only create and update content, but cannot publish it and the **Reviewer** can only update content. Let's use the default role **Editor** for the **Publisher**.

So, the problem of just the Publisher being able to publish and unpublish content is solved, but there is still something left to do.

We have to ensure that:

1. The **Creator** must set the initial status to `Draft`.
2. The **Creator** can only change the status to `Ready`.
3. The **Creator** can only update an article when the status is `Draft` or `Rejected`.
4. The **Reviewer** can only change the status to `Approved` or `Rejected`.
5. The **Publisher** can only set the status to `Published`.
6. The **Publisher** can only publish the content when its status is `Published` (to be consistent).

Not everything is shown in this tutorial, as there is too much information and the solution is the same for all roles, but let's see how to implement this for the **Creator**.

The solution is scripting. If you click the three dots in the schema editor, a menu will pop up with a menu item to the scripting editor. Here you can define scripts that are invoked when a content item is queried, created, updated, deleted or when a status has changed.

<div align="left"><figure><img src="/files/uv16kIOfnNkHJrlHsUP5" alt=""><figcaption><p>Scripting</p></figcaption></figure></div>

### The Create Script

The script for creating content is very simple:

```
// Check the status field is set to draft.
if (ctx.data.status.iv !== 'Draft') {
    // If not reject this operation with a custom validation message.
    reject('The status of a new article must be set to Draft');
}
```

That's it, there's nothing else to do because the permission system already enforces that only **Creators** can create content.

The UI will show an error message from the script if the status of a new article is set to anything other than `Draft`:

<div align="left"><figure><img src="/files/9zxHLp746gtHUG6qNWko" alt=""><figcaption><p>Error Message</p></figcaption></figure></div>

### The Update Script

Lets have a look to the `update` script:

```
// Our code for the Creator
if (ctx.user.claims.role.indexOf('Creator')) {
    // Check the old status of our content.
    if (ctx.oldData.status.iv !== 'Draft' && ctx.oldData.status.iv !== 'Rejected') {
        disallow('You are not allowed to edit content that has been published already.');
    }

    if (ctx.data.status.iv !== 'Draft' && ctx.data.status.iv !== 'Ready') {
        reject('You only set the status to Draft or Ready');
    }
}
```

What does it do:

1. Check if the current user is a **Creator**.
2. If the content has the wrong status, cancel the process with the Disallow function. Then the API responds with an `HTTP 403: Forbidden` and the UI will show an error message.
3. If the content is changed to an invalid status, cancel the update with the 'reject' function. Then the API responds with an `HTTP 400: Bad Request` and the UI will display the error message.

The rest of the requirements can be implemented with some more `if` statements.

## Summary

With the steps above you can implement custom workflows with Squidex, but this solution has several shortcomings:

1. You have a second field for the status of the addition.
2. The user experience could be better.
3. You have to define and write the scripts.

There are some benefits to this solution too, such as:

1. You can implement very finely grained workflows and you are not restricted by a built-in solution.
2. You can even make the *status* field localizable and ensure that all texts are reviewed from a different person before you publish the content.


# Custom Editors

Build and Use Custom Editors to Support Your Content Editors

## How to Write Your Own Editor

Custom editors enable developers to replace the default editors with HTML5 applications so that the editing experience of the Squidex Web App can be customized.

Technically speaking, a UI editor lives in a sandboxed `iframe`,which interacts with the web application through a small SDK using messaging. This SDK is a proxy of the Angular [ControlValueAccessor](https://angular.io/api/forms/ControlValueAccessor), without having the dependencies of Angular itself.

<div align="left"><figure><img src="/files/wegVZD9FctN1DqrlD1yK" alt=""><figcaption><p>Using an custom editor for a field</p></figcaption></figure></div>

Let's take a look at what the code looks like:

```markup
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">

    <!-- Load the editor sdk from the local folder or https://cloud.squidex.io/scripts/editor-sdk.js -->
    <script src="editor-sdk.js"></script>
    <script src="https://cdn.ckeditor.com/ckeditor5/10.0.0/classic/ckeditor.js"></script>

    <style>
        .ck-editor__editable {
            min-height: 250px;
        }
    </style>
</head>

<body>
    <textarea name="content" id="editor"></textarea>

    <script>
        var element = document.getElementById('editor');
        ClassicEditor
            .create(element)
            .catch(error => {
                console.error(error);
            })
            .then(editor => {
                // When the field is instantiated it notified the UI that it has been loaded.
                var field = new SquidexFormField();
                // Handle the value change event and set the text to the editor.
                field.onValueChanged(function (value) {
                    if (value) {
                        editor.setData(value);
                    }
                });
                // Disable the editor when it should be disabled.
                field.onDisabled(function (disabled) {
                    editor.set('isReadOnly', disabled);
                });
                editor.model.document.on('change', function () {
                    var data = editor.getData();
                    // Notify the UI that the value has been changed. Will be used to trigger validation.
                    field.valueChanged(data);
                });
                editor.ui.focusTracker.on('change:isFocused', function (event, name, isFocused) {
                    if (!isFocused) {
                        // Notify the UI that the value has been touched.
                        field.touched();
                    }
                });
            });
    </script>
</body>

</html>
```

You just have to reference the editor SDK and handle the events. You also have to push the current value to the web application whenever it changes. Validation will then happen automatically.

## API

The `SquidexFormField` class is the entry point to your editor.

Create a new instance when your editor is initialized.

### Methods

<table><thead><tr><th width="355">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>editor.getValue()</code></td><td>Gets the current value of the field.</td></tr><tr><td><code>editor.getContext()</code></td><td>Gets the current context information. More about that later.</td></tr><tr><td><code>editor.getFormValue()</code></td><td>Gets the current value of the content form. Can be used to access the values of other fields.</td></tr><tr><td><code>editor.getLanguage()</code></td><td>Get the language of the field editor. If the field is not localized the master language is passed through.</td></tr><tr><td><code>editor.getIndex()</code></td><td>Get the index of the array item when the field is used within an array schema field.</td></tr><tr><td><code>editor.isDisabled()</code></td><td>Get the current disabled state of the field form. Disabled (<code>true</code>) or Enabled (<code>false</code>).</td></tr><tr><td><code>editor.isFullscreen()</code></td><td>Get the current fullscreen state of the field form. Fullscreen on (<code>true</code>) or off (<code>false</code>).</td></tr><tr><td><code>editor.touched()</code></td><td>Notifies the control container that the editor has been touched, must be called when your custom editor looses the focus.</td></tr><tr><td><code>editor.clean()</code></td><td>Cleanup the editor. Usually it is not needed to call this method.</td></tr><tr><td><code>editor.onInit(cb)</code></td><td>Register a function that is invoked when the messaging communication with the management UI is established. After the callback is invoked you get retrieve values with the get methods. The context object will be passed to the callback.</td></tr><tr><td><code>editor.onDisabled(cb)</code></td><td>Register a function that is invoked whenever the editor should either be enabled or disabled. A boolean value will be passed with either <code>true</code> (disabled) or <code>false</code> (enabled).</td></tr><tr><td><code>editor.onMoved(cb)</code></td><td>Registers a function that is invoked whenever the editor has been moved within an array item. A number will passed to the function that represents the index, starting with 0.</td></tr><tr><td><code>editor.onValueChanged(cb)</code></td><td>Register a function that is invoked whenever the value of the field has changed. The value will be passed to the callback as argument.</td></tr><tr><td><code>editor.onFormValueChanged(cb)</code></td><td>Register a function that is invoked whenever the value of the content form has changed. The value will be passed to the callback as argument.</td></tr><tr><td><code>editor.onLanguageChanged(cb)</code></td><td>Register a function that is invoked whenever the language of the field is changed, because in same cases the editor is shared between languages. If the field is not localized the master language is passed through.</td></tr><tr><td><code>editor.onFullscreen(cb)</code></td><td>Register a function that is invoked whenever the fullscreen mode is changed. The function has one argument with the the fullscreen mode.</td></tr><tr><td><code>editor.navigate(url)</code></td><td>Navigates the Management UI to a new URL. Because the plugin is integrated as an iframe, you cannot use normal links as it would only change the URL within the iframe and not the URL of the Management UI.</td></tr><tr><td><code>editor.toggleFullscreen()</code></td><td>Toggle the fullscreen mode and sends the current state to the management UI. The field editor is destroyed and recreated because it has to be moved inside the DOM. Therefore <code>onInit</code> is invoked again.</td></tr><tr><td><code>editor.notifoInfo(text)</code></td><td>Shows an info notification with the given text.</td></tr><tr><td><code>editor.notifoError(text)</code></td><td>Shows an info notification with the given text.</td></tr><tr><td><code>editor.pickAssets(cb)</code></td><td>Shows the dialog to pick assets and invokes the callback with an array of the selected assets when the dialog is closed. If no asset is selected an empty array will be passed to the callback.</td></tr><tr><td><code>editor.confirm(title, text, cb)</code></td><td>Shows a confirm dialog with the given title and text and invokes the callback when the confirm or cancel button has been pressed or when the dialog is closed otherwise. The result will be passed to the dialog as boolean.</td></tr></tbody></table>

### Context

The context object contains application information, such as the username and access token.

Example:

```javascript
{
  "user": {
    "user": {
      "id_token": "TOKEN",
      "session_state": "TOKEN",
      "access_token": "TOKEN", // Access Token
      "token_type": "Bearer",  // Access Token Type
      "scope": "openid profile email squidex-profile role permissions squidex-api",
      "profile": {
        "s_hash": "Wn3eHEjfi65aLx-KioJ53g",
        "sid": "-S7htcpBlnhNKfBXLhl1rg",
        "sub": "5dc32104ebc77a363cca0e0c", // User Id
        "auth_time": 1573240790,
        "idp": "Google",
        "amr": [
          "external"
        ],
        "urn:squidex:name": "USERNAME",
        "urn:squidex:picture": "URL",
        "urn:squidex:permissions": "squidex.admin.*",
        "email": "hello@quidex.io",
        "email_verified": false
      },
      "expires_at": 1573405262
    }
  },
  "apiUrl": "http://localhost:5000/api",
  "appName": "my-app"
}
```

You can use `apiUrl`, `access_token` and `token_type` to retrieve additional information from the API, for example, when you build a special editor to manage references or assets.

## Example editors

Squidex contains a few example editors that can help you to understand the flow, for example:

* <https://cloud.squidex.io/scripts/editor-context.html>: Demonstrates the structure of the context object by displaying the JSON representation in a text field.
* <https://cloud.squidex.io/scripts/editor-combined.html>: Demonstrates how to use the values of the other fields to calculate the value of the current field.
* <https://cloud.squidex.io/scripts/editor-log.html>: Demonstrates the different callbacks by logging them in the browser console.
* <https://cloud.squidex.io/scripts/editor-simple.html>: Demonstrates how to integrate the CKE editor into Squidex.

## All Examples

There are more example that you can use on your Apps:

### 1. Simple CKE Editor

Required Field Type: `string`

Reference: <https://squidex.github.io/squidex-samples/editors/cke-simple.html>

<div align="left"><figure><img src="/files/ov5tOxzIIX8IwxXrnqoN" alt=""><figcaption><p>Example of a field using the simple CKE editor</p></figcaption></figure></div>

Clone the sample and configure the CKE editor as you need it.

### 2. Country Selector

Required Field Type: `string`

Reference: <https://squidex.github.io/squidex-samples/editors/country-selector.html>

Let's assume in the *FoodCrunch* use case, that a field needs to be added with the information about the country the startup is from. This can be achieved using the country selector editor:

<div align="left"><figure><img src="/files/mg64BhKqEwFrBDQgqYgr" alt=""><figcaption><p>Country selector editor</p></figcaption></figure></div>

### 3. Product Taxonomy

Required Field Type: json

Reference: [https://squidex.github.io/squidex-samples/editors/startups-category.html](https://squidex.github.io/squidex-samples/editors/tags-category.html)

The data format is a list of URL like paths for each product category that will be converted to a tree structure. An example for food startups category is listed below:

```javascript
[
  "/food-delivery/b2c/",
  "/food-delivery/b2b/",
  "/food-logistics/b2c/",
  "/food-logistics/b2b/",
  "/food-production/meat/",
  "/food-production/seafood/",
  "/food-production/vegetables/",
  "/food-production/fruits/",
  "/cloud-kitchen/home-delivery/",
  "/cloud-kitchen/restaurant-delivery/",
  "/cloud-kitchen/catering-services/",
  "/food-sustainability/surplus/",
  "/food-sustainability/wastage/"
]
```

Citing the *FoodCrunch* use case again, let's say there's need to use a taxonomy to tag startups based on their operations:

<div align="left"><figure><img src="/files/vJchdg8Qu54DBwUsjrh6" alt=""><figcaption></figcaption></figure></div>

### 4. JSON Tree

Required Field Type: `json`

Reference: <https://squidex.github.io/squidex-samples/editors/jstree-editor.html>

Create a visual tree for a JSON object. For example, one of the food startups wants to show a visual tree of the locations they operate in around the world:

<div align="left"><figure><img src="/files/ugcAjcP8yjshKO06Aadi" alt=""><figcaption><p>JSON Tree</p></figcaption></figure></div>

### 5. JSON Schema Editor

Required Field Type: `string`

Reference: [https://squidex.github.io/squidex-samples/editors/react-jsonschema.html](https://squidex.github.io/squidex-samples/editors/react-jsonschema.html#https://gist.githubusercontent.com/SebastianStehle/4bb9ef57258dd95e98a19d738fdc9c2b/raw/52112bc67905f27b6f6d7754bf2e40dc40646087/schema.json)

This editor downloads the JSON schema from the URL that is defined via the URL fragment (everything after #):

<https://squidex.github.io/squidex-samples/editors/react-jsonschema.html#https://gist.githubusercontent.com/SebastianStehle/4bb9ef57258dd95e98a19d738fdc9c2b/raw/52112bc67905f27b6f6d7754bf2e40dc40646087/schema.json>

<div align="left"><figure><img src="/files/LEmiqBwC7VtmPtgakK3d" alt=""><figcaption><p>JSON Schema from external URL</p></figcaption></figure></div>

<div align="left"><figure><img src="/files/KhNQxm5z0OQGv6wM0cPM" alt=""><figcaption><p>JSON schema used in the example above</p></figcaption></figure></div>


# Custom Sidebars

Build and Use Custom Sidebar Extensions to Support your Editorial Workflow

## How to Write Your Own Sidebar Plugin

Custom sidebar plugins enable developers to add custom UI extensions to the sidebar of a single content item, or to a list of content items.

Below is an example of a custom sidebar extension for a search:

<figure><img src="/files/pJJgy3aL7nT1qr71bIDN" alt=""><figcaption><p>Custom Sidebar Extension</p></figcaption></figure>

Technically speaking, a UI editor lives in a sandboxed `iframe`, which interacts with the web application through a small SDK using messaging. The UI editor cannot directly interact with other elements of the Management UI.

### Configure the URL to the sidebar plugin

You can configure the sidebar plugin for each schema individually. Each schema has two URLs, the first URL named **Contents Sidebar Extension** (notice Contents as plural) points to the sidebar plugin for the content list. The second URL,  named Content Sidebar Extension (notice Content as singular) points to the sidebar plugin for single content items. You can only configure one of them or both. You can point both settings to the same URL.

<figure><img src="/files/Tr48Ug3xP4XlE2zogqyF" alt=""><figcaption><p>Configuring sidebar plugin</p></figcaption></figure>

### Write a sidebar plugin

The following code snippet shows a very simple sidebar plugin. It renders two *TextArea* elements and displays the current content item and the context as JSON strings.

You create a very simple HTML page and then you reference the editor SDK. In your custom script element, you create a new instance of the `SquidexPlugin` class. This instance is responsible for communicating via the Management UI, through messaging:

1. First it sends a `ready` message to the Management UI to state that it is available now and ready for communication. It also sends the height of plugin to the Management UI.
2. When the Management UI receives the `ready` message, it sends an `init` message back with the context object which contains information such as the name of the API and the user profile. You should subscribe to the `init` message and then initialize your sidebar plugin.
3. When the sidebar plugin is used for a single content item, the full content item is also sent to the plugin and resent whenever it is changed. You can get the current content item by subscribing using the `onContentChanged` method. Intermediate updates are not sent to the editor, they are only sent after you have saved your changes.
4. The plugin also has a timer running in the background that periodically measures the height of the plugin. The problem is that when you use `iframes`, you have to give them a fixed height. You cannot make them resize them, automatically. Therefore the plugin has to notify the Management UI whenever the size changes.

```markup
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">

    <script src="https://cloud.squidex.io/scripts/editor-sdk.js"></script>
</head>

<body>
    <textarea name="content" id="content"></textarea>
    <textarea name="context" id="context"></textarea>

    <script>
        var contentElement = document.getElementById('content');
        var contextElement = document.getElementById('context');

        // When the field is instantiated it notifies the UI that 
        // it has been loaded.
        var plugin = new SquidexPlugin();

        // Init is called once with a context that contains 
        // the app name, schema name and authentication information.
        plugin.onInit(function (context) {
            contextElement.innerHTML = JSON.stringify(context, null, 2);
        });

        // The content is only available when it is used as a sidebar plugin for single content items.
        plugin.onContentChanged(function (content) {
            contentElement.innerHTML = JSON.stringify(content, null, 2);
        });
    </script>
</body>

</html>
```

## API

The `SquidexPlugin` class is the entry point to your custom sidebar.

Create a new instance when your plugin is initialized.

### Methods

| Name                                | Description                                                                                                                                                                                                                                |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `plugin.getContext()`               | Gets the current context information. More about that later.                                                                                                                                                                               |
| `plugin.clean()`                    | Cleanup the plugin. Usually it is not needed to call this method.                                                                                                                                                                          |
| `plugin.onInit(callback)`           | Register a function that is invoked when the messaging communication with the management UI is established. After the callback is invoked you get retrieve values with the get methods. The context object will be passed to the callback. |
| `plugin.onContentChanged(callback)` | Register a function that is invoked whenever the the current content item is changed. This callback is never triggered when your sidebar plugin is used for single content items.                                                          |
| `plugin.navigate(url)`              | Navigates the Management UI to a new URL. Because the plugin is integrated as an iframe, you cannot use normal links as it would only change the URL within the iframe and not the URL of the Management UI.                               |

### Context

The context object contains application information, such as the username and access token.

Example:

```javascript
{
  "user": {
    "user": {
      "id_token": "TOKEN",
      "session_state": "TOKEN",
      "access_token": "TOKEN", // Access Token
      "token_type": "Bearer",  // Access Token Type
      "scope": "openid profile email squidex-profile role permissions squidex-api",
      "profile": {
        "s_hash": "Wn3eHEjfi65aLx-KioJ53g",
        "sid": "-S7htcpBlnhNKfBXLhl1rg",
        "sub": "5dc32104ebc77a363cca0e0c", // User Id
        "auth_time": 1573240790,
        "idp": "Google",
        "amr": [
          "external"
        ],
        "urn:squidex:name": "USERNAME",
        "urn:squidex:picture": "URL",
        "urn:squidex:permissions": "squidex.admin.*",
        "email": "hello@quidex.io",
        "email_verified": false
      },
      "expires_at": 1573405262
    }
  },
  "apiUrl": "http://localhost:5000/api",
  "schemaName:" "my-schema",
  "schemaId": "213...",
  "appName": "my-app"
}
```

You can use `apiUrl`, `access_token` and `token_type` to retrieve additional information from the API, for example when you build a special editor to manage references or assets.

## Example Plugins

Squidex contains a few sample plugins that can help you to understand the flow, for example:

* [https://cloud.squidex.io/scripts/sidebar-context.html](https://cloud.squidex.io/scripts/editor-context.html): This demonstrates the structure of the context object by displaying the JSON representation in a text field.

## More Examples

Here are some more examples that you can use in your Apps.

### 1. Algolia Search

Reference: <https://cloud.squidex.io/scripts/sidebar-search.html>

This example demonstrates how to build a custom search for your content using Algolia.


# Preview Content

Provide a Live Preview on Your Website for Your Content Editors.

By default, Squidex only delivers content that has been published but it is beneficial to review the content in your production or staging environment before actually publishing it. This guide shows how you can use Preview URLs to do this.

## The Problem Statement

A traditional content management systems typically has a WYSIWYG (what you see is what you get) editor or other editing functionality where the editor can edit the content directly and understands how and where the content is used.&#x20;

This is a challenge for a headless CMS, because of the following reasons:

1. There might be more than one place where a content item is used. For example, multiple websites or multiple applications (e.g. mobile Apps).
2. The frontend is not controlled or created by the headless CMS. Therefore, it cannot provide direct WYSIWG functionality and inline editing in general is tricky.
3. Sometimes a content item has to go through multiple steps until can be published to a website. For example, when you have a complex workflow with several review steps or when the publishing process takes very long due to technical challenges.

Squidex cannot solve all these problems but with a little bit help from the frontend developers it can make life easier for content editors.

### Solutions

We have several solutions that work together to solve the challenges. They are are best described through use cases.

1. **Use Case 1: Display Unpublished Content Items**  \
   As a content editor you may wish to preview the content item even if it is not published or reviewed yet, perhaps to check if its appearance looks correct and that it's well-formatted in the production environment.
2. **Use Case 2: Link to the Usage of a Content Item**\
   As a content editor you might want to see all locations where a content item is used when you edit the content item in the Squidex Management UI.
3. **Use Case 3: Link to the Source of a Content Item**\
   As a content editor you might want to see which parts of the website come from Squidex and how you can edit them in the Squidex Management UI.

## User Case 1: Display Unpublished Content Items

By default, Squidex only provides published content items over the API, because the purpose is to only deliver finished content items that have followed a defined workflow.

This means that a content editor cannot preview content items while they them. Even if the content editor knows where a content item would be used, they cannot see it, because the application only receives published content items.

To solve this challenge, Squidex provides a special header:

Add the `X-Unpublished = 1` header to all requests to retrieve unpublished content items.

{% hint style="info" %}
You will receive all content items, even if they are in a state that is not required. For example, when they are in an archive state or a custom state that indicates that a content item should not be used anymore. Therefore, you might want to add a filter to your query to filter out these types of content items.
{% endhint %}

Of course, no one wants to display unpublished content items to the end users. Therefore, it makes sense to use the header conditionally. The condition could depend on the user, environment or just a secret setting.

* You can show unpublished content items in a development or staging version of your website where you test new features.
* Using a login system and distinguishing end users from content editors, you can then show unpublished content items meant only for content editors.
* When you neither have a login system nor a staging environment, you could just introduce a secret setting, for example a query string parameter to enable unpublished content items.

For the Squidex website there's a "Top Secret" query parameter that's just been added to the blog page. When this query parameter is set to true, it will add this header to the requests.

The following example shows how you can implement this with the C# client library:

```csharp
public BlogPost Post { get; set; }

public async Task<IActionResult> OnGet(string slug, bool secretQueryParameter = false)
{
    var postsClient = clientManager.GetClient<BlogPost, BlogPostData>("blog");

    var context = QueryContext.Default.Unpublished(secretQueryParameter);

    var posts = await postsClient.GetAsync(filter: $"data/slug/iv eq '{slug}'", context);
    var post = pages.Items.FirstOrDefault();

    if (post == null)
    {
        return NotFound();
    }

    Post = post;

    return Page();
}
```

## Use Case 2: Link to the Usage of a Content Item

For each content item you can define preview URLs. These are just normal links that point to the place where a content item is used. If you have multiple environments or frontends, you can also define multiple preview URLs.

### Define your Preview URLs

You can define preview URL per schema in the following menu item:

1. Go to **Schemas** (1) and select your **schema** (2).
2. Select the **More** tab (3).
3. Under **Preview URLs** section (4) click **+** (5) to add Preview URLs.

<figure><img src="/files/e9aFCJ6mN48hBcoW6bdE" alt=""><figcaption><p>Navigating to Preview URLs</p></figcaption></figure>

The following example shows 2 preview URLs:

1. The URL to the normal website.
2. The URL to an dedicated mobile website (just an example).

![Preview URLs](/files/8wgCDMokwACrweRC8Ybm)

As you can see, a placeholder can be used with the JavaScript interpolation syntax, e.g.

1. `${id}` of the content.
2. `${data.slug}`: slug field (if not localized).
3. `${data.slug.iv}`: slug field, alternative syntax.
4. `${data.title.en}`: title in English
5. `${data.title.en-US}`: title in American English
6. `${version}`: version of the content item.

If you use a query string to enable unpublished content items, you should also add it to the URL.

### Use the Preview Button

If you open a saved content item, you will see the buttons with the preview URLs:

<figure><img src="/files/Vxih4CJWAMgVkurg8aDc" alt=""><figcaption><p>Accessing the Preview button</p></figcaption></figure>

## **Use Case 3: Link to the Source of a Content Item**

The following screenshot from the Squidex website describes this feature best. The idea is to annotate content items on your website so that you receive links to the Squidex Management UI, where you can edit them.

![How content items are linked in the Squidex website](/files/b38wIl1FxfHlFfwO5FWT)

As mentioned above, Squidex has no control over your website, therefore, you have to make a few changes to implement this feature.

### Add the Embed SDK script

This script is a little helper that provides this functionality. You have to add a `script` tag that points to <https://cloud.squidex.io/scripts/embed-sdk.js>. If you have installed Squidex on your own servers, in general, the URL: `https://YOUR_DOMAIN/scripts/embed-sdk.js`.

Just add the script tag at the end of your `body` tag. This ensures that more important assets are loaded first and your website is as fast as possible.

The script is very small and only has **8.5kB** at the moment, so it is not a problem to include it for all users, but if you want to optimize it further you can implement one of the mechanisms described under "Use Case 1".

```html
<!DOCTYPE html>
<html>

<head>
    ...
</head>

<body>
    ...
    <script src="https://cloud.squidex.io/scripts/embed-sdk.js"></script>
</body>

</html>
```

The script works as follows:

1. When the user moves the mouse cursor over an element, the scripts tests whether this element or a parent element is annotated (more about this later).&#x20;
2. The annotation also contains information about the URL of the Squidex installation. This URL is used to test whether the current user is authenticated in the Squidex Management and to maintain a list of known Squidex URLs.
3. If the user is authenticated and the element is annotated, the scripts render the blue border with links to the Squidex Management UI (as long as the cursor is not moved away from the element).
4. When the user moves the mouse cursor over an image, the scripts check whether the source of the image matches to one of the Squidex URLs and if the user is authenticated. If both conditions are met, a blue border is rendered around the image (as long as the cursor is not moved away from the image).

{% hint style="info" %}
To check whether a user is authenticated, it is necessary to make a few changes to how cookies are used. Therefore, this might not work  if you are already logged into the Squidex Management UI. Try to logout and login again to get the correct cookie.
{% endhint %}

### How to Annotate Elements

When you query content items from the Squidex API, you also receive an edit token for each item.

![Edit Token from the Squidex API](/files/2Ht8rMphCuyjEdkbOh3q)

Just annotate your elements with the edit token, for example, you can display a list of blog posts like this:&#x20;

{% hint style="info" %}
The actual template syntax might look different.
{% endhint %}

```html
<div class="posts">
   @foreach (var post in Posts)
   {
      <div class="post" squidex-token={@post.EditToken}>
          <h3>{post.data.title.iv}</h3>
          
          <p>{post.data.content.iv}</p>
      </div>
   }
</div>
```

### The Structure of an Edit Token

The edit token is just a base64-encoded JSON object.

The property names are shortened to keep the edit token as small as possible. If necessary, you can also create the token manually.

#### Contents

```json
// eyJhIjoic3F1aWRleC13ZWJzaXRlIiwicyI6ImJsb2ciLCJpIjoiZWZmNGU3N2UtZDM3Zi00Zjk3LTk4YzUtZjAxYjRmYzRjNzkwIiwidSI6Imh0dHBzOi8vY2xvdWQuc3F1aWRleC5pby8ifQ==
{
   "a":"squidex-website",                      // App Name
   "s":"blog",                                 // Content Schema
   "i":"eff4e77e-d37f-4f97-98c5-f01b4fc4c790", // Content ID
   "u":"https://cloud.squidex.io/"             // Squidex Base URL
}
```

#### Assets

```json
// eyJhIjoic3F1aWRleC13ZWJzaXRlIiwiaSI6ImFjNWVlOTE5LWE1NDQtNDVjNC1iZmQ2LWY4YTk2MDcwMWJiNCIsInUiOiJodHRwczovL2Nsb3VkLnNxdWlkZXguaW8vIn0=
{
   "a":"squidex-website",                      // App Name
   "i":"ac5ee919-a544-45c4-bfd6-f8a960701bb4", // Asset ID
   "u":"https://cloud.squidex.io/"             // Squidex Base URL
}
```

{% hint style="info" %}
The script can detect assets automatically if the asset URL is coming from a known URL. This is useful if the image tag is rendered because you use an HTML or Markdown field and you have no direct control over how images are rendered on your website.&#x20;
{% endhint %}


# Rule Formatting

How to Use Scripting or Placeholders to Control the Output of Rules

This Page Explains the Rule Formatting System in Detail.

To gain an understanding of the rule system, read the following page:

{% content-ref url="/pages/-LrLSGJtMR37-NG2Rdd4" %}
[Rules](/id-02-documentation/concepts/rules)
{% endcontent-ref %}

## Introduction

Rule formatting is used to format your custom payloads in rule actions, for example, when you use email actions and you want to use certain values from the event that's inside the email subject or body.

To gain an understanding of what can be done with the rule system let's have a look at an event.

The example below is a full content event that has been triggered following creation of a new item:

```javascript
{
    "id": "123...", // Id of the content.
    "actor": { "type": "subject", "id": "123..." }, // Id of the user
    "appId": { "name": "my-app", "id": "123..." }, // App name and id
    "created": "2018-01-01T12:00:00Z",
    "createdBy":  { "type": "subject", "id": "123..." },
    "data": { // Content data
        "city": {
            "en": "Munich",
            "de": "München"
        },
        "population": {
            "iv": 123000
        }
    },
    "lastModified": "2018-01-01T12:00:00Z",
    "lastModifiedBy": { "type": "subject", "id": "123..." },
    "schemaId": { "name": "my-schema", "id": "123..." }, // Schema id
    "status": "Draft", // Status of the content: Draft, Archived, Published
    "timestamp": "2018-01-01T12:00:00Z",
    "type": "Created", // The type of the event.
    "user": { // The user information.
        "id": "123...",
        "name": "John Doe",
        "email": "john@email.com"
    },
    "version": 1 // Version of the content, increased with any operation
}
```

## Formatting Options

The rule formatter grants access to all of this event's properties.

At the moment, there are three options for formatting:

{% content-ref url="/pages/-MgPECy-JXkgObjJ9qds" %}
[Simple](/id-02-documentation/developer-guides/rule-formatting/simple)
{% endcontent-ref %}

{% content-ref url="/pages/-MgPEJnlPhM8TBWx0CPV" %}
[Script](/id-02-documentation/developer-guides/rule-formatting/script)
{% endcontent-ref %}

{% content-ref url="/pages/-MgPEMkeCvqaRFvg1NzX" %}
[Liquid](/id-02-documentation/developer-guides/rule-formatting/liquid)
{% endcontent-ref %}

The syntax is selected based on the value of the rule property.

* If a value follows the format `Liquid(<Template>)`, then `Template` is interpreted as a liquid syntax. It is interpreted using a template engine.
* If a value follows the format `Script(<Script>)`, then `Script` is interpreted as Javascript expression and interpreted using a scripting engine.
* If none of the syntaxes above are detected, the value is interpreted as simple formatting.  Therefore, it is interpreted using a built-in solution.

In newer versions of Squidex, the user interface has been improved and custom input fields have been introduced which allows you to select the syntax and automatically add the necessary prefix.

<div align="left"><img src="/files/-MgPFpnUwAJlUIrVZ-14" alt="Custom Editors for rules"></div>

{% hint style="info" %}
The liquid syntax is the most powerful and easiest option, which is why it is recommended for most user cases.
{% endhint %}


# Simple

How to Format Rules with a Custom-Built Solution for Basic Use Cases

When we started to introduce placeholders we defined limited sets of placeholders, but the system was just too inflexible for users. To make it more flexible, a new system was introduced, with 4.2.0.  This gives access to all properties.&#x20;

The syntax is very easy:

```javascript
${<TYPE>_<PROPERTYAME>} or 
${<TYPE>_<PROPERTYAME> | filters} or 
$<TYPE>_<PROPERTYNAME>
```

The `<TYPE>` is a prefix that has been used for backwards compatibility and users may use whatever they want but the best recommendation is to use prefixes that fit the type of event, for example, *content* or *asset*. The `<PROPERTY>` is the full path to the property in the event, for example:

* `$CONTENT_TYPE`: *Created*
* `$CONTENT_SCHEMAID.NAME`: *my-schema*
* `$CONTENT_DATA.CITY.en`: *Munich*

{% hint style="info" %}
If possible, always use the first variant with the brackets ( { and } ).
{% endhint %}

## Special Placeholders

Furthermore, there are still some special placeholders for backwards compatibility or to grant access to information that is not directly part of the event.

| Event                 | Description                                                                                                                                      | New Alternative       |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- |
| `$APP_ID`             | The id of your app (GUID).                                                                                                                       | `${EVENT_APPID.ID}`   |
| `$APP_NAME`           | The name of your app.                                                                                                                            | `${EVENT_APPID.NAME}` |
| `$USER_ID`            | The id of the user (or client).                                                                                                                  | `${EVENT_USER.ID}`    |
| `$USER_NAME`          | The display name of the user (or client name).                                                                                                   | `${EVENT_USER.NAME}`  |
| `$USER_EMAIL`         | The email address of the user (or client name).                                                                                                  | `${EVENT_USER.EMAIL`} |
| `$TIMESTAMP_DATE`     | The date when the event has happened (usually different from the time when the rule is executed) in the following format: `yyyy-MM-dd`.          |                       |
| `$TIMESTAMP_DATETIME` | The date when the event has happened (usually different from the time when the rule is executed) in the following format: `yyyy-MM-dd-hh-mm-ss`. |                       |

| Event             | Description                                        | New Alternative        |
| ----------------- | -------------------------------------------------- | ---------------------- |
| `$SCHEMA_ID`      | The name of the schema.                            | `${EVENT_SCHEMAID.ID}` |
| `$SCHEMA_NAME`    | The name of the schema.                            | `${EVENT_SCHEMAID.ID}` |
| `$CONTENT_URL`    | The URL to the content in the administration tool. |                        |
| `$CONTENT_ACTION` | The content action, e.g. *CityContentCreated*.     | `${EVENT_TYPE}`        |

#### Special Placeholders for Comment Events

<table><thead><tr><th>Event</th><th width="247.33333333333331">Description</th><th>New Alternative</th></tr></thead><tbody><tr><td><code>$MENTIONED_ID</code></td><td>The id of the mentioned user.</td><td><code>${EVENT_MENTIONEDUSER.ID}</code></td></tr><tr><td><code>$MENTIONED_NAME</code></td><td>The display name of the mentioned user.</td><td><code>${EVENT_MENTIONEDUSER.NAME}</code></td></tr><tr><td><code>$MENTIONED_EMAIL</code></td><td>The email address of the mentioned user.</td><td><code>${EVENT_MENTIONEDUSER.EMAIL}</code>S</td></tr></tbody></table>

#### Special Placeholders for Asset Events

| Event        | Description                    |
| ------------ | ------------------------------ |
| `$ASSET_URL` | The download URL of the asset. |

## Filters

This simple syntax also supports a few function filters when using the new syntax:

```
${<TYPE>_<PROPERTYAME> | filters} 
```

The following table uses:

<table><thead><tr><th width="189">Name</th><th width="277">Description</th><th>Example</th></tr></thead><tbody><tr><td><code>lower</code></td><td>Converts the input to lowercase.</td><td><p><code>Hello World</code> </p><p>=> <code>hello world</code></p></td></tr><tr><td><code>upper</code></td><td>Converts the output to uppercase.</td><td><p><code>Hello World</code> </p><p>=> <code>HELLO WORLD</code></p></td></tr><tr><td><code>escape</code></td><td>Escapes the input to a JSON string.</td><td><code>Hello "World"</code><br>=> <code>Hello \"World\"</code></td></tr><tr><td><code>slugify</code></td><td>Converts the input to a slow, which can only contain a-z0-9 and <code>-</code>.</td><td><p><code>Hello World</code> </p><p>=> <code>hello-world</code></p></td></tr><tr><td><code>trim</code></td><td>Removes whitespaces at the start and end of the input.</td><td> <code>Hello World</code> <br>=> <code>Hello World</code></td></tr><tr><td><code>timestamp</code></td><td>Parses the input as ISO8601 datetime string and converts it to a unix timestamp using milliseconds.</td><td><code>2023-01-10T10:41:32</code><br>=> <code>1673347292000</code></td></tr><tr><td><code>timestamp_sec</code></td><td>Parses the input as ISO8601 datetime string and converts it to a unix timestamp using seconds.</td><td><code>2023-01-10T10:41:32</code><br>=> <code>1673347292</code></td></tr></tbody></table>

Filters can also be combined:

```
${CONTENT_DATA.CITY.en | uppercase | escape} 
```


# Script

How to Format Rules with JavaScript Expressions

JavaScript expressions can be used with the following syntax:

```
Script(<YOUR_SCRIPT>)
```

{% hint style="info" %}
In newer versions of Squidex, the user interface has been improved and custom input fields have been introduced which allows selection of the syntax and adds the necessary prefix automatically.
{% endhint %}

## Basic Syntax

The scripting engine supports almost all ES6 features with a [few restrictions](https://github.com/sebastienros/jint#ecmascript-2015-es6).

Therefore, it is best to use the [Javascript template string](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/template_strings) syntax and just reference properties directly:

```javascript
Script(`${event.appId.id}`)
Script(`${event.appId.Name}`)
Script(`${event.user.id}`)
Script(`${event.user.email}`)

// For content events
Script(`${event.schemaId.id}`)
Script(`${event.schemaId.Name}`)
Script(`${contentUrl()}`)
Script(`${contentAction()}`)
Script(`${event.data.city.de}`)
```

## Special Functions

Squidex provides a set of general helper functions for scripting and rule formatting.

A value list can be found in the documentation concerning scripting helper methods here:

{% content-ref url="/pages/-MgPIF\_xXTjGDwutI\_x7" %}
[Scripting Helper Methods](/id-02-documentation/developer-guides/scripting/scripting-helper-methods)
{% endcontent-ref %}

Additionally, there are also methods which are only available for rule formatting.

<table><thead><tr><th width="252">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>contentAction()</code></td><td><p>The status of the content, when the event is a content event.</p><p>Otherwise <code>null</code>.</p></td></tr><tr><td><code>contentUrl()</code></td><td>The URL to the content in the Management UI, when the event is a content event. Otherwise <code>null</code>.</td></tr><tr><td><code>assetContentUrl</code></td><td>The URL to download the asset, when the event is an asset event.<br>Otherwise <code>null</code>.<br>This URL does not include the app name and is therefore not recommended.</td></tr><tr><td><code>assetContentAppUrl</code></td><td><p>The URL to download the asset by ID, when the event is an asset event.</p><p>Otherwise <code>null</code>.</p></td></tr><tr><td><code>assetContentSlugUrl</code></td><td><p>The URL to download the asset by slug, when the event is an asset event.</p><p>Otherwise <code>null</code>.</p></td></tr><tr><td><code>complete(value)</code></td><td>If you use an asynchronous operation, just like <code>getAssets</code> you have to tell the script engine, which value should be returned. Therefore you have call <code>complete(value</code>) with the result value. If you do not call this method, the result of the last statement is used.</td></tr></tbody></table>

## Examples

### Resolve References

You can use scripting to resolve references. You must pass over an array of content IDs and a callback (that is invoked) with the resulting list of content items.

```javascript
Script(
    getReferences(data.references.iv, function (references) {
        var actual1 = `Text: ${references[0].data.field1.iv} ${references[0].data.field2.iv}`;
        var actual2 = `Text: ${references[1].data.field1.iv} ${references[1].data.field2.iv}`;

        complete(`${actual1}\n${actual2}`);
    });
)
```

Or a single reference:

```javascript
Script(
    getReference(data.references.iv[0], function (references) {
        var actual1 = `Text: ${references[0].data.field1.iv} ${references[0].data.field2.iv}`;

        complete(`${actual1}`);
    })
)
```

### Resolve References

You can use scripting to resolve assets. You have to pass over an array of asset IDs and a callback (that is invoked) with the resulting list of assets.

```javascript
Script(
    getAssets(data.assets.iv, function (assets) {
        var actual1 = `Text: ${assets[0].fileName} ${assets[0].id}`;
        var actual2 = `Text: ${assets[1].fileName} ${assets[1].id}`;

        complete(`${actual1}\n${actual2}`);
    });
)
```

Or a single asset:

<pre class="language-javascript"><code class="lang-javascript">Script(
<strong>    getAsset(data.assets.iv[0], function (assets) {
</strong><strong>        var actual1 = `Text: ${assets[0].fileName} ${assets[0].id}`;
</strong>        
        complete(`${actual1}`);
    });
)
</code></pre>

### Conditional Formatting

You can use if-statements and other JavaScript language features for conditional formatting.

In the following example, different payloads have been created, depending on the asset size:

```javascript
Script(
    if (event.fileSize > 100000) {
        return `I just uploaded a large image ${event.fileName}`;
    } else {
        return `I just uploaded a small image ${event.fileName}`;
    }
)
```


# Liquid

How to Format Rules with Liquid Templates.

You can use liquid templates using the following syntax:

```
Liquid(<YOUR_SCRIPT>)
```

{% hint style="info" %}
In newer versions of Squidex, the user interface has been improved and custom input fields have been introduced which allow for selection of the syntax and adds the necessary prefix automatically.
{% endhint %}

## Basic Syntax

The liquid syntax is documented by Shopify at <https://shopify.github.io/liquid/>.

## Special Extensions

Squidex provides special extensions.

### Tags

#### reference

Resolves a content by ID and saves the content as a variable.

```
// Input


{% for id in event.data.references.iv %}
   {% reference 'ref', id %}
   Text: {{ ref.data.field1.iv }} {{ ref.data.field2.iv }} {{ ref.id }}
{% endfor %}



// Output
Text: Content1_Field1 Content1_Field2 Content1_ID
Text: Content2_Field1 Content2_Field2 Content1_ID
```

#### asset

Resolves an asset by ID and saves the asset as a variable.

```
// Input


{% for id in event.data.assets.iv %}
   {% asset 'ref', id %}
   Text: {{ ref.fileName }} {{ ref.id }}
{% endfor %}

// Output
Text: Asset1_FileName Asset1_ID
Text: Asset2_FileName Asset2_ID
```

### Filters

#### format\_date

Formats a date using a specified pattern.

```
{{event.timestamp | format_date: 'yyyy-MM-dd-hh-mm-ss'}}
```

#### timestamp

Returns the number of milliseconds between 1970/1/1 and a given date.

```
{{event.timestamp | timestamp}}
```

#### timestamp\_sec

Returns the number of seconds between 1970/1/1 and a given date.

```
{{event.timestamp_sec | timestamp}}
```

#### escape

Escapes a value to be a valid JSON string.

```
{{event.user.name | escape}}
```

#### html2text

Converts a HTML string to plain text.

```
{{event.data.body.iv | html2text}}
```

#### markdown2text

Converts a Markdown string to plain text.

```
{{event.data.body.iv | markdown2text}}
```

#### md5

Calculates the MD5 hash from a given string. Use this method for hashing passwords, when backwards compatibility is important

```
{{event.data.password.iv | md5}}
```

#### sha256

Calculates the SHA256 hash from a given string. Use this method for hashing passwords.

```
{{event.data.password.iv | sha256}}
```

#### slugify

Calculates the slug of a text by removing all special characters and white spaces to create a friendly term that can be used for SEO-friendly URLs.

```
{{event.data.title.iv | slugify}}
```

#### trim

The same as [strip](https://shopify.github.io/liquid/filters/strip/). Removes all white space (tabs, spaces, and newlines) from both the left and right sides of a string. This does not affect spaces between words.

## How to handle JSON

The template engine is not aware what kind of content you create. Therefore it cannot be optimized for JSON or other formats. If you inject strings into a JSON object or array you have to ensure that the value a valid JSON string.

The solution is to use the `escape`filter for string fields:

```liquid
{
    "title": "{{event.data.title.en-US | escape}}",
    "text": "{{event.data.text.en-US | escape}}",
    "date": "{{event.data.date.iv}}",
    "price": {{event.data.price}}"
}
```

You can omit the filter for fields that follow a specific format, as for the `date` field above.


# Tutorials

Read our Useful Tutorials


# Building a Blog with Squidex and Next.js

How to Build a Next.js Blog Powered by Content From the Squidex CMS

### Introduction

This tutorial guides on creating a blog application, setting up its schema and adding mock content on Squidex. Then, the guide demonstrates how to create a Next.js blog based on the data stored in a CMS.

## Prerequisites

Here's what's needed to complete this guide:

* A Squidex account. Create an account here: <https://cloud.squidex.io>.
* Node.js installed on a computer. Follow this guide to install [Node.js](https://phoenixnap.com/kb/install-node-js-npm-on-windows) if it is not yet installed.

## Step 1: Creating the App on Squidex

In Squidex, an application is an isolated data store with schemas that defines how its content is structured along with API end points to query its content. Visit [this page](https://docs.squidex.io/02-documentation/concepts/apps) of the Squidex docs to learn more about Apps in Squidex.

Start by logging into the Squidex account dashboard at [https://cloud.squidex.io/](https://cloud.squidex.io). Click **New App** (1) to create an empty application. Enter a unique **Name** (2) for the App in the space provided and click **Create** (3) to create the App. For the purposes of this guide, the Squidex application name is *squidex-blog*.

{% hint style="info" %}
App names in Squidex cloud are global, which is why the name must be unique. Add name initials or a word to make it unique.
{% endhint %}

<figure><img src="/files/gEfDRgXNI2gRdXeKHFsT" alt=""><figcaption><p>Create an empty app</p></figcaption></figure>

## Step 2: Creating the Posts schema on the CMS

Now that the App is created on Squidex, let's start creating the schema for the blog application. This schema will define the structure of the blog content.

On the Squidex dashboard, click *squidex-blog* (or whichever name you've used) to enter the dashboard for the newly created App.

<figure><img src="/files/fmF5sk42VfAMQzy6EaE9" alt=""><figcaption><p>Select the new app</p></figcaption></figure>

On the left panel, select **Schemas** (5) and click the **Plus(+)** (6) button to start creating the `posts` schema. Enter `posts` in the space provided under **Name** (7), select **Multiple contents** (8) as there will be multiple posts and click **Create** (9) to create the schema.

<figure><img src="/files/ecc4TmL78epyjNlSKH3A" alt=""><figcaption><p>Create the 'posts' schema</p></figcaption></figure>

The posts schema will have 3 fields, i.e. `Title` for the title of posts, `Slug` to define the URL of posts, and `Content` to contain the body of the blog posts.

| Field name | Type   | Editor   |
| ---------- | ------ | -------- |
| Title      | String | Input    |
| Slug       | String | Slug     |
| Content    | String | Markdown |

Ensure you are in **Posts** (11) schema under **Schemas** (10) and click **+Add Field** (12) to start adding fields.

<div align="left"><figure><img src="/files/am0JFfxuojp4pz1msyBI" alt=""><figcaption><p>Adding fields</p></figcaption></figure></div>

In the modal window, select **String** (13) enter `Title` (14) in the field provided and click **Create and edit field** (15) as shown below:

<div align="left"><figure><img src="/files/aAuPXQPRJYjmnoVXUOL5" alt=""><figcaption><p>Create the Title field</p></figcaption></figure></div>

Click the **Validation** (16) tab on the new modal window that opens up, then check **Required** (17). This is needed as every post must have a title, which is why it is a required / mandatory field. Click **Save and add field** (18) to save changes.

<div align="left"><figure><img src="/files/Hzbs0Z4uYlXGTqareKGD" alt=""><figcaption><p>Configure validation properties of title field</p></figcaption></figure></div>

Repeat the steps above to create the remaining two fields i.e, `Slug` and `Content`.

The `Slug` field is configured as shown in the screenshots below:

<div align="left"><figure><img src="/files/6o5rtKuYm2fDH9XPo6Jb" alt=""><figcaption><p>Create slug field</p></figcaption></figure></div>

As every post must have a slug (and it has to be unique), complete the *Validation* tab in the modal that opens as displayed below:

<figure><img src="/files/0oMxYjKQfvLFxzr2qP3p" alt=""><figcaption><p>Configure validation properties of slug field</p></figcaption></figure>

Finally, switch to the **Editing** tab and select **Slug** as the editor (as displayed below). The options in this *Editor* section allow the choosing of a preferred editor to be used to edit a particular field. Click **Save and add field** to save the `Slug` field and proceed to add the `Content` field.

<div align="left"><figure><img src="/files/zEIcVPQVCzt8dDw1qSH2" alt=""><figcaption><p>Configure editing properties of slug field</p></figcaption></figure></div>

The third field, i.e the `Content` field is formatted as [Markdown](https://daringfireball.net/projects/markdown/) text. Configure the `Content` field as displayed below. Click **Create and edit field** to continue:

<div align="left"><figure><img src="/files/hQRJRwrG1UlsaDHfLfnC" alt=""><figcaption><p>Create Content field</p></figcaption></figure></div>

Set the `Content` field to use a WYSIWYG editor by configuring the Editor to **Markdown** in the **Editing** tab as displayed below. Click **Save and close** to save the changes to the `Content` field.

<div align="left"><figure><img src="/files/hRXL0pvJI1wLCjrTmUUc" alt=""><figcaption><p>Configure editing properties of content field</p></figcaption></figure></div>

The `posts` schema and its fields should now be completed. To add content under the `posts` schema, it must be published. Click **Published** (situated on the top right corner of the page to do so).

<figure><img src="/files/ijhaaBOBUaXOX6KIMeMo" alt=""><figcaption><p>Publishing the schema</p></figcaption></figure>

Now that the schema is published, sample content can be added to it which will be displayed once the blog is built.

To add content, click **Content** (1) on the sidebar and select **posts** (2) if not already selected. Click **+New** (3) to create a new post.

<figure><img src="/files/c3fIuxWnXPDAbvDbRoRh" alt=""><figcaption><p>Create a new post / content</p></figcaption></figure>

To transfer existing content to Squidex, just paste it here. It's also possible to manually enter some placeholders for the title, slug and content. Next, click **Save and Publish** (4) to publish the created content.

<figure><img src="/files/BIIMVozY0kY3jWv1JPjl" alt=""><figcaption><p>Create content and publish</p></figcaption></figure>

Click the back icon to go back to the list of content for the posts' schema, click **+New** again and repeat the steps for as many posts as desired.

<figure><img src="/files/CaVckrLwvkT2IOQg3Ih3" alt=""><figcaption></figcaption></figure>

## Step 3: Setting Up the Next.js Workspace

Once the schema is created on Squidex, proceed to set up the Next.js workspace.

Open the terminal and run the following command to install Next.js and its dependencies.

```bash
npx create-next-app squidex-blog
```

If prompted to confirm installation of the create-next-App package, type `y` and press enter to continue with the installation.

Once Next.js is set up, run the following commands to enter the `squidex-blog` directory and start the development server.

```bash
cd squidex-blog
yarn dev
```

The `yarn dev` command starts a Next.js development server at `http://localhost:3000`. Visit `htttp://localhost:3000` to see the Next.js App. The text *Welcome to Next.js* should appear on the page. This confirms successful installation of Next.js and that the system is ready to for a blog to be built.

## Step 4: Create Global Styles and a Layout Component

Start by creating a `Layout` component that adds a header and footer to all the pages of the blog. Create a `components` folder in `squidex-blog` and create a `layout.js` file in the `squidex-blog/components` folder with the following content:

```jsx
import Link from "next/link";

export default function Layout({ children }) {
  return (
    <div className="container">
      <div className="header">
        <Link href="/">
          <a className="blog-heading">My Blog</a>
        </Link>
      </div>
      {children}
      <div className="footer">
        <div>My Blog</div>
        <div>(c) 2022 </div>
      </div>
    </div>
  );
}
```

This creates a header and footer with a link to the blog's homepage. The `children` prop allows embedding of whatever content is added into the `Layout` component between the header and footer.

After creating the `Layout` component, replace the contents of the `squidex-blog/styles/globals.css` file with the following:

```css
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 1em;
}

.header {
  margin-top: 2em;
  margin-bottom: 4em;
}

.blog-heading {
  font-size: 2em;
  font-weight: bold;
  text-decoration: none;
  color: black;
}

.footer {
  margin: 4em auto;
  width: fit-content;
}

.blog-item {
  margin-top: 20px;
}

.blog-item-title {
  font-size: 2em;
  text-decoration: none;
  color: black;
}

.blog-title {
  font-size: 3em;
}
```

Now that the website's stylesheet has been modified, continue to modify the custom Next.js `App` component of the blog to wrap the application in a header and footer with the `Layout` component already created. In Next.js, a custom app component is used to add a consistent layout to App pages and to add global CSS to the App. Learn more about this by reading the [Custom App page of the Next.js documentation](https://nextjs.org/docs/advanced-features/custom-app).

Modify `_app.js` in `squidex-blog/pages` should have the the following content:

```jsx
import Layout from "../components/layout";
import "../styles/globals.css";

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}
```

Visit `localhost:3000` (if you haven't already). The development server will rebuild the blog and you will be able to see the newly added header and footer.

Now that styles and a header and footer have been added to the application, proceed to set up authentication for requests to the Squidex API.

## Step 5: Preparing to Fetch Data From the Squidex API

To be able to fetch data from the Squidex App, create a client on Squidex.

Open the dashboard of the blog application(Squidex-blog or whatever name desired) from <https://cloud.squidex.io>. On the left sidebar, click the *Cog icon* at the bottom left corner of the page to open up the settings. Under the *Security* section click *Clients*. At the top of the page, there is a section where you can create a client. In the text box provided under *Add a New Client*, type `nextjs-blog` as the name of the client and click *Add Client* to create the client. When scrolling down the page, notice a new `nextjs-blog` client has been created. By default, this new client comes with the Editor role. In Squidex, roles define which schemas a client has access to and which operations (such as reading and writing) a client is authorised to do. As there won't be any changes to your content from the blog's front end, change the role of the client to Reader, i.e. with read-only permissions. In the dropdown for *Role*, select *Reader*.

Normally, an access token is required to query the Squidex API, but as the blog application will only be reading data from Squidex (not editing or deleting), simply create an anonymous client. Check the *Allow Anonymous Access* checkbox to allow this client to query published blog posts without an access token.

Now a new client has been created, and allowed anonymous access to published blog posts, create a file containing environment variables for the Next.js app. Create a `.env.local` file in the `squidex-blog` folder and add the following content:

```
SQUIDEX_API_URL=https://cloud.squidex.io/api/content/<YOUR_APP>/graphql
```

Replace `<YOUR_APP>` in the value of SQUIDEX\_API\_URL with the name of your own App on Squidex.

Next.js only loads data from the `.env.local` file once, when you start the development server. To load the environment variables created, restart the development server. Press the combination `Ctrl/CMD + C` on the terminal to stop the development server, then restart it by running the following:

```bash
yarn dev
```

Note the message in the terminal showing that environment variables have been loaded from `.env.local`. Visit the [Environment Variables page of the Next.js documentation](https://nextjs.org/docs/basic-features/environment-variables) to learn more about environment variables in Next.js. Now, proceed to create a helper function used to make GraphQL queries to Squidex.

Create a `lib` folder in `squidex-blog` and in this lib folder, create a `squidex.js` file. Add the following content to the `squidex-blog/lib/squidex.js` file:

```js
export default async function fetchAPI(query, { variables } = {}) {
  const res = await fetch(process.env.SQUIDEX_API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query,
      variables,
    }),
  });
  const json = await res.json();

  if (json.errors) {
    console.error(json.errors);
    throw new Error("Failed to fetch API");
  }

  return json.data;
}
```

This exports a `fetchAPI` function that receives a GraphQL query and its variables (as parameters), queries the Squidex API and returns the result.

Next, fetch content from Squidex and display it in your Next.js blog.

## Step 6: Displaying Blog Posts on the Home Page

In this step, learn to fetch the titles and links of blog posts and display them on the homepage of your blog.

Replace the content of `squidex-blog/pages/index.js` with the following:

```jsx
import Head from "next/head";

export default function Home() {
  return (
    <div>
      <Head>
        <title>My blog</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
    </div>
  );
}
```

Open `http://localhost:3000` in your browser (if it is not already open), to continue previewing changes.

At the top of the `pages/index.js` file, import the `fetchAPI` utility from `squidex-app/lib/squidex`:

```js
import fetchAPI from "../lib/squidex";
```

Also in `index.js`, export a `getStaticProps` function defined as follows:

```js
export async function getStaticProps() {
  const data = await fetchAPI(`
  {
    queryPostsContents {
      id,
      flatData {
        title,
        slug,
      }
    }
  }
  `);
  return {
    props: {
      posts: data.queryPostsContents,
    },
  };
}
```

This sends a GraphQL query to your Squidex App to get the slugs and titles of all blog posts and returns this data to your page.

In Next.js, `getStaticProps` is a function that is run at build time to fetch and store data needed to render a page. To learn more about data-fetching with `getStaticProps` in Next.js, see [the getStaticProps page of the Next.js documentation](https://nextjs.org/docs/basic-features/data-fetching/get-static-props). Save the file and refresh the browser to fetch page data from the CMS.

To make this data available to the `Home` page component of `index.js`, pass posts as a prop to `Home` in index.js:

```js
export default function Home({ posts }) {
...
}
```

Although the title and slugs of your blog posts are now available to the `Home` component, they will not be rendered on the homepage. Create a `BlogItem` component that will be used to display and link to all of your posts.

Create a `blogItem.js` file in the `squidex-blog/components` folder with the following content:

```jsx
import Link from "next/link";

export default function BlogItem({ title, slug }) {
  return (
    <div className="blog-item">
      <Link href={slug}>
        <a className="blog-item-title">{title}</a>
      </Link>
    </div>
  );
}
```

To use `BlogItem` on the homepage, import it in `pages/index.js`.

```jsx
import BlogItem from "../components/blogItem";
```

And in the `Home` function in `index.js`, after the `Head` tag, add the following:

```jsx
<div className="blog-post-list">
  {posts.map((post) => (
    <BlogItem
      title={post.flatData.title}
      slug={post.flatData.slug}
      key={post.id}
    />
  ))}
</div>
```

This loops through blog posts and creates a link to each of them.

The completed `index.js` file will look like this:

```jsx
import Head from "next/head";
import fetchAPI from "../lib/squidex";
import BlogItem from "../components/blogItem";

export default function Home({ posts }) {
  return (
    <div>
      <Head>
        <title>My blog</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="blog-post-list">
        {posts.map((post) => (
          <BlogItem
            title={post.flatData.title}
            slug={post.flatData.slug}
            key={post.id}
          />
        ))}
      </div>
    </div>
  );
}

export async function getStaticProps() {
  const data = await fetchAPI(`
  {
    queryPostsContents {
      id,
      flatData {
        title,
        slug,
      }
    }
  }
  `);
  return {
    props: {
      posts: data.queryPostsContents,
    },
  };
}
```

If you look at your blog homepage in a browser, note that the titles of posts are shown and they link to the page where the article can be read.

Now links to your posts have been successfully created, create a page to display the content of the blog posts.

## Step 7: Creating Blog Post Pages

In the `pages` folder, create a `[slug].js` file. In Next.js, a page is a React Component file in the pages directory. The component at pages/about.js, for example will be accessible at /about. A page enclosed in square brackets like \[slug].js is a fallback and Next.js will show this page for all URLs that are not handled by another page, and will pass a `slug` variable to the page. So when you visit a URL like /my-first-blog-post this page is used and `slug` will have the value my-first-blog-post.

You can learn more about this from the [dynamic routes page of the Next.js documentation.](https://nextjs.org/docs/routing/dynamic-routes).

```jsx
export default function BlogPost() {
  return <div>Body</div>;
}
```

Click one of the links on the homepage to visit the page where your blog posts will be rendered.

Since you will need to render markdown content on this page, you will install [next-mdx-remote](https://github.com/hashicorp/next-mdx-remote), a package that parses markdown content and renders it in your Next.js application. Stop the development server by pressing `Ctrl/Cmd + C` on your terminal and run the following command to install `next-mdx-remote`:

```bash
yarn add next-mdx-remote
```

Restart the development server after installation:

```
yarn dev
```

Import `serialize` and `MDXRemote` from `next-mdx-remote` at the top of `[slug].js`:

```jsx
import { serialize } from "next-mdx-remote/serialize";
import { MDXRemote } from "next-mdx-remote";
```

Import `fetchAPI` at the top of `[slug].js` as you will use it to fetch the contents your blog posts from Squidex:

```js
import fetchAPI from "../lib/squidex";
```

Export a `getStaticProps` function from `[slug].js`:

```js
export async function getStaticProps({ params }) {
  const data = await fetchAPI(
    `
  query QueryPosts($query: String!) {
    queryPostsContents(filter: $query) {
      id
      flatData {
        title
        content
      }
    }
  }
  `,
    {
      variables: {
        query: `data/Slug/iv eq '${params.slug}'`,
      },
    }
  );

  if (data.queryPostsContents.length === 0) {
    return {
      notFound: true,
    };
  }
  const post = data.queryPostsContents[0].flatData;

  const mdxSource = await serialize(post.content);

  return {
    props: {
      post: post,
      source: mdxSource,
    },
  };
}
```

This returns data for the blog post associated with a particular slug or returns a 404 error if no such post exists.

At this point, you may receive an error because when you use `getStaticPaths` with dynamic paths in Next.js, you are required to also export a `getStaticPaths` function that tells Next.js [what pages to generate at build time](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation).

Export a `getStaticPaths` function in `squidex-blog/pages/[slug].js`:

```js
export async function getStaticPaths() {
  const data = await fetchAPI(`
  {
    queryPostsContents {
      id,
      flatData {
        slug
      }
    }
  }
  `);

  return {
    paths: data.queryPostsContents.map((post) => {
      return {
        params: {
          slug: post.flatData.slug,
        },
      };
    }),
    fallback: false,
  };
}
```

The Next.js development server only runs `getStaticProps` to fetch the page data when the page is initally loaded. So, to preview the changes made to `getStaticProps`, you will refresh the page. When you do so, the data will be made available but since it has not been rendered, nothing will be visible. You will now proceed to render the blog post on this page.

Modify the `BlogPost` function in `[slug].js`:

```jsx
export default function BlogPost({ post, source }) {
  return (
    <div>
      <h1 className="blog-post-title">{post.title}</h1>
      <MDXRemote {...source} />
    </div>
  );
}
```

Once you save the file, the title of the blog post and the body of the post will be rendered on the page. If you did not refresh the page after adding `getStaticProps`, you may see an error because the page data has not been loaded. Refresh the page to fetch data from your Squidex application.

The finished `[slug].js` file will look like this:

```jsx
import { serialize } from "next-mdx-remote/serialize";
import { MDXRemote } from "next-mdx-remote";
import fetchAPI from "../lib/squidex";

export default function BlogPost({ post, source }) {
  return (
    <div>
      <h1 className="blog-post-title">{post.title}</h1>
      <MDXRemote {...source} />
    </div>
  );
}

export async function getStaticProps({ params }) {
  const data = await fetchAPI(
    `
    query QueryPosts($query: String!) {
      queryPostsContents(filter: $query) {
        id
        flatData {
          title
          content
        }
      }
    }
    `,
    {
      variables: {
        query: `data/Slug/iv eq '${params.slug}'`,
      },
    }
  );

  if (data.queryPostsContents.length === 0) {
    return {
      notFound: true,
    };
  }
  const post = data.queryPostsContents[0].flatData;

  const mdxSource = await serialize(post.content);

  return {
    props: {
      post: post,
      source: mdxSource,
    },
  };
}

export async function getStaticPaths() {
  const data = await fetchAPI(`
    {
      queryPostsContents {
        id,
        flatData {
          slug
        }
      }
    }
    `);

  return {
    paths: data.queryPostsContents.map((post) => {
      return {
        params: {
          slug: post.flatData.slug,
        },
      };
    }),
    fallback: false,
  };
}
```

Congratulations! You have now built a fully functional blog in Next.js that sources data from Squidex.

## Conclusion

Now that you have built your blog with Next.js and Squidex, learn how to deploy your blog to a live site. Check out this page on [Deploying a Next.js site](https://nextjs.org/docs/deployment). You may also want to explore the [Next.js Head component](https://nextjs.org/docs/api-reference/next/head) to see how adding meta tags to your pages enables search engines to better understand your content.


# Squidex 3.0: API Compatibility

API Compatibility

The API for Squidex 3.0 contains a lot of changes to 2.0. This document describes the main differences.

The good news first! There is no change in the endpoints to retrieve content or assets, including the GraphQL endpoint.

## Motivation

These changes are driven by two requirements:

1. `POST` and `PUT` endpoints must return the full entity (e.g. the schema object) so that the UI does not have to consider how the entity will be structured after an update.
2. Implement `HATEOAS` (Hypermedia as the Engine of Application State) to tell the client which operations are possible for a given entity and how to invoke them.

Given the list of Apps as an example, our JSON response has the following format now:

```javascript
{
   "items": [{
       "id": 1,
       "name": "my-app",
       "_links": {
          "delete": { "method": "DELETE", "href": "/api/apps/1" }
       }
    }],
    "_links": {
        "create": { "method": "POST", "href": "/api/apps" }
     }
}
```

This format will be called **Items-Object** from now.

A single App in 3.0 has the following format:

```javascript
{
    "id": 1,
    "name": "my-app",
    "_links": {
        "delete": { "method": "DELETE", "href": "/api/apps/1" }
    }
}
```

If Squidex 2.0 and a lower list of Apps was returned as a JSON array, it will not be  possible to add the links to create the endpoint.

```javascript
[{
    "id": 1,
    "name": "my-app",
    "_links": {
        "delete": { "method": "DELETE", "href": "/api/apps/1" }
    }
}]
```

## General Changes

### 1. HATEOAS

As described above, each entity or list of entities will now contain a `_links` object, with all possible operations. If the operation is not possible or the current user does not have the correct permission, the link will not be present.

> RISK to break something: **LOW**

### 2. Metadata

Data that is not part of the entity but must be returned as a result of the operation is added to a `_meta` object.

Example: When uploading an asset, the API checks if the same asset has already been uploaded. If this is the case, the response will contain the uploaded asset with additional information if the asset was already part of the App:

```javascript
{
    "id": 1,
    "fileName": "Logo.jpeg",
    "fileSize": 1024,
    "_links": {
        "delete": { "method": "DELETE", "href": "/api/assets/1" }
    },
    "_meta": {
        "isDuplicate": "1"
    }
}
```

> RISK to break something: **LOW**

### 3. Full Response Objects

All `POST` and `PUT` endpoints now return the full entity (e.g. the schema object). This is not a breaking change for you if your client displays the following behaviour:

1. The client does not break when the JSON response contains additional properties.
2. The client does not break when an endpoint that has previously returned a `204 No Content` status code now returns `200 OK` now (usually the case).
3. The client does not break when an endpoint that has not returned a JSON response before, now returns a response.

> RISK to break something: **MEDIUM**

## Specific Changes

### Contents

The following endpoints have been removed to prepare for the coming workflow system:

* `PUT /api/content/{app}/{name}/{id}/archive/`
* `PUT /api/content/{app}/{name}/{id}/publish/`
* `PUT /api/content/{app}/{name}/{id}/restore/`
* `PUT /api/content/{app}/{name}/{id}/unpublish/`

The replacement is a generalized status endpoint:

```javascript
PUT /api/content/{app}/{name}/{id}/status/

{
    "status": "Published"
}
```

> RISK to break something: **HIGH**

### Assets

1. `POST /api/apps/{app}/assets/` does not return the `isDuplicate` JSON property anymore, it has been replaced with metadata (see example above).

> RISK to break something: **LOW**

### Rules

1. `GET /api/apps/{app}/rules/` endpoint returns **Items-Object** instead of a JSON array.

> RISK to break something: **LOW**

### Apps

1. `GET /api/apps/` endpoint returns **Items-Object** instead of JSON array.
2. `GET /api/apps/{app}/clients/` endpoint returns **Items-Object** instead of JSON array.
3. `GET /api/apps/{app}/languages/` endpoint returns **Items-Object** instead of JSON array.
4. `GET /api/apps/{app}/contributors/` endpoint returns **Items-Object** instead of custom JSON object.
5. `GET /api/apps/{app}/patterns/` endpoint returns **Items-Object** instead of JSON array.
6. `GET /api/apps/{app}/roles/` endpoint returns **Items-Object** instead of custom JSON object.

> RISK to break something: **LOW**


