Documentation

Getting started

Getting started with unite cms, install unite cms using composer and basic configuration. 

A short introduction

unite cms is a decoupled content management system that allows you to manage all kind of content in one application. You can login into unite cms and configure any kind of data / user / settings types. Via the admin interface you and your content editors can manage content according to the defined types. unite cms does not provide any frontend rendering layer, so the only way to access the content is via a GraphQL API.

The big idea behind unite cms is to have a single system just for content management. All other features that are part of many state-of-the-art CMS but have nothing to do with content management (search server, image processing, template rendering etc.) are not part of unite cms and we are not planing to implement them in the future. Because of this, unite cms is designed to be integrated with other services.

One example would be to have a small website application, written in Symfony, that fetches content from unite cms and uses imgix.com for resizing images, that are stored in a S3 storage.

By focusing on content management only, we can put all our effort in the content management architecture and the content editor experience. Still, our SasS platform offers all features you would expect from a state-of-the-art CMS by integrating 3rd party open source services like minio.io.

Installation

To use unite cms, you can sign up for free on unitecms.io. Our cloud, hosted in Vienna (Austria) always runs the latest version of unite cms and is monitored by our dev ops team. At the moment unitecms.io is in public beta and totally free. Starting with 2019, there will be one free plan and multiple  paid plans for higher usage limits (details will be provided soon). 

Since the application is published under an open source license, you can always run it on your own infrastructure. unite cms is based on Symfony 4 and vue.js, the only server dependencies are PHP >= 7.1 and MySQL >= 5.7.9

Installation for development
composer create-project unite-cms/standard unitecms --stability dev
cd unitecms
bin/console doctrine:schema:update --force

# run the development server
bin/console serve:run

On composer install (and update) you will be asked to set all required environment (dotenv) variables.

Installation for production
composer create-project unite-cms/standard unitecms --stability dev --no-dev --no-scripts
cd unitecms

bin/console assets:install --env=prod
bin/console doctrine:schema:update --force --env=prod
bin/console cache:clear --env=prod

In order to run unite cms, all required settings must be available as environment variables. It is recommended to set this variables at a web server level.

After unite cms was successfully installed you can create a Platform Administrator and your first organization.

bin/console unite:user:create
bin/console unite:organization:create

Now you can login into unite cms and start using the cms.

Defining your structure

unite cms allows you to define your content structure by writing a single JSON document per domain. That means instead of an administration interface you will get an editor where you can define a schema for your content, webhooks, permissions etc.

Starting with version 0.7, for any custom installation of unite cms the domain configuration will (also) be saved to the filesystem and can be edited using any code editor. After changing a domain config in the filesystem, You can come import the changes using the unite cms domain editor.

After your registered on unitecms.io you can add your First Domain which brings you to the domain configuration screen. For a quick example, you can just copy the domain definition for a simple example blog. The only thing you need to do now is to enter s3 bucket information or to skip this, you can just delete the image field. 

Variables: Reusable snippets

Domain definitions have a special property: variables. This property allows you to define reusable snippets in your domain configuration. This snippets will get replaced just before the JSON gets parsed and validated. For example you can save the toolbar settings for a wysiwyg editor in a variable and use it on multiple wysiwyg fields:

Variables: 
{
  "title": "This is my domain",
  "identifier": "this_is_my_domain",

  "variables": {
    "@wysiwyg_settings": {
      "heading": [
        "h2",
        "h3",
        "p"
      ]
    }
  },

  "content_types": [
    {
      ...,
      "fields": [
        {
          "title": "Content",
          "identifier": "content",
          "type": "wysiwyg",
          "settings": "@wysiwyg_settings"
        },
        {
          "title": "Footer",
          "identifier": "footer",
          "type": "wysiwyg",
          "settings": "@wysiwyg_settings"
        }
      ]
    }
  ]
}

You can save any kind of (valid) JSON structure inside a variable, not only settings. This way you can define reusable fields or permissions or anything else.

Basic Concepts

Learn the basic architecture of unite cms and see how you can define your schema types.

Organizations

