Elemental is a Flask and MongoDB web CMS intended for developers.
Our main goal is to allow developers to create and maintain web portals or web applications using their preferred programming IDE like VS Code, PyCharm, Visual Studio, etc.
The main interaction with the tool takes place through its CLI, a self documented command line tool called "elemental-cms" which helps us perform deployment tasks directly from the terminal.
It relies on MongoDB to store the metadata, pages' content, snippets' content, dependencies information, and user session data.
Elemental CMS 2.0.6 is compatible with:
- Flask 2.2.5
- Werkzeug 2.2.3
- Flask-Babel 2.0.0
- Python 3.6+
For version history and changes, see our CHANGELOG.
- Official documentation construction
- Media files management on GCS module test classes
- Static files management on GCS module test classes
- Samples review and update
- Resources names validation for pages (similar to snippets)
- Configurations schema review
- Test coverage review
- Support for sample settings file generation
Elemental CMS uses a JSON configuration file that defines how the CMS operates. There are two main contexts:
DEBUG: Enable/disable debug mode (default: false)ENV: Environment name (e.g., "development", "production")SECRET_KEY: Flask secret key for session encryptionSITE_NAME: Your site's name, used in templatesCOMPANY: Your company nameCANONICAL_URL: Base URL for your site
LANGUAGES: List of supported language codes (e.g., ["en", "es"])DEFAULT_LANGUAGE: Default language code (e.g., "en")LANGUAGE_MODE: Either "single" or "multi" for language handling- "single": No language prefixes in URLs
- "multi": URLs prefixed with language code (e.g., /en/about)
STATIC_FOLDER: Local folder for static files (default: "static")MEDIA_FOLDER: Local folder for media files (default: "media")STATIC_URL: URL for static files- Local: "/static"
- Cloud: "https://storage.googleapis.com/your-bucket"
MEDIA_URL: URL for media files- Local: "/media"
- Cloud: "https://storage.googleapis.com/your-bucket"
STATIC_BUCKET: GCS bucket name for static filesMEDIA_BUCKET: GCS bucket name for media filesGOOGLE_SERVICE_ACCOUNT_INFO: GCS service account credentials- Required only when using Google Cloud Storage
- Used by CLI for file operations
GLOBAL_DEPS_FOLDER: Folder for global dependencies (default: "workspace/global_deps")PAGES_FOLDER: Folder for page files (default: "workspace/pages")SNIPPETS_FOLDER: Folder for snippet files (default: "workspace/snippets")
USER_IDENTITY_SESSION_KEY: Session key for user identity (default: "userIdentity")SESSION_STORAGE_ENABLED: Enable MongoDB session storage (default: true)SESSION_TIMEOUT_IN_MINUTES: Session timeout in minutes (default: 360)
DESIGN_MODE_ENABLED: Enable local file-based content editing (default: false)- When true: Content is read from local files
- When false: Content is read from MongoDB
id: Unique identifier for your MongoDB connectionconnectionString: MongoDB connection string- Format: "mongodb://username:password@hostname:port/admin?directConnection=true"
databaseName: Name of the MongoDB database to use
Elemental CMS uses two separate configuration files:
-
CLI Settings (
local.cli.json):- Used by the
elemental-cmscommand-line tool - Requires cloud storage configuration (
STATIC_BUCKET,MEDIA_BUCKET,GOOGLE_SERVICE_ACCOUNT_INFO) - Used for content management operations (push, pull, publish)
- Example operations that use CLI settings:
elemental-cms init -c settings/local.cli.json elemental-cms pages push -p home en elemental-cms media push image.jpg
- Used by the
-
Web App Settings (
local.www.json):- Used by your Flask web application
- Focuses on serving content and handling requests
- Does not need cloud storage credentials
- Used when running your web application
- Example usage:
CONFIG_FILEPATH = os.environ.get('CONFIG_FILEPATH', 'settings/local.www.json')
| Setting | CLI Config | Web App Config | Notes |
|---|---|---|---|
STATIC_BUCKET |
Required | Optional | CLI needs it for push/pull operations |
MEDIA_BUCKET |
Required | Optional | CLI needs it for media management |
GOOGLE_SERVICE_ACCOUNT_INFO |
Required | Not needed | Only CLI performs cloud operations |
STATIC_URL |
Cloud URL | Can be local | Web app can serve files locally |
MEDIA_URL |
Cloud URL | Can be local | Web app can serve files locally |
DESIGN_MODE_ENABLED |
Not used | Optional | Only affects web app behavior |
-
Use CLI config (
local.cli.json) to manage content:# Push content to cloud storage elemental-cms push --all -
Use Web config (
local.www.json) to serve content:# Web app serves content from local or cloud Elemental(www, elemental_context)
CLI Config (local.cli.json):
{
"cmsCoreContext": {
"DEBUG": true,
"ENV": "development",
"STATIC_URL": "https://storage.googleapis.com/static-bucket",
"MEDIA_URL": "https://storage.googleapis.com/media-bucket",
"STATIC_BUCKET": "static-bucket",
"MEDIA_BUCKET": "media-bucket",
"GOOGLE_SERVICE_ACCOUNT_INFO": {
"type": "service_account",
"project_id": "your-project"
},
"DESIGN_MODE_ENABLED": false
}
}Web App Config (local.www.json):
{
"cmsCoreContext": {
"DEBUG": true,
"ENV": "development",
"STATIC_URL": "/static",
"MEDIA_URL": "/media",
"DESIGN_MODE_ENABLED": true
}
}CLI Config (production.cli.json):
{
"cmsCoreContext": {
"DEBUG": false,
"ENV": "production",
"STATIC_URL": "https://storage.googleapis.com/static-bucket",
"MEDIA_URL": "https://storage.googleapis.com/media-bucket",
"STATIC_BUCKET": "static-bucket",
"MEDIA_BUCKET": "media-bucket",
"GOOGLE_SERVICE_ACCOUNT_INFO": {
"type": "service_account",
"project_id": "your-project"
}
}
}Web App Config (production.www.json):
{
"cmsCoreContext": {
"DEBUG": false,
"ENV": "production",
"STATIC_URL": "https://storage.googleapis.com/static-bucket",
"MEDIA_URL": "https://storage.googleapis.com/media-bucket",
"DESIGN_MODE_ENABLED": false
}
}Setup #
Once we have our project folder created and our virtual environment in place, we proceed to install Elemental CMS using pip.
pip install elemental-cmsThe CLI includes an "init" command which will create a basic working directory structure.
Before we can issue the "init" command, we have to create a config file inside a "settings" folder with at least the following content:
{
"cmsCoreContext": {
"DEBUG": true,
"ENV": "development",
"SECRET_KEY": "the-secret",
"SITE_NAME": "Elemental CMS",
"COMPANY": "Your company name",
"CANONICAL_URL": "https://elemental.cms",
"LANGUAGES": [
"en",
"es"
],
"DEFAULT_LANGUAGE": "en",
"LANGUAGE_MODE": "multi",
"STATIC_FOLDER": "static",
"MEDIA_FOLDER": "media",
"STATIC_URL": "https://storage.googleapis.com/static-files-bucket",
"MEDIA_URL": "https://storage.googleapis.com/media-files-bucket",
"STATIC_BUCKET": "static-files-bucket",
"MEDIA_BUCKET": "media-files-bucket",
"GLOBAL_DEPS_FOLDER": "workspace/global_deps",
"PAGES_FOLDER": "workspace/pages",
"SNIPPETS_FOLDER": "workspace/snippets",
"GOOGLE_SERVICE_ACCOUNT_INFO": {
"type": "service_account",
},
"USER_IDENTITY_SESSION_KEY": "userIdentity",
"SESSION_STORAGE_ENABLED": true,
"SESSION_TIMEOUT_IN_MINUTES": 360,
"DESIGN_MODE_ENABLED": true
},
"cmsDbContext": {
"id": "your-id",
"connectionString": "mongodb://your-username:your-pwd@your-host-name:27017/admin?directConnection=true",
"databaseName": "elemental_playground"
}
}After we create the config file under the name for example local.cli.json, we can issue the "init" command as shown below:
elemental-cms init -c settings/local.cli.jsonExecuting this command will create and update our .elemental metadata file setting the "configFilePath" property to "settings/local.cli.json", and it will update the folder structure which will ends looking like this:
workdir
└───media
└───settings
└───local.cli.json
└───static
└───templates
└───base.html
└───translations
└───workspace
└───global_deps
└───pages
└───snippets
└───.elemental
Be aware of that in Windows OS using Visual Studio 2019 after running the init command (and any other command that modify the folders and files structure), the created files and folders will not be added to the project automaticaly.
Elemental CMS supports shell autocomplete for commands and arguments, making it faster to work with snippets, pages, and global dependencies.
The completion scripts are included in the package. To enable autocomplete, run the setup script once:
# If you cloned the repo
./enable-completion.sh
# If you installed via pip, find and run the script
bash $(python -c "import elementalcms, os; print(os.path.join(os.path.dirname(os.path.dirname(elementalcms.__file__)), 'enable-completion.sh'))")
# Then reload your shell
source ~/.zshrc # for Zsh
# or
source ~/.bashrc # for BashOnce enabled, you can use TAB completion:
elemental-cms snippets diff -s <TAB> # Shows available snippet names
elemental-cms pages push -p home <TAB> # Shows available languages
elemental-cms global-deps push -d bootstrap <TAB> # Shows valid typesFor detailed instructions and troubleshooting, see COMPLETION.md.
To disable autocomplete:
./disable-completion.shSnippets are reusable HTML components that can be included in your pages. They are managed through the snippets command.
- Must be lowercase (e.g.,
header,nav-bar) - Must start with a letter
- Can only contain letters, numbers, and hyphens
- Examples of valid names:
nav-bar,footer-2,main-menu - Examples of invalid names:
Header(uppercase),1nav(starts with number),nav_bar(underscore)
elemental-cms snippets create -s nav-barThis will create two files in your SNIPPETS_FOLDER:
nav-bar.json: Contains snippet metadata and dependenciesnav-bar.html: Contains the HTML content
# List all snippets (shows * for snippets that: have local changes, are missing local files, or exist locally but not in the database)
elemental-cms snippets list
# Compare local and database versions of a snippet
elemental-cms snippets diff -s nav-bar
# Push a snippet to CMS
elemental-cms snippets push -s nav-bar
# Push all snippets
elemental-cms snippets push --all
# Pull a snippet from CMS
elemental-cms snippets pull -s nav-bar
# Remove a snippet
elemental-cms snippets remove -s nav-barPages are the main content units in Elemental CMS. Each page consists of a spec file (metadata) and a content file (HTML).
elemental-cms pages create -p home enThis creates two files in your PAGES_FOLDER/en directory:
home.json: Contains page metadata and dependencieshome.html: Contains the HTML content
# List all pages (shows * for pages that: have local changes, are missing local files, or exist locally but not in the database)
elemental-cms pages list
# Compare local and database versions of a page
elemental-cms pages diff -p home en
# Compare draft version
elemental-cms pages diff -p home en --drafts
# Push a page to CMS
elemental-cms pages push -p home en
# Push all pages
elemental-cms pages push --all
# Pull a page from CMS
elemental-cms pages pull -p home en
# Pull all pages
elemental-cms pages pull --all
# Publish a page
elemental-cms pages publish -p home en
# Publish all pages that have draft versions
elemental-cms pages publish --all
# Unpublish a page
elemental-cms pages unpublish -p home en
# Remove a page
elemental-cms pages remove -p home enThe spec file (home.json) contains the page metadata:
{
"_id": {
"$oid": "619b8f70f065731d43fb11fc"
},
"name": "home",
"language": "en",
"title": "home page",
"description": "",
"isHome": false,
"cssDeps": [],
"jsDeps": [],
"createdAt": {
"$date": 1637584752066
},
"lastModifiedAt": {
"$date": 1637584752066
}
}The content file will have the HTML for the page.
<div></div>-
Draft vs Published: When pushing a page, it creates a "draft" version in the database. Use
publishto make it accessible through the web application. -
Repository Difference Indicators: The
listcommand shows an asterisk (*) next to pages that:- Have differences between local and database versions
- Are missing their local files
- Exist locally but not in the database
-
Batch Operations: Some commands support
--allflag:push --all: Push all pagespull --all: Pull all pagespublish --all: Publish all pages that have draft versions
We have created a multilanguage page and successfully published it, but we are missing our application entry point.
Since this framework is based on Flask we can create an entry point just like we will do it for any other Flask application; a simple boilerplate can be found down below:
import json
import os
from elementalcms import Elemental, ElementalContext
from elementalcms.core import FlaskContext, MongoDbContext
from flask import Flask
www = Flask(__name__, template_folder='templates', static_folder='static')
CONFIG_FILEPATH = os.environ.get('CONFIG_FILEPATH', 'settings/local.www.json')
with open(CONFIG_FILEPATH) as config_file:
settings = json.load(config_file)
cms_core_context = FlaskContext(settings['cmsCoreContext'])
cms_db_context = MongoDbContext(settings['cmsDbContext'])
elemental_context = ElementalContext(cms_core_context, cms_db_context)
Elemental(www, elemental_context)
if __name__ == '__main__':
www.run(host='0.0.0.0', port=8000)Note that in order to run the app locally we need another setting file (local.www.sjon) with some minor modifications. Like the one shown below:
{
"cmsCoreContext": {
"DEBUG": true,
"ENV": "development",
"SECRET_KEY": "the-secret",
"SITE_NAME": "Elemental CMS",
"COMPANY": "Your company name",
"CANONICAL_URL": "https://elemental.cms",
"LANGUAGES": [
"en",
"es"
],
"DEFAULT_LANGUAGE": "en",
"LANGUAGE_MODE": "multi",
"STATIC_FOLDER": "static",
"MEDIA_FOLDER": "media",
"STATIC_URL": "/static",
"MEDIA_URL": "/media",
"GLOBAL_DEPS_FOLDER": "workspace/global_deps",
"PAGES_FOLDER": "workspace/pages",
"SNIPPETS_FOLDER": "workspace/snippets",
"USER_IDENTITY_SESSION_KEY": "userIdentity",
"SESSION_STORAGE_ENABLED": true,
"SESSION_TIMEOUT_IN_MINUTES": 360,
"DESIGN_MODE_ENABLED": true
},
"cmsDbContext": {
"id": "your-id",
"connectionString": "mongodb://your-username:your-pwd@your-host-name:27017/admin?directConnection=true",
"databaseName": "elemental_playground"
}
}In this file we do not need buckets information, sinces static and media resoruces will be served locally. We do not need a Google Service Account niether because that info is needed only by the cli tool to send local files to GCS.
In Visual Studio 2019 we have some minor challenges to get started due to "problems" related with the operative system security policies more than with the tool.
- We start by creating a Python project and adding a virtual environment
- Then visual studio offers developer terminals in at least 2 flavors:
- Developer PowerShell where the environment do not get activated by default, so we must activate it by running the command:
.\.venv\Scripts\Activate.ps1Depending on your security policies this command will or will not ork. When it does not work it will show an error telling you something like: "Activate.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170."
To overcome this situation we must enable running scripts at least to the current user, following the official Microsoft documentation.
- Developer Command Promt were the environment do not get activated by default, so we must activate it by running the command:
.venv\Scripts\activateIn both cases we assume you create the virtual environment under the name .venv
Resolving this minor setbacks we can go back to the Setup step and follow the getting started guide normaly.