Dictionary Items in Optimizely SaaS CMS — Part 2: Creating and Managing Dictionary Items

In Part 1, we modeled the content types — DictionaryItemCT with Namespace, Key, and Value, and a DictionaryFolderCT to hold them — and wired the frontend to read those values through Optimizely Graph and next-intl.

This post covers the recommended way to store dictionary items in Optimizely SaaS CMS — where they should live in the content tree, how to structure them so they stay manageable as the count grows, and both routes for getting them in: the CMS UI, and the Content Management API.

Where Dictionary Items Live

Dictionary items are shared content. The same Add to Cart label is used by every site in the instance, so the dictionary belongs under For All Sites, not inside any individual site’s tree. Put it under a single site and you’ve scoped it to that site — every other site loses access, and you end up maintaining the same keys in several places.

The structure looks like this:

Nesting folders per namespace keeps things navigable once the item count grows, and it mirrors the Namespace property on DictionaryItemCT so editors can find a key the same way the code queries for it.

There’s a catch, though.

The New Folder button in the CMS UI always creates the built-in system folder type. It doesn’t offer a type picker, so there’s no way to select DictionaryFolder from the UI at all. Instances of your custom folder type have to be created through the REST API.

Creating the Folder Through the REST API

All requests need a bearer token — see Optimizely’s authentication and authorization reference for generating one.

Create the folder with a POST to the Content Management API:

POST: https://api.cms.optimizely.com/v1/content

Request Payload:
{
  "contentType": "DictionaryFolder",
  "container": "e56f85d0e8334e02976a2d11fe4d598c",
  "initialVersion": {
    "displayName": "Dictionary",
    "properties": {}
  }
}

Three fields carry the work:

  • contentType — the custom type you pushed in Part 1. This is the whole reason for going through the API: it’s the one place you get to say DictionaryFolder instead of accepting the system default.
  • container — where the folder is created. e56f85d0e8334e02976a2d11fe4d598c is a well-known system GUID (E56F85D0-E833-4E02-976A-2D11FE4D598C) for the global assets folder, shown as For All Applications in the UI. It’s part of the CMS baseline system data, so it holds the same value in every SaaS instance — safe to hardcode in your scripts and configuration rather than looking it up at runtime.
  • initialVersion — the first version’s content. displayName is what editors see in the tree. properties is empty here because DictionaryFolderCT defines no properties of its own.

Note the container GUID has no hyphens. The API expects UUIDs in that form, and a hyphenated value is rejected.

The response returns the new folder’s key. That’s the value to store as DICTIONARY_FOLDER_GUID — you’ll need it to query the tree, and as the container for everything created beneath it.

Nested namespace folders are the same call with the parent swapped in:

{
  "contentType": "DictionaryFolder",
  "container": "<key of the Dictionary folder>",
  "initialVersion": {
    "displayName": "Common",
    "properties": {}
  }
}

Run that once per namespace and the structure for dictionary items in Optimizely SAAS looks like as below.

The Easy Part: Creating Dictionary Items

With the folders in place, the API is done. Dictionary items are created directly in the CMS UI, like any other block.

Open your Common folder and click Create Shared Block. You get the three properties from DictionaryItemCT to fill in.

PropertyExamplePurpose
NamespacecommonGroups related keys. Mirrors the folder the item sits in, and maps to the namespace you pass on the frontend.
KeyreadmoreThe lookup value your code asks for. Stable — never translated, never changed once shipped.
ValueRead MoreThe string that renders. Localized, so this is the only property that differs per market.

The dictionary item is then consumed exactly as Part 1 set up:

'use client';
import { useTranslations } from 'next-intl';

const t = useTranslations('common');
return <button>{t('readMore')}</button>;

Namespace picks the group, Key picks the entry within it. Getting those two right at creation time is what makes the frontend contract hold.

Because Value is the only localized property, adding a market means adding a language version of the same block — Namespace and Key stay identical, only Value changes. Editors handle that through the normal translation flow; no new items, no duplicated keys.

Final Thoughts

Dictionary items in Optimizely SaaS CMS aren’t a feature — they’re a pattern. There’s no built-in dictionary, so the work is in modeling it well and putting it somewhere sensible, and both parts of that pay off later.

The only real friction is the New Folder button. It creates the system folder type with no picker, so instances of DictionaryFolder have to come from the REST API. That’s a one-time setup cost — a handful of calls, run once per environment, and then you’re done with the API entirely. Everything after that is ordinary authoring: Create Shared Block, fill in Namespace, Key, Value, translate the value per market.

What you get for it is worth the detour. Dictionary items live under For All Applications, so every site in the instance reads the same strings. mayContainTypes keeps the tree clean. And the Namespace + Key contract means a label like Read More is edited once, by an editor, without a deploy — which was the whole point of moving off static JSON in the first place.

Happy Optimizing!!!

Leave a comment