Skip to content

Total CMS Twig Adapter

The Total CMS Twig Adapter provides access to all CMS data and functionality through the global cms variable in Twig templates. Methods are organized into namespaces for clarity.

NamespaceDescriptionDocumentation
cms.admin.*Dashboard stats, job queue, dev mode, URL helpersAdmin Reference
cms.auth.*Login/logout, user info, access control, passkeysAuth Reference
cms.barcode.*Barcode generation (Code 128, EAN-13, UPC-A, etc.)Barcode Reference
cms.collection.*Collection listing, objects, search, URLs, navigationCollections Reference
cms.data.*Typed data access (text, toggle, date, color, etc.)Data Reference
cms.edition.*Edition detection and feature gatingEdition Reference
cms.locale.*Localization and translationsLocale Reference
cms.media.*Image paths, gallery paths, downloads, streamingMedia Reference
cms.qrcode.*QR code generationQR Code Reference
cms.render.*HTML rendering for images, galleries, paginationRender Reference
cms.schema.*Schema listing, fetching, inheritance, decksSchemas Reference
cms.view.*Pre-computed data viewsViews Reference

Standalone systems:

SystemDescriptionDocumentation
ImageWorksImage transformation, watermarks, presets, cachingImageWorks Reference

These properties are accessed directly on cms without a namespace.

