Every Core dna product, release by release, newest first. Each product ships on its own schedule.
Platform2026.11
What’s new in Core dna Platform 2026.11
Container Pages, Purchase Controls and Bug Fixes
Container pages group pages under a shared URL segment, with no page at that address. Purchase controls set quantity rules on products and variants.
Key highlights
Container pages: group pages under a URL segment such as /solutions, with no page published at that address.
Purchase controls: set a minimum, maximum, quantity step, multiple and overall quantity per customer on each product and variant.
Changes in behaviour
Products with variants: you can no longer add a product that has variants to the cart. A shopper must choose one of its variants. Existing cart lines for such a product become unavailable with the message "Item has variants, one of them has to be chosen instead". Check that your product pages add a variant to the cart.
Translated blogs: on multi-language sites, the post, tag, category and author listings and the RSS feed of a blog now use the blog's language before the visitor's language cookie. Check that each language has its own translated blog.
Container pages
A container page is a node in the page tree. It gives its URL segment to the pages beneath it, but visitors never see it. A page called Consulting under a container with the slug solutions has the address /solutions/consulting, and /solutions itself is not a page.
To add a container, go to Content › Pages, open the add menu on the parent page's row and choose Add Container page. The same menu has Add subpage for an ordinary page. A container has a name, a slug, a parent and a site, but no layout and no content. Its name shows only in the page tree. A container can hold other containers. The root page cannot be a container.
Under Direct URL requests, Direct URL behavior sets what happens when someone opens the container's own address: Return a 404 or Redirect to the nominated page. A redirect goes to the page in Redirect to, with the Redirect typeTemporary (302) or Permanent (301), and keeps the query string. The target must be a page on the same site, and it cannot be another container. If the target is later deleted or unpublished, the container returns a 404. The Pages setting Default behavior for new container pages chooses which option the add form selects first.
A container has no address, so the sitemap and the search index leave it out. The pages beneath it stay in both. The frontend pages listing also leaves containers out. Menus have a new element type, Container page. A menu element for a container has an empty URL. In templates, isContainer() is true for a container and false for every other page. On a container, getFullPath() and getUrl() return an empty string. Use isContainer() to show a container as a label instead of a link, for example in a breadcrumb trail. In the API, each page record has a new container field.
Template code
{* Breadcrumb trail that shows container pages as labels *}
<nav aria-label="Breadcrumb"><ol class="breadcrumb">
{foreach $page->getAllParents() as $ancestor}
{if $ancestor->isContainer()}
<li>{$ancestor->getName()|escape}</li>
{else}
<li><a href="{$ancestor->getFullPath()}">{if $ancestor@first}Home{else}{$ancestor->getName()|escape}{/if}</a></li>
{/if}
{/foreach}
<li aria-current="page">{$page->getName()|escape}</li>
</ol></nav>
{# Breadcrumb trail that shows container pages as labels #}
<nav aria-label="Breadcrumb"><ol class="breadcrumb">
{% for ancestor in page.getAllParents() %}
{% if ancestor.isContainer() %}
<li>{{ ancestor.getName()|e }}</li>
{% else %}
<li><a href="{{ ancestor.getFullPath() }}">{% if loop.first %}Home{% else %}{{ ancestor.getName()|e }}{% endif %}</a></li>
{% endif %}
{% endfor %}
<li aria-current="page">{{ page.getName()|e }}</li>
</ol></nav>
Purchase controls
Products and variants have a new Purchase controls tab with six rules. Each field is empty by default, and an empty field sets no rule.
Minimum quantity: the smallest quantity a customer can buy.
Maximum quantity: the largest quantity a customer can have in the cart.
Quantity step: quantities count up from the minimum in these steps. Minimum 2 and step 3 allow 2, 5, 8 and so on.
Multiple of: only multiples of this number can be bought. 5 allows 5, 10, 15 and so on.
Overall quantity: the total a customer can buy, counting the orders they have already placed.
Overall period: how far back the overall quantity counts, in seconds (2592000 is 30 days). Empty or 0 counts every past order.
The rules of a variant apply to that variant only. A variant does not take the rules of its product. Cart lines with the same product code count together. The cart and the checkout check the rules when the quantity changes. When a quantity breaks a rule, the lines stay at the quantity the shopper chose but become unavailable, and a message names the rule, for example "At least 6 of Shiraz is required; the cart has 4". A shopper can always remove a line. While a line breaks a rule, the shopper cannot start the checkout or pay. A guest with no customer record is checked against the cart only.
In templates, getMinQuantity(), getMaxQuantity(), getQuantityStep(), getQuantityMultiple(), getOverallQuantity() and getOverallPeriod() return the rules, for example to set the min and step of a quantity input. In the API, products and variants accept and return the same fields.
Fixed an issue where billable request exports failed on some days or left out rows between export pages.
Added a Refresh counts button to System › Sites › Billable Requests. It collects the request counts again for the current month or one of the five months before it.
Fixed an issue where a duplicated job's Source action settings pointed to actions in the original job, and where a job with a web event could not be duplicated.
Fixed an issue where the listings and RSS feed of a translated blog showed interface text in the wrong language.
Fixed an issue where signed-in users did not always get the default view permissions set for their site.
Fixed an issue where a second "proceed to checkout" from the same cart started a new checkout, and the shopper lost the details already entered.
Checkout responses in the API now include the checkout's messages, and a new route removes one message.
Theme deploys from Bitbucket now use Bitbucket's SSH host ssh.bitbucket.org. Existing sites change to it on their next deploy.