Object Linking
This document covers how to create and manage URLs for collection objects in Total CMS, including the powerful URL templating system for creating SEO-friendly, human-readable URLs.
Overview
Total CMS provides flexible URL generation for collection objects:
- Simple URLs:
/blog/my-post-id - Query String URLs:
/blog/?id=my-post-id - Templated URLs:
/blog/technology/2025/my-post-id
Collection URL Settings
Each collection has URL-related settings configured in the collection form:
| Setting | Description |
|---|---|
url |
Base URL path for the collection (e.g., /blog/) |
prettyUrl |
Enable pretty URLs (/blog/my-post) vs query strings (/blog/?id=my-post) |
URL Templating
URL templates allow you to include object field values in the URL path, creating SEO-friendly, descriptive URLs.
Basic Syntax
Use double curly braces to insert field values:
/blog/{{ category }}/{{ id }}
For an object with category: "technology" and id: "my-post", this generates:
/blog/technology/my-post
Template Examples
# Include category in URL
/blog/{{ category }}/{{ id }}
# Result: /blog/technology/my-post
# Multi-level hierarchy
/campsites/{{ region }}/{{ county }}/{{ id }}
# Result: /campsites/pacific-northwest/king-county/pine-grove-camp
# Year-based blog URLs
/news/{{ year }}/{{ month }}/{{ id }}
# Result: /news/2025/12/holiday-announcement
Auto-Appending ID
If your template doesn't include {{ id }}, Total CMS automatically appends it:
# Template configured as:
/blog/{{ category }}
# Automatically becomes:
/blog/{{ category }}/{{ id }}
Available Filters
Filters transform field values before inserting them into the URL. Chain multiple filters with the pipe (|) character:
| Filter | Description | Example |
|---|---|---|
lower |
Convert to lowercase | {{ category \| lower }} |
upper |
Convert to uppercase | {{ category \| upper }} |
trim |
Remove leading/trailing whitespace | {{ category \| trim }} |
raw |
Skip automatic slug conversion | {{ category \| raw }} |
Filter Examples
# Force lowercase (values are auto-slugified anyway)
/blog/{{ category | lower }}/{{ id }}
# Chain multiple filters
/blog/{{ category | trim | lower }}/{{ id }}
# Preserve exact value (skip slugify)
/files/{{ path | raw }}
Automatic Slugification
All template values are automatically converted to URL-safe slugs unless the raw filter is used:
- Converts to lowercase
- Replaces spaces and special characters with hyphens
- Removes leading/trailing hyphens
Input: "Technology & Science"
Output: "technology-science"
Input: "My Great Post!"
Output: "my-great-post"
Pretty URL Requirement
URL templates only work when prettyUrl is enabled for the collection. If prettyUrl is disabled, the template syntax is ignored and query string format is used:
# With prettyUrl: true
Template: /blog/{{ category }}/{{ id }}
Result: /blog/technology/my-post
# With prettyUrl: false
Template: /blog/{{ category }}/{{ id }}
Result: /blog/{{ category }}/{{ id }}?id=my-post
Twig Functions
cms.objectUrl()
Get the URL for an object. Supports both simple ID lookup and full object data.
{# Using object ID #}
<a href="{{ cms.objectUrl('blog', 'my-post') }}">Read More</a>
{# Using full object (more efficient for templated URLs) #}
{% for post in cms.objects('blog') %}
<a href="{{ cms.objectUrl('blog', post) }}">{{ post.title }}</a>
{% endfor %}
Parameters:
collectionId(string): The collection IDidOrObject(string|array): Object ID or full object array
Returns: The relative URL path
cms.canonicalObjectUrl()
Get the absolute canonical URL for an object, including scheme and domain.
{# Generate canonical URL for SEO #}
<link rel="canonical" href="{{ cms.canonicalObjectUrl('blog', post) }}">
{# Use in Open Graph tags #}
<meta property="og:url" content="{{ cms.canonicalObjectUrl('blog', post) }}">
Parameters:
collectionId(string): The collection IDidOrObject(string|array): Object ID or full object array
Returns: Absolute URL (e.g., https://example.com/blog/technology/my-post)
cms.redirectToCanonicalUrl()
Redirect visitors to the canonical URL if they accessed the page via a non-canonical path. Essential for SEO when using templated URLs.
{# At the top of your object template #}
{{ cms.redirectToCanonicalUrl('blog', post) }}
{# With custom redirect method #}
{{ cms.redirectToCanonicalUrl('blog', post, 'meta') }}
{# With custom HTTP status (302 temporary instead of 301 permanent) #}
{{ cms.redirectToCanonicalUrl('blog', post, 'header', 302) }}
Parameters:
collectionId(string): The collection IDidOrObject(string|array): Object ID or full object arraymethod(string, optional): Redirect method -'header'(default),'meta','js', or'both'httpStatus(int, optional): HTTP status code for header redirects -301(default) or302
Redirect Methods:
| Method | Description |
|--------|-------------|
| header | HTTP 301/302 redirect (best for SEO, must be before output) |
| meta | HTML meta refresh tag |
| js | JavaScript redirect |
| both | Both meta refresh and JavaScript |
Returns: HTML string for non-header methods, or exits immediately for header redirects
Use Case: Old URL Compatibility
When users access an old URL format, redirect them to the canonical URL:
{# Someone visits /blog/?id=my-post but canonical is /blog/technology/my-post #}
{{ cms.redirectToCanonicalUrl('blog', post) }}
{# Query parameters (like UTM tracking) are preserved on redirect #}
URL Validation Functions
cms.hasTemplateUrl()
Check if a collection uses templated URLs.
{% if cms.hasTemplateUrl('blog') %}
<p>This collection uses templated URLs</p>
{% endif %}
cms.validateUrlTemplateFields()
Validate URL template fields against the schema. Returns warnings about potential issues.
{% set validation = cms.validateUrlTemplateFields('blog') %}
{% if validation.notIndexed is not empty %}
<div class="warning">
Fields not in index (won't work in sitemap/RSS):
{{ validation.notIndexed | join(', ') }}
</div>
{% endif %}
{% if validation.notRequired is not empty %}
<div class="warning">
Fields not required (may cause broken URLs):
{{ validation.notRequired | join(', ') }}
</div>
{% endif %}
{% if validation.prettyUrlDisabled %}
<div class="warning">
Pretty URLs are disabled - template syntax will be ignored
</div>
{% endif %}
Returns:
[
'notIndexed' => ['category'], // Fields not in schema index
'notRequired' => ['category'], // Fields not marked as required
'prettyUrlDisabled' => false // Whether prettyUrl is disabled
]
cms.objectUrlHasEmptySegments()
Check if an object's URL has empty segments (missing template data).
{% if cms.objectUrlHasEmptySegments('blog', post) %}
<div class="warning">
This post has missing URL fields and won't appear in sitemaps
</div>
{% endif %}
cms.getUrlTemplateFields()
Get the list of fields used in a collection's URL template.
{% set fields = cms.getUrlTemplateFields('blog') %}
{# Returns: ['category', 'id'] for template /blog/{{ category }}/{{ id }} #}
<p>URL uses these fields: {{ fields | join(', ') }}</p>
Best Practices
1. Use Indexed Fields
Fields used in URL templates should be added to the schema's index array. This ensures the field data is available when generating sitemaps and RSS feeds.
{
"id": "blog",
"index": ["title", "category", "date"]
}
2. Use Required Fields
Mark URL template fields as required to prevent broken URLs from missing data:
{
"id": "blog",
"required": ["title", "category"]
}
3. Pass Full Objects When Possible
When iterating over objects, pass the full object to URL functions for better performance:
{# Good - uses existing object data #}
{% for post in cms.objects('blog') %}
<a href="{{ cms.objectUrl('blog', post) }}">{{ post.title }}</a>
{% endfor %}
{# Less efficient - fetches object again #}
{% for post in cms.objects('blog') %}
<a href="{{ cms.objectUrl('blog', post.id) }}">{{ post.title }}</a>
{% endfor %}
4. Always Use Canonical Redirects
When using templated URLs, implement canonical redirects to handle old URL formats:
{# At the top of your object detail template #}
{{ cms.redirectToCanonicalUrl('blog', post) }}
5. Handle Empty Segments
Objects with missing template data will have broken URLs. The sitemap and RSS builders automatically skip these objects, but you may want to warn content editors:
{% if cms.objectUrlHasEmptySegments('blog', post) %}
{# Show warning or handle gracefully #}
{% endif %}
Sitemap and RSS Feed Integration
Objects with valid templated URLs are automatically included in sitemaps and RSS feeds. Objects with empty URL segments (missing template data) are automatically excluded.
{# Sitemap generation handles templated URLs automatically #}
{{ cms.sitemap('blog') }}
{# RSS feed also uses templated URLs #}
{{ cms.rssFeed('blog') }}
Migration from Simple URLs
When migrating from simple URLs to templated URLs:
- Update the collection's
urlsetting with the template - Ensure all required fields have values in existing objects
- Implement
redirectToCanonicalUrl()to handle old URL formats - Update any hardcoded URLs in your templates
- Submit updated sitemap to search engines