A webapp UI which allows browsing/editing the json content served by the acms-api service.
Copy config.json.template into /public/config.json and change values accordingly. config.jsonis served by vite dev server or provided in a production environment.
acmsAssetsUri: Fully qualified URI to a WebDav enabled server, acms-assets.
acmsApiUri: Fully qualified URI to the acms-api service.
acmsConfigPath: Relative directory path inside the acms-config to the ACMS UI configuration JSON file (usually acmsConfig, see below).
defaultBranch: The default Git branch used for reading and writing content, default: "main". Breaking change: this used to default to "master" — existing deployments relying on the old default must explicitly set "defaultBranch": "master" in their config.json.
Create a configuration file (e.g. acmsConfig.json) inside the acms-config repo:
The user management is not safe and only meant to provide convenience views on the content. Its possible to filter fields and children by path (see users/permissions) to filter internal data.
This application uses Redux Toolkit. For the best debugging experience, install the Redux DevTools Chrome Extension. It allows you to inspect the state, replay actions, and trace changes without needing redux-logger.
Template files specify the structure of the content data while the actual values are located in the content directory. Every template entity can have children which themself are structured by a template. Nested templates within the templates directory are referenced via local path. E.g.:
/templates/template1.json // => `template1`
/templates/template2.json // => `template2`
/templates/template3/index.json // => `template3`
/templates/template3/template4.json // => `template3/template4`
{
"fields": [
// optional list of fields
{
"id": "title", // unique id within the whole template
"name": "Main Title", // optional display name shown in the ACMS UI, default startCase(id)
"type": "string", // field type see below
"maxLength": 8, // optional type specific properties
"localization": ["en", "ar"], // optional localization
"preview": "https://previewer.de/?fieldValue=${value}", // optional preview link
},
{
"id": "enabled",
"name": "Page Enabled",
"type": "boolean",
},
{
"id": "numLoops",
"name": "Number of Loops performed",
"type": "number",
"integer": true,
},
],
"fixedChildren": [
// optional list of fixed/named children
{
"id": "frontScreen", // unique id within the whole template
"name": "Front of the Entry", // optional display name shown in the ACMS UI, default startCase(id)
"template": "location", // template id/path
},
{
"id": "backScreen",
"name": "Back of the Entry",
"template": "location",
},
],
"children": [
// optional list of of allowed templates for the children
"template1",
"template2",
],
"enabledField": "enabled", // optional field reference defining if the instance is shown enabled or disabled in its parent children list
"subtitleField": "title", // optional field reference defining a subtitle shown in its parent children list
}The content tree is loaded from the ACMS API, which merges all JSON files below the content path into one nested structure. A save sends the whole content directory back as a map of files. The ACMS UI writes the files in the structure they were loaded from:
- An entity stored as
about.jsonis saved asabout.json, an entity stored asabout/index.jsonasabout/index.json. New entities are created as<id>/index.json. - Entries without a
templatekey are not managed by the ACMS UI. They are hidden, left untouched on load and written back unchanged: their original files are copied, or they stay inside the index file of their parent if they were stored there. - Entries whose template is not listed in
childrenof their parent, and values that are neither fields nor children, are deleted on save.
- All managed files are rewritten as strict JSON with the fields in template order. Comments (JSON5) are lost.
- Files that are not JSON are removed by the ACMS API on every save.
- If an entity exists in two forms at once (
about.jsonandabout/index.json), the API merges both on load and the ACMS UI saves oneabout/index.json. If both files set the same key, only one value is kept.
The following field types are supported:
An uploadable asset which is stored on the asset server with a unique (hashed) filename.
Example:
{
"id": "bachelorThesis",
"name": "Bachelor Thesis",
"type": "file",
"allowedMimeTypes": ["application/pdf"], // optional list of allowed mime types
}An uploadable asset which is stored on the asset server with a unique (hashed) filename
allowedMimeTypes: optional list of allowed mime types, default:["image/*"]width: optional width of the imageminWidth: optional minimum width of the imagemaxWidth: optional maximum width of the imageheight: optional height of the imageminHeight: optional minimum height of the imagemaxHeight: optional maximum height of the imageaspectRatio: optional aspect ratio of the image
Example:
{
"id": "coverImage",
"name": "Front Cover Image",
"type": "image",
"allowedMimeTypes": ["image/jpeg", "image/png"],
"width": 1920,
"minWidth": 800,
"maxWidth": 2200,
"height": 1080,
"minHeight": 800,
"maxHeight": 1600,
"aspectRatio": "16:9",
}A string type with the following otional properties:
multiline: Defines wether the string can have multiple lines, default:falsemaxLength: Defines the maximum number of characters, default:Infinity
Example:
{
"id": "label",
"name": "Start Label",
"type": "string",
"maxLength": 32,
"multiline": true,
}A number type with the following optional properties:
min: The minimum value, default-Infinitymax: The maximum value, defaultInfinityinteger: Defines wether the number is an integer, default:false
Example:
{
"id": "numLoops",
"name": "Number of loops",
"type": "number",
"min": 0,
"max": 100,
}A geolocation type with values for lat (Latitude) and long (Longitude).
min: The minimum value, defaultlat: -90,long: -180max: The maximum value, defaultlat: 90,long: 180
Example:
{
"id": "location",
"type": "geolocation",
}A boolean either being true or false.
Example:
{
"id": "active",
"name": "Active State",
"type": "boolean",
}A list of selectable values.
Example:
{
"id": "myEnum",
"name": "My Enum Type",
"type": "enum",
"values": [
{
"value": "value1",
"name": "First Value",
},
{
"value": [2, 3, 4],
"name": "Second Value",
},
{
"value": {
"thirdValue": "thirdValue",
},
"name": "Third Value",
},
{
"value": true,
"name": "Fourth Value",
},
{
"value": "no name",
},
],
}Note: As shown in the last entry of the values array, the name property is not required. The displayed name will then be generated based on the type of value.
It is possible to hide/show fields in the CMS frontend depending on sibling fields value. A typical case is an enum field which specifies a layout type (e.g. videoCover/imageCover). Based on the field value you want to either show an image or video field. This can be achieved with the following GETand EQUALS operators:
{
"id": "layoutType",
"type": "enum",
"values": ["videoCover", "imageCover"]
},
{
"id": "coverVideo",
"type": "video",
"condition": [ "EQUALS", [ "GET", "layoutType" ], "videoCover" ]
},
{
"id": "coverImage",
"type": "image",
"condition": [ "EQUALS", [ "GET", "layoutType" ], "imageCover" ]
}If several values should be considered you can use the IN and LIST operator:
{
"id": "coverVideo",
"type": "string",
"condition": ["IN", ["GET", "layoutType"], ["LIST", "videoCover", "imageCover"]],
}- Save on top of the loaded commit. The ACMS UI sends the default branch name as parent version when saving. The ACMS API then writes on top of the latest commit of the branch and does not merge. Changes committed by others after the content was loaded are overwritten without a conflict. Using the commit hash of the loaded content as parent version would make the API merge the changes or report a conflict.
- Keep split entries without a template intact. If an entry without a
templatekey is stored partly in the file of its parent and partly in its own files, only its own files are written back and the part inside the parent file is lost. - Write only changed files. The ACMS API deletes the whole content directory on every save. Sending only changed files and an explicit list of deletions would keep untouched files and formatting.
{ "title": "ACMS", // title shown in the header, default: null "logoImageUri": "https://assets/logo.jpg", // logo shown in the header, default: null "contentPath": "content", // root directory containing the content data, default: "content" "templatesPath": "templates", // root directory containing the (nested) template files, default: "templates" "childrenLabel": "Children", // label shown above the children, default: "Children" "fieldsLabel": "Fields", // label shown above the fields, default: "Fields" "saveLabel": "Save", // label of the save button, default: "Save" "textDirection": "ltr", // optional text direction for non localized text, default: "ltr", see https://developer.mozilla.org/de/docs/Web/CSS/ "languages": [ // optional language configuration { "id": "en", // unique language id (e.g. "en"), see language subtag registry: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry "name": "English", // language name, default: id "textDirection": "ltr", // direction of the text, default: "ltr", see https://developer.mozilla.org/de/docs/Web/CSS/direction }, ], "users": [ // list of users, default: shown here { "id": "admin", // unique user id "name": "Admin", // user name, default: id "permissions": { // permission settings, default: shown here "include": [ "**", // list of glob patterns defining which fields to include, default: shown here ], "exclude": [], // list of glob patterns defining which fields to exclude, default: shown here }, }, ], "customTypes": { // a collection of custom types with preconfigured properties, the custom type id can be used in templates "myCustomString": { "type": "string", "maxLength": 123, "multiline": true, }, }, }