Organizations in unite cms are the top level entities that contain all users and domains. On our SaaS platform, each subdomain belongs to one organization (your-org-name.unitecms.io). Multiple organizations are completely separate and will never share any informations. When you create an account on unitecms.io, you will be asked to create your first organization. You can always create new organizations or get invited to other organizations. One organization contains the following entities: 

User

Each user of an organization can login into this organization but cannot access any domains unless he_she is a member of the domain. Users can become organization admins. Organization admins can invite new users, assign the administration role to existing users, create api keys, create new domains and are allowed to access all domains.

Note: Because organization admins can bypass all security checks you should use an user account with organization admin privileges only for administrative tasks and use normal users for content editing.

API Key

Organization admins can create API keys. By using this keys, clients can access the GraphQL API. API keys can only access domains, they are members of. One API key can be member of multiple domains, which is especially necessary if you want to create a reference between different domains.

Domain

One organization can hold an unlimited number of domains, where all of the content management is happening.

Domains

A domain in unite cms groups together related content and setting types.

An example of a domain could be "Website" and contains "Pages", "Page Settings" and "Blog Articles". Another example would be a "Employees" domain that contains a list of all employees and vacation planing.

Domains can have reference fields to other domains which allows you to create very powerful content structures.

Domain configuration is done by creating or updating its JSON configuration. Example of a very simple domain: 

{
    "identifier": "website",
    "title": "Website",
    "content_types": [
        {
            "title": "Pages",
            "identifier": "pages",
            "fields": [
              { "title": "Headline", "identifier": "headline", "type": "text" }
            ]
        }
    ]
}

This domain would provide one content type "Website" and no setting types. By default, each domain contains an "Editor" and "Viewer" member type. Editors have the permission to manage all content and settings, viewers can only read it.

For the website example, you could create an API key and add it as a Viewer Domain Member to the website domain. Now your website can do an GraphQL API request to the unite cms api endpoint of your domain and gets all page headlines to display them:

# https://{YOUR-ORG}.unitecms.io/website/api?token={TOKEN}

query {
    findPages {
        result {
            headline
        }
    }
}

Each domain can define an infinit number of content_types, setting_types and at least one domain_member_types. For each of them you can define an infinit number of fields. Domains also contains the permissions config, which is configured to allow all members to view the domain but only the organization administrator to update the domain schema. To find out more about defining permissions in unite cms, see the topic Permissions.

Content Types

Each new content type in unite cms comes with no fields and one view ("all") per default. By adding fields, you can define all kind of content schemas (for example news articles, web pages, invoices or products). By changing the settings of the "all" view or by adding additional views, you can define different management collections of you content. For example a content type "Webpages" could have a default view that allows sorting the content for displaying a navigation list and a second view that shows all pages that are marked for internal review. For more information about all available views, see chapter "Views".

To improve the UX for your content editors, you can set an optional icon from the icon set, unite cms is using (Feather).