{{ cms.env }} {# Current environment (development, production) #}
{{ cms.api }} {# API base URL #}
{{ cms.dashboard }} {# Admin dashboard URL #}
{{ cms.domain }} {# Current domain name #}
{{ cms.clearcache }} {# Emergency cache clear URL #}
{{ cms.currentUrl }} {# Current request URI #}
{{ cms.version }} {# Version information object #}

Get a configuration value by key, with optional nested setting.

{{ cms.config('debug') }}
{{ cms.config('key', 'setting') }}
ParameterTypeDefaultDescription
keystringrequiredTop-level config key
settingstring|nullnullNested setting within the key

Log messages from templates. Written to twig.log and viewable in the admin log analyzer.

{{ cms.log('Something unexpected happened') }}
{{ cms.log('Missing image for product', 'error') }}
{{ cms.log('Debug info', 'debug', {id: object.id}) }}
ParameterTypeDefaultDescription
messagestringrequiredThe message to log
levelstring'warning'Log level: debug, info, warning, error
contextobject{}Additional context data

For the complete ImageWorks reference (resizing, cropping, effects, watermarks, presets, defaults, color palettes), see the dedicated ImageWorks documentation.

Galleries support two independent caption options:

  • captions - Captions inside the lightbox overlay
  • gridCaptions - Captions below thumbnails in the grid

Both accept either true for default behavior or a Twig template string for custom formatting.

When set to true, captions use this fallback chain: alt text → EXIF title → EXIF description. If none are available, no caption is shown. Filenames are never used as captions.

{{ cms.render.gallery('id', {w: 300}, {w: 1500}, {
captions: true,
gridCaptions: true
}) }}

Pass a template string to customize caption content. Use single curly braces {variable} for template variables — they are automatically converted to Twig syntax before rendering.

{# Simple alt text caption #}
{% set caption = "{alt}" %}
{{ cms.render.gallery('id', {}, {}, {captions: caption}) }}
{# Photography captions with EXIF data #}
{% set caption %}
<h4>{alt}</h4>
<p>{exif.camera} · {exif.lens}</p>
<p>f/{exif.aperture} · {exif.shutterSpeed} · ISO {exif.iso}</p>
{% endset %}
{{ cms.render.gallery('id', {}, {}, {captions: caption}) }}
{# Different templates for grid and lightbox #}
{% set gridCaption = "{alt}" %}
{% set lightboxCaption %}
<h4>{alt}</h4>
<p>{exif.description}</p>
<p>{exif.camera} — f/{exif.aperture}</p>
{% endset %}
{{ cms.render.gallery('id', {w: 300}, {w: 1500}, {
gridCaptions: gridCaption,
captions: lightboxCaption
}) }}

When a template string is used, HTML is preserved (not escaped). When set to true, plain text captions are HTML-escaped. If all template variables resolve to empty, the caption is suppressed entirely.

Image fields:

VariableDescription
altAlt text
nameFilename
widthImage width in pixels
heightImage height in pixels
mimeMIME type
sizeFile size in bytes
linkOptional link URL
tagsArray of tags
uploadDateUpload date (ISO 8601)

EXIF metadata (available when the image contains EXIF data):

VariableDescription
exif.titleImage title
exif.descriptionImage description
exif.cameraCamera model
exif.makeCamera manufacturer
exif.lensLens model
exif.aperturef-number (e.g., 1.8)
exif.shutterSpeedShutter speed (e.g., 1/125)
exif.isoISO sensitivity
exif.focalLengthFocal length in mm
exif.datePhoto capture date
exif.authorPhotographer name
exif.copyrightCopyright notice
exif.cityCity
exif.stateState/Province
exif.countryCountry
exif.sublocationSpecific location
exif.latitudeGPS latitude
exif.longitudeGPS longitude
exif.altitudeGPS altitude

By default, gallery images display in their stored order. Use the sort option with both cms.render.gallery() and cms.render.galleryLauncher().

{# Simple sort #}
{{ cms.render.gallery('id', {}, {}, {sort: 'name'}) }}
{{ cms.render.gallery('id', {}, {}, {sort: 'uploadDate'}) }}
{# Descending (prefix with -) #}
{{ cms.render.gallery('id', {}, {}, {sort: '-exif.date'}) }}
{# Multi-criteria sort #}
{{ cms.render.gallery('id', {}, {}, {sort: [
{property: 'featured', reverse: true},
{property: 'exif.date', reverse: true}
]}) }}

Sortable Properties:

PropertyDescription
nameFilename (natural sort)
uploadDateUpload date (ISO 8601)
sizeFile size in bytes
widthImage width in pixels
heightImage height in pixels
featuredBoolean — useful for sorting featured images first
exif.dateDate photo was taken
exif.cameraCamera model
exif.focalLengthFocal length
exif.apertureAperture value
exif.isoISO sensitivity
{% set post = cms.collection.object('blog', 'my-post-id') %}
<article>
<h1>{{ post.title }}</h1>
<time>{{ post.date|dateRelative }}</time>
{{ post.content|markdown }}
{{ cms.render.image(post.id, {w: 800, h: 400, fit: 'crop'}) }}
</article>
{% set product = cms.collection.object('products', 'widget-pro') %}
<div class="product-gallery">
{{ cms.render.gallery(product.id, {w: 100, h: 100}, {w: 1200}, {
maxVisible: 4,
viewAllText: 'View all images'
}) }}
</div>
{% set product = cms.collection.object('products', 'widget-pro') %}
{{ cms.render.galleryLauncher(product.id, {w: 300, h: 300}, {w: 1920}, {
captions: true,
trigger: '.product-thumb',
plugins: ['zoom', 'fullscreen']
}) }}
<div class="product-images">
<img class="product-thumb main-image"
data-gallery-image="front.jpg"
src="{{ cms.media.galleryPath(product.id, 'front.jpg', {w: 600, h: 400}) }}">
<div class="thumbnail-strip">
<img class="product-thumb"
data-gallery-image="side.jpg"
src="{{ cms.media.galleryPath(product.id, 'side.jpg', {w: 100, h: 100}) }}">
<img class="product-thumb"
data-gallery-image="detail.jpg"
src="{{ cms.media.galleryPath(product.id, 'detail.jpg', {w: 100, h: 100}) }}">
</div>
<button data-gallery="gallery-{{ product.id }}">View All Photos</button>
</div>
{% if cms.auth.verifyFilePassword(password, 'documents', docId, 'file') %}
<a href="{{ cms.media.download(docId, {pwd: password}) }}">Download Document</a>
{% else %}
<p>Invalid password</p>
{% endif %}
{% set results = cms.collection.search('blog', query, ['title', 'content', 'tags']) %}
{% set page = app.request.get('page', 1) %}
{% set perPage = 10 %}
{% set paged = results|paginate(perPage, page) %}
{% for item in paged %}
<article>{{ item.title }}</article>
{% endfor %}
{{ cms.render.paginationFull(results|length, page, perPage) }}
{# Simple text watermark #}
{{ cms.media.imagePath('hero-image', {
w: 1200,
h: 600,
marktext: 'Copyright 2024'
}) }}
{# Styled text watermark with custom font #}
{{ cms.media.imagePath('product-photo', {
w: 800,
marktext: 'Premium Quality',
marktextfont: 'Dorsa-Regular',
marktextsize: 120,
marktextcolor: 'ffffff',
marktextbg: '000000',
marktextpad: 20,
marktextpos: 'bottom-right',
marktextalpha: 80
}) }}
{# Rotated watermark #}
{{ cms.media.imagePath('landscape', {
marktext: 'DRAFT',
marktextsize: 200,
marktextangle: -45,
marktextcolor: 'ff0000',
marktextpos: 'center',
marktextalpha: 50
}) }}