{
    ...

    "content_types": [
        {
            "title": "Webpages",
            "identifier": "pages",
            "icon": "file",
            "fields": [
              { "title": "Headline", "identifier": "headline", "type": "text" },
              { "title": "Needs review", "identifier": "needs_review", "type": "checkbox" },
              { "title": "Position", "identifier": "position", "type": "sortindex" }
            ]
        }
    ],
    "views": [
        {
           "title": "All",
           "identifier": "all",
           "type": "sortable",
           "settings": {
               "sort_field": "position"
           }
       },
       {
           "title": "Needs review",
           "identifier": "review",
           "type": "table",
           "settings": { "filter": { "field": "needs_review", "operator": "=", "value": "1" }
       }
    ]
}

For each content type you can define permissions, using a very powerful expression engine that is described in chapter "Permissions".

For each content type, the GraphQL API of your domain defines a find{CONTENT_TYPE} query object as well as an create{CONTENT_TYPE} and update{CONTENT_TYPE} mutation object. If you want to query content from multiple content types together, you can use the generic find query object.

# https://{YOUR-ORG}.unitecms.io/website/api?token={TOKEN}

query {
 find(types: ["pages"]) {
   result {
     ... on PagesContent {
      headline
    }
   }
 }

 findPages(
    filter: { field: "needs_review", operator: "!=", value: "1" },
    sort: {field: "position", order: "ASC"}
  ) {
   result {
     headline
   }
 }
}

To learn more about the GraphQL API, see the chapter "GraphQL API" or use a client like GraphiQL to explore API of your domain.

Setting Types

Setting types are very similar to content types, however in contrast to content types, there is exactly one setting instance for each setting type. Therefore there are no views and only a "view"  and an "update" permission, but no "create" and "delete".

"setting_types": [
  {
     "title": "Frontpage",
     "identifier": "frontpage",
     "fields": [
       {
         "title": "Header content",
         "identifier": "header",
         "type": "wysiwyg",
         "description": "Enter an introduction text that will be shown next to the video",
         "settings": {
           "heading": ["p", "h1", "h2", "h3", "h4", "h5"]
         }
       }
     ]
  }
]

Like for content types, setting types can be queried using the GraphQL API (Note: Mutations are not implement yet for setting types):

query {
  FrontpageSetting {
    header
  }
}

To learn more about the GraphQL API, see the chapter "GraphQL API" or use a client like GraphiQL to explore API of your domain.

Domain member types

Each domain comes with two domain member types per default: "Viewer" and "Editor". You can modify or delete this types and add any number of types, however there must be at least one domain member type for each domain. 

Each API key and each CMS user of your organization must become a member of your domain in order to get access to it. So even if you set a content type read permission to "true" (= always grant access) for example, only members of the domain can actually see the content. 

Domain member types are similar to content types and can also define any number of fields. At the moment this fields can only be used to save content and inside a permission check expression, in the future we might implement an API endpoint to allow you to query and create members.

"domain_member_types": [
   {
     "title": "Editor",
     "identifier": "editor",
     "domain_member_label": "{accessor}",
     "fields": []
   },
   {
     "title": "Viewer",
     "identifier": "viewer",
     "domain_member_label": "{accessor} {department}",
     "fields": [
         { "title": "Department", "identifier": "department", "type": "choice", "settings": {...} }
     ]
   }
 ]

The domain_member_label is used whenever a domain member needs to get displayed (for example the title on the member update screen). Per default it shows the name of the member accessor (API key name or user name).

Defining Permissions

unite cms allows you to define permissions for accessing domains, content types and setting types by writing a short expression statement. This expression statement gets compiled and evaluated and must return true or false to allow or deny access.

We are using the Symfony ExpressionLanguage component, so you can use all common syntax elements like "==", "!=" and arithmetic operators (+, -, % etc.). For a full reference see the Symfony ExpressionLanguage syntax docs

Inside an expression you can access the current domain member object and for content and setting types the current content object:

member: {
    type: "editor",
    accessor: {
        name: "User name or API key name",
        id: "XXX-YYY-ZZZ",
        type: "api_key"
    }
    data: { ... }
}

content: {
    locale: "en",
    data: { ... }
}

By using properties (and especially the data property that holds all of the defined fields) of this two objects you can define very powerful expressions to check permission for a user and a content object (or a domain). Here are some examples:

Domain

"permissions": {
    "view domain": "true",
    "update domain": "member.type == \"editor\" or member.data.can_edit_domains == true"
}

Content Type / Setting Type

"permissions": {
    "view content": "member.data.project_admin_for == content.project",
    "list content": "member.type == \"editor\" or member.accessor.type == \"api_key\"",
    "create content": "member.type == \"editor\"",
    "update content": "member.accessor.id == content.my_author_field",
    "delete content": "member.data.manager_since < content.date_of_receipt"
}

Since we are just checking the result of the expression, you can allow access for all domain members with "true" and deny for all members with "false".

Note: Permission expressions are only used to check permission for domain members. So even if you define a "true" permission, only members of the current domain are allowed to access the resource. Therefore you cannot create an anonymous public API endpoint, that can be accessed without API Key, however if you want to deliver public webpages for example you can create an API Key that must not be absolutely private.

Organization admins are allowed to manage all domains and all content for their organizations, independently from defined permissions. Because of this, the default permissions for domains are: view: "true" and update: "false" which means that all domain members can view the domain but only organization admins are allowed to update them.

Defining Webhooks

unite cms allows you to define webhooks for Content Types and Setting Types, which will be fired on different CRUD Actions.

The following Settings are provided:

query (mandatory):    qraphQL query to query the current Content- or Setting Type Object   this data will be sent as JSON format during the POST request   (string)

url (mandatory):    webhook URL, which the request will be sent to   (url)

condition (mandatory):      ExpressionLanguage to define on which condition the webhook is fired  the following events are provided:  Content type: create, update, delete  Setting Type: update  (string)

authentication_header (optional):    will be passed as HTTP header "Authorization" to secure the webhook   (string, default: null)

check_ssl (optional):    possibility to disable webhook ssl check (not recommended)   (boolean, default: true)

Content Type / Setting Type

"webhooks": [    
  {
         "query": "query { type, id, text, longtext }",
         "url": "http://www.myfrontendapp.com/hooks/delete",
         "condition": "event == \"delete\""
  },
  {
         "query": "query {  text }",
         "url": "http://www.myfrontendapp.com/hooks/update",
         "condition": "event == \"update\"",
         "authentication_header": "79437f5edda13f9c0669b978dd7a9066dd2059f1"
  }
]

Defining Previews

unite cms allows you to define a preview for each Content- or Setting Type.  This gives you the possibility to check how the Content- or Setting Type will look in your design. After a preview was defined, a preview window will appear right to the content update interface and will listen to your changes automatically.

The following Settings are provided:

url (mandatory):    your preview url   (url)

query (mandatory):    qraphQL query to query the current Content- or Setting Type Object   this data will be passed as GET parameters to your preview    (string)

Content Type / Setting Type

"preview": {
  "url": "http://www.myfrontendapp.com/preview.php",
  "query": "query { firstname, lastname }"
}

For example in preview.php you can access the values the following way

$data = json_decode($_GET['data']);
print $data->firstname;
print $data->lastname;

Defining Validations

unite cms allows you to define validations for Content- or Setting Types.  This gives you the possibility to define fine grained validators for your fields.

Not Empty

Starting with version 0.7, there is also a new "not_empty" field setting for most of the core field types. This setting tags the form field as required and adds a validation for this field: 

{
    "title": "Headline", 
    "identifier": "headline",
    "settings": {
        "not_empty": true
    }
}

The "not_empty" setting is available for all unite cms core field types except: collection, checkbox, sortindex, state.

Generic validations

The following Settings are provided:

expression (mandatory):    the expression which should be validated    check Symfony ExpressionLanguage syntax docs again   (string)

message (optional):    A error message which will be presented to the user in case the validation is thrown   (string default: a general message like 'Invalid value')

path (optional):   A Path where the message should appear.   In most cases this will be a field identifier like "firstname"   If no path is given the message will appear on the top of the form.   (string default: '')

groups (optional):    on which event the validation should run   (array, default: ['CREATE', 'UPDATE'])

Content Type / Setting Type

The following example shows a validator which prevents an empty firstname on content creation. A custom validation message is used and will be displayed underneath the firstname field.

"validations": [    
  {
         "expression": "data.firstname != ''",
         "message": "This field firstname is required.",
         "path": "firstname",
         "groups": [
           "CREATE"
         ]
  }
]

Multilingual

Per default, each new content and setting type comes without an location. However, unite cms allows you to define multiple locales per type by adding a "locales" entry: 

{
    "content_types": [
        {
            "title": "Content Type",
            ...
            "locales": ["en", "de", "fr"]
        }
    ],
    "setting_types": [
        {
            "title": "Setting Type",
            ...
            "locales": ["en", "de"]
        }
    ]
}

Now you can select a locale per content (and setting) and reference translations of this content (or setting) via the content context menu.

Via the GraphQL API, you can use filter by "locale" like any other field on the content:

query {
    findPages(filter: { field: "locale", operator: "=", value: "en" }) {
        ...
    }
}

And you can access the locale and one or multiple translations of your content / setting:

query {
    findPages {
        result {
            locale,
            translations(locales: null) {
                id,
                locale,
                headline
            }
        }
    }
}

GraphQL API

The heart of unite cms is its GraphQL API. It allows you to query and manipulate your content and settings. GraphQL APIs are very powerful and allows you to get exactly the fields you need from exactly the content items you want to query. If you are new to GraphQL you should read the introduction from graphql.org before you continue.

Each domain comes with its own API endpoint: # https://{ORG}.unitecms.io/{DOMAIN}/api. In order to access the api, you need to provide an authentication token from an API Key that is a member of the domain. You should always use the HTTP Authentication head field to send the token, but it is also possible to add a GET token query parameter to the API url. 

The unite cms API has the following structure, that you can easily explore using one of the GraphQL clients (for example GraphiQL):

query {  
 find {
   total
   page
   result { ... }
 }
 findPages {
   total
   page
   result { ... }
 }
 WebsiteSetting { ... }
}

mutation {
 createPage {
   ...
 }
 updatePage {
   ...
 }
}

You can use the generic find query object to get results from one or multiple content types. This also allows you to combine multiple content types in one query. In the following example, assumed that there is a "news" and an "events" content type, you would get the 5 newest content items from both types. With to separate find queries you would get the 5 newest news AND the 5 newest events.

query {  
 find(
   types: ["news", "events"],
   limit: 5,
   sort:  { field: "created", order: "DESC" },
   filter: { field: "published", operator: "=", value:"1" }
 ) {
   total,
   result {
     id,
     type,
     created,
     updated,
     
     ... on NewsContent {
       headline
     }
     
     ... on EventsContent {
       location
     }
   }
 }
}

Beside the generic find query object, unite cms creates one find object for each content type ("news" => "findNews", "events" => "findEvents") that has the same filters and structure like find but no types argument. Use this objects to get content from a single content type:

query {
  findEvents {
    result {
      id,
      location
    }
}

For each setting of your domain, there will be one setting query object ("website" => "WebsiteSetting") you can use to get the setting fields. Note: At the moment you can only read settings but not write them. This will be implemented in a future release!

query {
  WebsiteSettings {
    title,
    description,
    footer_text
  }
}

The current version of the API (0.5) allows you to create and update content items, but not to delete them: 

mutation {
 createNews(data: { headline: "Hello World"}) {
   id,
   headline
 }

 updateNews(id: "XXX-YYY-ZZZ", data: {headline: "Updated headline"}) {
   id,
   headline
 }
}

Filtering

All query find objects allows you to reduce the result, using an optional filter input parameter. The filter input has the following basic structure: 

filter: {
  field: "headline",
  operator: "LIKE",
  value: "%Hello%"
}

At the moment the following operators are supported: "=", "<>", "<", "<=", ">", ">=", "IS NULL", "IS NOT NULL", "LIKE". To combine multiple filters, you can create a nested input element using the AND or OR fields.

filter: {
   AND: [
     { field: "published", operator: "=", value:"1"},
     {
       OR: [
         { field: "is_very_important", operator: "=", value:"
         { field: "created", operator: ">", value:"1530611415"}
       ]
     }
   ]
 }

Pagination

All query find objects allows you to limit the result using a limit and page input field. To get the first 10 items, you could do: 

find(limit: 10, page: 1) { ... }

To get the next 10 items, use the page parameter:

find(limit: 10, page: 2) { ... }

Fields

A reference of all fields that are part of unite cms core. You can use this fields for content types, setting types and domain member types.

General information

The basic structure of each field in unite cms looks like this: 

{
  "title": "My field",
  "identifier": "my_field",
  "type": "text",
  "settings": {}
}

Depending on the field type there are different settings allowed and required. For example a choice field type needs a choices setting with all allowed possible values for the dropdown. In this chapter you will find information and examples for each field type in unite with all possible settings.

Common settings

All field types allow to define at least the optional "description" setting which is just a text and will be rendered below the field.

All (core) field types except collection, checkbox, sortindex and state also allow to define an "not_empty"  option which will make this field required (and also add validation). 

All (core) field types except collection, reference, sortindex, state,  also allow to define an "default" option. The content of "default" will be used as a field value when you create a new content / setting object and can be changed by the user. Note: Depending on the field type, the default option excepts a string or a nested structure: 

"fields": [
  {
    ...,
    "type": "choice", 
    "default": "blue"
  },
  {
    ...,
    "type": "choices",
    "default": ["yellow", "blue"]
  }
]

 

Checkbox

A checkbox field that can be true or false. This field type has no configureable settings.

{
    ...,
    "type": "checkbox"
}

Choice

A choice field type that allows to select on value of a predefined set of values using a HTML select element. A "choices" setting is required.

{
    ..., 
    "type": "choice",
    "settings": {
        "choices": {
            "Red color": "red",
            "Green color": "green",
            "Blue color": "blue"
        }
    }
}

Choices

A choices field type that allows to select multiple values of a predefined set of values using a HTML select element. A "choices" setting is required.

{
 ...
 "type": "choices",
 "settings": {
   "choices": {
     "Monday": 1,
     "Tuesday": 2,
     "Wednesday": 3,
     "Thursday": 4,
     "Friday": 5,
     "Saturday": 6,
     "Sunday": 7
   }
 }
}

Date

Renders a HTML5 date input element. This field has no settings.

{
    ...,
    "type": "date"
}

DateTime

Renders a HTML5 date-time input element. This field has no settings.

{
    ...,
    "type": "datetime"
}

Email

Renders a HTML5 email input element. Input will be validated to be a valid email address. This field has no settings.

{
    ...,
    "type": "email"
}

Integer

Renders an input element that accepts integer numbers. This field has no settings.

{
    ...,
    "type": "integer"
}

Number

Renders an input element that accepts any numeric input. This field has no settings.

{
    ...,
    "type": "number"
}

Phone

Renders a HTML 5 tel input element. Note: The phone field to not validate any input. This field has no settings.

{
    ...,
    "type": "phone"
}

Range

Renders a slider input element that allows to select one value between min and max. The default settings are:

{
    ...,
    "type": "range",
    "settings": {
        "min": "0",
        "max": "100",
        "step": "1"
    }
}

Reference

This field holds a reference to an content element. The content element can be of this or any other domain in this organization. Note: Make sure, that the content editor is allows to access the referenced domain and content type, otherwise he_she will not be able to fill out this field.

{
    "title": "Related page",
    "identifier": "related_page",
    "type": "reference",
    "settings": {
        "domain": "website",
        "content_type": "page",
        "view": "all",
        "content_label": "{headline}"
    }
}

When using the GraphQL API, referenced content will be resolved automatically allowing you to selected nested fields of the referenced content:

{
    query {
        findCategories {
            result {
                related_page {
                    headline
                }
            }
        }
    }
}

The domain and content_type settings are required, view defaults to all, if content_label is left empty, "content type #{id}" will be used.

Sort Index

A sort index is a special field that stores an inter value (the sort index) and makes sure that all sort indexes of this content type are in sync if a new content item was added or removed and when a sort index was updated. 

{
    ...,
    "type": "sortindex"
}

For example you have the following pages: 

[
  { title: "Start", sort_index: 0},
  { title: "About us", sort_index: 1},
  { title: "Contact", sort_index: 2}
]

Now, if you remove the "About us" page, the sort indexes will automatically be updated:

[
  { title: "Start", sort_index: 0},
  { title: "Contact", sort_index: 1}
]

Sort indexes are needed if you want to have a sortable view, however you can also use them for other purposes.

Textarea

Renders a multi-row textarea. Allows you to set an optional rows setting to configure the textarea HTML rows attribute.

{
    "type": "textarea",
    "settings": {
        "rows": 2
    }
}

Text

Renders a text input field.

{
    "type": "text"
}

Collection

Collection fields allows you to create a repeatable container of one or multiple subfields. They can be used to allow multiple values for one field or to create complex content structures.

The following example would create an input element that allows to add 1-5 tags (min_rows and max_rows are optional).

{
  "title": "Tags",
  "identifier": "tags",
  "type": "collection",
  "settings": {
    "min_rows": "1",
    "max_rows": "5",
    "fields": [
      { "type": "text", "identifier": "name", "title": "Name" }
    ]
  }
}

Using the API, this collection field resolves in a nested GraphQL object:

{
  findNews {
    result {
      tags {
        name
      }
    }
  }
}

Collection fields can also be nested to allow to have multiple levels of collections inside each other:

{
  "title": "Content Blocks",
  "identifier": "content_blocks",
  "type": "collection",
  "settings": {
    "fields": [
      { "type": "text", "identifier": "headline", "title": "Headline" },
      { "type": "collection", "identifier": "blocks", "title": "Blocks", "settings": {
        "fields": [
          { "type": "wysiwyg", "identifier": "content", "title": "Content" }
        ]
      } }
    ]
  }
}

File

unite cms does not manage any files directly but provides a file field that stores a reference using any s3 compatible API (Amazon, minio.io etc.). The file field renders an upload input element that allows the content editors to upload files directly to the s3 compatible server, using a presgined upload url. The file filed also reacts on content delete and update events and tries to delete files, that are not used anymore. In order to use the file field, set the required bucket and optional file_type settings:

{
  "type": "file",
  "settings": {
    "bucket": {
      "endpoint": "S3 Endpoint",
      "bucket": "S3 Bucket",
      "path": "myfiles"
    },
    "file_types": "txt,pdf,doc"
  }
}

A typical Amazon S3 configuration with location EU (Frankfurt) would be: 

{
  "bucket":{
    "endpoint": "https://s3.eu-central-1.amazonaws.com",
    "region": "eu-central-1",
    "key":"S3 KEY",
    "secret":"S3 SECRET",
    "bucket":"my_bucket"
  }
}

A typical minio.io configuration would be:

{
  "bucket": { 
    "endpoint": "https://example.com:9000",
     "key": "S3 KEY", 
     "secret": "S3 SECRET", 
     "bucket": "my_bucket"
  }
}

Image

The image type is an extension of the file input type that renders a thumbnail preview next to the upload input type and limits file_type to "png,gif,jpeg,jpg":

{
  "type": "image",
  "settings": {
    "bucket": { ... },
    "thumbnail_url": "your_thumbnailing_service.com/{endpoint}/{id}/{name}"
  }
}

The optional thumbnail url allows you to add a link to the file directly or to any thumbnailing service. For a description of the bucket setting, please see the file type documentation.

WYSIWYG Editor

The wysiwyg field renders a ckeditor5 "Classic" editor. You can set toolbar items and heading elements, according to the ckeditor documentation: 

{
  "type": "wysiwyg",
  "settings": {
    "toolbar": ["bold", "italic", "|", "link", "|", "bulletedList", "numberedList", "|", "blockQuote"],
    "heading": ["p", "h1", "h2", "h3", "h4", "h5", "h6", "code"]
  }
}

Variants field type

With the variants field you can define multiple variants, the editor can choose from. Each variant has a title, identifier, description, icon and a list of fields. Variants fields can be used if you want to create a dynamic content schema, for example a "name" in a CRM: For a company this would be the company name, for a person it would be the combination of a firstname and lastname field:

{
  "identifier": "name",
  "type": "variants",
  "settings": {
    "variants": [
      {
        "title": "Company",
        "identifier": "company",
        "description": "This is a company",
        "icon": "home",
        "fields": [
          {
            "title": "Company name",
            "identifier": "company_name",
            "type": "text"
          }
        ]
      },
      {
        "title": "Person",
        "identifier": "person",
        "description": "This is a person",
        "icon": "user",
        "fields": [
          {
            "title": "Firstname",
            "identifier": "firstname",
            "type": "text"
          },
          {
            "title": "Lastname",
            "identifier": "lastname",
            "type": "text"
          }
        ]
      }
    ]
  }
}

When accessing variants via the GraphQL API, you can use inline fragments to access the variant for each content item:

query {
  findContacts {
    result {
      name {
        type,
        
        ... on ContactsContentVariantCompanyVariant {
          company_name
        }

        ... on ContactsContentVariantPersonVariant {
          firstname,
          lastname
        }
      }
    }
  }
}

Another use case for variants fields (together with a collection field) are website layouts, where you want to allow the content editor to select one of multiple defined blocks (text, image, text+image):

(Coming soon)

Link

A Link field type that allows to define an external Link. Optionally a Link Title and Link Target Setting can be activated with the following Settings:

{
 ...
 "type": "link",
 "settings": {
   "title_widget": true,
   "target_widget": true
 }
}

State

A state field that allows to define workflows for your content.

The following settings are mandatory:

initial_place: The initial place for your content, usually this is the place "draft". This place has to be defined inside places.

places: All places a content piece can be transisted to. A place must have a label and can have a category which is used for different colors. Currently the following categories are provided: primary / notice / info / success / warning / error / danger

transitions: All transitions for the defined places. A transition must have a label, a "from" setting (which is an array of the allowed places keys) and a "to" setting, which is the place where the content should be transisted to.

A very common example would be:

{
 ...
 "type":"state",
 "settings":{
   "initial_place":"draft",
   "places":{
     "draft":{
       "label":"Draft",
       "category":"primary"
     },
     "review":{
       "label":"Review",
       "category":"info"
     },
     "published":{
       "label":"Published",
       "category":"success"
     }
   },
   "transitions":{
     "to_draft":{
       "label":"Set this content back to draft state",
       "from":[
         "published"
       ],
       "to":"draft"
     },
     "to_review":{
       "label":"Set this content to review state",
       "from":[
         "draft"
       ],
       "to":"review"
     },
     "to_published":{
       "label":"Set this content to published state",
       "from":[
         "review"
       ],
       "to":"published"
     }
   }
 }
}

Views

For each Content Type you can define one or multiple views for the management view of your content. The default view is a table, that just displays field columns and action buttons for each content row. Another example would be a media grid with drag and drop support view, that allows the user to sort images directly in the view. At the moment there are just two basic views available, however are planing to implement many different views (Kanban board, etc.) in the future!

Table

The following example defines a Table view for the Webpages Content Type. 

The following Settings are available:

sort (optional): Define the order of the content and if this order can be updated via drag and drop  fields (optional): Set the fields to display as table columns. If this field is left empty, unite tries to find a text field filter (optional): again a filter possibility (see Section Filtering in GraphQL API), in this case we are filtering all results for subline = "My Subline"

{
   ...,
   "content_types": [
       {
           "title": "Webpages",
           "identifier": "pages",
           "icon": "file",
           "fields": [
             { "title": "Headline", "identifier": "headline", "type": "text" },
             { "title": "Subline", "identifier": "subline", "type": "text" },
             { "title": "Description", "identifier": "description", "type": "text" },
             { "title": "Position", "identifier": "position", "type": "sortindex" }
           ]
       }
   ],
   "views": [
      {
          "title": "My Webpages Table",
          "identifier": "mytableview",
          "type": "table",
          "settings": {
            "sort": {
              "field": "position",
              "asc": true,
              "sortable": true
            },
            "fields": ["headline", "subline"],
            "filter": {
               "field": "subline",
               "operator": "=",
               "value": "My Subline"
            }
          }
      }
   ]
}

Fields can be set in the one of the following formats: 

Only identifiers

unite cms will use the field type of the fields to render the fields and the field title as column headline

{
  ...,
  "fields": ["id", "title", "updated"]
}
Fields and headlines

unite cms will use the field type of the fields to render the fields and your label 

{
  ...,
  "fields": {
    "id": "This is my id",
    "headline": "My custom label"
  }
}
Full field configuration

Configure title, identifier, type and settings for your fields. The following example would display one title column and one blocks column where all collection rows will be displayed.

{
  ...,
  "fields": [
    {
      "title": "Title",
      "identifier": "title",
      "type": "text"
    },
    {
      "identifier": "blocks",
      "title": "Blocks",
      "type": "collection",
      "settings": {
      "fields": {
        "icon": { "label": "Icon", "type": "text", "settings": [] },
        "content": { "label": "Content", "type": "wysiwyg" }
      }
    }
  ]

 

Grid

The grid view type displays your content in 2d grid. The grid view type accepts the same config as the table view type, however for each field you can set one additional boolean option "meta". If meta is set to true, the field will be rendered below the grid item. This should be used for non-primary information. A good example would be a media management view where the image of media content is the primary information and the updated date is the meta information: 

"views": [
  {
    "title": "All",
    "identifier": "all",
    "type": "grid",
    "settings": {
      "sort": {
        "field": "position",
        "sortable": true
      },
      "fields": {
        "position": "Position",
        "image": "Image",
        "updated": {
          "meta": true
        }
      }
    }
  }
]