@ebitex/content-mcp 0.1.0 → 0.1.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -102
- package/dist/main.js +0 -0
- package/dist/resources/helpCorpus.generated.js +21 -21
- package/dist/resources/helpCorpus.generated.js.map +1 -1
- package/package.json +39 -39
package/README.md
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
# @ebitex/content-mcp
|
|
2
|
-
|
|
3
|
-
An [MCP](https://modelcontextprotocol.io) server for **ebitex Content**. Point an AI agent at it and
|
|
4
|
-
it can build a content model end to end — Contracts and their fields, Templates, Components, the
|
|
5
|
-
Experience tree — and publish it.
|
|
6
|
-
|
|
7
|
-
It runs locally, over `stdio`, authenticated by a Content management key you mint yourself. Nothing
|
|
8
|
-
is hosted by ebitex, so your write-capable key never leaves your machine.
|
|
9
|
-
|
|
10
|
-
## Configure it
|
|
11
|
-
|
|
12
|
-
```json
|
|
13
|
-
{
|
|
14
|
-
"mcpServers": {
|
|
15
|
-
"ebitex-content": {
|
|
16
|
-
"command": "npx",
|
|
17
|
-
"args": ["-y", "@ebitex/content-mcp"],
|
|
18
|
-
"env": { "EBITEX_CONTENT_MANAGEMENT_KEY": "frm_live_..." }
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
| Variable | |
|
|
25
|
-
|---|---|
|
|
26
|
-
| `EBITEX_CONTENT_MANAGEMENT_KEY` | **Required.** Mint one in Composer under Settings → Management keys |
|
|
27
|
-
| `EBITEX_CONTENT_API_BASE` | Defaults to `https://api.ebitex.io` |
|
|
28
|
-
| `EBITEX_CONTENT_MCP_READ_ONLY` | Set to `1` to hide every write tool regardless of the key's scopes |
|
|
29
|
-
| `EBITEX_CONTENT_TARGET_MANAGEMENT_KEY` | A second key, for `content_transfer` to import into |
|
|
30
|
-
|
|
31
|
-
## Mint the key deliberately
|
|
32
|
-
|
|
33
|
-
A management key is bound to **one organization and one authoring environment**, and acts as **one
|
|
34
|
-
role**. That binding is the real safety boundary here, and it is the one this server cannot supply
|
|
35
|
-
for you:
|
|
36
|
-
|
|
37
|
-
- **Bind it to a non-production authoring environment.** This server has no way to tell which of
|
|
38
|
-
your environments is production — that is your own topology, and a management key deliberately
|
|
39
|
-
cannot see it. A heuristic would be worse than nothing, because it would be believed.
|
|
40
|
-
- **Leave the authoring scope off unless the agent needs to write.** Without it, the write tools are
|
|
41
|
-
not merely refused; they are not offered at all.
|
|
42
|
-
- **Give it a role with the access it needs and no more.** Branch access rules and workflow gating
|
|
43
|
-
apply to a key exactly as they do to a person.
|
|
44
|
-
|
|
45
|
-
On startup the server prints, to stderr, which organization and environment the key reached and
|
|
46
|
-
which role it acts as. Read it once; if it is not what you expected, stop.
|
|
47
|
-
|
|
48
|
-
## The tools
|
|
49
|
-
|
|
50
|
-
Eight, shaped around what an agent does rather than around REST routes.
|
|
51
|
-
|
|
52
|
-
| | |
|
|
53
|
-
|---|---|
|
|
54
|
-
| `content_describe` | What can exist here: every field type with its **own settings schema**, plus this environment's Contracts and Templates |
|
|
55
|
-
| `content_find` | Locate entities by kind, search or external id |
|
|
56
|
-
| `content_get` | Read one entity in full |
|
|
57
|
-
| `content_write` | Create or update one entity |
|
|
58
|
-
| `content_delete` | Delete one, behind an explicit confirmation |
|
|
59
|
-
| `content_publish` | Plan, read the blockers, then publish |
|
|
60
|
-
| `content_operation_status` | Poll a job |
|
|
61
|
-
| `content_transfer` | Move a whole model between environments, atomically |
|
|
62
|
-
|
|
63
|
-
Which of them appear depends on your key's scopes: a read-scoped key gets a server with no write
|
|
64
|
-
tools at all, rather than tools that always fail.
|
|
65
|
-
|
|
66
|
-
## Why it cannot drift
|
|
67
|
-
|
|
68
|
-
The server encodes **no knowledge of the content model**. It does not know what field types exist,
|
|
69
|
-
what a Contract may contain, or what makes a document valid. All three are answered at runtime:
|
|
70
|
-
`content_describe` returns each field type's own declared schema, and every write is validated by the
|
|
71
|
-
server that stores it. There is nothing here to fall out of step, because nothing is duplicated.
|
|
72
|
-
|
|
73
|
-
That also shapes how errors work. A refusal is relayed **whole**, with its field paths intact and no
|
|
74
|
-
summarizing, because for an agent the error is the control loop: try, read the error, fix the one
|
|
75
|
-
field it names, call again.
|
|
76
|
-
|
|
77
|
-
## Resources
|
|
78
|
-
|
|
79
|
-
Alongside the tools, the server offers the Content documentation as MCP resources — start with
|
|
80
|
-
`ebitex-content://help/agent-authoring`, which explains what Contracts, Templates, Presentations,
|
|
81
|
-
Adapters and the Experience tree are for, and the order to create them in.
|
|
82
|
-
|
|
83
|
-
## Embedding it
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
import { buildServer, readConfig } from '@ebitex/content-mcp'
|
|
87
|
-
|
|
88
|
-
const { server, banner } = await buildServer({ config: readConfig() })
|
|
89
|
-
console.error(banner) // never stdout — on stdio, that stream is the protocol
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## A note on `zod`
|
|
93
|
-
|
|
94
|
-
This package pins `zod` to `^3.25.76` rather than `^4`, even though `@modelcontextprotocol/sdk`
|
|
95
|
-
accepts either. The reason is type identity, not capability: the SDK's schema types are written
|
|
96
|
-
against whichever `zod` resolves for *it*, and a second copy at a different major produces a type
|
|
97
|
-
mismatch on every tool registration. Pinning to the version the SDK resolves keeps one `zod` in the
|
|
98
|
-
tree. This matters at build time only; at runtime the SDK handles both.
|
|
99
|
-
|
|
100
|
-
## Licence
|
|
101
|
-
|
|
102
|
-
UNLICENSED — © ebitex software LLC.
|
|
1
|
+
# @ebitex/content-mcp
|
|
2
|
+
|
|
3
|
+
An [MCP](https://modelcontextprotocol.io) server for **ebitex Content**. Point an AI agent at it and
|
|
4
|
+
it can build a content model end to end — Contracts and their fields, Templates, Components, the
|
|
5
|
+
Experience tree — and publish it.
|
|
6
|
+
|
|
7
|
+
It runs locally, over `stdio`, authenticated by a Content management key you mint yourself. Nothing
|
|
8
|
+
is hosted by ebitex, so your write-capable key never leaves your machine.
|
|
9
|
+
|
|
10
|
+
## Configure it
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"ebitex-content": {
|
|
16
|
+
"command": "npx",
|
|
17
|
+
"args": ["-y", "@ebitex/content-mcp"],
|
|
18
|
+
"env": { "EBITEX_CONTENT_MANAGEMENT_KEY": "frm_live_..." }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Variable | |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `EBITEX_CONTENT_MANAGEMENT_KEY` | **Required.** Mint one in Composer under Settings → Management keys |
|
|
27
|
+
| `EBITEX_CONTENT_API_BASE` | Defaults to `https://api.ebitex.io` |
|
|
28
|
+
| `EBITEX_CONTENT_MCP_READ_ONLY` | Set to `1` to hide every write tool regardless of the key's scopes |
|
|
29
|
+
| `EBITEX_CONTENT_TARGET_MANAGEMENT_KEY` | A second key, for `content_transfer` to import into |
|
|
30
|
+
|
|
31
|
+
## Mint the key deliberately
|
|
32
|
+
|
|
33
|
+
A management key is bound to **one organization and one authoring environment**, and acts as **one
|
|
34
|
+
role**. That binding is the real safety boundary here, and it is the one this server cannot supply
|
|
35
|
+
for you:
|
|
36
|
+
|
|
37
|
+
- **Bind it to a non-production authoring environment.** This server has no way to tell which of
|
|
38
|
+
your environments is production — that is your own topology, and a management key deliberately
|
|
39
|
+
cannot see it. A heuristic would be worse than nothing, because it would be believed.
|
|
40
|
+
- **Leave the authoring scope off unless the agent needs to write.** Without it, the write tools are
|
|
41
|
+
not merely refused; they are not offered at all.
|
|
42
|
+
- **Give it a role with the access it needs and no more.** Branch access rules and workflow gating
|
|
43
|
+
apply to a key exactly as they do to a person.
|
|
44
|
+
|
|
45
|
+
On startup the server prints, to stderr, which organization and environment the key reached and
|
|
46
|
+
which role it acts as. Read it once; if it is not what you expected, stop.
|
|
47
|
+
|
|
48
|
+
## The tools
|
|
49
|
+
|
|
50
|
+
Eight, shaped around what an agent does rather than around REST routes.
|
|
51
|
+
|
|
52
|
+
| | |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `content_describe` | What can exist here: every field type with its **own settings schema**, plus this environment's Contracts and Templates |
|
|
55
|
+
| `content_find` | Locate entities by kind, search or external id |
|
|
56
|
+
| `content_get` | Read one entity in full |
|
|
57
|
+
| `content_write` | Create or update one entity |
|
|
58
|
+
| `content_delete` | Delete one, behind an explicit confirmation |
|
|
59
|
+
| `content_publish` | Plan, read the blockers, then publish |
|
|
60
|
+
| `content_operation_status` | Poll a job |
|
|
61
|
+
| `content_transfer` | Move a whole model between environments, atomically |
|
|
62
|
+
|
|
63
|
+
Which of them appear depends on your key's scopes: a read-scoped key gets a server with no write
|
|
64
|
+
tools at all, rather than tools that always fail.
|
|
65
|
+
|
|
66
|
+
## Why it cannot drift
|
|
67
|
+
|
|
68
|
+
The server encodes **no knowledge of the content model**. It does not know what field types exist,
|
|
69
|
+
what a Contract may contain, or what makes a document valid. All three are answered at runtime:
|
|
70
|
+
`content_describe` returns each field type's own declared schema, and every write is validated by the
|
|
71
|
+
server that stores it. There is nothing here to fall out of step, because nothing is duplicated.
|
|
72
|
+
|
|
73
|
+
That also shapes how errors work. A refusal is relayed **whole**, with its field paths intact and no
|
|
74
|
+
summarizing, because for an agent the error is the control loop: try, read the error, fix the one
|
|
75
|
+
field it names, call again.
|
|
76
|
+
|
|
77
|
+
## Resources
|
|
78
|
+
|
|
79
|
+
Alongside the tools, the server offers the Content documentation as MCP resources — start with
|
|
80
|
+
`ebitex-content://help/agent-authoring`, which explains what Contracts, Templates, Presentations,
|
|
81
|
+
Adapters and the Experience tree are for, and the order to create them in.
|
|
82
|
+
|
|
83
|
+
## Embedding it
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { buildServer, readConfig } from '@ebitex/content-mcp'
|
|
87
|
+
|
|
88
|
+
const { server, banner } = await buildServer({ config: readConfig() })
|
|
89
|
+
console.error(banner) // never stdout — on stdio, that stream is the protocol
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## A note on `zod`
|
|
93
|
+
|
|
94
|
+
This package pins `zod` to `^3.25.76` rather than `^4`, even though `@modelcontextprotocol/sdk`
|
|
95
|
+
accepts either. The reason is type identity, not capability: the SDK's schema types are written
|
|
96
|
+
against whichever `zod` resolves for *it*, and a second copy at a different major produces a type
|
|
97
|
+
mismatch on every tool registration. Pinning to the version the SDK resolves keeps one `zod` in the
|
|
98
|
+
tree. This matters at build time only; at runtime the SDK handles both.
|
|
99
|
+
|
|
100
|
+
## Licence
|
|
101
|
+
|
|
102
|
+
UNLICENSED — © ebitex software LLC.
|
package/dist/main.js
CHANGED
|
File without changes
|
|
@@ -6,107 +6,107 @@ export const HELP_TOPICS = [
|
|
|
6
6
|
{
|
|
7
7
|
"slug": "agent-authoring",
|
|
8
8
|
"title": "Building a content model through the API",
|
|
9
|
-
"body": "# Building a content model through the API\
|
|
9
|
+
"body": "# Building a content model through the API\n\nThe rest of this help corpus describes Content as you drive it in Composer — tabs, panels, buttons.\nThe concepts all transfer; the steps do not. This page is the same model described in terms of the\nAPI, for a script or an agent working through the Content Management API rather than the interface.\n\nRead this first if you are about to create anything. Everything here is about *what the pieces are*;\nthe exact shape of each request is answered live by the API itself, and you should never guess it.\n\n## The one rule that saves the most time\n\n**Ask the server what things look like; do not assume.**\n\n`GET /field-types` returns every field type this platform has, each with its **own JSON Schema for\nits settings**. That schema is generated from the field type's own definition, so it is never out of\ndate — Composer's Contract builder renders its settings form from exactly this, rather than\nhard-coding a form per type. Use it the same way.\n\n`GET /contracts` and `GET /templates` tell you what this environment already has. Check before\ncreating: a duplicate is much harder to undo than it is to avoid.\n\nWhen a write is refused, the refusal names the failing field and says what is wrong with it. That\nmessage is the fastest route to a correct request. Read it and correct the one thing it names,\nrather than rewriting the payload.\n\n## The pieces, and what each one is for\n\nContent separates four concerns that many systems collapse into one. The separation is the reason\nthe same content can be rendered several ways, and reused in places its author never anticipated.\n\n### Contract — what content *is*\n\nA Contract is a type. It has a name, an external id, and a list of **fields**. Each field names a\nfield type (short text, rich text, number, date, blob, reference, component, presentation, category,\nlink, and so on) and carries that type's own settings.\n\nContracts support **inheritance**. A Contract may name a parent, and it then has the parent's fields\nplus its own. A Contract may be **abstract**, meaning nothing is ever an instance of it directly —\nit exists to be inherited from, and to be named where several concrete types should be accepted.\n\nContracts describe content and nothing about how it looks. No colours, no layout, no \"variant\".\n\n### Component — an instance of a Contract\n\nA Component is a piece of content: a Contract, plus a **document** holding a value for each field.\nIt has its own name and external id and lives in a folder.\n\nThe document's shape follows the Contract's fields exactly. What a field's value looks like depends\non its type *and* on its modifiers, below.\n\n### Template — how content is *rendered*\n\nA Template names which Contracts it can render, and has its own **settings Contract**: an ordinary\nContract describing the presentational choices that template offers — a variant, an alignment, how\nmany columns.\n\nThis is why Contracts stay free of presentation. \"A statement, centred, with a ghost button\" is one\nTemplate's settings over a plain statement Contract, not three fields on the Contract itself.\n\n### Presentation — a Template bound to content\n\nA **presentation value** is where the two meet: it names a Template, binds a Component (or holds an\ninline document), and carries settings and contextual values for this one use.\n\nA page is usually a Component whose fields hold presentation values — a hero, then some sections —\nand each of those points at content that may be used elsewhere, rendered differently.\n\n### Adapter — a reusable mapping between Contracts\n\nSometimes a Template needs a `card` and what you have is a `blog-page`. An Adapter is a named,\nreusable mapping from one Contract to another: one rule per output field, plus a small set of logic\nnodes for lists, conditions and type dispatch.\n\nAn Adapter is applied by the server — at preview, at publish, and at delivery — so the mapping is\ndefined once and behaves identically everywhere.\n\n### Experience — where content lives on the web\n\nThe Experience tree is the site's address space. A **site** is a root; **nodes** under it are pages,\neach with a slug. A node's path is its ancestors' slugs plus its own.\n\nA node's **payload** says what it renders — usually a presentation value, sometimes a redirect,\nsometimes nothing at all (a node with no payload is pure structure, giving its children a path\nsegment without being a page itself).\n\nCreating a node and setting its payload are **two separate calls**. That is not an oversight: a\nnode's identity and location change for different reasons than its content does.\n\n## The five modifiers\n\nAny field can carry any combination of these. They compose, and each one changes the shape of the\nvalue stored in the document — which is the part worth understanding before writing one.\n\n| Modifier | What it means | Effect on the value |\n|---|---|---|\n| Mandatory | A value is required | Validation refuses an empty one |\n| Enumerable | Many rather than one | The value becomes a list |\n| Localizable | Translatable per locale | The value becomes `{ default, translations }` |\n| Personalizable | Varies by audience | The value becomes `{ default, variants }` |\n| Contextual | Overridable per use | The value may be supplied where the Component is *used*, not only where it is defined |\n\nCardinality is Enumerable's job, never the field type's. There is no \"list of text\" field type;\nthere is a text field that is enumerable.\n\n**Contextual** is the one worth dwelling on, because it has no counterpart in most systems. A\ncontextual field's value can be set on the Component (a default) *and* overridden by whoever binds\nthat Component somewhere — a heading that reads one way on the home page and another on a listing,\nfrom one Component with one document. Those overrides are the `contextualValues` that travel with a\nreference or a presentation value.\n\n## An order of operations that works\n\nDependencies run one way, so build in this order and nothing is ever waiting on something that does\nnot exist yet.\n\n1. **Describe.** `GET /field-types`, `/contracts`, `/templates`. Learn what exists before adding to\n it.\n2. **Contracts.** Parents before children, since a child names its parent.\n3. **Templates.** After the Contracts they render, since a Template names them.\n4. **Components.** After their Contract, and after any Contract they reference.\n5. **A site**, if the environment has none. Then **nodes** under it, parents before children.\n6. **Node payloads.** After both the node and the Component it binds.\n7. **Publish.** Plan first, read the blockers, then publish.\n\nTwo ordering facts that are easy to get wrong: a Component referencing another Component needs that\none to exist first, and publishing follows the dependency closure automatically — so publishing a\npage will normally publish the content it binds, and the plan is where you see exactly what that\nmeans before it happens.\n\n## Publishing\n\nPublishing is two calls, and collapsing them would hide the thing that matters.\n\n**Plan** resolves the full dependency closure of what you asked to publish and reports every\n**blocker** — an unresolvable reference, a workflow state that forbids publishing, a document that\nfails validation. **Execute** publishes what the plan named.\n\nBoth are jobs: each returns an operation id, and you poll it. A real closure is too large to finish\ninside an HTTP request, which is why.\n\nRead the plan. It is the only place the system tells you what is about to change beyond what you\nexplicitly named.\n\n## Where writes land\n\nA management key is bound to exactly one organization and one **authoring environment**, and it acts\nas one **role**. It cannot reach another environment, and it is subject to the same branch access\nrules and workflow gating a person with that role is.\n\nThat binding is the real safety boundary, so it is worth confirming rather than assuming: ask the\nAPI who the key is before writing anything. If the answer is not the environment you expected, stop\n— nothing else about the request will tell you.\n\n## Things that commonly go wrong\n\n- **Guessing a field type's settings** instead of reading its schema. Every field type declares its\n own; there is nothing to guess.\n- **Creating a duplicate** because an external id was not checked first. External ids are how a\n model refers to itself across environments; a duplicate is a lasting problem.\n- **Putting presentation on a Contract.** If you are adding a field called `variant`, `align`,\n `theme` or `columns` to a Contract, it almost certainly belongs in a Template's settings.\n- **Forgetting a node's payload.** A node created and never given one is a real, published,\n completely blank page.\n- **Treating a publish plan as a formality.** It is where blockers appear, and they do not appear\n anywhere else until something is already wrong.\n\n## Related\n\n- [Contracts](contracts.md) — the type system in depth\n- [Templates](templates.md) — presentation\n- [Components](components.md) — authoring content\n- [Content Adapters](content-adapters.md) — mappings\n- [Composer](content-composer.md) — the Experience tree\n- [Publishing](publishing.md) — the publish pipeline\n- [Management keys](management-keys.md) — minting and scoping a key\n"
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"slug": "blog-series",
|
|
13
13
|
"title": "Blog series",
|
|
14
|
-
"body": "# Blog series\
|
|
14
|
+
"body": "# Blog series\n\nA series groups posts that are meant to be read in order — a tutorial, a multi-part explainer, a\nwalkthrough split across several weeks. Readers get a position (\"Part 3 of 12\"), previous and next\nlinks, and a landing page listing every part.\n\nPosts that aren't part of a series are completely unaffected. Series are opt-in, per post.\n\n## Creating a series\n\n1. In **Content**, create a Component using the **Blog series** Contract.\n2. Fill in:\n - **Heading** — the series name, shown on its landing page and on every part.\n - **Title** and **Description** — the page title and meta description for the landing page.\n - **Summary** *(optional)* — what the series covers. Shown above the list of parts.\n - **Media** *(optional)* — a lead image, also used as the landing page's social preview.\n3. In **Composer**, add a page under **Blog → Series** and bind your new Component to it. Its\n address will be `/blog/series/<slug>`.\n4. Publish the series.\n\nA series with no parts yet is a normal, valid thing — the landing page simply says so.\n\n## Adding posts to a series\n\nOn each post, set:\n\n- **Series** — pick the series.\n- **Part** — the reading position: 1, 2, 3…\n\nThen publish the post.\n\nThat's all. The series doesn't hold a list of its parts, so you never edit the series when adding\none — publishing the post is what puts it in the sequence.\n\n## Things worth knowing\n\n**Order comes from Part, never the publish date.** You can publish part 4 before revising part 3;\nreaders still see them in the right order.\n\n**Changing a part number takes effect when you republish that post.** Part numbers are part of the\npost's published content, so an edit that isn't published yet doesn't move anything. If you\nre-number a part and the order looks unchanged, publish it.\n\n**A part with no number sorts last.** You can add a post to a series before deciding where it goes.\nIt will appear at the end until you give it a number.\n\n**\"Part 3 of 12\" counts published parts only.** While a series is still being written, a reader on\npart 3 of an eventual twelve sees \"Part 3 of 3\". That's deliberate — the count reflects what they\ncan actually reach, and we'd rather understate than offer links that go nowhere.\n\n**Two posts with the same number won't break anything**, but they'll appear in an arbitrary (though\nstable) order relative to each other. Worth fixing when you notice it.\n\n**Unpublishing a part removes it from the sequence**, and the parts either side become adjacent.\nNothing else needs republishing.\n\n## Where a series appears\n\n- **Its landing page** at `/blog/series/<slug>` — the canonical place to read it in order.\n- **On each part** — the position, the series name, and previous/next links.\n- **In the blog archive** at `/blog` — every part is listed individually, each badged with its\n series and part number. The archive isn't collapsed to one entry per series, so a post stays\n findable by search and by topic exactly as any other post would.\n\n## Related\n\n- [Components](components.md) — creating and editing content\n- [Publishing](publishing.md) — how publishing and freezing work\n"
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
"slug": "components",
|
|
18
18
|
"title": "Authoring Components",
|
|
19
|
-
"body": "# Authoring Components\r\n\r\nA Component is one piece of content built from a Contract — for example,\r\na specific article. This guide covers creating and editing Components,\r\norganizing them in folders, and the friction the app shows you when a\r\nContract changes underneath a Component you've already created. See\r\n[Modeling content with Contracts](contracts.md) first if you haven't\r\ncreated a Contract yet.\r\n\r\n## Creating a Component\r\n\r\n1. From the Content page, select **New Component** (optionally after\r\n opening the folder you want it created in).\r\n2. Enter a **Name** — the **Id** fills in from it automatically and can\r\n still be edited by hand.\r\n3. Choose a **Contract**. If you started from a specific folder that\r\n restricts which Contracts are allowed there, only those (and their\r\n descendants) are offered.\r\n4. Fill in the form that appears — every field from the Contract, laid\r\n out in order. Fields marked with a red **\\*** are required.\r\n5. Select **Create**. If a required field is missing or a value doesn't\r\n match the Contract's rules, the field is highlighted with an\r\n explanation — fix it and select **Create** again.\r\n\r\n## Filling in fields\r\n\r\nMost fields are a straightforward text box, number box, or checkbox.\r\nA few work differently:\r\n\r\n- **Reference** and **Nested** are older field types kept working for\r\n existing Contracts, but no longer offered when adding a new field — new\r\n Contracts should use **Component** instead (below), which offers the\r\n same two behaviors plus a third, chosen per value instead of committed\r\n to for the whole field.\r\n- **Component** fields ask you to choose, each time you fill one in:\r\n **Choose…** to link to an existing Component (same search box as\r\n Reference, with **Change**/**Remove** once chosen); **Create new…** to\r\n create a brand-new Component and link to it in one step (fill in its\r\n name, Contract, and any required fields right there); or **Create\r\n inline…** to embed a value that only ever exists inside this one — pick\r\n a Contract and an **Edit** panel slides in, exactly like Nested below.\r\n If the Contract only allows one of these choices, only that one is\r\n offered. If a linked Component can't be found — most often because it\r\n was skipped during a promotion into this environment (see\r\n [content-environments.md](content-environments.md)'s \"Unresolved\r\n references\" section) — you'll see a distinct **Unresolved** badge\r\n instead of the usual resolved name, with a link to Component Sync to\r\n see the full picture and fix it. **Change**/**Remove** still work\r\n normally on an unresolved value.\r\n- **Blob** fields (images and other files) show an **Upload** button.\r\n Once uploaded, you'll see a preview (for images) and a link to download\r\n the file, plus a **Replace** button.\r\n- **Nested** fields show a small **Edit** panel once a Contract is chosen\r\n for them (automatically, if only one is allowed for that field, or via a\r\n picker if more than one is allowed) — selecting **Edit** opens the\r\n nested content in its own panel, sliding in over the current one. Any\r\n unsaved changes you've made — at every level — are kept even if you\r\n drill several levels deep and back out again; nothing is lost until you\r\n actually leave the page without saving. A Component field's own **Create\r\n inline…** choice opens the identical panel.\r\n- Fields marked **Localizable** always show a small translations icon next\r\n to their label — select it to add per-locale overrides alongside the\r\n default value. It's disabled (with an explanation on hover) until your\r\n organization has a language configured under **Localization** settings\r\n with at least one language added under it to translate into; once one\r\n exists, the icon becomes selectable. This works the same way for every\r\n field type, including Blob, Reference, Nested, Component, and\r\n Presentation fields: each locale gets its own independent\r\n **Upload**/**Edit**/**Choose…** affordance, so a Nested value's translated\r\n content — or a different linked Component or uploaded file per locale —\r\n is fully separate from the default. The default-locale row in the\r\n translations panel is always read-only (a reflection of the default\r\n value, not a second copy of it); translate from the other locale rows.\r\n- **Presentation** fields let you instantiate a Template inline — pick a\r\n Template, bind it to a Component, and set contextual values and settings\r\n for this specific placement. See\r\n [Presenting content with Templates](templates.md) for the full flow.\r\n\r\n## Contextual defaults\r\n\r\nFields marked Contextual on the Contract appear in a separate\r\n**\"Defaults for usage sites\"** section, clearly labeled optional — even if\r\nthe Contract also marks them Mandatory. That's intentional: a Contextual\r\nfield's real value is supplied wherever the content is *used*, not here;\r\nwhat you set in this section is only a fallback for places that don't\r\nsupply their own value.\r\n\r\n### Setting per-usage values on a Reference\r\n\r\nWhen you link to another Component via a Reference field (or a Component\r\nfield's **Choose…**/**Create new…** path — both link the same way), select\r\n**Contextual values** to open a panel listing that target's own Contextual\r\nfields, each with three distinct states you can switch between:\r\n\r\n- **Inheriting the Component's default** — nothing set here; the linked\r\n Component's own default value is used.\r\n- **Overridden** — set a value here to use instead of the default, just\r\n for this one link. This works for every field type the target Contract\r\n declares as Contextual, not just simple text/number/checkbox fields — a\r\n Contextual Reference field gets the same **Choose…** picker, a Contextual\r\n Blob field the same **Upload** button, and a Contextual Nested field the\r\n same drill-in **Edit** panel you'd see editing that field directly on a\r\n Component.\r\n- **Explicitly cleared** — select **Clear** to force \"no value\" at this\r\n usage, ignoring the default entirely (only available when the field\r\n isn't required).\r\n\r\nSelect **Reset to default** at any time to go back to inheriting. The same\r\nthree states and the same per-type widgets apply to a Presentation field's\r\nown contextual-values panel (see\r\n[Presenting content with Templates](templates.md)).\r\n\r\n### Editing the linked Component itself\r\n\r\nNext to **Contextual values**, a linked Component also offers **Quick\r\nedit**, which opens that Component in a layer over the page you're on —\r\nits own fields, its own **Save**, and a note of everywhere else it's used,\r\nsince saving changes it there too. How layers stack, and the difference\r\nbetween a layer that saves on its own and one that's part of your page, is\r\nexplained in\r\n[Editing something from where it's used](content-authoring.md#editing-something-from-where-its-used).\r\n\r\n## Organizing with folders\r\n\r\nThe **Content** page shows your folder tree on the left. Select the\r\n**+** icon next to a folder to create a subfolder inside it, the pencil\r\nicon to rename or move it, or the trash icon to delete it (only possible\r\nonce it's empty). Selecting a folder shows two tabs: **Contents** (the\r\nComponents inside it) and, if you have the right permissions, **Settings**\r\n(that folder's **Access rules**, **Allowed Contracts**, and editorial\r\nworkflow assignment).\r\n\r\nEach Component in the Contents tab is shown as a row with its Contract,\r\nName, Id, Version, and when it was last updated. Select the **Name** or\r\n**Last updated** column heading to sort by it. Above the list, a search\r\nbox and a single **Filter** button (showing a count once you've set any\r\nfilters) let you narrow by category and/or Contract; pagination appears\r\nonce a folder holds more Components than fit on one page.\r\n\r\nUnder **Settings**:\r\n\r\n- **Access rules** restrict which roles can view, edit, delete, or publish\r\n content in that folder and everything below it (until a subfolder sets\r\n its own, narrower rules). No rules anywhere on a branch means it's open\r\n to every member. Select **Add Rule** to grant a role access: pick the\r\n role from the list and check whichever of View/Edit/Delete/Publish it\r\n should have. Editing, deleting, or publishing all imply viewing, so you\r\n don't need to check View separately for those.\r\n- **Allowed Contracts** restrict which Contracts new Components in that\r\n folder (and its subfolders, unless they set their own) can use — useful\r\n for keeping a folder focused on one kind of content. Select **Add\r\n Contract** and search for the Contract to allow; select **Remove** next\r\n to an already-allowed Contract to take it off the list. This only\r\n affects *new* or *moved* Components; it never breaks ones already there,\r\n though they'll show a **Non-conforming** badge as a reminder.\r\n\r\nIf you don't have permission to manage a folder's access rules or allowed\r\nContracts, you can still see what you're allowed to do there — the panel\r\njust won't let you change it.\r\n\r\n### Moving a Component to another folder\r\n\r\nOpen the Component, select the **Settings** tab, and select **Move…**\r\nnext to the **Folder** label. A picker lists every folder (and **Root**);\r\npick one to move the Component there once you save. A folder you don't\r\nhave edit access to is shown but disabled, with an explanation. A folder\r\nwhose **Allowed Contracts** don't include this Component's Contract is\r\ndisabled too — unlike an *existing* Component whose folder's rules later\r\nchange (the **Non-conforming** badge case above), a move that would\r\nviolate the target folder's rules is rejected outright, so the picker\r\nwon't offer it as an option in the first place.\r\n\r\n### Finding your way back to a folder\r\n\r\nThe Component editor shows a breadcrumb above its title (**Content** /\r\nthe folder it's in / the Component's name) — select the folder name to\r\nreturn straight to that folder's Contents tab, rather than using your\r\nbrowser's back button.\r\n\r\n## The upgrade badge\r\n\r\nIf a Contract changes after a Component was created from it, that\r\nComponent keeps working exactly as it did — nothing breaks automatically.\r\nInstead:\r\n\r\n- If the Component still satisfies the Contract's latest version, you'll\r\n see a badge offering **Upgrade to v_N_** — select it to move the\r\n Component onto the latest version immediately.\r\n- If it no longer satisfies the latest version, you'll see a\r\n **Not valid against latest** badge listing exactly what's wrong. Select\r\n **Start guided upgrade** to switch into editing against the latest\r\n version, fix the listed issues, and save — the Component moves onto the\r\n new version as part of that save.\r\n\r\nYou're never forced to upgrade; an older Component keeps validating against\r\nthe version it was created with until you choose to move it forward.\r\n"
|
|
19
|
+
"body": "# Authoring Components\n\nA Component is one piece of content built from a Contract — for example,\na specific article. This guide covers creating and editing Components,\norganizing them in folders, and the friction the app shows you when a\nContract changes underneath a Component you've already created. See\n[Modeling content with Contracts](contracts.md) first if you haven't\ncreated a Contract yet.\n\n## Creating a Component\n\n1. From the Content page, select **New Component** (optionally after\n opening the folder you want it created in).\n2. Enter a **Name** — the **Id** fills in from it automatically and can\n still be edited by hand.\n3. Choose a **Contract**. If you started from a specific folder that\n restricts which Contracts are allowed there, only those (and their\n descendants) are offered.\n4. Fill in the form that appears — every field from the Contract, laid\n out in order. Fields marked with a red **\\*** are required.\n5. Select **Create**. If a required field is missing or a value doesn't\n match the Contract's rules, the field is highlighted with an\n explanation — fix it and select **Create** again.\n\n## Filling in fields\n\nMost fields are a straightforward text box, number box, or checkbox.\nA few work differently:\n\n- **Reference** and **Nested** are older field types kept working for\n existing Contracts, but no longer offered when adding a new field — new\n Contracts should use **Component** instead (below), which offers the\n same two behaviors plus a third, chosen per value instead of committed\n to for the whole field.\n- **Component** fields ask you to choose, each time you fill one in:\n **Choose…** to link to an existing Component (same search box as\n Reference, with **Change**/**Remove** once chosen); **Create new…** to\n create a brand-new Component and link to it in one step (fill in its\n name, Contract, and any required fields right there); or **Create\n inline…** to embed a value that only ever exists inside this one — pick\n a Contract and an **Edit** panel slides in, exactly like Nested below.\n If the Contract only allows one of these choices, only that one is\n offered. If a linked Component can't be found — most often because it\n was skipped during a promotion into this environment (see\n [content-environments.md](content-environments.md)'s \"Unresolved\n references\" section) — you'll see a distinct **Unresolved** badge\n instead of the usual resolved name, with a link to Component Sync to\n see the full picture and fix it. **Change**/**Remove** still work\n normally on an unresolved value.\n- **Blob** fields (images and other files) show an **Upload** button.\n Once uploaded, you'll see a preview (for images) and a link to download\n the file, plus a **Replace** button.\n- **Nested** fields show a small **Edit** panel once a Contract is chosen\n for them (automatically, if only one is allowed for that field, or via a\n picker if more than one is allowed) — selecting **Edit** opens the\n nested content in its own panel, sliding in over the current one. Any\n unsaved changes you've made — at every level — are kept even if you\n drill several levels deep and back out again; nothing is lost until you\n actually leave the page without saving. A Component field's own **Create\n inline…** choice opens the identical panel.\n- Fields marked **Localizable** always show a small translations icon next\n to their label — select it to add per-locale overrides alongside the\n default value. It's disabled (with an explanation on hover) until your\n organization has a language configured under **Localization** settings\n with at least one language added under it to translate into; once one\n exists, the icon becomes selectable. This works the same way for every\n field type, including Blob, Reference, Nested, Component, and\n Presentation fields: each locale gets its own independent\n **Upload**/**Edit**/**Choose…** affordance, so a Nested value's translated\n content — or a different linked Component or uploaded file per locale —\n is fully separate from the default. The default-locale row in the\n translations panel is always read-only (a reflection of the default\n value, not a second copy of it); translate from the other locale rows.\n- **Presentation** fields let you instantiate a Template inline — pick a\n Template, bind it to a Component, and set contextual values and settings\n for this specific placement. See\n [Presenting content with Templates](templates.md) for the full flow.\n\n## Contextual defaults\n\nFields marked Contextual on the Contract appear in a separate\n**\"Defaults for usage sites\"** section, clearly labeled optional — even if\nthe Contract also marks them Mandatory. That's intentional: a Contextual\nfield's real value is supplied wherever the content is *used*, not here;\nwhat you set in this section is only a fallback for places that don't\nsupply their own value.\n\n### Setting per-usage values on a Reference\n\nWhen you link to another Component via a Reference field (or a Component\nfield's **Choose…**/**Create new…** path — both link the same way), select\n**Contextual values** to open a panel listing that target's own Contextual\nfields, each with three distinct states you can switch between:\n\n- **Inheriting the Component's default** — nothing set here; the linked\n Component's own default value is used.\n- **Overridden** — set a value here to use instead of the default, just\n for this one link. This works for every field type the target Contract\n declares as Contextual, not just simple text/number/checkbox fields — a\n Contextual Reference field gets the same **Choose…** picker, a Contextual\n Blob field the same **Upload** button, and a Contextual Nested field the\n same drill-in **Edit** panel you'd see editing that field directly on a\n Component.\n- **Explicitly cleared** — select **Clear** to force \"no value\" at this\n usage, ignoring the default entirely (only available when the field\n isn't required).\n\nSelect **Reset to default** at any time to go back to inheriting. The same\nthree states and the same per-type widgets apply to a Presentation field's\nown contextual-values panel (see\n[Presenting content with Templates](templates.md)).\n\n### Editing the linked Component itself\n\nNext to **Contextual values**, a linked Component also offers **Quick\nedit**, which opens that Component in a layer over the page you're on —\nits own fields, its own **Save**, and a note of everywhere else it's used,\nsince saving changes it there too. How layers stack, and the difference\nbetween a layer that saves on its own and one that's part of your page, is\nexplained in\n[Editing something from where it's used](content-authoring.md#editing-something-from-where-its-used).\n\n## Organizing with folders\n\nThe **Content** page shows your folder tree on the left. Select the\n**+** icon next to a folder to create a subfolder inside it, the pencil\nicon to rename or move it, or the trash icon to delete it (only possible\nonce it's empty). Selecting a folder shows two tabs: **Contents** (the\nComponents inside it) and, if you have the right permissions, **Settings**\n(that folder's **Access rules**, **Allowed Contracts**, and editorial\nworkflow assignment).\n\nEach Component in the Contents tab is shown as a row with its Contract,\nName, Id, Version, and when it was last updated. Select the **Name** or\n**Last updated** column heading to sort by it. Above the list, a search\nbox and a single **Filter** button (showing a count once you've set any\nfilters) let you narrow by category and/or Contract; pagination appears\nonce a folder holds more Components than fit on one page.\n\nUnder **Settings**:\n\n- **Access rules** restrict which roles can view, edit, delete, or publish\n content in that folder and everything below it (until a subfolder sets\n its own, narrower rules). No rules anywhere on a branch means it's open\n to every member. Select **Add Rule** to grant a role access: pick the\n role from the list and check whichever of View/Edit/Delete/Publish it\n should have. Editing, deleting, or publishing all imply viewing, so you\n don't need to check View separately for those.\n- **Allowed Contracts** restrict which Contracts new Components in that\n folder (and its subfolders, unless they set their own) can use — useful\n for keeping a folder focused on one kind of content. Select **Add\n Contract** and search for the Contract to allow; select **Remove** next\n to an already-allowed Contract to take it off the list. This only\n affects *new* or *moved* Components; it never breaks ones already there,\n though they'll show a **Non-conforming** badge as a reminder.\n\nIf you don't have permission to manage a folder's access rules or allowed\nContracts, you can still see what you're allowed to do there — the panel\njust won't let you change it.\n\n### Moving a Component to another folder\n\nOpen the Component, select the **Settings** tab, and select **Move…**\nnext to the **Folder** label. A picker lists every folder (and **Root**);\npick one to move the Component there once you save. A folder you don't\nhave edit access to is shown but disabled, with an explanation. A folder\nwhose **Allowed Contracts** don't include this Component's Contract is\ndisabled too — unlike an *existing* Component whose folder's rules later\nchange (the **Non-conforming** badge case above), a move that would\nviolate the target folder's rules is rejected outright, so the picker\nwon't offer it as an option in the first place.\n\n### Finding your way back to a folder\n\nThe Component editor shows a breadcrumb above its title (**Content** /\nthe folder it's in / the Component's name) — select the folder name to\nreturn straight to that folder's Contents tab, rather than using your\nbrowser's back button.\n\n## The upgrade badge\n\nIf a Contract changes after a Component was created from it, that\nComponent keeps working exactly as it did — nothing breaks automatically.\nInstead:\n\n- If the Component still satisfies the Contract's latest version, you'll\n see a badge offering **Upgrade to v_N_** — select it to move the\n Component onto the latest version immediately.\n- If it no longer satisfies the latest version, you'll see a\n **Not valid against latest** badge listing exactly what's wrong. Select\n **Start guided upgrade** to switch into editing against the latest\n version, fix the listed issues, and save — the Component moves onto the\n new version as part of that save.\n\nYou're never forced to upgrade; an older Component keeps validating against\nthe version it was created with until you choose to move it forward.\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"slug": "content-adapters",
|
|
23
23
|
"title": "Bridging Contracts with Adapters",
|
|
24
|
-
"body": "# Bridging Contracts with Adapters\r\n\r\nWhen you bind a Component to a Template (see [Authoring a presentation](content-authoring.md)), the\r\nComponent's Contract normally has to be exactly what the Template needs, or a more specific kind of\r\nit. Sometimes the Component you want to use has a *different* Contract entirely — an **Adapter**\r\nbridges that gap by mapping the mismatched Contract's fields onto the ones the Template expects.\r\n\r\n## When the wizard offers one\r\n\r\nIn the Presentation wizard's **Select existing** tab, a Component whose Contract the Template\r\ndoesn't recognize at all lands in the **Needs an Adapter** group. Selecting one shows why it doesn't\r\nfit, and three buttons appear:\r\n\r\n- **Use existing Adapter** — pick an already-created Adapter that maps this Component's Contract onto\r\n something the Template accepts.\r\n- **Create Adapter** — build a new, reusable, named Adapter right there in the wizard, for this\r\n Contract pairing. It's saved to the **Adapters** page (under Configure) so anyone can reuse it\r\n later.\r\n- **Connect manually** — build a one-off mapping that's saved directly on this Presentation only,\r\n never listed on the Adapters page. Useful for a mapping you don't expect to need again. A **Save as\r\n Adapter…** action turns it into a named one later, without redoing the mapping.\r\n\r\nEither way, once a mapping is attached the field-by-field connections are drawn between the\r\nComponent's fields and the Template's, Complete becomes available, and the banner reads \"Mapped via\r\n*Adapter name*\" (or \"Mapped manually\" for a one-off).\r\n\r\nA Component that's already Compatible can still get an Adapter attached deliberately — useful when\r\nyou want to reshape its content on the way into this particular Template, even though it didn't\r\nstrictly need mapping.\r\n\r\n## After it's mapped\r\n\r\nOnce a mapping is attached, the set-value card offers a few more actions beside the Adapter's name:\r\n\r\n- **Edit mapping** — reopens the same field-by-field canvas, already filled in with the current\r\n mapping, so you can adjust it without starting over. This never changes *which* Component is\r\n bound, and editing a named Adapter's mapping this way never touches the shared Adapter itself (so\r\n every other Presentation using it is unaffected) — it becomes this Presentation's own one-off\r\n mapping instead, which you can turn back into a named Adapter with **Save as Adapter…** if you\r\n want to reuse it elsewhere.\r\n- **Change** — picks a different Component for this Presentation entirely. If the new Component's\r\n Contract still works with the mapping that was already in place, it's kept automatically; if not,\r\n it's dropped and the reason is explained right there, so you can map fresh.\r\n- **Save as Adapter…** — only shown for a one-off (\"manual mapping\") — turns it into a named,\r\n reusable Adapter without redoing any of the mapping work.\r\n\r\n## The four rule kinds, in plain language\r\n\r\nEvery field the Template needs gets exactly one rule saying where its value comes from:\r\n\r\n- **Copy** — take the value straight from one of the Component's own fields (the field types have to\r\n be compatible — see below).\r\n- **Compute** — build the value from a small set of building blocks: one of the Component's fields, a\r\n fixed piece of text, joining a few things together with an optional separator, falling back\r\n through a list of options until one isn't empty, converting a value from one field type to a\r\n compatible one (e.g. plain text into rich text), taking just the first item of a list, reading a\r\n field out of a referenced/nested item, mapping every item of a list, dispatching on which kind of\r\n item you have, or a simple condition/comparison — see **Logic and structure blocks** below for all\r\n of these. There's no free-text formula box — you build the computation visually, block by block, on\r\n the canvas.\r\n- **Map inner content** — for a field that holds another whole piece of content inside it (a nested\r\n or embedded Component), apply a second, smaller Adapter to that inner content: pick an existing\r\n named Adapter, or **Connect manually** to name that inner content's own two Contracts and map them\r\n right there, the same way you'd map the outer fields. (This is also available as the **For each\r\n item** block's own \"Adapt to another Contract\" mode — see below — which is the way to reach it when\r\n you're adding a fresh block rather than mapping a whole field.)\r\n- **Look up a fixed item** — instead of taking a value from the Component being mapped, always pull it\r\n from one specific, chosen Component or item.\r\n\r\n## Field compatibility\r\n\r\nTwo fields need to either already match, or be *coercible*, to connect — you can't map a text field\r\nstraight onto a number field, for example, but a few kinds of mismatch have a small conversion block\r\nthe canvas offers to insert for you automatically:\r\n\r\n- A single value can feed a field that accepts multiple values (it becomes a one-item list) — no\r\n extra block needed.\r\n- A field that accepts multiple values can feed a single-value field by inserting a **Take first\r\n item** block, which uses the first value and drops the rest.\r\n- Some field types can be converted into others by inserting a **Convert** block — plain text into\r\n rich text, plain text into a number (fails at delivery time if the text isn't actually numeric), a\r\n number into text, or rich text into plain text (its formatting is discarded).\r\n- A field that isn't translatable can feed a translatable one (it becomes that language's default,\r\n with no translations yet).\r\n- A field restricted to certain kinds of related content requires the source to already be\r\n restricted to the same (or a more specific) set.\r\n\r\nWhen you connect two fields that need one of these conversions, a small prompt appears offering to\r\ninsert the right block between them — accept it and the connection completes with that block already\r\nwired in place. Everything else — a translatable field into a non-translatable one, a personalizable\r\nfield into a non-personalizable one (or vice versa), or two field types with no registered\r\nconversion at all — isn't allowed, and the canvas explains why right there instead of drawing a\r\nconnection (incompatible fields are also visibly dimmed while you have a source selected).\r\n\r\n## What \"unmapped — delivers null\" means\r\n\r\nEvery field the Template *requires* needs a rule — the canvas won't let you save until they all do.\r\nA field that's merely optional can be left unmapped; the card for it reads **unmapped — delivers\r\nnull**, meaning a person or system reading this content afterward will see that field as empty,\r\nexactly as if it had no value at all. This is often exactly what you want for a field the source\r\nContract has no equivalent of.\r\n\r\n## The mapping canvas\r\n\r\nWhether you're on the standalone **Adapters** page (Configure → Adapters → New Adapter) or inside the\r\nwizard, mapping works the same way: every building block you add is its own node on one draggable,\r\nzoomable canvas — the **Input document** card starts on the left, the Template's (or the other\r\nContract's) fields on the right. Drag a line from one connector to another to connect them, or click a\r\nname to select it as the starting point and then click the one it should feed — both do exactly\r\nthe same thing, so use whichever is easier at the time. Click an existing connection, or the field's own\r\ndelete button, to remove it. `Escape` cancels a pending click-to-connect selection.\r\n\r\nThe **Input document** card is everything you're mapping *from*, in one place. It's always there, and\r\nit has three parts:\r\n\r\n- **Fields** — one connector per field of the Contract you're mapping from. This is the everyday\r\n one: drag a field straight across to the output it should fill.\r\n- **Input document** — the whole piece of content, as one thing. Use this when the same Adapter runs\r\n over several *different* kinds of content (a search result over every kind of page, say) and you\r\n need to branch on which kind each one actually is — connect it to a **Switch on Contract** block.\r\n- **System** — facts about the run itself rather than about the content: **Source contract external\r\n id** and **Source contract id**, which tell a consumer *which kind* of content a result came from.\r\n\r\nThe **+ Add node** button adds a building block to the canvas — **Join text**, **First non-null**, a\r\n**Constant value**, **Convert type**, **Take first item**, **Look up in** (a fixed reference to one\r\nComponent), **Expand fields**, **Build value**, **For each item**, **Switch on Contract**, **If / else**,\r\n**Equals**, or **Exists** (the last seven are covered in **Logic and structure blocks** below). Drag or click to\r\nwire fields into and out of it just like any other node; each block has its own small settings right on\r\nthe node itself (which characters to join with, what to convert into, and so on). Delete a block with\r\nthe **✕** in its own corner.\r\n\r\nA required field the Template needs that's still unconnected is called out in red at the bottom of the\r\ncanvas, and saving stays disabled until every one of them has a rule.\r\n\r\nA **Full screen** button, above the canvas, expands it to fill the whole screen for a more complex\r\nmapping that needs more room to work in — nothing about the mapping is lost switching in or out, and\r\n`Escape` exits back to the normal view.\r\n\r\nA **Preview** button lets you pick a sample piece of content of the source Contract and see exactly\r\nwhat the mapped result looks like, using the real mapping engine — no guessing.\r\n\r\nThe canvas remembers however you arrange the nodes — drag them into a layout that makes sense to\r\nyou, save, and it's still that way next time you (or anyone else) open this mapping, whether it's a\r\nnamed Adapter or a one-off mapping on a single Presentation. Dragging snaps each node to a light\r\ngrid so things line up cleanly. A node you add but never connect to anything doesn't have its\r\nposition remembered, since it isn't part of the saved mapping at all yet.\r\n\r\n## Logic and structure blocks\r\n\r\n**Lists always go through a \"For each item\" block.** None of the other blocks below — Expand fields,\r\nSwitch on Contract, If/else, Equals, Exists — ever work directly on a list; each expects one item.\r\nWhen the field you're reading from holds a list, add a **For each item** block first: it's the one\r\nblock that turns a list into \"one item at a time\" and back into a list again. Everything else you\r\nconnect after it (an Expand, a Switch, a comparison, whatever the mapping needs for one item) runs\r\nonce per item automatically.\r\n\r\n- **Expand fields** — reads a field out of something that isn't a plain value: a referenced item, a\r\n nested piece of content, or a component field. Pick which Contract you're expecting (skipped\r\n automatically when there's only one possibility), and the block grows one output for each of that\r\n Contract's own fields — connect whichever ones you need onward, just like any other field.\r\n- **Expand Linked Experience** — for a link field that points at a page in your Experience tree:\r\n opens the linked page in one step. Its outputs are the page's **Name**, **URL**, **Component** (the\r\n content it's built from — pick a Contract under \"Read content as…\" to get that content's own fields\r\n as further outputs) and **Presentation** — the page's own rendering, exactly as it's configured\r\n (its Template, its content, its settings). Connect **Presentation** to a Presentation-typed output\r\n to show that page's presentation inline on yours — a \"featured pages\" section that re-hosts each\r\n page's real rendering rather than rebuilding a card from its fields. An outside link, or a page\r\n with no presentation of its own, gives nothing here; inside a **For each item**, pair it with\r\n **Keep item when** (below) to drop those. If the output field only allows certain Templates, a\r\n relayed presentation built on a different Template comes out empty (with a warning) rather than\r\n slipping through — widen the field's allowed Templates if that's not what you want.\r\n- **Build value** — the opposite of Expand fields: assembles a nested piece of content, or a component\r\n field's inline value, out of other fields in this mapping. Pick which Contract you're building, and\r\n the block grows one input for each of that Contract's own fields (fields with no separate override —\r\n the \"context\" ones you'd only ever fill in on a specific usage — never appear here at all). Fields\r\n marked **Required** need a connection before the mapping can be saved; anything else is optional and\r\n comes out empty if you leave it unconnected. Pick **Nested** or **Component** to match whatever field\r\n you're feeding the result into.\r\n- **For each item** — the only block that crosses a list boundary, as above. Choose one of two modes:\r\n - **Map each item** — click **Edit mapping** to open a dedicated screen (it takes over the whole\r\n canvas, with a **Back** button to return) where you build the per-item transformation using an\r\n **Item** starting point and a **Result** ending point, with any of the other blocks in between —\r\n including another **For each item**, for a list inside a list. That screen also has a second\r\n ending point, **Keep item when**: wire a yes/no value into it (an Exists or Equals block,\r\n usually) and only the items where it comes out *yes* make it into the list — the rest are left\r\n out entirely, with no empty placeholder and no warning for them. Leave it unconnected to keep\r\n every item. The typical use is a list of links that mixes pages with outside links: **Expand\r\n Linked Experience → Component → Exists → Keep item when** drops everything that isn't a real\r\n page, instead of shipping an empty card for it. One output can feed several blocks at once, so\r\n the same Expand can supply both the check and the result.\r\n - **Adapt to another Contract** — apply a whole second, smaller Adapter to each item, exactly like\r\n **Map inner content** above (pick an existing named Adapter, or **Connect manually**).\r\n- **Switch on Contract** — for a field that could hold more than one kind of thing (several different\r\n Contracts), branch the mapping by which one it actually is. Add a case for each Contract you want to\r\n handle by name; each case gets its own **As *(Contract name)*** output (typically feeding an Expand\r\n fields block for that specific Contract) and its own **Value for this case** input for what should\r\n come out when it matches. An optional **Default** input covers any Contract you didn't add a case\r\n for. See **Reading one page type's fields into several outputs**, below, for the two ways to wire\r\n it up.\r\n- **If / else** — picks between two values based on a yes/no condition: wire something into **When**\r\n (must resolve to yes/no — an Exists or Equals block is the usual source), and one value each into\r\n **Then** and **Else**.\r\n- **Equals** — compares two values and outputs yes/no; both sides need to already be the same field\r\n type (no automatic conversion here — insert a Convert type block first if they aren't).\r\n- **Exists** — outputs yes unless the value it's checking is completely missing or an empty piece of\r\n text; useful as the condition for an If/else block (\"only use this if there's actually something\r\n there\").\r\n\r\n## Reading one page type's fields into several outputs\r\n\r\nA common shape: the same Adapter runs over several kinds of content, and one of those kinds has a\r\nhandful of extra fields you want to carry through. A search result over every kind of page, where a\r\nblog post additionally has a heading, an author, a publish date, an image, topics and an excerpt, is\r\nthe worked example.\r\n\r\nWire it exactly the way you'd draw it:\r\n\r\n1. **Input document → Switch on Contract** (its **Value to switch on** connector). Add one case,\r\n *Blog post*.\r\n2. **As Blog post → Expand fields.** The Expand grows one output per field a blog post has.\r\n3. Connect each of those outputs **straight across to the output field it fills** — heading to\r\n Heading, author to Author, and so on. You do not wire anything back into the Switch.\r\n\r\nThat's it. Behind the scenes each of those outputs becomes its own rule, gated on \"only when this\r\nreally is a blog post\"; anything that isn't a blog post simply leaves those fields empty, which is\r\nwhat you want. Reopening the Adapter later shows you the same one Switch and one Expand you drew.\r\n\r\n**Fields every kind of content has come off the Input document, not through the Switch.** Title and\r\nDescription exist on every page, so wire them straight from the Input document's **Fields** section.\r\nRouting them through a case would gain nothing and would leave them empty for anything the Switch\r\ndoesn't have a case for.\r\n\r\n**When one output needs a *different* value per type**, that's the Switch's own arms — \"Title is the\r\nblog post's heading if it's a blog post, otherwise the page's own title\":\r\n\r\n1. **Expand fields → heading → Value for this case** (the *Blog post* arm).\r\n2. **Input document → Fields → Title → Default.**\r\n3. **Switch → Result → Title.**\r\n\r\nOne connector can feed several places at once, so the same Expand's *heading* output can go to both\r\nthe Heading output field and the Switch's arm — it's still one Switch and one Expand on the canvas.\r\n\r\n**One thing the canvas won't do:** read *two different cases'* fields into a single output without\r\ngoing through the arms. If you try, that output field shows \"Fields read from different cases of one\r\nSwitch must go through the case arms and Output\" and saving is blocked until you rewire it — combining\r\nper-case values is exactly what the arms and **Result** are for. Every other output field keeps\r\nworking normally in the meantime.\r\n\r\n## Editing a named Adapter later\r\n\r\nAdapters created via **Create Adapter**, or saved via **Save as Adapter…**, live on the **Adapters**\r\npage (Configure → Adapters), listed with their input and output Contracts. Editing one there updates\r\nevery Presentation using it — but only once each of those is **published again**. Content that's\r\nalready published keeps its existing shape until republished, so a mapping change never silently\r\nchanges something a visitor is looking at right now.\r\n\r\nIf a named Adapter is deleted while still bound to a Presentation, the field-by-field connections stop\r\nresolving correctly — a warning appears on the affected field, and saving is blocked until you either\r\nrestore the Adapter, pick a different one, or unbind it.\r\n"
|
|
24
|
+
"body": "# Bridging Contracts with Adapters\n\nWhen you bind a Component to a Template (see [Authoring a presentation](content-authoring.md)), the\nComponent's Contract normally has to be exactly what the Template needs, or a more specific kind of\nit. Sometimes the Component you want to use has a *different* Contract entirely — an **Adapter**\nbridges that gap by mapping the mismatched Contract's fields onto the ones the Template expects.\n\n## When the wizard offers one\n\nIn the Presentation wizard's **Select existing** tab, a Component whose Contract the Template\ndoesn't recognize at all lands in the **Needs an Adapter** group. Selecting one shows why it doesn't\nfit, and three buttons appear:\n\n- **Use existing Adapter** — pick an already-created Adapter that maps this Component's Contract onto\n something the Template accepts.\n- **Create Adapter** — build a new, reusable, named Adapter right there in the wizard, for this\n Contract pairing. It's saved to the **Adapters** page (under Configure) so anyone can reuse it\n later.\n- **Connect manually** — build a one-off mapping that's saved directly on this Presentation only,\n never listed on the Adapters page. Useful for a mapping you don't expect to need again. A **Save as\n Adapter…** action turns it into a named one later, without redoing the mapping.\n\nEither way, once a mapping is attached the field-by-field connections are drawn between the\nComponent's fields and the Template's, Complete becomes available, and the banner reads \"Mapped via\n*Adapter name*\" (or \"Mapped manually\" for a one-off).\n\nA Component that's already Compatible can still get an Adapter attached deliberately — useful when\nyou want to reshape its content on the way into this particular Template, even though it didn't\nstrictly need mapping.\n\n## After it's mapped\n\nOnce a mapping is attached, the set-value card offers a few more actions beside the Adapter's name:\n\n- **Edit mapping** — reopens the same field-by-field canvas, already filled in with the current\n mapping, so you can adjust it without starting over. This never changes *which* Component is\n bound, and editing a named Adapter's mapping this way never touches the shared Adapter itself (so\n every other Presentation using it is unaffected) — it becomes this Presentation's own one-off\n mapping instead, which you can turn back into a named Adapter with **Save as Adapter…** if you\n want to reuse it elsewhere.\n- **Change** — picks a different Component for this Presentation entirely. If the new Component's\n Contract still works with the mapping that was already in place, it's kept automatically; if not,\n it's dropped and the reason is explained right there, so you can map fresh.\n- **Save as Adapter…** — only shown for a one-off (\"manual mapping\") — turns it into a named,\n reusable Adapter without redoing any of the mapping work.\n\n## The four rule kinds, in plain language\n\nEvery field the Template needs gets exactly one rule saying where its value comes from:\n\n- **Copy** — take the value straight from one of the Component's own fields (the field types have to\n be compatible — see below).\n- **Compute** — build the value from a small set of building blocks: one of the Component's fields, a\n fixed piece of text, joining a few things together with an optional separator, falling back\n through a list of options until one isn't empty, converting a value from one field type to a\n compatible one (e.g. plain text into rich text), taking just the first item of a list, reading a\n field out of a referenced/nested item, mapping every item of a list, dispatching on which kind of\n item you have, or a simple condition/comparison — see **Logic and structure blocks** below for all\n of these. There's no free-text formula box — you build the computation visually, block by block, on\n the canvas.\n- **Map inner content** — for a field that holds another whole piece of content inside it (a nested\n or embedded Component), apply a second, smaller Adapter to that inner content: pick an existing\n named Adapter, or **Connect manually** to name that inner content's own two Contracts and map them\n right there, the same way you'd map the outer fields. (This is also available as the **For each\n item** block's own \"Adapt to another Contract\" mode — see below — which is the way to reach it when\n you're adding a fresh block rather than mapping a whole field.)\n- **Look up a fixed item** — instead of taking a value from the Component being mapped, always pull it\n from one specific, chosen Component or item.\n\n## Field compatibility\n\nTwo fields need to either already match, or be *coercible*, to connect — you can't map a text field\nstraight onto a number field, for example, but a few kinds of mismatch have a small conversion block\nthe canvas offers to insert for you automatically:\n\n- A single value can feed a field that accepts multiple values (it becomes a one-item list) — no\n extra block needed.\n- A field that accepts multiple values can feed a single-value field by inserting a **Take first\n item** block, which uses the first value and drops the rest.\n- Some field types can be converted into others by inserting a **Convert** block — plain text into\n rich text, plain text into a number (fails at delivery time if the text isn't actually numeric), a\n number into text, or rich text into plain text (its formatting is discarded).\n- A field that isn't translatable can feed a translatable one (it becomes that language's default,\n with no translations yet).\n- A field restricted to certain kinds of related content requires the source to already be\n restricted to the same (or a more specific) set.\n\nWhen you connect two fields that need one of these conversions, a small prompt appears offering to\ninsert the right block between them — accept it and the connection completes with that block already\nwired in place. Everything else — a translatable field into a non-translatable one, a personalizable\nfield into a non-personalizable one (or vice versa), or two field types with no registered\nconversion at all — isn't allowed, and the canvas explains why right there instead of drawing a\nconnection (incompatible fields are also visibly dimmed while you have a source selected).\n\n## What \"unmapped — delivers null\" means\n\nEvery field the Template *requires* needs a rule — the canvas won't let you save until they all do.\nA field that's merely optional can be left unmapped; the card for it reads **unmapped — delivers\nnull**, meaning a person or system reading this content afterward will see that field as empty,\nexactly as if it had no value at all. This is often exactly what you want for a field the source\nContract has no equivalent of.\n\n## The mapping canvas\n\nWhether you're on the standalone **Adapters** page (Configure → Adapters → New Adapter) or inside the\nwizard, mapping works the same way: every building block you add is its own node on one draggable,\nzoomable canvas — the **Input document** card starts on the left, the Template's (or the other\nContract's) fields on the right. Drag a line from one connector to another to connect them, or click a\nname to select it as the starting point and then click the one it should feed — both do exactly\nthe same thing, so use whichever is easier at the time. Click an existing connection, or the field's own\ndelete button, to remove it. `Escape` cancels a pending click-to-connect selection.\n\nThe **Input document** card is everything you're mapping *from*, in one place. It's always there, and\nit has three parts:\n\n- **Fields** — one connector per field of the Contract you're mapping from. This is the everyday\n one: drag a field straight across to the output it should fill.\n- **Input document** — the whole piece of content, as one thing. Use this when the same Adapter runs\n over several *different* kinds of content (a search result over every kind of page, say) and you\n need to branch on which kind each one actually is — connect it to a **Switch on Contract** block.\n- **System** — facts about the run itself rather than about the content: **Source contract external\n id** and **Source contract id**, which tell a consumer *which kind* of content a result came from.\n\nThe **+ Add node** button adds a building block to the canvas — **Join text**, **First non-null**, a\n**Constant value**, **Convert type**, **Take first item**, **Look up in** (a fixed reference to one\nComponent), **Expand fields**, **Build value**, **For each item**, **Switch on Contract**, **If / else**,\n**Equals**, or **Exists** (the last seven are covered in **Logic and structure blocks** below). Drag or click to\nwire fields into and out of it just like any other node; each block has its own small settings right on\nthe node itself (which characters to join with, what to convert into, and so on). Delete a block with\nthe **✕** in its own corner.\n\nA required field the Template needs that's still unconnected is called out in red at the bottom of the\ncanvas, and saving stays disabled until every one of them has a rule.\n\nA **Full screen** button, above the canvas, expands it to fill the whole screen for a more complex\nmapping that needs more room to work in — nothing about the mapping is lost switching in or out, and\n`Escape` exits back to the normal view.\n\nA **Preview** button lets you pick a sample piece of content of the source Contract and see exactly\nwhat the mapped result looks like, using the real mapping engine — no guessing.\n\nThe canvas remembers however you arrange the nodes — drag them into a layout that makes sense to\nyou, save, and it's still that way next time you (or anyone else) open this mapping, whether it's a\nnamed Adapter or a one-off mapping on a single Presentation. Dragging snaps each node to a light\ngrid so things line up cleanly. A node you add but never connect to anything doesn't have its\nposition remembered, since it isn't part of the saved mapping at all yet.\n\n## Logic and structure blocks\n\n**Lists always go through a \"For each item\" block.** None of the other blocks below — Expand fields,\nSwitch on Contract, If/else, Equals, Exists — ever work directly on a list; each expects one item.\nWhen the field you're reading from holds a list, add a **For each item** block first: it's the one\nblock that turns a list into \"one item at a time\" and back into a list again. Everything else you\nconnect after it (an Expand, a Switch, a comparison, whatever the mapping needs for one item) runs\nonce per item automatically.\n\n- **Expand fields** — reads a field out of something that isn't a plain value: a referenced item, a\n nested piece of content, or a component field. Pick which Contract you're expecting (skipped\n automatically when there's only one possibility), and the block grows one output for each of that\n Contract's own fields — connect whichever ones you need onward, just like any other field.\n- **Expand Linked Experience** — for a link field that points at a page in your Experience tree:\n opens the linked page in one step. Its outputs are the page's **Name**, **URL**, **Component** (the\n content it's built from — pick a Contract under \"Read content as…\" to get that content's own fields\n as further outputs) and **Presentation** — the page's own rendering, exactly as it's configured\n (its Template, its content, its settings). Connect **Presentation** to a Presentation-typed output\n to show that page's presentation inline on yours — a \"featured pages\" section that re-hosts each\n page's real rendering rather than rebuilding a card from its fields. An outside link, or a page\n with no presentation of its own, gives nothing here; inside a **For each item**, pair it with\n **Keep item when** (below) to drop those. If the output field only allows certain Templates, a\n relayed presentation built on a different Template comes out empty (with a warning) rather than\n slipping through — widen the field's allowed Templates if that's not what you want.\n- **Build value** — the opposite of Expand fields: assembles a nested piece of content, or a component\n field's inline value, out of other fields in this mapping. Pick which Contract you're building, and\n the block grows one input for each of that Contract's own fields (fields with no separate override —\n the \"context\" ones you'd only ever fill in on a specific usage — never appear here at all). Fields\n marked **Required** need a connection before the mapping can be saved; anything else is optional and\n comes out empty if you leave it unconnected. Pick **Nested** or **Component** to match whatever field\n you're feeding the result into.\n- **For each item** — the only block that crosses a list boundary, as above. Choose one of two modes:\n - **Map each item** — click **Edit mapping** to open a dedicated screen (it takes over the whole\n canvas, with a **Back** button to return) where you build the per-item transformation using an\n **Item** starting point and a **Result** ending point, with any of the other blocks in between —\n including another **For each item**, for a list inside a list. That screen also has a second\n ending point, **Keep item when**: wire a yes/no value into it (an Exists or Equals block,\n usually) and only the items where it comes out *yes* make it into the list — the rest are left\n out entirely, with no empty placeholder and no warning for them. Leave it unconnected to keep\n every item. The typical use is a list of links that mixes pages with outside links: **Expand\n Linked Experience → Component → Exists → Keep item when** drops everything that isn't a real\n page, instead of shipping an empty card for it. One output can feed several blocks at once, so\n the same Expand can supply both the check and the result.\n - **Adapt to another Contract** — apply a whole second, smaller Adapter to each item, exactly like\n **Map inner content** above (pick an existing named Adapter, or **Connect manually**).\n- **Switch on Contract** — for a field that could hold more than one kind of thing (several different\n Contracts), branch the mapping by which one it actually is. Add a case for each Contract you want to\n handle by name; each case gets its own **As *(Contract name)*** output (typically feeding an Expand\n fields block for that specific Contract) and its own **Value for this case** input for what should\n come out when it matches. An optional **Default** input covers any Contract you didn't add a case\n for. See **Reading one page type's fields into several outputs**, below, for the two ways to wire\n it up.\n- **If / else** — picks between two values based on a yes/no condition: wire something into **When**\n (must resolve to yes/no — an Exists or Equals block is the usual source), and one value each into\n **Then** and **Else**.\n- **Equals** — compares two values and outputs yes/no; both sides need to already be the same field\n type (no automatic conversion here — insert a Convert type block first if they aren't).\n- **Exists** — outputs yes unless the value it's checking is completely missing or an empty piece of\n text; useful as the condition for an If/else block (\"only use this if there's actually something\n there\").\n\n## Reading one page type's fields into several outputs\n\nA common shape: the same Adapter runs over several kinds of content, and one of those kinds has a\nhandful of extra fields you want to carry through. A search result over every kind of page, where a\nblog post additionally has a heading, an author, a publish date, an image, topics and an excerpt, is\nthe worked example.\n\nWire it exactly the way you'd draw it:\n\n1. **Input document → Switch on Contract** (its **Value to switch on** connector). Add one case,\n *Blog post*.\n2. **As Blog post → Expand fields.** The Expand grows one output per field a blog post has.\n3. Connect each of those outputs **straight across to the output field it fills** — heading to\n Heading, author to Author, and so on. You do not wire anything back into the Switch.\n\nThat's it. Behind the scenes each of those outputs becomes its own rule, gated on \"only when this\nreally is a blog post\"; anything that isn't a blog post simply leaves those fields empty, which is\nwhat you want. Reopening the Adapter later shows you the same one Switch and one Expand you drew.\n\n**Fields every kind of content has come off the Input document, not through the Switch.** Title and\nDescription exist on every page, so wire them straight from the Input document's **Fields** section.\nRouting them through a case would gain nothing and would leave them empty for anything the Switch\ndoesn't have a case for.\n\n**When one output needs a *different* value per type**, that's the Switch's own arms — \"Title is the\nblog post's heading if it's a blog post, otherwise the page's own title\":\n\n1. **Expand fields → heading → Value for this case** (the *Blog post* arm).\n2. **Input document → Fields → Title → Default.**\n3. **Switch → Result → Title.**\n\nOne connector can feed several places at once, so the same Expand's *heading* output can go to both\nthe Heading output field and the Switch's arm — it's still one Switch and one Expand on the canvas.\n\n**One thing the canvas won't do:** read *two different cases'* fields into a single output without\ngoing through the arms. If you try, that output field shows \"Fields read from different cases of one\nSwitch must go through the case arms and Output\" and saving is blocked until you rewire it — combining\nper-case values is exactly what the arms and **Result** are for. Every other output field keeps\nworking normally in the meantime.\n\n## Editing a named Adapter later\n\nAdapters created via **Create Adapter**, or saved via **Save as Adapter…**, live on the **Adapters**\npage (Configure → Adapters), listed with their input and output Contracts. Editing one there updates\nevery Presentation using it — but only once each of those is **published again**. Content that's\nalready published keeps its existing shape until republished, so a mapping change never silently\nchanges something a visitor is looking at right now.\n\nIf a named Adapter is deleted while still bound to a Presentation, the field-by-field connections stop\nresolving correctly — a warning appears on the affected field, and saving is blocked until you either\nrestore the Adapter, pick a different one, or unbind it.\n"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"slug": "content-authoring",
|
|
28
28
|
"title": "Authoring a presentation",
|
|
29
|
-
"body": "# Authoring a presentation\
|
|
29
|
+
"body": "# Authoring a presentation\n\nA Contract field with the **Presentation** field type lets a Component\ninstantiate a Template inline — the common pattern for a page's body is an\n*enumerable* Presentation field, so the page becomes a list of presented\nsections. See [Presenting content with Templates](templates.md) for what a\nTemplate and a Presentation are; this page walks through the wizard you use\nto create or change one.\n\n## Adding a presentation\n\nWhen you reach an empty Presentation field while editing a Component (or an\nExperience node's payload, or a presentation embed in a Rich Text field),\nselect **+ Add presentation** to open the wizard.\n\n### Step 1 — Choose Template\n\nA searchable, sortable list of the Templates this field allows. Type to\nfilter by name or Id, or filter to Templates that support a specific\nContract. Select a row to move on to step 2.\n\nIf nothing is eligible yet, a link takes you to the Templates page to create\none.\n\n### Step 2 — Bind Component\n\nTwo tabs:\n\n- **Select existing** — a three-column view: the Component you're\n considering on the left, a compatibility summary in the middle, and what\n the Template needs on the right.\n- **Create new** — the same on-the-spot creation flow as before: pick a\n Contract the Template accepts and fill in the required fields, without\n leaving the page.\n\nOn **Select existing**, the left column lists every Component, grouped:\n\n- **Compatible** — a Component whose Contract is exactly what the Template\n needs, or a more specific kind of it (a subtype). Selecting one shows\n every field lining up on the right, marks Complete as ready, and you're\n done.\n- **Needs an Adapter** — everything else, collapsed by default with a count\n so the list stays short. A Component here has a Contract the Template\n doesn't recognize at all. Selecting one shows *why* it doesn't fit, and\n three buttons let you bridge the gap: **Use existing Adapter**, **Create\n Adapter**, or **Connect manually** — see [Bridging Contracts with\n Adapters](content-adapters.md) for what each does and how the field\n mapping works. Until you attach one, Complete stays disabled — there's no\n way to force a mismatched binding through unmapped.\n\nA Template that doesn't restrict which Contracts it accepts treats every\nComponent as compatible. A Template that accepts more than one Contract\nshows a small switcher on the right so you can see the fields for each one\nit supports.\n\nSelect **Complete** once you're happy with the binding. Nothing is saved to\nthe field until you do — closing the wizard at any point beforehand leaves\nthings exactly as they were.\n\n## Changing or removing a presentation\n\nOnce bound, the field shows a card with the Component's name and:\n\n- **Change** re-opens the wizard directly on step 2, so you can pick a\n different Component (or select **Back** to choose a different Template\n entirely — doing so resets any per-usage Contextual values and settings,\n the same as a first-time pick).\n- **Contextual values** opens a panel for the bound Component's own\n Contextual fields — present only when the bound Contract actually has\n any. If a mandatory Contextual field still needs a value, a **Contextual\n values needed** chip appears next to the button as a heads-up before you\n try to save; selecting it opens the same panel.\n- **Settings** opens the Template's own settings form for this specific\n placement.\n- **Quick edit** opens the bound Component itself, right here — see\n [Editing something from where it's used](#editing-something-from-where-its-used)\n below.\n- **Remove** clears the Presentation value entirely.\n\nIf the Template has been updated to a newer version since this Presentation\nwas created, a small notice appears — **Update to v_N_** moves it onto the\nnew version. Until you do, the existing Presentation keeps working exactly\nas before.\n\n## Editing something from where it's used\n\nAnything you open *from inside* an editor — a Component bound to a\nPresentation, a Component linked through a Reference field, the page a\nLink points at, a Nested value, a set of Contextual values — opens as a\n**layer** over the page you're on, instead of taking you somewhere else.\nLayers stack: open a Component from a page, then a Link target from inside\nthat Component, and you're three deep, with each layer you came through\ncollapsed to a title strip above the one you're in. Select any strip, or\nany name in the trail at the top of a layer, to go back to it.\n\nEvery layer says what it is and whose save it belongs to:\n\n- **Own save** (the pink chip) — this layer is a *separate thing*: a shared\n Component, or a page in the Experience tree. It has its own **Save**\n button, and saving changes it *everywhere it's used* — the panel on the\n right lists every place, and the sentence at the bottom names exactly\n what does and doesn't change. **Cancel** throws the layer's edits away and\n leaves the page you came from untouched. Use **Open full editor** when you\n need more than a quick change (publishing, workflow, moving folders).\n- **Part of …** (the blue chip) — this layer is a *view into the page you're\n on*: a Nested value, Contextual values, Presentation settings. There's no\n Save here — select **Done** and the edit rides along with the page's own\n Save, exactly as if you'd made it in the page directly.\n\nClosing a layer that has unsaved changes asks first, inside the layer\nitself — **Keep editing** is selected by default, so a stray Enter never\ndiscards work. **Esc** closes one layer at a time. Quick edit is only offered\nfor content that lives in this organization; an item from a connected\nexternal provider has no editor here.\n"
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"slug": "content-composer",
|
|
33
33
|
"title": "Building your site in Composer",
|
|
34
|
-
"body": "# Building your site in Composer\r\n\r\nComposer is where content becomes a website. A **site** is a named tree of\r\n**pages**; each page's slug (and its ancestors' slugs) form its **path** —\r\nthe URL it will eventually be served at. A page can be pure structure (a\r\n**Folder**, with no page of its own, just a place to organize its\r\nchildren), or it can carry a payload: either a **Presentation** (content\r\nshown at that path) or a **Redirect** (send visitors somewhere else).\r\nContracts, Components, and Templates (see\r\n[Modeling content with Contracts](contracts.md),\r\n[Authoring Components](components.md), and\r\n[Presenting content with Templates](templates.md)) define *what* content\r\nis and *how* it's presented; Composer decides *where* it lives. Nothing\r\nbuilt here is publicly visible yet — publishing is a later phase.\r\n\r\nComposer is the suite's *top-down* authoring surface — site, then tree,\r\nthen page. [Content](content-authoring.md) is its *bottom-up* counterpart:\r\nthe Component library, edited independently of any one page. Both appear\r\nas their own item in the sidebar.\r\n\r\n## Choosing a site\r\n\r\nComposer's header has a site switcher — select it to see every site in\r\nthis organization and pick one, or select **Manage sites →** to create,\r\nrename, or delete a site. Composer's tree always shows exactly one site's\r\npages at a time; switching sites swaps the whole tree beneath it.\r\n\r\nSites themselves are managed from **Settings > Sites**: create a new one\r\nby name, rename an existing one, or delete one (deleting a site deletes\r\nits entire page tree, including every page's payload — typing the site's\r\nname confirms you mean it).\r\n\r\n## Building the tree\r\n\r\nA large tree can be searched by category — see \"Filtering by category\" in\r\n[Taxonomy](content-taxonomy.md) for how the tree's own **Filter by\r\ncategory** control auto-expands to reveal matches without hiding anything\r\nelse.\r\n\r\nTo add a page:\r\n\r\n1. Select the parent page (the site's own root, shown at the top of the\r\n tree, or any page under it), then select **+ Add page** at the top of\r\n the tree panel.\r\n2. Pick a type — **Presentation**, **Redirect**, or **Folder** —\r\n Presentation is pre-selected, since it's what most new pages are for.\r\n You can change a page's type at any time later, from its own\r\n Experience tab.\r\n3. Enter a **Name** — the path preview beneath it fills in from it\r\n automatically (`/campaigns-launch`, for example). You can give the page\r\n a different slug later, from its **Configuration** tab.\r\n4. Select **Create**.\r\n\r\nEvery page shows its computed **path** at the top of the detail panel —\r\nthe site root's own path is always `/`.\r\n\r\n### Localized slugs\r\n\r\nA page's slug can have a different value per locale, just like other\r\nlocalizable content — select the translate icon beside **Slug** (in the\r\n**Configuration** tab) to add per-locale overrides. Two sibling pages can\r\nnever resolve to the same slug in the same locale, whether that's their\r\nshared default or one sibling's override colliding with another's\r\ndefault; you'll see exactly which locale and which sibling conflicts if\r\nthat happens.\r\n\r\n### Moving and reordering pages\r\n\r\nThe tree supports two different drags, depending on where you drop:\r\n\r\n- Drop a page **onto** another page to reparent it there — the dropped\r\n page becomes that page's child.\r\n- Drop a page **between** two sibling rows to reorder it among its\r\n current siblings, without changing its parent.\r\n\r\nEither kind of drag can also be done without a mouse: select **Move…** in\r\na page's **Configuration** tab and pick a target the same way dragging\r\nwould. You can't move a page into itself or one of its own descendants,\r\nand a site's root can't be moved or reordered at all — it has no grip\r\nhandle to drag by, and its Configuration tab shows no **Move…** button.\r\n\r\nIf someone else changes the tree while you're mid-drag, Composer notices,\r\nrefreshes the sibling list automatically, and asks you to try again rather\r\nthan silently applying a stale order.\r\n\r\nDeleting a page requires it to have no children first; a page that's the\r\ntarget of a **Redirect** elsewhere in the tree can't be deleted until that\r\nredirect is removed or repointed. A site's root can't be deleted from\r\nComposer at all — that's a **Settings > Sites** action, since it deletes\r\nthe whole tree beneath it.\r\n\r\n## The Experience tab\r\n\r\nA page's **Experience** tab is where you choose its type and, for a\r\nPresentation page, what actually renders:\r\n\r\n- **Template** shows which Template is bound and the Contract it\r\n requires.\r\n- **Satisfied by** shows the actual chain that fills that requirement —\r\n either a Component directly, or one or more Adapters translating a\r\n Component's content into what the Template expects (see\r\n [Adapters](content-adapters.md) if that's new to you). This is a live\r\n view of the binding actually in effect, not just two separate names, and\r\n it's also where you do the actual work: the innermost box in the chain\r\n is where you pick a Template, bind a Component (or create one on the\r\n spot), and fine-tune it through the **Contextual values** and\r\n **Settings** panels described in\r\n [Presenting content with Templates](templates.md). If the Template\r\n declares a settings field that's both mandatory and Contextual, it must\r\n be filled in here — a page is the last stop for that value, so there's\r\n nowhere further downstream to inherit it from. **Quick edit** opens the\r\n bound Component itself in a layer over the tree — and the same layer\r\n opens *this* page from any Link or Experience-reference field elsewhere\r\n that points at it; see\r\n [Editing something from where it's used](content-authoring.md#editing-something-from-where-its-used).\r\n\r\nSwitching a page's type here always lands you on that type's own normal\r\nstarting point (no Template chosen yet, no redirect target chosen yet) —\r\nit never carries over or invents a binding from whatever the page was\r\nbefore.\r\n\r\nFor a **Redirect** page, select **Pick a target page…** and browse to it.\r\nA page can't redirect to itself. A **Folder** page has no payload of its\r\nown — it exists only to organize the pages beneath it.\r\n\r\n## The Configuration tab\r\n\r\nEverything about a page other than what it renders lives in\r\n**Configuration**: its name and slug, its location in the tree (parent,\r\nMove…), access rules and workflow assignment, classification tags, and —\r\nfor a non-root page — the option to delete it.\r\n\r\nPublish status and the page's actions — **Preview…**, **Promote…**, and a\r\n**Publish…**/**Republish…** button (once published, its own chevron opens\r\n**Unpublish**) — sit together in a row above the page name, visible no\r\nmatter which tab is open, since they matter regardless of what you're\r\ncurrently editing. A small workflow chip appears there too, once a page\r\nhas an editorial workflow assigned — select it to jump straight to the\r\nfull workflow panel in Configuration.\r\n\r\n## Live preview\r\n\r\nClick **Preview…** on any page to open live preview: your site, rendered\r\nright there in Composer, showing your unsaved draft — not what's actually\r\npublished. Nothing you do here goes live until you choose to publish it.\r\n\r\nThe page renders with numbered pins over anything you can edit. Shape\r\ntells you what a pin is, so it reads even in black and white:\r\n\r\n- A **magenta circle** is a single field — a headline, a paragraph, a date.\r\n- A **solid blue rounded rectangle** is a whole Component — either the\r\n Presentation bound at that spot, or something rendered outside the page\r\n itself (see \"Also on this page\" below).\r\n- A **dashed blue rounded rectangle** is content produced by an Adapter —\r\n mapped in from somewhere else, with no field of its own on this page.\r\n- A **red rectangle** marks something that couldn't be resolved.\r\n\r\nEvery pinned part of the page also gets a light outline in its pin's own\r\ncolor the whole time you're in Edit mode, not just on hover — so you can\r\nsee everything on the page that's editable at a glance, before you click\r\nanything. Hovering or selecting a pin (from the page or from its row in\r\nthe panel on the right) strengthens that outline and shows a tag naming\r\nwhat it is.\r\n\r\nWhat clicking a pin does depends on its shape:\r\n\r\n- A **field** pin opens a small **docked editor** right at the top of the\r\n panel — no matter how deep inside the page's content that field actually\r\n lives — with its name, its type, and a magenta **Own save** badge, above\r\n the rest of the page summary (dimmed while you're editing). Deep inside\r\n something (a section, a card, a list item), the docked editor also shows\r\n a breadcrumb (\"Referenced by *the page* → *Section 4* *Statement* →\r\n `actions`\") so you know exactly where you are. What docks depends on the\r\n field:\r\n - A **Component**, **Reference**, or **Presentation** field — the kind\r\n that points at other content — gets a compact list: one row per item,\r\n with a label, where it links to (if anywhere), a drag handle to\r\n reorder, an edit button, a remove button, and an **Add item** row at\r\n the bottom. Click a row's edit button to open that item — an item\r\n authored right here opens its own full editor; an item that's really a\r\n separate, shared Component re-targets this same panel to that\r\n Component instead of opening a second one.\r\n - Every other field (Rich text, a plain text field, a date, and so on)\r\n gets its own usual editor, docked as a single row.\r\n Type, then **Save** or **Cancel** — or **Open full editor** if you'd\r\n rather see the whole Component or page it belongs to.\r\n A pin whose field can't be matched this way falls back to opening the\r\n whole Component or page in full, with that field already focused.\r\n- A **Component** pin (solid rectangle) opens that Component for editing\r\n in full, the same as clicking **Quick edit** next to it — a magenta\r\n **Own save** badge marks it while you're in it.\r\n- An **Adapter-mapped** pin (dashed rectangle) never opens an editor\r\n directly — there's no field of yours behind it. Instead it explains\r\n where the content comes from: which Adapter produced it, and a look at\r\n the actual source underneath. From there, **Edit source field** jumps\r\n straight to the one field that's a direct, one-to-one copy (when there\r\n is one), and **Edit mapping…** opens the mapping itself for a deeper\r\n change.\r\n- A **red (unresolved)** pin opens on whatever it belongs to, so you can\r\n see and fix why it isn't resolving.\r\n\r\nType in a docked or full editor, and the page updates within a second or\r\nso either way.\r\n\r\nA **Browse** toggle switches off the pins so you can click around the page\r\nthe way a visitor would — following links to see other pages of the same\r\nsite, still against your unsaved drafts.\r\n\r\n**Also on this page** lists anything the site renders that isn't part of\r\nthe page itself — a shared header or footer, for instance. These are\r\nedited and saved the same way as anything else with a pin: click **Quick\r\nedit** next to it, make your change, and save. Because a shared header or\r\nfooter isn't part of any one page, saving it doesn't publish it along with\r\nthe page — after saving, use the **Publish…** button that appears right\r\nthere to make the change live. If a site is set up to show something\r\nthat doesn't exist in the environment you're working in, it's listed as\r\n**not in this environment** — nothing to worry about, and never something\r\nthat stops the rest of the page from previewing normally.\r\n\r\n## Opening a page from the site itself\r\n\r\nWhen you're signed in to Content and browsing one of your own published sites, a small **Click to\r\nedit** button appears in the corner of the page. It takes you straight to that page in Composer, with\r\nlive preview already open.\r\n\r\nIf the page belongs to a **different organization** than the one you're currently working in, Content\r\nswitches for you and shows a notice saying so, with a **Switch back** button. That switch applies to\r\nyour whole ebitex session — Hub and Forms in other tabs will show the new organization the next time\r\nyou navigate in them — so the notice stays until you dismiss it.\r\n\r\nA few cases where the link won't open a page, and what each means:\r\n\r\n- **\"This page no longer exists, or belongs to an organization you are not currently signed in to.\"**\r\n Either the page was deleted, or it belongs to an organization you're not a member of. If a\r\n colleague sent you the link, ask them to add you to that organization.\r\n- **Nothing switches and the page doesn't load.** The site's hostname isn't mapped to a site in any\r\n organization you belong to. Check **Settings > Delivery** for that organization's host mappings.\r\n- **Two organizations claim the same hostname.** Content won't guess which one you meant. Fix the\r\n duplicate mapping in **Settings > Delivery**, then follow the link again.\r\n\r\n## Restricting what can go where\r\n\r\nTwo governance controls apply to a branch of the tree, the same way they\r\napply to Component folders (see \"Folders and access rules\" in\r\n[Authoring Components](components.md)) — both live in the **Configuration**\r\ntab's \"Access & workflow\" section:\r\n\r\n- **Access rules** grant specific roles view/edit/delete/publish on a\r\n branch — no rules anywhere on the branch means it's open to everyone; a\r\n rule anywhere on a root even restricts an entire site. A role needs view\r\n to see a branch at all; edit, delete, and publish are each independent\r\n beyond that.\r\n- **Allowed Templates** restricts which Templates a Presentation payload\r\n can use anywhere at or beneath a page — useful for keeping, say, a\r\n campaigns section limited to a handful of approved layouts. Leaving it\r\n empty allows any Template. Tightening this later never breaks a payload\r\n already in place; it's only checked when a payload is assigned or\r\n changed.\r\n\r\nBoth panels are only editable by someone with elevated access-management\r\npermission; everyone else sees a read-only summary of what they\r\npersonally can do on that branch.\r\n"
|
|
34
|
+
"body": "# Building your site in Composer\n\nComposer is where content becomes a website. A **site** is a named tree of\n**pages**; each page's slug (and its ancestors' slugs) form its **path** —\nthe URL it will eventually be served at. A page can be pure structure (a\n**Folder**, with no page of its own, just a place to organize its\nchildren), or it can carry a payload: either a **Presentation** (content\nshown at that path) or a **Redirect** (send visitors somewhere else).\nContracts, Components, and Templates (see\n[Modeling content with Contracts](contracts.md),\n[Authoring Components](components.md), and\n[Presenting content with Templates](templates.md)) define *what* content\nis and *how* it's presented; Composer decides *where* it lives. Nothing\nbuilt here is publicly visible yet — publishing is a later phase.\n\nComposer is the suite's *top-down* authoring surface — site, then tree,\nthen page. [Content](content-authoring.md) is its *bottom-up* counterpart:\nthe Component library, edited independently of any one page. Both appear\nas their own item in the sidebar.\n\n## Choosing a site\n\nComposer's header has a site switcher — select it to see every site in\nthis organization and pick one, or select **Manage sites →** to create,\nrename, or delete a site. Composer's tree always shows exactly one site's\npages at a time; switching sites swaps the whole tree beneath it.\n\nSites themselves are managed from **Settings > Sites**: create a new one\nby name, rename an existing one, or delete one (deleting a site deletes\nits entire page tree, including every page's payload — typing the site's\nname confirms you mean it).\n\n## Building the tree\n\nA large tree can be searched by category — see \"Filtering by category\" in\n[Taxonomy](content-taxonomy.md) for how the tree's own **Filter by\ncategory** control auto-expands to reveal matches without hiding anything\nelse.\n\nTo add a page:\n\n1. Select the parent page (the site's own root, shown at the top of the\n tree, or any page under it), then select **+ Add page** at the top of\n the tree panel.\n2. Pick a type — **Presentation**, **Redirect**, or **Folder** —\n Presentation is pre-selected, since it's what most new pages are for.\n You can change a page's type at any time later, from its own\n Experience tab.\n3. Enter a **Name** — the path preview beneath it fills in from it\n automatically (`/campaigns-launch`, for example). You can give the page\n a different slug later, from its **Configuration** tab.\n4. Select **Create**.\n\nEvery page shows its computed **path** at the top of the detail panel —\nthe site root's own path is always `/`.\n\n### Localized slugs\n\nA page's slug can have a different value per locale, just like other\nlocalizable content — select the translate icon beside **Slug** (in the\n**Configuration** tab) to add per-locale overrides. Two sibling pages can\nnever resolve to the same slug in the same locale, whether that's their\nshared default or one sibling's override colliding with another's\ndefault; you'll see exactly which locale and which sibling conflicts if\nthat happens.\n\n### Moving and reordering pages\n\nThe tree supports two different drags, depending on where you drop:\n\n- Drop a page **onto** another page to reparent it there — the dropped\n page becomes that page's child.\n- Drop a page **between** two sibling rows to reorder it among its\n current siblings, without changing its parent.\n\nEither kind of drag can also be done without a mouse: select **Move…** in\na page's **Configuration** tab and pick a target the same way dragging\nwould. You can't move a page into itself or one of its own descendants,\nand a site's root can't be moved or reordered at all — it has no grip\nhandle to drag by, and its Configuration tab shows no **Move…** button.\n\nIf someone else changes the tree while you're mid-drag, Composer notices,\nrefreshes the sibling list automatically, and asks you to try again rather\nthan silently applying a stale order.\n\nDeleting a page requires it to have no children first; a page that's the\ntarget of a **Redirect** elsewhere in the tree can't be deleted until that\nredirect is removed or repointed. A site's root can't be deleted from\nComposer at all — that's a **Settings > Sites** action, since it deletes\nthe whole tree beneath it.\n\n## The Experience tab\n\nA page's **Experience** tab is where you choose its type and, for a\nPresentation page, what actually renders:\n\n- **Template** shows which Template is bound and the Contract it\n requires.\n- **Satisfied by** shows the actual chain that fills that requirement —\n either a Component directly, or one or more Adapters translating a\n Component's content into what the Template expects (see\n [Adapters](content-adapters.md) if that's new to you). This is a live\n view of the binding actually in effect, not just two separate names, and\n it's also where you do the actual work: the innermost box in the chain\n is where you pick a Template, bind a Component (or create one on the\n spot), and fine-tune it through the **Contextual values** and\n **Settings** panels described in\n [Presenting content with Templates](templates.md). If the Template\n declares a settings field that's both mandatory and Contextual, it must\n be filled in here — a page is the last stop for that value, so there's\n nowhere further downstream to inherit it from. **Quick edit** opens the\n bound Component itself in a layer over the tree — and the same layer\n opens *this* page from any Link or Experience-reference field elsewhere\n that points at it; see\n [Editing something from where it's used](content-authoring.md#editing-something-from-where-its-used).\n\nSwitching a page's type here always lands you on that type's own normal\nstarting point (no Template chosen yet, no redirect target chosen yet) —\nit never carries over or invents a binding from whatever the page was\nbefore.\n\nFor a **Redirect** page, select **Pick a target page…** and browse to it.\nA page can't redirect to itself. A **Folder** page has no payload of its\nown — it exists only to organize the pages beneath it.\n\n## The Configuration tab\n\nEverything about a page other than what it renders lives in\n**Configuration**: its name and slug, its location in the tree (parent,\nMove…), access rules and workflow assignment, classification tags, and —\nfor a non-root page — the option to delete it.\n\nPublish status and the page's actions — **Preview…**, **Promote…**, and a\n**Publish…**/**Republish…** button (once published, its own chevron opens\n**Unpublish**) — sit together in a row above the page name, visible no\nmatter which tab is open, since they matter regardless of what you're\ncurrently editing. A small workflow chip appears there too, once a page\nhas an editorial workflow assigned — select it to jump straight to the\nfull workflow panel in Configuration.\n\n## Live preview\n\nClick **Preview…** on any page to open live preview: your site, rendered\nright there in Composer, showing your unsaved draft — not what's actually\npublished. Nothing you do here goes live until you choose to publish it.\n\nThe page renders with numbered pins over anything you can edit. Shape\ntells you what a pin is, so it reads even in black and white:\n\n- A **magenta circle** is a single field — a headline, a paragraph, a date.\n- A **solid blue rounded rectangle** is a whole Component — either the\n Presentation bound at that spot, or something rendered outside the page\n itself (see \"Also on this page\" below).\n- A **dashed blue rounded rectangle** is content produced by an Adapter —\n mapped in from somewhere else, with no field of its own on this page.\n- A **red rectangle** marks something that couldn't be resolved.\n\nEvery pinned part of the page also gets a light outline in its pin's own\ncolor the whole time you're in Edit mode, not just on hover — so you can\nsee everything on the page that's editable at a glance, before you click\nanything. Hovering or selecting a pin (from the page or from its row in\nthe panel on the right) strengthens that outline and shows a tag naming\nwhat it is.\n\nWhat clicking a pin does depends on its shape:\n\n- A **field** pin opens a small **docked editor** right at the top of the\n panel — no matter how deep inside the page's content that field actually\n lives — with its name, its type, and a magenta **Own save** badge, above\n the rest of the page summary (dimmed while you're editing). Deep inside\n something (a section, a card, a list item), the docked editor also shows\n a breadcrumb (\"Referenced by *the page* → *Section 4* *Statement* →\n `actions`\") so you know exactly where you are. What docks depends on the\n field:\n - A **Component**, **Reference**, or **Presentation** field — the kind\n that points at other content — gets a compact list: one row per item,\n with a label, where it links to (if anywhere), a drag handle to\n reorder, an edit button, a remove button, and an **Add item** row at\n the bottom. Click a row's edit button to open that item — an item\n authored right here opens its own full editor; an item that's really a\n separate, shared Component re-targets this same panel to that\n Component instead of opening a second one.\n - Every other field (Rich text, a plain text field, a date, and so on)\n gets its own usual editor, docked as a single row.\n Type, then **Save** or **Cancel** — or **Open full editor** if you'd\n rather see the whole Component or page it belongs to.\n A pin whose field can't be matched this way falls back to opening the\n whole Component or page in full, with that field already focused.\n- A **Component** pin (solid rectangle) opens that Component for editing\n in full, the same as clicking **Quick edit** next to it — a magenta\n **Own save** badge marks it while you're in it.\n- An **Adapter-mapped** pin (dashed rectangle) never opens an editor\n directly — there's no field of yours behind it. Instead it explains\n where the content comes from: which Adapter produced it, and a look at\n the actual source underneath. From there, **Edit source field** jumps\n straight to the one field that's a direct, one-to-one copy (when there\n is one), and **Edit mapping…** opens the mapping itself for a deeper\n change.\n- A **red (unresolved)** pin opens on whatever it belongs to, so you can\n see and fix why it isn't resolving.\n\nType in a docked or full editor, and the page updates within a second or\nso either way.\n\nA **Browse** toggle switches off the pins so you can click around the page\nthe way a visitor would — following links to see other pages of the same\nsite, still against your unsaved drafts.\n\n**Also on this page** lists anything the site renders that isn't part of\nthe page itself — a shared header or footer, for instance. These are\nedited and saved the same way as anything else with a pin: click **Quick\nedit** next to it, make your change, and save. Because a shared header or\nfooter isn't part of any one page, saving it doesn't publish it along with\nthe page — after saving, use the **Publish…** button that appears right\nthere to make the change live. If a site is set up to show something\nthat doesn't exist in the environment you're working in, it's listed as\n**not in this environment** — nothing to worry about, and never something\nthat stops the rest of the page from previewing normally.\n\n## Opening a page from the site itself\n\nWhen you're signed in to Content and browsing one of your own published sites, a small **Click to\nedit** button appears in the corner of the page. It takes you straight to that page in Composer, with\nlive preview already open.\n\nIf the page belongs to a **different organization** than the one you're currently working in, Content\nswitches for you and shows a notice saying so, with a **Switch back** button. That switch applies to\nyour whole ebitex session — Hub and Forms in other tabs will show the new organization the next time\nyou navigate in them — so the notice stays until you dismiss it.\n\nA few cases where the link won't open a page, and what each means:\n\n- **\"This page no longer exists, or belongs to an organization you are not currently signed in to.\"**\n Either the page was deleted, or it belongs to an organization you're not a member of. If a\n colleague sent you the link, ask them to add you to that organization.\n- **Nothing switches and the page doesn't load.** The site's hostname isn't mapped to a site in any\n organization you belong to. Check **Settings > Delivery** for that organization's host mappings.\n- **Two organizations claim the same hostname.** Content won't guess which one you meant. Fix the\n duplicate mapping in **Settings > Delivery**, then follow the link again.\n\n## Restricting what can go where\n\nTwo governance controls apply to a branch of the tree, the same way they\napply to Component folders (see \"Folders and access rules\" in\n[Authoring Components](components.md)) — both live in the **Configuration**\ntab's \"Access & workflow\" section:\n\n- **Access rules** grant specific roles view/edit/delete/publish on a\n branch — no rules anywhere on the branch means it's open to everyone; a\n rule anywhere on a root even restricts an entire site. A role needs view\n to see a branch at all; edit, delete, and publish are each independent\n beyond that.\n- **Allowed Templates** restricts which Templates a Presentation payload\n can use anywhere at or beneath a page — useful for keeping, say, a\n campaigns section limited to a handful of approved layouts. Leaving it\n empty allows any Template. Tightening this later never breaks a payload\n already in place; it's only checked when a payload is assigned or\n changed.\n\nBoth panels are only editable by someone with elevated access-management\npermission; everyone else sees a read-only summary of what they\npersonally can do on that branch.\n"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"slug": "content-environments",
|
|
38
38
|
"title": "Content environments",
|
|
39
|
-
"body": "# Content environments\r\n\r\nBy default, your organization has one authoring space and one place published content goes live —\r\nyou'll never need this page if that's all you use. If you want separate spaces to draft in (a\r\nStaging environment you can experiment in before anything reaches Production, say), this is where\r\nthat's configured.\r\n\r\n## What's an environment?\r\n\r\nThere are two kinds:\r\n\r\n- **Authoring environments** are separate drafting spaces. Contracts, Templates, folders,\r\n Components, the Experience tree, and Audiences are all specific to whichever authoring\r\n environment you're currently working in — content in one is completely invisible from another,\r\n as if they were separate organizations.\r\n- **Delivery environments** are separate published targets — what the Content Delivery API actually\r\n serves to the outside world, and what a delivery key reads from.\r\n\r\nEvery organization starts with one of each, marked **Default**. The default is where publishing,\r\nnew delivery keys, and the environment switcher land when nothing more specific is chosen — you can\r\nchange which environment holds that role at any time (see **Managing environments** below).\r\n\r\n## Switching environments\r\n\r\nIf more than one authoring environment exists, a switcher appears in the header next to your\r\naccount. Pick one to start working there — every page reloads to show that environment's own\r\ncontent. Your choice is remembered per-organization, so it's still selected next time you sign in.\r\n\r\n## Managing environments\r\n\r\nOpen **Environments** under **Settings** in the app navigation (requires the \"Manage Environments\"\r\npermission — ask an Owner or administrator if it's not available to you).\r\n\r\n- **Create** a new authoring or delivery environment, give it a name. Your plan may limit how many\r\n environments you can have in total. A newly created environment is added to the end of its\r\n list.\r\n- **Reorder** the authoring or delivery list to whatever arrangement makes sense to your team —\r\n drag a row to where you want it, or use the ▲/▼ buttons on each row. While dragging, the row\r\n shows as a placeholder that moves to preview exactly where it'll land if you release. This is\r\n the same order the environment mapping canvas below lays its columns out in, so arranging the\r\n lists arranges the canvas too. (A list with only one environment has nothing to reorder against,\r\n so dragging is unavailable there.)\r\n- **Rename** any environment at any time — nothing else changes.\r\n- **Set as default**: on any non-default environment's row, this moves the Default badge to it and\r\n clears it from wherever it was. Publishing, new delivery keys, and the environment switcher's own\r\n default all follow immediately.\r\n- **Delete** any environment except the last remaining one of its kind. This is permanent and\r\n immediate: everything authored (or, for a delivery environment, everything published) there is\r\n gone, with no recovery. You'll be asked to type the environment's name to confirm. If the\r\n environment currently publishes to a delivery target, promotes into another environment, or is\r\n itself a promotion target, the confirmation names those connections — deleting still removes them\r\n along with everything else. The current default can't be deleted directly while another\r\n environment of its kind exists — set a different one as default first, then delete it.\r\n- **Refresh from…**: replace this environment's content wholesale from another authoring\r\n environment — the routine \"copy Production down into Staging\" move. See\r\n [environment-refresh.md](environment-refresh.md) for the full workflow, including how to rescue\r\n work that would otherwise be lost.\r\n\r\n### Environment mapping\r\n\r\nOne canvas configures both ways environments connect to each other: authoring environments are\r\nlisted on the left, delivery environments on the right, and lines drawn between them show the\r\nexisting connections — a solid line for publish mapping, a dashed line for a promotion path (the\r\nlegend above the canvas shows which is which). Both are routing, not content — changing either\r\nredirects where the next publish or promote action lands, immediately.\r\n\r\nTo create a connection:\r\n\r\n- **With a mouse**, drag from an environment's **Connect…** button to whichever environment you\r\n want to connect it to — another authoring environment (to allow promoting into it) or a delivery\r\n environment (to publish to it).\r\n- **With a keyboard** (or any assistive technology), select **Connect…**, then choose the target\r\n from the list of buttons that appears — delivery environments say \"Connect to {name}\", other\r\n authoring environments say \"Promote into {name}\". Press <kbd>Escape</kbd>, or select **Cancel**,\r\n to back out without changing anything.\r\n\r\n**A delivery environment can be mapped from only one authoring environment at a time** — if you\r\nchoose one that's already mapped from a different authoring environment, you'll be asked to confirm\r\nreassigning it, which removes the other mapping. If only one delivery environment is mapped to your\r\ncurrent authoring environment, publishing and creating a delivery key never ask you to choose —\r\nthey just use it, exactly as if this page didn't exist.\r\n\r\n**An authoring environment can promote into any number of other authoring environments**, and any\r\nnumber of environments can promote into the same target — there's no exclusivity the way delivery\r\nmapping has one. An environment can never promote into itself, even indirectly through a chain of\r\nothers (see \"Promoting content\" below).\r\n\r\nRemove an existing connection — either kind — at any time using the **×** next to it.\r\n\r\n## Promoting content\r\n\r\nPromoting copies a Component or Experience node — and everything it needs (its Contract, Template,\r\nany Audiences its variants reference, referenced Components, ancestor folders) — from the\r\nenvironment you're in into another one, ready to be worked on or published there. This is different\r\nfrom publishing: promoting moves *draft* content between authoring spaces; publishing takes draft\r\ncontent live to visitors.\r\n\r\nSelect **Promote…** on a Component or Experience node — a target is already picked for you (the\r\nfirst one configured, changeable from the dropdown if more than one exists), so you'll usually go\r\nstraight to reviewing the plan below rather than choosing first. If your current environment has no\r\npromotion target configured at all, **Promote…** is disabled with an explanation — set one up under\r\n[Managing environments](#managing-environments) first. Once a target is resolved, you'll see\r\neverything about to move, each marked:\r\n\r\n- **New** — doesn't exist in the target yet.\r\n- **Update** — already exists there, unchanged since it was last promoted; safe to overwrite.\r\n- **Diverged** — already exists there, but has been edited directly in the target (or promoted from\r\n somewhere else) since the last time this same source promoted it. You'll need to explicitly\r\n confirm overwriting it — nothing diverged is ever silently replaced. Whatever was there before\r\n stays recoverable in its own version history.\r\n\r\nContracts, Templates, Audiences, and ancestor folders/nodes always move along with the item you're\r\npromoting — there's no separate choice for those. Referenced Components can be unchecked\r\nindividually if you don't want them to move yet; a skipped reference is simply left as-is in the\r\ntarget, pointing at whatever (if anything) is already there. If the item you're skipping doesn't\r\nexist in the target at all yet, a note appears explaining that this leaves a dangling reference\r\nthere until it's promoted — see [Unresolved references](#unresolved-references-component-sync)\r\nbelow for how to find and fix one later.\r\n\r\nPromoting requires the same **Publish** permission the item's own branch requires; promoting a\r\nContract, Template, or Audience along with it additionally requires the permission to manage that\r\nkind of thing directly.\r\n\r\n### Promoting a Contract, Template, or Audience on its own\r\n\r\nA schema-only change — a field added to a Contract, an adjusted Template, a fixed Audience\r\ncondition — can be promoted by itself, without touching any Component that happens to use it.\r\nSelect **Promote…** directly from the Contract builder, the Template builder, or next to an\r\nAudience on the Audiences settings page. The same **New**/**Update**/**Diverged** states and\r\noverwrite confirmation apply; a Template's own settings Contract and a provider-contributed\r\nContract can't be promoted this way (promote the Template instead, or refresh/manage the provider\r\nregistration). This needs the permission to manage that kind of thing directly — there's no\r\n**Publish** branch involved, since Contracts/Templates/Audiences aren't organized into branches.\r\n\r\n## Unresolved references (Component Sync)\r\n\r\nSkipping a referenced Component during promotion (above) — or publishing a Component whose\r\nreference was already skipped in an earlier promotion — leaves that reference pointing at\r\nsomething that doesn't exist in this environment. This is allowed on purpose: it never blocks a\r\npromotion or a publish, and delivery itself handles it gracefully (the missing reference is simply\r\nomitted from what's served). But nothing else ever tells you it happened unless you go looking, so\r\n**Component Sync**, under **Settings** in the app navigation, lists every one currently in your\r\nenvironment: which Component or Experience node has the broken reference, which field it's in, and\r\n— where this environment has a direct upstream promotion source that still has the missing item —\r\nwhich environment that is.\r\n\r\nYou'll also see it called out in two other places, right where it matters: opening a Component\r\nwhose reference is broken shows an **Unresolved** badge in place of the usual resolved name, with a\r\nlink straight to its Component Sync entry; and publishing a closure that contains one shows a\r\nnon-blocking banner naming the affected item (publishing still proceeds — this is a heads-up, not a\r\nnew restriction).\r\n\r\n**To fix one:** promote the missing item forward from the environment Component Sync names — but\r\nthat step alone isn't enough on its own, because promoting the missing item doesn't touch the\r\nComponent that references it. Once the missing item exists here, **re-promote the referencing\r\nComponent too** (from wherever you originally promoted it), so its reference is re-checked against\r\nwhat's now available. After that second promotion, the entry disappears from Component Sync, the\r\nbadge on the Component resolves back to a normal name, and any publish banner clears.\r\n\r\n## Sharing content across environments (inheritance)\r\n\r\nPromoting is a deliberate, one-time push — good for moving a specific change from Staging to\r\nProduction. If instead you have content a *whole family* of environments should all draw from —\r\nshared design-system blocks, legal boilerplate, a common set of media Contracts — set up\r\ninheritance instead: link a \"Shared\" environment as the **parent** of each sibling that should\r\nreceive its content, and every relevant item in Shared shows up as a read-only copy in each child,\r\nkept up to date with a button press rather than a promotion per environment per change.\r\n\r\n**To link a parent:** open **Environments**, find the child environment's row, and choose the\r\nparent from the **Inherits from** dropdown next to it (or **None** to unlink). An environment can\r\nhave at most one parent, and a chain is fine (Shared → Region → Brand), but a loop is rejected.\r\n\r\n**To bring the parent's content in:** select **Sync now** on the child's row. This copies the\r\nparent's Contracts, Templates, Audiences, taxonomy, folders, and Components into the child,\r\nmarked as inherited. Running it again later picks up anything the parent has changed since —\r\nnew items appear, changed ones update, and anything the parent no longer has disappears from the\r\nchild too (unless something local in the child still depends on it, in which case the sync stops\r\nand tells you what's blocking it, so you can resolve that first). Nothing is ever half-applied — a\r\nsync either finishes completely or leaves the child exactly as it was.\r\n\r\n**Inherited items are read-only** in the child — you'll see an \"Inherited\" label on them, and any\r\nattempt to edit, rename, move, or delete one is refused with an explanation pointing you back to\r\nthe parent (or the Environments page, to unlink). You can still **publish** or **promote** an\r\ninherited Component from the child normally — it publishes as the child's own copy, just like\r\nanything else there.\r\n\r\n**Unlinking** a parent (or switching to a different one) doesn't delete anything — the child's\r\ncopies simply become ordinary local content you can edit freely from then on. If you relink the\r\nsame parent later and it now has a different version of something the child already has, syncing\r\nwill flag it and ask you to confirm before overwriting — your child's edit is version-recorded\r\nfirst, so nothing is lost even then.\r\n\r\nDeleting a parent environment automatically unlinks every child that pointed to it, the same way —\r\ntheir copies stay in place and become editable, nothing is deleted along with the parent.\r\n\r\n## How this affects everything else\r\n\r\nIf you never create a second environment, nothing here changes anything: the switcher never\r\nappears, publishing and delivery keys keep using your one delivery environment automatically, and\r\nthere's no promotion to do. See [publishing.md](publishing.md) for the publish lifecycle,\r\n[content-personalization.md](content-personalization.md) for Audiences (which, like everything else\r\nyou author, belong to one environment), and [content-providers.md](content-providers.md) for\r\ncopying a registered content provider between environments.\r\n"
|
|
39
|
+
"body": "# Content environments\n\nBy default, your organization has one authoring space and one place published content goes live —\nyou'll never need this page if that's all you use. If you want separate spaces to draft in (a\nStaging environment you can experiment in before anything reaches Production, say), this is where\nthat's configured.\n\n## What's an environment?\n\nThere are two kinds:\n\n- **Authoring environments** are separate drafting spaces. Contracts, Templates, folders,\n Components, the Experience tree, and Audiences are all specific to whichever authoring\n environment you're currently working in — content in one is completely invisible from another,\n as if they were separate organizations.\n- **Delivery environments** are separate published targets — what the Content Delivery API actually\n serves to the outside world, and what a delivery key reads from.\n\nEvery organization starts with one of each, marked **Default**. The default is where publishing,\nnew delivery keys, and the environment switcher land when nothing more specific is chosen — you can\nchange which environment holds that role at any time (see **Managing environments** below).\n\n## Switching environments\n\nIf more than one authoring environment exists, a switcher appears in the header next to your\naccount. Pick one to start working there — every page reloads to show that environment's own\ncontent. Your choice is remembered per-organization, so it's still selected next time you sign in.\n\n## Managing environments\n\nOpen **Environments** under **Settings** in the app navigation (requires the \"Manage Environments\"\npermission — ask an Owner or administrator if it's not available to you).\n\n- **Create** a new authoring or delivery environment, give it a name. Your plan may limit how many\n environments you can have in total. A newly created environment is added to the end of its\n list.\n- **Reorder** the authoring or delivery list to whatever arrangement makes sense to your team —\n drag a row to where you want it, or use the ▲/▼ buttons on each row. While dragging, the row\n shows as a placeholder that moves to preview exactly where it'll land if you release. This is\n the same order the environment mapping canvas below lays its columns out in, so arranging the\n lists arranges the canvas too. (A list with only one environment has nothing to reorder against,\n so dragging is unavailable there.)\n- **Rename** any environment at any time — nothing else changes.\n- **Set as default**: on any non-default environment's row, this moves the Default badge to it and\n clears it from wherever it was. Publishing, new delivery keys, and the environment switcher's own\n default all follow immediately.\n- **Delete** any environment except the last remaining one of its kind. This is permanent and\n immediate: everything authored (or, for a delivery environment, everything published) there is\n gone, with no recovery. You'll be asked to type the environment's name to confirm. If the\n environment currently publishes to a delivery target, promotes into another environment, or is\n itself a promotion target, the confirmation names those connections — deleting still removes them\n along with everything else. The current default can't be deleted directly while another\n environment of its kind exists — set a different one as default first, then delete it.\n- **Refresh from…**: replace this environment's content wholesale from another authoring\n environment — the routine \"copy Production down into Staging\" move. See\n [environment-refresh.md](environment-refresh.md) for the full workflow, including how to rescue\n work that would otherwise be lost.\n\n### Environment mapping\n\nOne canvas configures both ways environments connect to each other: authoring environments are\nlisted on the left, delivery environments on the right, and lines drawn between them show the\nexisting connections — a solid line for publish mapping, a dashed line for a promotion path (the\nlegend above the canvas shows which is which). Both are routing, not content — changing either\nredirects where the next publish or promote action lands, immediately.\n\nTo create a connection:\n\n- **With a mouse**, drag from an environment's **Connect…** button to whichever environment you\n want to connect it to — another authoring environment (to allow promoting into it) or a delivery\n environment (to publish to it).\n- **With a keyboard** (or any assistive technology), select **Connect…**, then choose the target\n from the list of buttons that appears — delivery environments say \"Connect to {name}\", other\n authoring environments say \"Promote into {name}\". Press <kbd>Escape</kbd>, or select **Cancel**,\n to back out without changing anything.\n\n**A delivery environment can be mapped from only one authoring environment at a time** — if you\nchoose one that's already mapped from a different authoring environment, you'll be asked to confirm\nreassigning it, which removes the other mapping. If only one delivery environment is mapped to your\ncurrent authoring environment, publishing and creating a delivery key never ask you to choose —\nthey just use it, exactly as if this page didn't exist.\n\n**An authoring environment can promote into any number of other authoring environments**, and any\nnumber of environments can promote into the same target — there's no exclusivity the way delivery\nmapping has one. An environment can never promote into itself, even indirectly through a chain of\nothers (see \"Promoting content\" below).\n\nRemove an existing connection — either kind — at any time using the **×** next to it.\n\n## Promoting content\n\nPromoting copies a Component or Experience node — and everything it needs (its Contract, Template,\nany Audiences its variants reference, referenced Components, ancestor folders) — from the\nenvironment you're in into another one, ready to be worked on or published there. This is different\nfrom publishing: promoting moves *draft* content between authoring spaces; publishing takes draft\ncontent live to visitors.\n\nSelect **Promote…** on a Component or Experience node — a target is already picked for you (the\nfirst one configured, changeable from the dropdown if more than one exists), so you'll usually go\nstraight to reviewing the plan below rather than choosing first. If your current environment has no\npromotion target configured at all, **Promote…** is disabled with an explanation — set one up under\n[Managing environments](#managing-environments) first. Once a target is resolved, you'll see\neverything about to move, each marked:\n\n- **New** — doesn't exist in the target yet.\n- **Update** — already exists there, unchanged since it was last promoted; safe to overwrite.\n- **Diverged** — already exists there, but has been edited directly in the target (or promoted from\n somewhere else) since the last time this same source promoted it. You'll need to explicitly\n confirm overwriting it — nothing diverged is ever silently replaced. Whatever was there before\n stays recoverable in its own version history.\n\nContracts, Templates, Audiences, and ancestor folders/nodes always move along with the item you're\npromoting — there's no separate choice for those. Referenced Components can be unchecked\nindividually if you don't want them to move yet; a skipped reference is simply left as-is in the\ntarget, pointing at whatever (if anything) is already there. If the item you're skipping doesn't\nexist in the target at all yet, a note appears explaining that this leaves a dangling reference\nthere until it's promoted — see [Unresolved references](#unresolved-references-component-sync)\nbelow for how to find and fix one later.\n\nPromoting requires the same **Publish** permission the item's own branch requires; promoting a\nContract, Template, or Audience along with it additionally requires the permission to manage that\nkind of thing directly.\n\n### Promoting a Contract, Template, or Audience on its own\n\nA schema-only change — a field added to a Contract, an adjusted Template, a fixed Audience\ncondition — can be promoted by itself, without touching any Component that happens to use it.\nSelect **Promote…** directly from the Contract builder, the Template builder, or next to an\nAudience on the Audiences settings page. The same **New**/**Update**/**Diverged** states and\noverwrite confirmation apply; a Template's own settings Contract and a provider-contributed\nContract can't be promoted this way (promote the Template instead, or refresh/manage the provider\nregistration). This needs the permission to manage that kind of thing directly — there's no\n**Publish** branch involved, since Contracts/Templates/Audiences aren't organized into branches.\n\n## Unresolved references (Component Sync)\n\nSkipping a referenced Component during promotion (above) — or publishing a Component whose\nreference was already skipped in an earlier promotion — leaves that reference pointing at\nsomething that doesn't exist in this environment. This is allowed on purpose: it never blocks a\npromotion or a publish, and delivery itself handles it gracefully (the missing reference is simply\nomitted from what's served). But nothing else ever tells you it happened unless you go looking, so\n**Component Sync**, under **Settings** in the app navigation, lists every one currently in your\nenvironment: which Component or Experience node has the broken reference, which field it's in, and\n— where this environment has a direct upstream promotion source that still has the missing item —\nwhich environment that is.\n\nYou'll also see it called out in two other places, right where it matters: opening a Component\nwhose reference is broken shows an **Unresolved** badge in place of the usual resolved name, with a\nlink straight to its Component Sync entry; and publishing a closure that contains one shows a\nnon-blocking banner naming the affected item (publishing still proceeds — this is a heads-up, not a\nnew restriction).\n\n**To fix one:** promote the missing item forward from the environment Component Sync names — but\nthat step alone isn't enough on its own, because promoting the missing item doesn't touch the\nComponent that references it. Once the missing item exists here, **re-promote the referencing\nComponent too** (from wherever you originally promoted it), so its reference is re-checked against\nwhat's now available. After that second promotion, the entry disappears from Component Sync, the\nbadge on the Component resolves back to a normal name, and any publish banner clears.\n\n## Sharing content across environments (inheritance)\n\nPromoting is a deliberate, one-time push — good for moving a specific change from Staging to\nProduction. If instead you have content a *whole family* of environments should all draw from —\nshared design-system blocks, legal boilerplate, a common set of media Contracts — set up\ninheritance instead: link a \"Shared\" environment as the **parent** of each sibling that should\nreceive its content, and every relevant item in Shared shows up as a read-only copy in each child,\nkept up to date with a button press rather than a promotion per environment per change.\n\n**To link a parent:** open **Environments**, find the child environment's row, and choose the\nparent from the **Inherits from** dropdown next to it (or **None** to unlink). An environment can\nhave at most one parent, and a chain is fine (Shared → Region → Brand), but a loop is rejected.\n\n**To bring the parent's content in:** select **Sync now** on the child's row. This copies the\nparent's Contracts, Templates, Audiences, taxonomy, folders, and Components into the child,\nmarked as inherited. Running it again later picks up anything the parent has changed since —\nnew items appear, changed ones update, and anything the parent no longer has disappears from the\nchild too (unless something local in the child still depends on it, in which case the sync stops\nand tells you what's blocking it, so you can resolve that first). Nothing is ever half-applied — a\nsync either finishes completely or leaves the child exactly as it was.\n\n**Inherited items are read-only** in the child — you'll see an \"Inherited\" label on them, and any\nattempt to edit, rename, move, or delete one is refused with an explanation pointing you back to\nthe parent (or the Environments page, to unlink). You can still **publish** or **promote** an\ninherited Component from the child normally — it publishes as the child's own copy, just like\nanything else there.\n\n**Unlinking** a parent (or switching to a different one) doesn't delete anything — the child's\ncopies simply become ordinary local content you can edit freely from then on. If you relink the\nsame parent later and it now has a different version of something the child already has, syncing\nwill flag it and ask you to confirm before overwriting — your child's edit is version-recorded\nfirst, so nothing is lost even then.\n\nDeleting a parent environment automatically unlinks every child that pointed to it, the same way —\ntheir copies stay in place and become editable, nothing is deleted along with the parent.\n\n## How this affects everything else\n\nIf you never create a second environment, nothing here changes anything: the switcher never\nappears, publishing and delivery keys keep using your one delivery environment automatically, and\nthere's no promotion to do. See [publishing.md](publishing.md) for the publish lifecycle,\n[content-personalization.md](content-personalization.md) for Audiences (which, like everything else\nyou author, belong to one environment), and [content-providers.md](content-providers.md) for\ncopying a registered content provider between environments.\n"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"slug": "content-links",
|
|
43
43
|
"title": "Linking to pages and external sites",
|
|
44
|
-
"body": "# Linking to pages and external sites\
|
|
44
|
+
"body": "# Linking to pages and external sites\n\nA **Link** field lets you point at a page in your own site (an Experience node) or at an external\nURL, with an optional query string and anchor. It replaces the older **URL** field type, which\nstill works on any field that already used it but no longer appears as a choice for a new one.\n\n## Adding a link\n\nSelect **Choose page…** or the kind toggle (if the field allows both **Page** and **External URL**)\nto pick which kind of link this is.\n\n### Linking to a page\n\nSelecting **Page** opens a browser: a list of your sites, then each one's pages, drilling in as you\ngo — the same navigation the Experience tree page itself uses. Select **Select** next to any page to\npick it, or its name to browse into its own children first.\n\nOnce picked, the field shows the page's name, its publish status, and a preview of the current URL\nit resolves to. **This URL is not fixed** — it's recalculated every time the page is delivered, so\nif the page is later renamed or moved, the link updates automatically the next time it's viewed. You\nnever need to re-save anything that links to it.\n\nIf you pick a page that hasn't been published yet, you'll see a warning — you can still save the\nlink, but it won't resolve to a real address until that page is published. Once it is, the link\nstarts working with no further action from you.\n\n### Linking to an external URL\n\nSelecting **External URL** shows a plain address field. If the field's settings restrict which\nprotocols are allowed (for example, only `https`), you'll see that listed, and an address using a\ndifferent one is flagged before you can save.\n\n## Query and anchor\n\nBelow the kind you picked, **Query** and **Anchor** fields let you add extra address detail — for\nexample a query string like `utm_campaign=spring` or an anchor like `specs`, which is appended after\nthe resolved address (`…widget-x?utm_campaign=spring#specs`). These are available for a page link\njust as much as an external one — unless the field's settings turn suffixes off entirely, in which\ncase neither shows.\n\n## If the field only allows pages\n\nSome fields are set up to only ever point at a page in your own site — no kind toggle, no external\noption, no query/anchor. These use a simpler **Experience Reference** field, which behaves exactly\nlike the page-picking half of a Link field above, without any of the extra chrome. You'll typically\nsee this on a field meant to feed something else — a \"Featured pages\" list used to build navigation\ncards, for instance — rather than to be delivered as a clickable link by itself.\n\n## Deleting a linked-to page\n\nA page that's targeted by a Link (or an Experience Reference field) can't be deleted while that link\nexists — you'll see the same \"this can't be deleted, here's what's using it\" message you'd get\ntrying to delete a Component that's still referenced elsewhere.\n"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"slug": "content-personalization",
|
|
48
48
|
"title": "Personalization & RichText",
|
|
49
|
-
"body": "# Personalization & RichText\
|
|
49
|
+
"body": "# Personalization & RichText\n\nShow different content to different visitors, and write flowing rich-text content that can weave\nin references, presentations, and personalized paragraphs — all without leaving one continuous\nwriting surface.\n\n## The three ways to personalize\n\nPick the smallest mechanism that does the job:\n\n- **Whole-value variants** — a field whose entire value changes by audience (e.g. a headline that\n reads differently for returning customers). Turn on **Personalizable** for the field in the\n Contract builder.\n- **Personalized paragraphs** — inside a RichText field, swap out one phrase or section while the\n rest of the document stays the same. Insert a **Personalized** embed inline.\n- **Inline tokens** — drop a single dynamic word or phrase into an otherwise-fixed, translatable\n sentence (e.g. \"Welcome back, {{ctx.firstName}}!\"). Turn on **Allow token injection** for the\n field.\n\nDon't split a sentence into separate fragments just to swap one word in the middle — that breaks\ntranslation, since a translator can no longer reorder the sentence. Use a token instead.\n\n## Audiences\n\nAn audience is a named, reusable condition — \"returning customers,\" \"on the enterprise plan,\" and so\non — that you define once and reference from any number of variants. Open **Audiences** in the app\nnavigation to manage them.\n\nEach audience has:\n\n- A **key** (used internally; safe to keep even if you rename the audience later)\n- A **name** (what you see everywhere else)\n- A **condition** — the simple builder covers \"property equals/doesn't equal/is one of/compares\n to/is set/is not set a value\"; an **Advanced** raw-JSON option is available for more elaborate\n conditions.\n\nAn audience can't be deleted while any variant still references it — you'll see how many places\nreference it if you try.\n\n## Adding a whole-value variant\n\nIn the Contract builder, mark a field **Personalizable**. In the Component editor, that field looks\njust like any other field of its type — the value you see and edit is the **default**, what every\nvisitor gets unless a variant applies — with a small **Personalize** button (the people icon) next\nto its label, the same way a localizable field has a translations button there. Click it to open\nthe personalization panel: the default plus an **Add variant** button. Each variant picks an\naudience and its own value; the first variant whose audience matches the visitor wins, otherwise\nthe default is shown. Reorder variants (the up/down arrows) to change which one takes priority when\nmore than one could match. Click the button again to fold the panel away; a field that has\nvariants opens with the panel showing and keeps its button highlighted so you can always tell it's\npersonalized. The button is greyed out, with an explanation on hover, until your organization has\nat least one audience to personalize for.\n\n## Writing RichText content\n\nA RichText field is one continuous writing surface — a single markdown editor, not a stack of\nseparately-inserted blocks. Write your prose directly, then embed the other kinds of content\n**inline**, mid-sentence, wherever they belong:\n\n- **Presentation** — embed a fully-presented piece of content (a Template + Component pairing). This\n is the right choice for embedding another piece of content — its Template tells the delivery\n consumer how to render it, which a bare Reference can't. Presentation always sits on its own\n paragraph, wherever your cursor was when you inserted it — since it's typically rendered as its\n own block (a card, a video, and so on), it can't sit mid-sentence. When you bind it, you can\n either pick an existing Component (**Browse existing**) or give the embed its own content with\n **Create inline** — the same choice a Presentation field offers. Inline content lives only inside\n this one embed: nothing else can reference or reuse it, which is exactly what you want for a\n one-off block, and exactly what you don't for something that appears on several pages.\n- **Reference** *(deprecated for new use)* — bind to another piece of content with no rendering\n information attached. New reference embeds aren't offered in the Embed picker; use Presentation\n instead. An already-existing reference embed keeps working and can still be edited.\n- **Personalized** — swap one phrase or section by audience, exactly like a whole-value variant,\n but scoped to just that spot in the sentence instead of the whole field. You can't nest a\n personalized embed inside another one.\n\nSee \"Embedding a reference, presentation, or personalized value inline\" below for how. Raw HTML\nisn't allowed in the markdown itself (this keeps your content usable everywhere it might be\npublished, not just on a web page). Deleting a piece of content that's referenced anywhere in a\nRichText field — even inside a personalized variant that isn't currently winning for anyone — is\nblocked, the same as any other reference.\n\nIf a field allows multiple values (its **Multiple values** setting is on), you'll see several\nindependent editors stacked with **Add**/**Remove** controls — the same multiple-values mechanism\nevery other field type uses, not something specific to RichText.\n\n### The markdown editor\n\nThe editor is a real editor, not a plain text box: a toolbar for **Bold**, *Italic*,\nStrikethrough, Inline code, Code block, Blockquote, Bullet/Ordered list, Link, Image, Table, and\nDivider, plus syntax-aware highlighting as you type. A Contract author can restrict which of these\na field allows (e.g. plain prose only — no tables, no headings) from the field's settings in the\nContract builder; a restricted button just doesn't appear, and typing the construct anyway (or\npasting it in) shows a wavy underline naming what's disabled. Saving still enforces the same\nrestriction even if you work around the underline — it's a heads-up, not a lock.\n\nA preview pane shows how your prose will actually render, side-by-side by default. A Contract\nauthor can instead make it toggle-on-demand (a **Show preview** button) or turn it off entirely,\nfrom the same field settings.\n\n### Embedding a presentation or personalized value inline\n\nEmbed a Presentation or Personalized value **inline**, mid-sentence — \"Our flagship plan pairs\nwell with *[this add-on]*, available through the end of the month.\" Click **Embed** in the\nmarkdown toolbar, pick a kind, and it appears as a small pill right where your cursor was.\n(Reference doesn't appear in this picker — it's deprecated for new inline use, see above; a pill\nfor one already placed before the deprecation still opens and edits normally.)\nClick the pill to bind it to a Component (or configure its Template, or its audience variants, for\na Presentation/Personalized embed) in a panel that opens below the editor. Deleting the pill (select\nit and press Delete/Backspace, or use the panel's own remove button) removes the embedded content\ntoo. A Contract author can restrict which kinds are embeddable, from the field's settings. An\ninline embed delete-blocks its target exactly like any other reference.\n\n## Inline tokens\n\nTurn on **Allow token injection** on a text field (or on markdown inside RichText) to use\n`{{ctx.property}}` or `{{ctx.property | fallback text}}` inside your writing. At delivery time,\n`property` is filled in from whatever context the request carries; if it's missing, the fallback\ntext is used instead (or nothing, if you didn't give one). Tokens only work on fields where you've\nexplicitly turned this on — anywhere else, `{{` is treated as a typo and blocked when you try to\nsave.\n\n## Preview as context\n\nWhile editing a Component (or an Experience node), select **Preview…** to see exactly what a\nspecific visitor would see — before you save anything. Choose a locale, tick the audiences you want\nto simulate, and add any other properties by hand, then **Resolve**. The preview reflects your\ncurrent unsaved edits, using the exact same logic that runs at delivery time — what you see is\nwhat will actually be served.\n\n## How this reaches visitors\n\nContent you publish stays personalization-agnostic — publishing never resolves a variant for you,\nso editing an audience's condition changes what visitors see immediately, with no need to republish\nanything. See [publishing.md](publishing.md) for the publish lifecycle itself,\n[content-providers.md](content-providers.md) for how external content fits alongside your own, and\n[content-environments.md](content-environments.md) if your organization authors in more than one\nenvironment — audiences, like everything else you author, belong to one authoring environment at a\ntime.\n"
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"slug": "content-providers",
|
|
53
53
|
"title": "Content providers",
|
|
54
|
-
"body": "# Content providers\
|
|
54
|
+
"body": "# Content providers\n\nContent providers let you bring in read-only content from an external system — a product catalog,\na digital asset manager, anything a plugin has been built for — so it sits alongside your own\nComponents everywhere you'd normally reference one: Reference fields, Presentation bindings,\npublished pages.\n\n## Registering a provider\n\nOpen **Plugins** under **Settings** in the app navigation. Registering requires a plugin to\nalready be **activated** for your organization (an administrator does this from Hub's plugin\nsettings first — this page just registers an *instance* of an already-activated plugin).\n\n1. Choose the plugin from the **Plugin** dropdown.\n2. Give it a **provider key** — a short, lowercase, hyphenated name (e.g. `example-products`).\n This becomes part of every reference stored against items from this provider, so **choose it\n once and don't expect to rename it later**; changing your mind means unregistering and\n registering again under a new key.\n3. Fill in whatever configuration the plugin asks for — this varies per plugin, and any field\n marked sensitive (an API key, say) is write-only: once saved, you'll only ever see that it's\n set, never the value itself.\n4. Set a **cache TTL** — how long (in seconds) a fetched item is reused before Content checks the\n external system again when serving published content. Lower values mean fresher data with more\n load on the external system; higher values mean better performance but slower-to-appear\n changes.\n5. Select **Register**. Content contacts the provider and pulls in the Contracts (content types)\n it describes — you'll see them show up in the Contract library, badged with the provider's\n name.\n\nIf your organization authors in more than one environment and a provider is already registered in\none of them, use **Copy from another environment** instead of registering from scratch — pick the\nsource environment and the provider key, and its full configuration (including anything sensitive)\ncomes along without you re-entering it. See [content-environments.md](content-environments.md).\n\n## Using provider content\n\nWherever you can pick a Component — a Reference field, or a Presentation's bound Component — the\npicker now shows a row of tabs: **Core** (your own content) plus one tab per registered provider.\nSwitch tabs to browse or search that provider's items; not every provider supports search, in\nwhich case the search box is disabled for that tab. Once picked, an external item behaves like any\nother reference: you can still add your own decoration on top of it (a headline, a promotional\nlabel — whatever the provider's Contract declares as an editable-here field) without touching the\nexternal system at all.\n\nIf an external item is later removed on the provider's side, or the provider becomes temporarily\nunreachable, the reference shows as unresolved rather than blocking your save — you can fix it\nwhenever the item (or the provider) is available again.\n\n## Refreshing and unregistering\n\nProvider Contracts don't update automatically — select **Refresh Contracts** on a registration\nwhenever the external system's own content types have changed. Refreshing can add new Contracts,\nupdate existing ones, and remove ones no longer described (removal is blocked if something still\ndepends on it — a Reference field constraint or a Template's supported-Contracts list — with the\nusages listed so you can address them first).\n\n**Unregister** removes a provider entirely, along with its synced Contracts — blocked the same\nway if any content still references it. Once you confirm, existing references to that provider\nkey become permanently unresolved.\n\n## Permissions\n\nRegistering, updating, refreshing, and unregistering providers requires its own \"Manage Content\nproviders\" permission — ask an Owner or administrator to grant it if the register/refresh/\nunregister actions aren't available to you. Anyone with organization membership can view\nregistered providers and use them in a picker.\n"
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"slug": "content-streams",
|
|
58
58
|
"title": "Streams: saved queries as delivery sources",
|
|
59
|
-
"body": "# Streams: saved queries as delivery sources\
|
|
59
|
+
"body": "# Streams: saved queries as delivery sources\n\nA **stream** is a named, saved query over your published content that any site or app can consume\nthrough the Delivery API at a stable address — `…/streams/<key>`. Instead of a consuming page\nhard-coding *which* content it lists and *how*, the stream's definition carries those decisions:\nwhich Contracts it draws from, how items are ordered, what's excluded, how items are shaped, and\nwhich filters callers may use. Editors then re-scope what a listing shows without any deploy —\nedits to a stream apply to the very next request.\n\nFind them under **Configure → Streams**.\n\n## What a stream defines\n\n- **Source Contracts** — one or more Contracts whose published Components the stream returns.\n Matching includes descendants: choosing an abstract ancestor (say, `web-page`) matches every\n published Component of every Contract built on it.\n- **Order** — a top-level field (like `publish-date`) and a direction. Items without the field\n always sort last.\n- **Projection** — optionally, a named Adapter applied to every item. The consumer then receives\n the Adapter's *output* shape (a flattened \"card\", say) instead of each item's full document,\n which keeps list payloads small. Every source Contract must satisfy the Adapter's input\n Contract; the editor checks this when you save.\n- **Resolve depth** — how deeply each item's own references are expanded, decided here rather\n than by the caller.\n- **Exclusions** — specific Components the stream never returns, whatever the query says.\n- **Declared filters** — the *only* query parameters the delivery endpoint accepts, each with a\n key you choose:\n - a **string** filter matches a top-level field's value exactly (e.g. `filter.author=ada`);\n - a **category** filter matches classified content, including everything under the given\n category (e.g. `filter.topic=topics/tech`);\n - a **fullText** filter (at most one per stream) searches all the text of each published item\n (e.g. `filter.q=postgres search`) — this is what powers a site-wide search box.\n\n## Things to know\n\n- Streams read **published** content only, always live — publishing or unpublishing something\n changes stream results immediately, and so does editing the stream or its Adapter's rules.\n- A stream itself is never published or promoted with content; it's configuration, like\n environments or delivery keys. It can be promoted between environments from its row's Promote\n button.\n- Deleting a stream is never blocked — a consumer still calling it simply gets a \"not found\"\n answer. Deleting a *Contract or Adapter a stream uses* is blocked until the stream stops using\n it.\n- Use **Preview** on a stream you're editing to run the real query against a delivery\n environment's published content and see exactly what consumers get.\n"
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"slug": "content-taxonomy",
|
|
63
63
|
"title": "Taxonomy",
|
|
64
|
-
"body": "# Taxonomy\
|
|
64
|
+
"body": "# Taxonomy\n\nOrganize your content with named classification trees — usable both as tags on Components,\nExperience pages, and Contracts (for findability and bulk operations) and as a field type your\nContracts can use directly.\n\n## CategoryGroups and Categories\n\nA **CategoryGroup** is the root of one tree — \"Topics,\" \"Regions,\" \"Campaigns.\" Open **Taxonomy**\nunder **Configure** in the app navigation to manage groups and their trees.\n\nA **Category** is one node in a group's tree. Each category has:\n\n- A **key** (used in paths and safe to keep even if you change the display value)\n- A **display value** — localizable, like any other text in the app\n- **Selectable** — turned off, a category becomes a purely structural branch: something you can\n nest other categories under, but never assign to content directly. Use this for organizing\n headings like \"Seating\" that shouldn't themselves be a tag.\n\nA category can be nested under another category in the same group, to any depth. You can't delete\na category (or turn off its Selectable flag) while it still has child categories, tags, or field\nvalues referencing it — you'll see how many places reference it if you try.\n\n### Metadata\n\nWhen creating a group, you can optionally attach a **metadata Contract** — the same kind of\nstructure definition you use for content, but describing extra information every category in this\ngroup should carry (an icon name, a color, a short blurb). If a group has a metadata Contract,\nevery category in it gets a metadata form to fill in.\n\n## Tagging content\n\nOpen any Component (under its **Settings** tab), Experience page, or Contract (under its\n**General** tab) and find the **Categories** panel. **Add tag…** opens a picker scoped to one\ngroup at a time (or a specific group, if the page only allows one); pick any selectable category\nand it's added. Tags are separate from any Category field value the item's Contract might also\nhave — think of tags as sticky notes for finding and grouping things, not part of the content\nitself. Tagging a Contract needs the same permission that lets you edit the Contract at all;\nTemplates can't be tagged yet.\n\n## Filtering by category\n\nThe Component library has a **Filter by category** control — pick a category and the list narrows\nto everything tagged with it (or a category nested under it). Select several items from a filtered\nlist and use **Publish N selected…** to publish them all together through the usual publish dialog,\nwhich shows the combined set of everything each selected item depends on.\n\nThe Experience page has the same **Filter by category** control on its site tree. Because the tree\nonly loads a branch's children when you expand it, picking a category automatically expands every\nancestor branch needed to reach each matching page, highlights the matches, and dims everything\nelse that's visible without hiding it — so you can still see where a match sits relative to the\nrest of the site. Clearing the filter leaves the tree as you left it.\n\n## Filtering which Contract you pick\n\nWhen adding new content from an Experience page's payload editor — \"New Component\" or \"Create\ninline\" — the Contract picker offers a **Filter by category** button. Pick a category and the\nchoices narrow to Contracts tagged with it; **Clear** brings back the full list. This is a\nconvenience for a large Contract library — it defaults to showing every eligible Contract, and you\ncan always clear the filter to reach one that isn't tagged.\n\n## Using a Category field on a Contract\n\nWhen building a Contract, add a field with type **Category**. In its settings, you can restrict\nwhich CategoryGroup(s) the field accepts — leave it empty to allow any group. In the Component\neditor, that field shows a **Choose…** button opening the same category picker; the chosen\ncategory displays as its full path (e.g. `topics/tech/ai`).\n\n## What happens when you rename or move a category\n\nRenaming a category, or moving it to a different parent, updates everywhere it's referenced\nimmediately — no republishing needed. What *does* require republishing is a **tag added after\npublish**: a Component's set of published categories is frozen at publish time, exactly like its\ncontent. If you tag something new and want that reflected in delivered results, publish it again.\n\n## External taxonomy\n\nIf your organization has registered a content provider that also supplies taxonomy, its groups and\ncategories appear in the picker and the Taxonomy page alongside your own — marked **synced** and\nread-only. They work identically to your own categories everywhere except editing: you can tag\ncontent with them and use them in Category fields, but changing them happens on the provider's own\nside, refreshed the same way its other content is.\n"
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"slug": "content-workflow",
|
|
68
68
|
"title": "Editorial workflow",
|
|
69
|
-
"body": "# Editorial workflow\
|
|
69
|
+
"body": "# Editorial workflow\n\nA workflow adds a review step in front of publishing: assign one to a folder or a section of your\nExperience tree, and content there can't go live until someone carries it through the review states\nyou've defined. Nothing changes anywhere you haven't assigned a workflow — publishing works exactly\nas it always has.\n\n## Creating a workflow\n\nOpen **Workflow** under **Configure** in the app navigation and choose **New workflow**. First pick\nthe workflow's **kind** — what this workflow governs. It can't be changed after creation:\n\n- **Publishing workflow** — governs a Component or page: Draft → your review steps → Approved,\n where Approved is the only state publishing is allowed from.\n- **Translation workflow** — governs a translation job's proposed values before they're written\n into the Component. See [Translation jobs](translation-jobs.md) for how to kick one off and\n review its results.\n\nA workflow's states come from its kind, and each state's meaning is fixed — shown on the node as\nbadges and a one-line description, never as switches you configure:\n\n- **Anchor states** (marked STATE · ANCHOR) are placed for you and can't be deleted or renamed —\n for a publishing workflow, **Draft** (where authors work freely) and **Approved** (locked, and\n the one publishable state).\n- **Step templates** are the states you add yourself, as many as you need: a **Review step**\n (content is locked while the reviewer decides) or a **Rework step** (back with the author,\n editable again). The name is yours — add two Review steps called \"Editorial review\" and \"Legal\n review\" for a two-stage approval, each with its own approver role on its transition.\n\nGive the workflow a name, then wire the graph:\n\n- **Add a step**: **+ Add node**, pick a step template, and type its name on the node.\n- **Connect two states into a transition**: every state has a small connector on each of its four\n sides — click one, then click a connector on the state you want to connect it to (or drag from\n one to the other). Click the new transition's label to name it and, optionally, restrict who may\n fire it to specific role ids (find a role's id on Hub's Roles page) — leave it blank to let any\n organization member fire it. A state without any wiring simply never occurs — there's no way to\n reach it. A state whose outcome ends the flow — a translation workflow's **Approved** and\n **Rejected** — still shows all four of its connectors, but only to receive an *incoming*\n transition: there's nothing to drag or click to start one from it, since it can never be the\n source of an outgoing transition. (A publishing workflow has no such state — even **Approved**\n can have an outgoing transition, such as a \"Revert to draft\" step, if you wire one.)\n- Every state shows how many items are **currently sitting in it** — a live count that updates as\n people submit, approve, and reject.\n\nOpen an existing workflow from the list to edit its graph the same way. A structural change (adding\nor removing states or transitions) is blocked while any item has an active review in progress — the\nerror explains this; renaming the workflow and editing attached actions (below) are always allowed\nregardless.\n\n## Attaching a Notify or Webhook to a state or transition\n\nBeyond states and transitions, the canvas offers two action node kinds via the same **+ Add node**\nmenu:\n\n- **Notify** — pick a channel (email or in-app) and one or more recipients. **This doesn't send\n anything yet** — no notification channel is configured for this organization. The attempt is still\n recorded (see Activity Log below), so you can see exactly when it *would* have fired once sending\n is available.\n- **Webhook** — a URL your own system controls. This one is real: it actually `POST`s the transition\n details to that URL. A URL that resolves to a private or internal address is rejected when you try\n to save it.\n\nAttach either one to a **state** (fires the moment an item enters that state — click one of the\naction's own connectors, then one of the state's) or to a **transition** (fires the moment that\ntransition is used — click the action's connector, then the transition's own label). An action must\nbe attached to something before you can save. A Notify or Webhook never blocks the review itself —\neven if a webhook fails, the transition still goes through; the failure is just recorded.\n\n## Assigning a workflow to a branch\n\nOpen a folder's (or an Experience node's) **governance panel** — the same place you manage access\nrules — and find the **Editorial workflow** section. Choose a workflow from the dropdown, or:\n\n- **Inherit from ancestor** — use whatever the nearest parent branch has assigned (or nothing, if\n none does). This is the default for every branch.\n- **None (explicit)** — opt this branch and everything beneath it out of review, even if a parent\n branch has a workflow assigned. Useful for a sub-folder that should publish freely while the rest\n of the section stays gated.\n\n## Submitting and reviewing\n\nOpen a Component (under its **Settings** tab) or an Experience node under a governed branch and\nyou'll see a workflow panel showing its current state. If it's still in a non-publishable state,\nthe **Publish** button — always visible in the Component editor's header, regardless of which tab\nyou're on — is disabled and explains why.\n\nSelect an available transition (e.g. **Submit for review**), optionally add a comment, and confirm.\nWhile an item sits in a **Review step** or in **Approved**, its content is locked — what was\nreviewed is exactly what publishes. If a reviewer sends it to a **Rework step**, it's editable\nagain while the review stays open; the review tracks the version you submitted, and if you edit\nduring rework, the panel tells you the item has moved past the reviewed version.\n\nOnce it reaches a publishable state, **Publish** works as normal — it publishes the reviewed\nversion and marks the review complete. If someone edited the item after submission, it will show as\n\"Modified since publish\" immediately, since the head and the published version now differ — that's\nexpected, not an error.\n\nA reviewer sees the same panel with **Approve**/**Reject** buttons (only the transitions their role\nis allowed to perform are enabled). Rejecting sends it back to the start with the reviewer's comment\nattached, and it can be resubmitted. Resubmitting while a review is already in progress starts a\nfresh one, pinned to your latest version.\n\nYou (or anyone with access-management permission) can cancel an in-progress review from the same\npanel.\n\n## Permissions\n\nCreating, renaming, and deleting workflow definitions requires its own \"Manage Content workflows\"\npermission. Assigning a workflow to a branch is governed by the same permission as access rules\n(\"Manage access\"), since it's branch governance, not workflow authoring. Submitting for review\nrequires edit access on the branch; approving/rejecting is limited to whichever role(s) the\ntransition names (or anyone, if none are named) — an access-management holder can always advance a\nstuck review.\n\n## Activity Log\n\nEvery submission, approval, rejection, and cancellation appears in Hub's Activity Log with the\nactor and any comment attached — the review trail is part of your organization's audit history.\nAttached Notify and Webhook actions appear there too, one entry per attempt: a Webhook shows the URL\nand the response it got back (or that it failed); a Notify always shows \"not delivered (no\nnotification channel configured)\" — the reminder that it's recorded, not sent, until this\norganization has a notification channel set up.\n"
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"slug": "contracts",
|
|
73
73
|
"title": "Modeling content with Contracts",
|
|
74
|
-
"body": "# Modeling content with Contracts\r\n\r\nContracts define the field structure your content adheres to — think of a\r\nContract as a blueprint (for example \"Article\" or \"Author\") that every\r\nComponent built from it must follow. You need to belong to an organization\r\nwith Content's Early Access enabled to use it — see\r\n[Creating an organization and inviting teammates](../hub/organizations.md) if you\r\nhaven't set one up yet.\r\n\r\n## Creating a Contract\r\n\r\n1. From the Contracts list, select **New Contract**.\r\n2. Switch to the **General** tab. Enter a **Name** — everything else below\r\n fills in from it, so filling it in first saves retyping. **Id** — a\r\n short, URL-safe identifier made of lowercase letters, numbers, and\r\n hyphens — fills in from Name automatically; you can still edit it by\r\n hand before saving.\r\n3. Turn on **Abstract** if this Contract exists only to be extended by\r\n other Contracts, or to constrain what a field is allowed to point at —\r\n an abstract Contract can't be used to create content directly.\r\n4. Optionally choose a **Parent Contract**. Every field the parent\r\n declares is inherited automatically and shown read-only under\r\n \"Inherited fields\" on the **Fields** tab — you can't redefine or hide an\r\n inherited field, only add new ones of your own.\r\n5. Switch to the **Fields** tab (the default tab) and select **Add field**\r\n to add a field. The Fields list is a set of collapsible rows —\r\n selecting a row's name expands it to fill in its Name/Id, pick a\r\n **Field type**, and set its modifiers (see below), and collapses\r\n whichever row was open before it, so only one field's details are ever\r\n in view at once. Repeat for every field the Contract needs; drag a row\r\n by its handle to reorder it (or, from the keyboard, focus the handle,\r\n press Space to pick it up, use the arrow keys to move it, and Space\r\n again to drop it), or select the trash icon to remove one. A row with\r\n an unresolved error shows a warning icon even while collapsed, and a\r\n failed save automatically expands the first field that needs\r\n attention — or, for an error on the General tab (Name, Id, Parent\r\n Contract), switches you back to that tab.\r\n6. Select **Save** (visible from either tab). Before anything is saved, you'll see a banner telling\r\n you whether this is a brand-new Contract, an in-place update, or a\r\n change that creates a new *version* (see \"Versions\" below) — confirm to\r\n go ahead, or cancel to keep editing.\r\n\r\n## Field types and modifiers\r\n\r\nEvery field has a **Field type** (Short Text, Number, Boolean, URL, Date,\r\nComponent, Blob, Presentation, Rich Text, or Category — Long Text,\r\nReference, and Nested are deprecated and no longer offered for new\r\nfields, though any field already using one keeps working unchanged; see\r\n\"Component fields\" below) plus up to five\r\nindependent modifiers, each shown as a toggle whose label changes with\r\nits state:\r\n\r\n- **Mandatory / Optional** — required, or not. For a Contextual field,\r\n this only applies where the field is actually used, not on the\r\n Component itself (see \"Contextual fields\" in\r\n [Authoring Components](components.md)).\r\n- **Multiple / Single** — a list of values instead of one.\r\n- **Localized / Same in every locale** — a default value plus per-locale\r\n overrides.\r\n- **Contextual / On the Component** — the value lives where the field is\r\n *used*, not on the Component that declares it — useful for something\r\n like a \"theme\" that should vary by where content is placed, not by the\r\n content itself.\r\n- **Personalized / Same for every audience** — audience-conditioned\r\n variant alternatives — see\r\n [Personalizing content by audience](content-personalization.md).\r\n\r\nSome field types add an extra toggle of their own alongside these five —\r\nfor example, Short Text, Long Text, and Rich Text all show an **Allow\r\ntoken injection** toggle, since it's specific to text-bearing fields\r\nrather than something every field type needs.\r\n\r\nEach field type has its own extra settings, shown once you pick a type —\r\nfor example, Short Text and Long Text share an optional pattern plus\r\nlength limits, all grouped together under **Validation** (a label you\r\nselect to expand, staying expanded automatically whenever something\r\ninside still needs your attention), Component lets you restrict which\r\nContracts are allowed (and, optionally, which of the two modes below), and\r\nBlob lets you restrict file types and maximum size.\r\n\r\n### Date fields\r\n\r\nA **Date** field has one extra setting, **Include time** — off shows and\r\nedits just a calendar date; on adds a time of day too. Either way, the\r\ndate/time you enter is shown in your own local time zone, and switching\r\nthis setting on or off later is a normal (additive) change, not something\r\nthat requires a new version.\r\n\r\n### Restricting a field to a fixed list of values\r\n\r\nShort Text, Long Text, Number, and Date fields can be restricted to a\r\nfixed set of **Allowed values** in their settings — useful for something\r\nlike a status field that should only ever be \"Draft,\" \"In review,\" or\r\n\"Published,\" rather than any free-text string. Leave it empty to allow\r\nany value.\r\n\r\nHow the field is edited then depends on whether it also allows multiple\r\nvalues and how many options you listed: a small list of single-value\r\noptions shows as buttons to pick one of; a longer list shows as a\r\ndropdown; combined with **Multiple**, a small list shows as checkboxes and\r\na longer one as a searchable picker. Narrowing this list later (removing a\r\nvalue that was previously allowed) is a breaking change, since an\r\nexisting Component might already hold it; adding a new value is additive.\r\n\r\n### Picking options from another system\r\n\r\nInstead of typing a fixed list of Allowed values, a Short Text field can\r\nname a **Choice source** — an activated plugin that supplies the option\r\nlist live, kept automatically in sync with whatever it's connected to.\r\nThe first one, **ebitex Forms**, lists your organization's own published\r\nForms, so you can pick a form by name rather than typing its id — useful\r\nfor a field like \"which form should appear on this page.\" A field can use\r\neither Allowed values or a Choice source, never both.\r\n\r\nOnce a Choice source plugin is activated (in your organization's Settings)\r\nand picked in a field's settings, editing a Component with that field\r\nlooks exactly like a normal fixed-list field: buttons, a dropdown,\r\ncheckboxes, or a searchable picker, depending on how many options there\r\nare. If the source is temporarily unavailable — a misconfigured\r\nconnection, for example — you'll see a short message explaining that, and\r\nthe field falls back to a plain text box so you can still see and edit\r\nwhatever value is already saved; nothing else on the page is blocked from\r\nsaving while that happens.\r\n\r\n### Giving a field a default value\r\n\r\nMost field types — anything other than Component, Presentation, Rich\r\nText, and Category, which all point at other content rather than holding\r\na plain value — can be given a **Default value** in their expanded row.\r\nOnce set, every brand-new Component created from this Contract starts\r\nwith that value already filled in, instead of empty; editing or clearing\r\nthe default afterward never changes a Component that already exists. For a\r\nContextual field, its default becomes the Component's own fallback value —\r\nused whenever a specific placement doesn't override it.\r\n\r\n## Component fields\r\n\r\nA **Component** field holds a reference to another Component, an inline\r\n(embedded) value, or either — decided by whoever is *filling in* the field,\r\nnot fixed when you design the Contract:\r\n\r\n- **Linked** — points at another Component elsewhere in your organization;\r\n the two stay separate, and deleting a Component that's linked elsewhere\r\n is blocked until the link is removed first. Covers both picking an\r\n existing Component and creating a brand-new one to link to.\r\n- **Inline** — embeds another Component's content directly inside; there's\r\n no separate entity to manage, it's just part of the same document. Best\r\n for content that only ever makes sense as part of its parent (for\r\n example, an article's author details).\r\n\r\nIf you want to guarantee only one of these for a given field — say, a\r\n\"related articles\" field that should always link to a reusable Component,\r\nnever an inline one-off — the field's settings offer **Allowed value\r\nmodes**, a checklist of the two options above (at least one must stay\r\nchecked). Leaving both checked (the default) lets whoever fills in the\r\nfield choose either way each time. Either way, **allowedContractIds** lets\r\nyou restrict which Contracts are allowed — leave it empty to allow any\r\nContract (including descendants of one you do list).\r\n\r\nExisting Contracts may still have the older, separate **Reference** and\r\n**Nested** field types — a Reference field is equivalent to a Component\r\nfield restricted to Linked only, and a Nested field to one restricted to\r\nInline only. They keep working exactly as before; a new field just uses\r\nComponent instead.\r\n\r\n## Giving a Contract a title field\r\n\r\nContent built from a Contract needs a human label — the name shown in a site's\r\nnavigation menu, in a breadcrumb trail, and beside each item in a listing.\r\n**Title field** on the **General** tab is where you choose which field that\r\nlabel comes from.\r\n\r\nIt is a picker, not something you type. Choose a field; if the field you chose\r\nholds a nested block, a second dropdown appears so you can choose the field\r\ninside it — up to four levels deep. Only fields a title can actually come from\r\nare offered: text fields as the final choice, and nested blocks as the steps in\r\nbetween.\r\n\r\n**Set it once, on the most general Contract that has the field.** The choice is\r\ninherited, so if your \"Web page\" Contract declares its `title` field, every\r\nContract that extends it — Article, Landing page, and so on — uses the same one\r\nwith nothing more to set up. A Contract that needs a different field can choose\r\nits own, which takes over. When a Contract inherits its title field, the picker\r\nshows it read-only and names the Contract it came from, with **Override** to\r\nchoose a different one and **Clear** to remove it.\r\n\r\nA few notes on what to expect:\r\n\r\n- **It applies from the next publish.** Choosing a title field doesn't change\r\n anything already published — each Component picks it up when it is next\r\n published.\r\n- **Editing the title text is a Component publish.** Change the wording and\r\n republish that Component, and its label updates everywhere it appears. You do\r\n not need to republish the pages that use it.\r\n- **If it can't be resolved, the page's own name is used instead** — its\r\n internal name from Composer. That happens when no Contract in the chain\r\n declares a title field, when the field named no longer exists, or when the\r\n content isn't published. Nothing breaks; you just see the internal name, which\r\n is a good hint that the title field needs attention.\r\n- **A localizable title field is worth choosing.** One value covers every\r\n locale, and each visitor sees theirs.\r\n\r\nIf the same piece of content appears in more than one place and should be named\r\ndifferently at each, make the title field **contextual** — then each page that\r\nuses it supplies its own wording. Bear in mind that a contextual title belongs to\r\nthe page, so changing it means republishing that page rather than the content.\r\n\r\n## Versions\r\n\r\nEvery save is classified automatically:\r\n\r\n- **Additive** changes (adding an optional field, loosening a limit, …)\r\n update the Contract's current version in place.\r\n- **Breaking** changes (removing a field, adding a *mandatory* field,\r\n tightening a limit, …) create a new version, leaving the old one intact\r\n so Components already pinned to it keep working.\r\n\r\nThe **History** tab on a Contract's page lists every version and\r\nwhat fields it resolved to. Existing Components aren't forced to update\r\nwhen a Contract changes — see \"The upgrade badge\" in\r\n[Authoring Components](components.md) for how that friction is surfaced\r\nand resolved.\r\n"
|
|
74
|
+
"body": "# Modeling content with Contracts\n\nContracts define the field structure your content adheres to — think of a\nContract as a blueprint (for example \"Article\" or \"Author\") that every\nComponent built from it must follow. You need to belong to an organization\nwith Content's Early Access enabled to use it — see\n[Creating an organization and inviting teammates](../hub/organizations.md) if you\nhaven't set one up yet.\n\n## Creating a Contract\n\n1. From the Contracts list, select **New Contract**.\n2. Switch to the **General** tab. Enter a **Name** — everything else below\n fills in from it, so filling it in first saves retyping. **Id** — a\n short, URL-safe identifier made of lowercase letters, numbers, and\n hyphens — fills in from Name automatically; you can still edit it by\n hand before saving.\n3. Turn on **Abstract** if this Contract exists only to be extended by\n other Contracts, or to constrain what a field is allowed to point at —\n an abstract Contract can't be used to create content directly.\n4. Optionally choose a **Parent Contract**. Every field the parent\n declares is inherited automatically and shown read-only under\n \"Inherited fields\" on the **Fields** tab — you can't redefine or hide an\n inherited field, only add new ones of your own.\n5. Switch to the **Fields** tab (the default tab) and select **Add field**\n to add a field. The Fields list is a set of collapsible rows —\n selecting a row's name expands it to fill in its Name/Id, pick a\n **Field type**, and set its modifiers (see below), and collapses\n whichever row was open before it, so only one field's details are ever\n in view at once. Repeat for every field the Contract needs; drag a row\n by its handle to reorder it (or, from the keyboard, focus the handle,\n press Space to pick it up, use the arrow keys to move it, and Space\n again to drop it), or select the trash icon to remove one. A row with\n an unresolved error shows a warning icon even while collapsed, and a\n failed save automatically expands the first field that needs\n attention — or, for an error on the General tab (Name, Id, Parent\n Contract), switches you back to that tab.\n6. Select **Save** (visible from either tab). Before anything is saved, you'll see a banner telling\n you whether this is a brand-new Contract, an in-place update, or a\n change that creates a new *version* (see \"Versions\" below) — confirm to\n go ahead, or cancel to keep editing.\n\n## Field types and modifiers\n\nEvery field has a **Field type** (Short Text, Number, Boolean, URL, Date,\nComponent, Blob, Presentation, Rich Text, or Category — Long Text,\nReference, and Nested are deprecated and no longer offered for new\nfields, though any field already using one keeps working unchanged; see\n\"Component fields\" below) plus up to five\nindependent modifiers, each shown as a toggle whose label changes with\nits state:\n\n- **Mandatory / Optional** — required, or not. For a Contextual field,\n this only applies where the field is actually used, not on the\n Component itself (see \"Contextual fields\" in\n [Authoring Components](components.md)).\n- **Multiple / Single** — a list of values instead of one.\n- **Localized / Same in every locale** — a default value plus per-locale\n overrides.\n- **Contextual / On the Component** — the value lives where the field is\n *used*, not on the Component that declares it — useful for something\n like a \"theme\" that should vary by where content is placed, not by the\n content itself.\n- **Personalized / Same for every audience** — audience-conditioned\n variant alternatives — see\n [Personalizing content by audience](content-personalization.md).\n\nSome field types add an extra toggle of their own alongside these five —\nfor example, Short Text, Long Text, and Rich Text all show an **Allow\ntoken injection** toggle, since it's specific to text-bearing fields\nrather than something every field type needs.\n\nEach field type has its own extra settings, shown once you pick a type —\nfor example, Short Text and Long Text share an optional pattern plus\nlength limits, all grouped together under **Validation** (a label you\nselect to expand, staying expanded automatically whenever something\ninside still needs your attention), Component lets you restrict which\nContracts are allowed (and, optionally, which of the two modes below), and\nBlob lets you restrict file types and maximum size.\n\n### Date fields\n\nA **Date** field has one extra setting, **Include time** — off shows and\nedits just a calendar date; on adds a time of day too. Either way, the\ndate/time you enter is shown in your own local time zone, and switching\nthis setting on or off later is a normal (additive) change, not something\nthat requires a new version.\n\n### Restricting a field to a fixed list of values\n\nShort Text, Long Text, Number, and Date fields can be restricted to a\nfixed set of **Allowed values** in their settings — useful for something\nlike a status field that should only ever be \"Draft,\" \"In review,\" or\n\"Published,\" rather than any free-text string. Leave it empty to allow\nany value.\n\nHow the field is edited then depends on whether it also allows multiple\nvalues and how many options you listed: a small list of single-value\noptions shows as buttons to pick one of; a longer list shows as a\ndropdown; combined with **Multiple**, a small list shows as checkboxes and\na longer one as a searchable picker. Narrowing this list later (removing a\nvalue that was previously allowed) is a breaking change, since an\nexisting Component might already hold it; adding a new value is additive.\n\n### Picking options from another system\n\nInstead of typing a fixed list of Allowed values, a Short Text field can\nname a **Choice source** — an activated plugin that supplies the option\nlist live, kept automatically in sync with whatever it's connected to.\nThe first one, **ebitex Forms**, lists your organization's own published\nForms, so you can pick a form by name rather than typing its id — useful\nfor a field like \"which form should appear on this page.\" A field can use\neither Allowed values or a Choice source, never both.\n\nOnce a Choice source plugin is activated (in your organization's Settings)\nand picked in a field's settings, editing a Component with that field\nlooks exactly like a normal fixed-list field: buttons, a dropdown,\ncheckboxes, or a searchable picker, depending on how many options there\nare. If the source is temporarily unavailable — a misconfigured\nconnection, for example — you'll see a short message explaining that, and\nthe field falls back to a plain text box so you can still see and edit\nwhatever value is already saved; nothing else on the page is blocked from\nsaving while that happens.\n\n### Giving a field a default value\n\nMost field types — anything other than Component, Presentation, Rich\nText, and Category, which all point at other content rather than holding\na plain value — can be given a **Default value** in their expanded row.\nOnce set, every brand-new Component created from this Contract starts\nwith that value already filled in, instead of empty; editing or clearing\nthe default afterward never changes a Component that already exists. For a\nContextual field, its default becomes the Component's own fallback value —\nused whenever a specific placement doesn't override it.\n\n## Component fields\n\nA **Component** field holds a reference to another Component, an inline\n(embedded) value, or either — decided by whoever is *filling in* the field,\nnot fixed when you design the Contract:\n\n- **Linked** — points at another Component elsewhere in your organization;\n the two stay separate, and deleting a Component that's linked elsewhere\n is blocked until the link is removed first. Covers both picking an\n existing Component and creating a brand-new one to link to.\n- **Inline** — embeds another Component's content directly inside; there's\n no separate entity to manage, it's just part of the same document. Best\n for content that only ever makes sense as part of its parent (for\n example, an article's author details).\n\nIf you want to guarantee only one of these for a given field — say, a\n\"related articles\" field that should always link to a reusable Component,\nnever an inline one-off — the field's settings offer **Allowed value\nmodes**, a checklist of the two options above (at least one must stay\nchecked). Leaving both checked (the default) lets whoever fills in the\nfield choose either way each time. Either way, **allowedContractIds** lets\nyou restrict which Contracts are allowed — leave it empty to allow any\nContract (including descendants of one you do list).\n\nExisting Contracts may still have the older, separate **Reference** and\n**Nested** field types — a Reference field is equivalent to a Component\nfield restricted to Linked only, and a Nested field to one restricted to\nInline only. They keep working exactly as before; a new field just uses\nComponent instead.\n\n## Giving a Contract a title field\n\nContent built from a Contract needs a human label — the name shown in a site's\nnavigation menu, in a breadcrumb trail, and beside each item in a listing.\n**Title field** on the **General** tab is where you choose which field that\nlabel comes from.\n\nIt is a picker, not something you type. Choose a field; if the field you chose\nholds a nested block, a second dropdown appears so you can choose the field\ninside it — up to four levels deep. Only fields a title can actually come from\nare offered: text fields as the final choice, and nested blocks as the steps in\nbetween.\n\n**Set it once, on the most general Contract that has the field.** The choice is\ninherited, so if your \"Web page\" Contract declares its `title` field, every\nContract that extends it — Article, Landing page, and so on — uses the same one\nwith nothing more to set up. A Contract that needs a different field can choose\nits own, which takes over. When a Contract inherits its title field, the picker\nshows it read-only and names the Contract it came from, with **Override** to\nchoose a different one and **Clear** to remove it.\n\nA few notes on what to expect:\n\n- **It applies from the next publish.** Choosing a title field doesn't change\n anything already published — each Component picks it up when it is next\n published.\n- **Editing the title text is a Component publish.** Change the wording and\n republish that Component, and its label updates everywhere it appears. You do\n not need to republish the pages that use it.\n- **If it can't be resolved, the page's own name is used instead** — its\n internal name from Composer. That happens when no Contract in the chain\n declares a title field, when the field named no longer exists, or when the\n content isn't published. Nothing breaks; you just see the internal name, which\n is a good hint that the title field needs attention.\n- **A localizable title field is worth choosing.** One value covers every\n locale, and each visitor sees theirs.\n\nIf the same piece of content appears in more than one place and should be named\ndifferently at each, make the title field **contextual** — then each page that\nuses it supplies its own wording. Bear in mind that a contextual title belongs to\nthe page, so changing it means republishing that page rather than the content.\n\n## Versions\n\nEvery save is classified automatically:\n\n- **Additive** changes (adding an optional field, loosening a limit, …)\n update the Contract's current version in place.\n- **Breaking** changes (removing a field, adding a *mandatory* field,\n tightening a limit, …) create a new version, leaving the old one intact\n so Components already pinned to it keep working.\n\nThe **History** tab on a Contract's page lists every version and\nwhat fields it resolved to. Existing Components aren't forced to update\nwhen a Contract changes — see \"The upgrade badge\" in\n[Authoring Components](components.md) for how that friction is surfaced\nand resolved.\n"
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
"slug": "environment-refresh",
|
|
78
78
|
"title": "Environment refresh",
|
|
79
|
-
"body": "# Environment refresh\
|
|
79
|
+
"body": "# Environment refresh\n\nIf you periodically want a lower environment (Staging, Dev) to look like Production again, refresh\ndoes that in one step — replacing an authoring environment's content wholesale from another\nenvironment, without disturbing anything about how that environment is set up (its publish\nmapping, promotion connections, and delivery keys all stay exactly as they were).\n\nThis is a bigger action than [export/import](export-import.md): it doesn't add content, it\n**replaces everything** in the environment you're refreshing.\n\n## Refreshing an environment\n\nOpen **Environments** under **Settings**, find the environment you want to refresh, and click\n**Refresh from…**. Choose which other authoring environment to copy from.\n\nYou'll see a plan showing every item that's currently only in the environment being refreshed, or\nthat differs from the source — this is what would be lost. Items only found here are checked for\n**rescue** by default; uncheck any you're fine losing.\n\nIf a checked item depends on something else that's only here and isn't itself checked, you'll see\na warning — that dependency won't survive the refresh, and the rescued item may reference something\nmissing afterward.\n\nType the environment's name to confirm, then click **Refresh**. This:\n\n1. Saves everything you checked for rescue as a bundle (before anything is touched).\n2. Replaces the environment's content with the source environment's content.\n3. Leaves the environment's own setup — its publish mapping, promotion connections, delivery\n keys, and position in the environments list — untouched.\n\n## After a refresh\n\nThe refreshed environment is **unpublished everywhere** — refresh doesn't automatically republish\nanything, since what should be live again is a deliberate decision, not an automatic one. Republish\nwhatever needs to go live from there.\n\nIf you rescued anything, click **Re-import rescued items…** to bring it back in, the same way as\n[importing](export-import.md) any other bundle. A rescued item that depended on something not\nrescued may show up with a broken reference — this is the same \"referenced item is missing\" state\nthis app shows anywhere else, and you can find these under **Component Sync**.\n\n## What doesn't come back automatically\n\nA rescued item's edit history doesn't survive — the bundle only carries its current state, not its\npast versions. And if you refresh again later without unlinking first, an environment can't be\nrefreshed while it's set to inherit from a parent environment — unlink it on this same page first.\n"
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
"slug": "export-import",
|
|
83
83
|
"title": "Export and import",
|
|
84
|
-
"body": "# Export and import\
|
|
84
|
+
"body": "# Export and import\n\nPromotion (see [Content environments](content-environments.md)) moves content one direction along\na configured pipeline. Export and import is the general-purpose alternative: take a portable copy\nof something and bring it into any authoring environment — the same organization or a different\none, with or without a promotion pipeline set up between them.\n\nUse this for:\n\n- **Moving content backward** (\"demote\") — content edited in a downstream environment, brought\n back to where it came from.\n- **Copying between environments with no pipeline configured**, or **between organizations**.\n- **Backing up** a Contract, Template, page, or Component, or **seeding** a fresh environment with\n a starting set of content.\n\n## Exporting\n\nOn a Contract, Template, page, Component, Audience, or Adapter's own editor, look for the\n**Export…** button (next to Promote, where that's also available).\n\nExporting shows you everything that will be included — the item itself plus everything it depends\non (its Contract, any Templates it uses, referenced Audiences, and so on) — before you commit to\nanything. Check **Portable** if you're exporting to a different organization and the content\nincludes images or other files; this packages those files' bytes along with the content so they\ndon't arrive broken. Leave it unchecked for a same-organization export — the files are already\nvisible everywhere in your organization regardless of environment.\n\nClick **Export** to produce a bundle file, then **Download bundle file** to save it.\n\n## Importing\n\nOpen **Transfer** under **Tools** in the app navigation, then **Upload bundle file…** to stage a\nbundle you've downloaded (or received from someone else). Once staged, click **Import…** next to\nit.\n\nYou'll be asked to choose:\n\n- **Preserve identity** — items keep the same identity they had when exported. Use this for a\n same-organization import; it's also what makes a \"demote\" work cleanly, since the item you bring\n back is recognized as the same one you'll later send forward again.\n- **Fresh identity** — every item gets a brand-new identity, guaranteed not to collide with\n anything. Required when importing into a different organization than the one the bundle came\n from.\n\nThe import plan then shows each item's state:\n\n- **New** — doesn't exist here yet, will be created.\n- **Identical** — already here, exactly matching — nothing changes.\n- **Conflict** — already exists here with *different* content. You'll need to check a box\n confirming you want the bundle's version to replace what's here. (The version being replaced is\n saved first, so it isn't lost — see the item's own version history afterward if you need it\n back.)\n\nA bundle imports as a whole — you can't pick and choose individual items partway through (do that\nat export time instead, by choosing a narrower starting point). Click **Import** to apply it.\n\n## Staged bundles\n\nThe Transfer page lists every bundle you've exported or uploaded. From there you can download a\nbundle again, re-run its import, or delete it once you're done with it. An ordinary export expires\nautomatically after 30 days; delete it sooner if you're finished with it.\n\n## What travels in a bundle, and what doesn't\n\nA bundle carries the *current* state of what you selected — the live content, not its full edit\nhistory. It does **not** carry: published copies (the destination publishes on its own schedule),\nin-progress workflow reviews, delivery keys or host mappings (those belong to the environment\nitself, not its content), or activity history.\n"
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"slug": "management-keys",
|
|
88
88
|
"title": "Management keys",
|
|
89
|
-
"body": "# Management keys\
|
|
89
|
+
"body": "# Management keys\n\nA **management key** lets a script, a build pipeline, or another system create and publish content in\nContent — the things you would otherwise do by hand in the app.\n\nThey're for automation. If you're editing content yourself, you don't need one.\n\n> Management keys need the **Pro** plan or above. A trial counts, so you can use them straight away.\n\n## What a key can do\n\nTwo things decide that, and you choose both when you create the key.\n\n**Its role** decides *which content* the key can touch. A key acts exactly as the role you give it —\nthe same folder permissions, the same approval workflows, the same limits. A key bound to a role that\ncan't edit a folder can't edit that folder, and a page waiting for approval can't be published by a\nscript any more than by a person.\n\n**Its permissions** decide *what kind of thing* the key can do:\n\n| Permission | Lets the key |\n|---|---|\n| **Read** | Look at what's there, and preview what a change would do |\n| **Write** | Apply a whole bundle of changes at once, and upload files |\n| **Publish** | Make content live |\n| **Authoring** | Create, edit and **delete** individual items — a Contract, a Component, a page |\n\nThey combine. A key with Read only is genuinely safe to use in a check that comments on a pull\nrequest: it can report exactly what *would* change and is incapable of changing it.\n\n**Authoring is the only one that can delete anything**, which is why it is separate from Write rather\nthan part of it. Write applies a bundle, and a bundle only ever adds or updates — it never removes a\nContract, a Component or a page. So a key that has always worked fine for your pipeline keeps doing\nexactly what it did; nothing widens on its own. Add Authoring when you want a script or a tool to\nbuild a model item by item, and leave it off when you do not.\n\n**Its environment.** A key works in one authoring environment and no other. To run a pipeline against\nstaging and production, create two keys. That's deliberate — it means a staging pipeline cannot\ntouch production even by mistake.\n\n## Creating a key\n\n1. Go to **Settings → API Keys**, and find **Management keys**.\n2. Check the **environment** shown in the switcher at the top of the page. The key is bound to\n whichever authoring environment you are currently in, so switch first if that is not the one you\n want.\n3. Choose a **name** you'll recognize later — \"Marketing site CI\", not \"key 2\".\n4. Choose the **role** it acts as. Pick the least powerful role that can do the job.\n5. Tick the **permissions** it needs. If in doubt, start with Read and add more when something fails.\n6. Optionally, restrict it to **IP ranges** — if your build server has a fixed address, this is worth\n doing.\n\n**The key is shown once.** Copy it into your secret store before closing the dialog; it can't be\nshown again. If you lose it, revoke it and create another.\n\n> You can't create a key more powerful than you are. If you don't hold a permission yourself, you\n> can't give it to a key.\n\n## Keeping a key safe\n\nA management key can change your content. Treat it like a password.\n\n- **Never put one in a browser, a web page, or a front-end bundle.** It won't work there — we\n deliberately prevent it — but the attempt means the key has been somewhere it shouldn't.\n- Store it in your CI system's secret store, never in a repository or a command you type.\n- Give it the narrowest role and fewest permissions that work.\n- Set an expiry if the key is for something temporary.\n- Restrict it to your build server's IP range where you can.\n\n## Revoking a key\n\n**Settings → API Keys → Management keys → Revoke.** It stops working immediately.\n\nThe record stays — who created it, when, and when it was last used — because that history is exactly\nwhat you need after a key has leaked.\n\nDeleting the **role** a key is bound to also stops that key working, immediately and permanently.\nThat's intentional: taking away a role takes away everything acting on its behalf.\n\n## Seeing what a key did\n\nEvery change a key makes is recorded in your organization's activity log under the key's name rather\nthan a person's, so you can always tell an automated change from someone's edit.\n\n## Troubleshooting\n\n| What you see | What it means |\n|---|---|\n| \"Invalid API key\" | Revoked, expired, mistyped — or the role it was bound to was deleted |\n| \"Scope denied\" | The key lacks that permission. It names which one |\n| \"Tier required\" | Your plan doesn't include management keys. Pro or above is needed |\n| \"IP denied\" | The key is restricted to IP ranges and the caller isn't in one |\n| \"Confirm overwrite required\" | Something changed in Content since your script last looked. Review before overwriting |\n| \"Unknown property\" | Your request had a field we don't recognize — usually a typo. It names the field |\n| \"Quota exceeded\" | Too many requests for your plan. It names the limit |\n\n## Related\n\n- [Building a content model through the API](agent-authoring.md) — the model in API terms\n- [Getting help](../general/getting-help.md)\n- For developers: the [Content Management API reference](../../docs/content-management-api.md),\n and the [Content MCP server](../../docs/content-mcp.md), which lets an AI agent build a content\n model using a key you mint. Mint it against a **non-production** authoring environment, and leave\n the authoring scope off unless the agent needs to write.\n"
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
"slug": "publishing",
|
|
93
93
|
"title": "Publishing content",
|
|
94
|
-
"body": "# Publishing content\r\n\r\nEverything you build in Content — Components and Experience nodes — stays\r\nprivate to your organization until you **publish** it. Publishing takes a\r\nsnapshot of what you're looking at and copies it into a separate,\r\nread-optimized space that the Content Delivery API serves to the outside\r\nworld. Editing something after it's published never changes what's live;\r\nyou have to publish again. (Developers building the site that shows this\r\ncontent start from `docs/content-sdk.md`.)\r\n\r\n## The lifecycle\r\n\r\nEvery publishable item shows a status badge:\r\n\r\n- **Draft** — never published.\r\n- **Published** — a snapshot is live, and it matches your current edits.\r\n- **Modified since publish** — a snapshot is live, but you've made\r\n changes since. Visitors still see the old snapshot until you publish\r\n again.\r\n- **Unpublished** — was published once, then taken down. The item still\r\n exists in Content; it's just not being served anymore.\r\n\r\n## Publishing an item\r\n\r\nSelect **Publish…** on a Component or an Experience node's detail panel.\r\nThis opens the **publish plan** — a list of everything that needs to go\r\nout together: the item itself, plus anything it depends on (a node's\r\nbound Component, a Component's own references, and so on, however deep\r\nthat chain goes).\r\n\r\nOn an item that's already live, the same button reads **Republish…** and\r\nsits next to **Unpublish** — use it to push new snapshots without taking\r\nanything down first. This is also how a change *inside* the dependency\r\nchain goes live: editing a published node's bound Component doesn't\r\nchange the node's own badge, but republishing the node re-freezes the\r\nwhole plan, the edited Component included.\r\n\r\nFor each item in the plan you'll see:\r\n\r\n- Its current status.\r\n- Whether it's valid to publish as-is. An invalid item is shown with its\r\n error and can't be included — fix the problem first, then reopen the\r\n plan.\r\n- A warning (not a blocker) if the item doesn't validate against the\r\n *latest* version of its Contract or Template, even though it's fine\r\n against the version it's actually pinned to. It will still publish; the\r\n warning is a heads-up that something upstream has moved on.\r\n\r\nUncheck anything you don't want to publish yet — a skipped item simply\r\nisn't included this time; nothing else in the plan is affected. If your\r\norganization has mapped more than one delivery environment from where\r\nyou're authoring (see [content-environments.md](content-environments.md)),\r\nchoose which one to publish to — otherwise it goes to the one delivery\r\nenvironment mapped here, exactly as before. Select **Publish** to go live\r\nwith everything still checked.\r\n\r\nPublishing requires the **Publish** permission on that item's branch\r\n(folder for Components, node branch for Experience nodes) — a separate\r\npermission from **Edit**, so an organization can have editors who can't\r\npush content live, and publishers who review before it goes out. If you\r\ndon't have it, the button is disabled and explains why.\r\n\r\n## Unpublishing\r\n\r\nSelect **Unpublish** to take a live item down immediately. Its URL (if it\r\nhad one) stops resolving. The item itself is untouched in Content — you\r\ncan publish it again at any time.\r\n\r\n**Images and files are different: unpublishing doesn't take them down.**\r\nThe first time an image or file field is published, it gets a permanent,\r\npublic link — anyone who has that link can still open it even after you\r\nunpublish the item that used it, or after you replace the image with a\r\ndifferent one. This is because the file's link never changes once\r\npublished (so it keeps working in caches, emails, and anywhere else it was\r\ncopied), and there's no way to \"recall\" a link once it's been shared.\r\nNothing changes about who can *edit* the file or where it appears in your\r\nsite — only the direct link to the file itself stays reachable.\r\n\r\nIf you publish a Component or page whose image field hasn't been made\r\npublic yet, **publishing itself requires a one-time setup on our end** —\r\nif that hasn't happened yet, publishing is blocked with a message saying\r\nso rather than silently publishing a broken image link. Contact support if\r\nyou see this.\r\n\r\n## Promoting content between environments\r\n\r\nIf your organization authors in more than one environment (Staging and\r\nProduction, say), **Promote…** — next to **Publish…** on a Component or\r\nExperience node — copies it, and everything it depends on, into another\r\nauthoring environment, ready to be published there in turn. This is\r\nseparate from publishing: promoting moves draft content between authoring\r\nspaces; publishing takes draft content live. See\r\n[content-environments.md](content-environments.md) for the full picture.\r\n\r\n## Renaming or moving a published Experience node\r\n\r\nIf you change a published node's slug (or move it), republishing\r\nre-computes every affected path. The **old** path automatically becomes a\r\nredirect to the new one, so existing links and bookmarks keep working —\r\nyou don't have to do anything extra. You can review and remove these\r\nredirects from a site's own **Manage** view under **Sites** (see below).\r\n\r\n## Keeping a page out of search results\r\n\r\nSome published pages shouldn't be advertised to search engines — a search\r\nresults page, an internal index, a section you'd rather people reached only\r\nfrom a link you sent them.\r\n\r\nOpen the page in **Composer**, go to the **Configuration** tab, and use\r\n**Search visibility**. Turning it off means the page is left out of the\r\n**sitemap** — the list of URLs your site gives search engines. Everything\r\nelse stays exactly as it was: the page is still published, still live, and\r\nstill works for anyone who visits or links to its URL.\r\n\r\nTwo things to know:\r\n\r\n- **It applies to everything beneath it.** Excluding a section page\r\n excludes every page under it too, however deep. A page excluded this way\r\n shows you which parent is responsible, with a link to it, so you can\r\n change it there.\r\n- **It takes effect when you publish.** Toggling it marks the page as\r\n **Modified**, exactly like editing its content — publish the page and the\r\n change reaches your live site.\r\n\r\nThis isn't the same as telling search engines not to *index* a page they\r\nalready know about. Leaving a page out of the sitemap means you aren't\r\nadvertising it; a search engine that finds it another way (a link from\r\nsomewhere else, say) can still list it.\r\n\r\n## When search engines are told a page changed\r\n\r\nYour sitemap tells search engines when each page last changed, so they know\r\nwhich pages are worth re-reading. You don't configure this and there's\r\nnothing to keep up to date — but it's worth knowing what it reflects:\r\n\r\n- It's the date the page's **content** last changed, not the date it was\r\n last published. Republishing a page you haven't edited doesn't move it,\r\n so publishing a whole site doesn't tell search engines that everything\r\n changed at once.\r\n- Renaming a page or editing its content moves it. Reordering pages or\r\n moving one to a different parent doesn't — neither changes what's on the\r\n page.\r\n- Editing the content a page is built from — the Component it's bound to —\r\n moves it too, even if you only publish that Component. Content nested a\r\n further step away (a Component referenced by *that* Component) doesn't,\r\n so the date can occasionally be a little older than the last real change.\r\n That's deliberate: an early date costs nothing, while a date that's too\r\n recent teaches search engines to stop trusting it.\r\n\r\n## Delivery settings\r\n\r\nThe **Delivery** page (in the app navigation) is where you manage delivery\r\nkeys — credentials external systems use to call the Content Delivery API.\r\nSelect **Create key**, give it a name, and copy the value shown — it's only\r\ndisplayed once. If more than one delivery environment is mapped here,\r\nchoose which one the key reads from. Revoke a key at any time to cut off\r\nwhatever's using it immediately.\r\n\r\nThere are two ways to use a key, and the **Browser-safe key** switch decides\r\nwhich you're creating:\r\n\r\n- **Server-side** (the default) — works from anywhere, so it must stay on\r\n your server, in a build step, or behind an edge function. Never put one in\r\n a web page.\r\n- **Browser-safe** — only works from the **allowed origins** (the websites\r\n you list, pre-filled from your sites' host mappings; add\r\n `http://localhost:<port>` for local development) and, optionally, the\r\n **allowed IP ranges** you list. Because it can't be used from anywhere\r\n else, it's safe to include in a website's own code. Everything a delivery\r\n key can read is already published, so a browser-safe key never exposes\r\n anything a visitor to your site couldn't see.\r\n\r\nEach key shows a **Browser-safe** or **Server-side** badge, and **Manage**\r\nlets you change a key's restrictions later — for example to add a new\r\nhostname. Removing every restriction from a browser-safe key asks you to\r\nconfirm, since that key may already be in a shipped site.\r\n\r\nRunning the same site against several delivery environments (development,\r\ntest, acceptance, production)? Create one browser-safe key per environment\r\nand give the site the matching key per deployment — the site's code stays\r\nidentical. To keep a non-production environment private, restrict its key\r\nto your office or VPN address range; the CMS never refuses to issue a key\r\nfor an environment. Not sure which address your office or server shows up\r\nas? Open `https://api.ebitex.io/diagnostics/client-address` from it — the\r\n`clientAddress` it returns is the one to allow.\r\n\r\nHost mapping and redirect aliases live on the **Sites** page instead — see\r\nbelow.\r\n\r\n## Host mapping and redirect aliases\r\n\r\nOpen **Sites** (in the app navigation) and select **Manage** on a site to\r\nreach:\r\n\r\n- **Host mapping** — which hostname(s) serve this site, for systems that\r\n resolve content by host rather than passing a site id explicitly. Enter\r\n one hostname per line and select **Save hosts**.\r\n- **Redirect aliases** — the list of old paths automatically created by\r\n renaming/moving this site's published nodes (see above). Delete one if\r\n you no longer want that old link to redirect.\r\n\r\nIf your authoring environment publishes to more than one delivery\r\nenvironment, a **Delivery environment** picker appears above the host\r\nmapping and redirect-alias sections — each delivery environment has its\r\nown independent set of hostnames and aliases, so choose which one you're\r\nviewing or editing before making a change. With a single delivery\r\nenvironment mapped, the picker stays hidden and there's nothing extra to\r\nchoose.\r\n\r\nManaging keys uses the same permission as Hub's API Keys page; host\r\nmapping and aliases use the same access-management permission as branch\r\nrules.\r\n"
|
|
94
|
+
"body": "# Publishing content\n\nEverything you build in Content — Components and Experience nodes — stays\nprivate to your organization until you **publish** it. Publishing takes a\nsnapshot of what you're looking at and copies it into a separate,\nread-optimized space that the Content Delivery API serves to the outside\nworld. Editing something after it's published never changes what's live;\nyou have to publish again. (Developers building the site that shows this\ncontent start from `docs/content-sdk.md`.)\n\n## The lifecycle\n\nEvery publishable item shows a status badge:\n\n- **Draft** — never published.\n- **Published** — a snapshot is live, and it matches your current edits.\n- **Modified since publish** — a snapshot is live, but you've made\n changes since. Visitors still see the old snapshot until you publish\n again.\n- **Unpublished** — was published once, then taken down. The item still\n exists in Content; it's just not being served anymore.\n\n## Publishing an item\n\nSelect **Publish…** on a Component or an Experience node's detail panel.\nThis opens the **publish plan** — a list of everything that needs to go\nout together: the item itself, plus anything it depends on (a node's\nbound Component, a Component's own references, and so on, however deep\nthat chain goes).\n\nOn an item that's already live, the same button reads **Republish…** and\nsits next to **Unpublish** — use it to push new snapshots without taking\nanything down first. This is also how a change *inside* the dependency\nchain goes live: editing a published node's bound Component doesn't\nchange the node's own badge, but republishing the node re-freezes the\nwhole plan, the edited Component included.\n\nFor each item in the plan you'll see:\n\n- Its current status.\n- Whether it's valid to publish as-is. An invalid item is shown with its\n error and can't be included — fix the problem first, then reopen the\n plan.\n- A warning (not a blocker) if the item doesn't validate against the\n *latest* version of its Contract or Template, even though it's fine\n against the version it's actually pinned to. It will still publish; the\n warning is a heads-up that something upstream has moved on.\n\nUncheck anything you don't want to publish yet — a skipped item simply\nisn't included this time; nothing else in the plan is affected. If your\norganization has mapped more than one delivery environment from where\nyou're authoring (see [content-environments.md](content-environments.md)),\nchoose which one to publish to — otherwise it goes to the one delivery\nenvironment mapped here, exactly as before. Select **Publish** to go live\nwith everything still checked.\n\nPublishing requires the **Publish** permission on that item's branch\n(folder for Components, node branch for Experience nodes) — a separate\npermission from **Edit**, so an organization can have editors who can't\npush content live, and publishers who review before it goes out. If you\ndon't have it, the button is disabled and explains why.\n\n## Unpublishing\n\nSelect **Unpublish** to take a live item down immediately. Its URL (if it\nhad one) stops resolving. The item itself is untouched in Content — you\ncan publish it again at any time.\n\n**Images and files are different: unpublishing doesn't take them down.**\nThe first time an image or file field is published, it gets a permanent,\npublic link — anyone who has that link can still open it even after you\nunpublish the item that used it, or after you replace the image with a\ndifferent one. This is because the file's link never changes once\npublished (so it keeps working in caches, emails, and anywhere else it was\ncopied), and there's no way to \"recall\" a link once it's been shared.\nNothing changes about who can *edit* the file or where it appears in your\nsite — only the direct link to the file itself stays reachable.\n\nIf you publish a Component or page whose image field hasn't been made\npublic yet, **publishing itself requires a one-time setup on our end** —\nif that hasn't happened yet, publishing is blocked with a message saying\nso rather than silently publishing a broken image link. Contact support if\nyou see this.\n\n## Promoting content between environments\n\nIf your organization authors in more than one environment (Staging and\nProduction, say), **Promote…** — next to **Publish…** on a Component or\nExperience node — copies it, and everything it depends on, into another\nauthoring environment, ready to be published there in turn. This is\nseparate from publishing: promoting moves draft content between authoring\nspaces; publishing takes draft content live. See\n[content-environments.md](content-environments.md) for the full picture.\n\n## Renaming or moving a published Experience node\n\nIf you change a published node's slug (or move it), republishing\nre-computes every affected path. The **old** path automatically becomes a\nredirect to the new one, so existing links and bookmarks keep working —\nyou don't have to do anything extra. You can review and remove these\nredirects from a site's own **Manage** view under **Sites** (see below).\n\n## Keeping a page out of search results\n\nSome published pages shouldn't be advertised to search engines — a search\nresults page, an internal index, a section you'd rather people reached only\nfrom a link you sent them.\n\nOpen the page in **Composer**, go to the **Configuration** tab, and use\n**Search visibility**. Turning it off means the page is left out of the\n**sitemap** — the list of URLs your site gives search engines. Everything\nelse stays exactly as it was: the page is still published, still live, and\nstill works for anyone who visits or links to its URL.\n\nTwo things to know:\n\n- **It applies to everything beneath it.** Excluding a section page\n excludes every page under it too, however deep. A page excluded this way\n shows you which parent is responsible, with a link to it, so you can\n change it there.\n- **It takes effect when you publish.** Toggling it marks the page as\n **Modified**, exactly like editing its content — publish the page and the\n change reaches your live site.\n\nThis isn't the same as telling search engines not to *index* a page they\nalready know about. Leaving a page out of the sitemap means you aren't\nadvertising it; a search engine that finds it another way (a link from\nsomewhere else, say) can still list it.\n\n## When search engines are told a page changed\n\nYour sitemap tells search engines when each page last changed, so they know\nwhich pages are worth re-reading. You don't configure this and there's\nnothing to keep up to date — but it's worth knowing what it reflects:\n\n- It's the date the page's **content** last changed, not the date it was\n last published. Republishing a page you haven't edited doesn't move it,\n so publishing a whole site doesn't tell search engines that everything\n changed at once.\n- Renaming a page or editing its content moves it. Reordering pages or\n moving one to a different parent doesn't — neither changes what's on the\n page.\n- Editing the content a page is built from — the Component it's bound to —\n moves it too, even if you only publish that Component. Content nested a\n further step away (a Component referenced by *that* Component) doesn't,\n so the date can occasionally be a little older than the last real change.\n That's deliberate: an early date costs nothing, while a date that's too\n recent teaches search engines to stop trusting it.\n\n## Delivery settings\n\nThe **Delivery** page (in the app navigation) is where you manage delivery\nkeys — credentials external systems use to call the Content Delivery API.\nSelect **Create key**, give it a name, and copy the value shown — it's only\ndisplayed once. If more than one delivery environment is mapped here,\nchoose which one the key reads from. Revoke a key at any time to cut off\nwhatever's using it immediately.\n\nThere are two ways to use a key, and the **Browser-safe key** switch decides\nwhich you're creating:\n\n- **Server-side** (the default) — works from anywhere, so it must stay on\n your server, in a build step, or behind an edge function. Never put one in\n a web page.\n- **Browser-safe** — only works from the **allowed origins** (the websites\n you list, pre-filled from your sites' host mappings; add\n `http://localhost:<port>` for local development) and, optionally, the\n **allowed IP ranges** you list. Because it can't be used from anywhere\n else, it's safe to include in a website's own code. Everything a delivery\n key can read is already published, so a browser-safe key never exposes\n anything a visitor to your site couldn't see.\n\nEach key shows a **Browser-safe** or **Server-side** badge, and **Manage**\nlets you change a key's restrictions later — for example to add a new\nhostname. Removing every restriction from a browser-safe key asks you to\nconfirm, since that key may already be in a shipped site.\n\nRunning the same site against several delivery environments (development,\ntest, acceptance, production)? Create one browser-safe key per environment\nand give the site the matching key per deployment — the site's code stays\nidentical. To keep a non-production environment private, restrict its key\nto your office or VPN address range; the CMS never refuses to issue a key\nfor an environment. Not sure which address your office or server shows up\nas? Open `https://api.ebitex.io/diagnostics/client-address` from it — the\n`clientAddress` it returns is the one to allow.\n\nHost mapping and redirect aliases live on the **Sites** page instead — see\nbelow.\n\n## Host mapping and redirect aliases\n\nOpen **Sites** (in the app navigation) and select **Manage** on a site to\nreach:\n\n- **Host mapping** — which hostname(s) serve this site, for systems that\n resolve content by host rather than passing a site id explicitly. Enter\n one hostname per line and select **Save hosts**.\n- **Redirect aliases** — the list of old paths automatically created by\n renaming/moving this site's published nodes (see above). Delete one if\n you no longer want that old link to redirect.\n\nIf your authoring environment publishes to more than one delivery\nenvironment, a **Delivery environment** picker appears above the host\nmapping and redirect-alias sections — each delivery environment has its\nown independent set of hostnames and aliases, so choose which one you're\nviewing or editing before making a change. With a single delivery\nenvironment mapped, the picker stays hidden and there's nothing extra to\nchoose.\n\nManaging keys uses the same permission as Hub's API Keys page; host\nmapping and aliases use the same access-management permission as branch\nrules.\n"
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
"slug": "sample-references",
|
|
98
98
|
"title": "Linking a post to sample code",
|
|
99
|
-
"body": "# Linking a post to sample code\
|
|
99
|
+
"body": "# Linking a post to sample code\n\nA **Sample reference** block tells a reader where to get the code for the step a tutorial post just\nwalked through. It renders as a distinct, boxed block so someone skimming a long post can find it\nwithout reading the prose around it.\n\nUse one wherever a post reaches a point the reader could check out and run.\n\n## Adding one to a post\n\n1. Edit the post's **Body**.\n2. Insert an embed and choose the **Sample reference** Template.\n3. Create a Component for it and fill in:\n\n| Field | What to put | Required |\n|---|---|---|\n| **Repository** | The repository's home page — `https://github.com/ebitex-io/samples` | Yes |\n| **Tag** | The git tag for this step — `northwind-coffee/step-07` | Yes |\n| **Path** | A folder inside the repo to point at | No |\n| **Seed bundle** | A link to the content export for this step | No |\n| **Label** | The sample's name. Left empty, the repository name is used | No |\n| **Note** | One line on what this step adds | No |\n\nA block with just a repository and a tag is complete and looks finished — most steps have nothing\nelse to say.\n\n## Where the links go\n\n**Repository is the repo's home page, not a link to the tagged code.** The link to the code *at*\nthat tag is built for you from the repository and the tag. This is deliberate: if the tagging scheme\never changes, it's one fix rather than an edit to every post ever published.\n\nFor repositories on hosts we know how to build links for, the reader gets a direct link to the code\nat that exact tag and path. For any other host, they get the repository home page and the tag shown\nas text to check out themselves — never a broken link.\n\n## Seed bundles\n\n**Only add a Seed bundle link on steps that actually have one.** In a tutorial, code and content\nadvance together, and seed bundles are usually published at a handful of checkpoints rather than at\nevery step. A reader joining mid-series imports the nearest bundle at or before their step.\n\nLeaving it empty is the normal case, and the block simply doesn't show a download.\n\n## Using it outside a post\n\nThe same block can be added as a section on an ordinary page, not just inside a post body — useful\non a landing page for a sample.\n\n## Related\n\n- [Blog series](blog-series.md) — grouping tutorial posts in order\n- [Components](components.md) — creating and editing content\n"
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
"slug": "templates",
|
|
103
103
|
"title": "Presenting content with Templates",
|
|
104
|
-
"body": "# Presenting content with Templates\
|
|
104
|
+
"body": "# Presenting content with Templates\n\nA Template defines how a piece of content gets shown — for example\n\"Hero banner\" or \"Card grid\" — and which kinds of Component it can present.\nA Presentation is a specific use of a Template: pick a Template, bind it\nto a Component, and optionally fine-tune it with per-placement settings\nand overrides. Templates are the presentation half of the system;\nContracts and Components (see [Modeling content with Contracts](contracts.md)\nand [Authoring Components](components.md)) are the content half — they\nstay deliberately separate, so the same piece of content can be presented\ndifferent ways in different places.\n\n## Creating a Template\n\n1. From the Templates list, select **New Template**.\n2. Switch to the **General** tab. Enter a **Name** — the **Id** fills in\n from it automatically.\n3. On the same tab, choose which **Supported Contracts** this Template can\n present: select **Add Contract** and pick from the searchable list.\n Leave it empty to allow any Component; constraining it to one Contract\n (often an abstract one, so several kinds of content can share the same\n Template) is the common case — a note above the list reminds you of\n this.\n4. Switch to the **Fields** tab (the default tab) to optionally add\n **Settings fields** — knobs like Theme, Padding, or Variant that apply\n to this particular placement, not to the content itself. A Template\n with no settings fields is completely normal; you can always add some\n later. You have two choices here:\n - **Author fields inline** (the default) creates a dedicated settings\n Contract for this Template automatically — you just add fields the\n same way you would on a Contract (see \"Field types and modifiers\" in\n [Modeling content with Contracts](contracts.md)): the same field\n types and modifiers, the same generated settings form, except a new\n settings field starts **Contextual** by default (a Template field is\n almost always meant as a per-usage override); you can still switch it\n off for a field that should live on the Component itself.\n - **Use an existing Contract** lets you pick an ordinary Contract from\n your library instead — useful when you want two or more Templates to\n share the exact same settings shape (for example, several card-style\n Templates all taking the same Theme/Padding knobs). Editing that\n Contract's fields later changes what every Template using it sees.\n5. Select **Save** (visible from either tab). As with Contracts, you'll\n see a banner first telling you whether this is a new Template, an\n in-place update, or a change that creates a new version — confirm to\n go ahead.\n\n## A Template's settings Contract is an ordinary Contract\n\nA Template's settings fields live on a real Contract, listed in your\nContract library alongside every other Contract — there's nothing hidden\nabout it. Once a Template is saved, its Fields tab shows that Contract's\nname and field count with two actions:\n\n- **Quick edit** opens its fields in a small side panel without leaving the\n Template — add, remove, reorder, or reconfigure fields, then Save. If the\n same Contract is used by other Templates too, they all see the change.\n- **Change Contract** points this Template at a different existing Contract\n instead. The Template's Save button still needs to be clicked to commit\n the change — until then it's just a pending pick.\n\nYou can also open and edit a settings Contract directly from the Contract\nlibrary like any other Contract.\n\n## Using a Template on a Component\n\nA Contract field can be given the **Presentation** field type, letting a\nComponent instantiate a Template inline — the common pattern for a page's\nbody is an *enumerable* Presentation field, so the page becomes a list of\npresented sections.\n\nWhen you reach a Presentation field while editing a Component, select\n**+ Add presentation** to open a wizard that walks you through picking a\nTemplate and binding a Component to it — including a preview of whether a\ncandidate Component actually fits before you commit. See\n[Authoring a presentation](content-authoring.md) for the full walkthrough,\nincluding what \"Compatible\" vs. \"Needs an Adapter\" mean and the Contextual\nvalues / Settings / Change / Remove controls on an already-bound field.\n\nIf the Template has been updated to a newer version since this Presentation\nwas created, a small notice appears — **Update to v_N_** moves it onto the\nnew version. Until you do, the existing Presentation keeps working exactly\nas before; a Template change never breaks something already in place.\n\n## Deleting a Template\n\nA Template can't be deleted while any Presentation still uses it, or while\na Presentation field's settings still name it as an allowed Template —\nyou'll see exactly what's blocking the delete so you can address it first.\nDeleting a Template does **not** delete its settings Contract — it stays in\nyour Contract library afterward (in case another Template uses it too, or\nyou want to reuse it later), and you can delete it separately from there if\nyou no longer need it. A Contract still assigned as some Template's\nsettings is itself blocked from deletion until you either delete that\nTemplate or change its settings Contract first.\n"
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
"slug": "translation-jobs",
|
|
108
108
|
"title": "Translation jobs",
|
|
109
|
-
"body": "# Translation jobs\
|
|
109
|
+
"body": "# Translation jobs\n\nTranslate a Component's text into another locale automatically, then review the result before it\nlands in your content — or skip the review entirely if you'd rather it apply right away.\n\n## Kicking off a translation\n\nOpen a Component and choose **Translate…** in its header. Pick one or more target locales (only\nlocales your organization has configured beyond the default show up here — add one in Settings\nfirst if you don't see the one you need), pick a translator if your organization has more than one\nactivated, and confirm. One job starts per locale you selected.\n\nOnly certain fields are ever translated: plain text and rich text fields marked as localizable,\nand only their default (root-locale) value — fields that hold a list of values, or that vary by\naudience, aren't picked up by this pass. A field whose target locale already has a value is left\nalone unless you check **Retranslate fields that already have a value in the target locale**.\n\n## What \"review\" means\n\nWhether a job needs your approval before it takes effect depends on whether the folder the\nComponent lives in has a **translation workflow** assigned (see\n[Editorial workflow](content-workflow.md)) — the same governance mechanism that gates publishing,\napplied here to translated text instead:\n\n- **No translation workflow assigned**: the job applies immediately — the proposed translations are\n written straight into the Component, no review step in between.\n- **A translation workflow is assigned**: the job's proposed values sit in **awaiting review** until\n someone acts on them. Depending on how the workflow was built, that might mean an automatic\n decision (for example, routing certain locales straight through with no human step at all) or a\n real review step where a person has to look at it.\n\nEither way, once a job applies, the translated text is simply part of the Component from then on —\nthere's nothing left to indicate it arrived via a translation job.\n\n## Reviewing proposed translations\n\nOpen the **Translations** panel on a Component (or click through from wherever the job is listed)\nto see each job's status and, for a job awaiting review, every proposed value side by side with the\noriginal: the source text above, an editable text box below holding the machine translation. Edit\nthe proposed text directly if it needs a tweak, then mark each item **Accept** or **Reject** —\naccepting is also the default if you don't touch an item at all, since a translated value with no\nobjection raised is assumed good.\n\nOnce every item you care about has been handled, use the workflow's own transition buttons (for\nexample **Approve**) to move the job forward. Depending on how many review steps the workflow\ndefines, approving might finish the job right away or hand it to a second reviewer — the panel\nalways shows what happens next.\n\nRejecting sends the job to its own end state without writing anything into the Component — nothing\nis applied, and the proposed translations are discarded.\n\n## What \"stale\" means\n\nIf someone edits the Component's own text while a translation job is sitting in review, the job\nnotices at the moment it's finally approved: any item whose original source text has since changed\nis marked **stale** rather than applied, since the translation was proposed against text that no\nlonger exists. A stale item doesn't block the rest of the job — every other item still applies\nnormally — but the stale field itself is left untouched, and translating it again (a fresh job) is\nthe way to pick up the new wording.\n\n## Deleting a Component with translation history\n\nA Component that still has a translation job **in progress** (queued, translating, or awaiting\nreview) can't be deleted until that job finishes one way or another — finish the review, or reject\nit, then delete. A Component whose translation jobs have all already finished (applied, discarded,\nor failed) has no such restriction — deleting the Component removes that history along with it.\n\n## Permissions\n\nKicking off a translation job requires edit access to the Component. Acting on a job under review\nfollows the same rules as any other workflow transition — see\n[Editorial workflow](content-workflow.md#permissions).\n"
|
|
110
110
|
}
|
|
111
111
|
];
|
|
112
112
|
//# sourceMappingURL=helpCorpus.generated.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpCorpus.generated.js","sourceRoot":"","sources":["../../src/resources/helpCorpus.generated.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,EAAE;AACF,kGAAkG;AAClG,oGAAoG;AAIpG,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC;QACE,MAAM,EAAE,iBAAiB;QACzB,OAAO,EAAE,0CAA0C;QACnD,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"helpCorpus.generated.js","sourceRoot":"","sources":["../../src/resources/helpCorpus.generated.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,EAAE;AACF,kGAAkG;AAClG,oGAAoG;AAIpG,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC;QACE,MAAM,EAAE,iBAAiB;QACzB,OAAO,EAAE,0CAA0C;QACnD,MAAM,EAAE,0rSAA0rS;KACnsS;IACD;QACE,MAAM,EAAE,aAAa;QACrB,OAAO,EAAE,aAAa;QACtB,MAAM,EAAE,wnGAAwnG;KACjoG;IACD;QACE,MAAM,EAAE,YAAY;QACpB,OAAO,EAAE,sBAAsB;QAC/B,MAAM,EAAE,igVAAigV;KAC1gV;IACD;QACE,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,kCAAkC;QAC3C,MAAM,EAAE,6okBAA6okB;KACtpkB;IACD;QACE,MAAM,EAAE,mBAAmB;QAC3B,OAAO,EAAE,0BAA0B;QACnC,MAAM,EAAE,6oLAA6oL;KACtpL;IACD;QACE,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,gCAAgC;QACzC,MAAM,EAAE,gsbAAgsb;KACzsb;IACD;QACE,MAAM,EAAE,sBAAsB;QAC9B,OAAO,EAAE,sBAAsB;QAC/B,MAAM,EAAE,4gbAA4gb;KACrhb;IACD;QACE,MAAM,EAAE,eAAe;QACvB,OAAO,EAAE,qCAAqC;QAC9C,MAAM,EAAE,q3FAAq3F;KAC93F;IACD;QACE,MAAM,EAAE,yBAAyB;QACjC,OAAO,EAAE,4BAA4B;QACrC,MAAM,EAAE,2jRAA2jR;KACpkR;IACD;QACE,MAAM,EAAE,mBAAmB;QAC3B,OAAO,EAAE,mBAAmB;QAC5B,MAAM,EAAE,6/HAA6/H;KACtgI;IACD;QACE,MAAM,EAAE,iBAAiB;QACzB,OAAO,EAAE,4CAA4C;QACrD,MAAM,EAAE,wzFAAwzF;KACj0F;IACD;QACE,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,UAAU;QACnB,MAAM,EAAE,wvJAAwvJ;KACjwJ;IACD;QACE,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,oBAAoB;QAC7B,MAAM,EAAE,4mPAA4mP;KACrnP;IACD;QACE,MAAM,EAAE,WAAW;QACnB,OAAO,EAAE,iCAAiC;QAC1C,MAAM,EAAE,+2XAA+2X;KACx3X;IACD;QACE,MAAM,EAAE,qBAAqB;QAC7B,OAAO,EAAE,qBAAqB;QAC9B,MAAM,EAAE,+iFAA+iF;KACxjF;IACD;QACE,MAAM,EAAE,eAAe;QACvB,OAAO,EAAE,mBAAmB;QAC5B,MAAM,EAAE,2jHAA2jH;KACpkH;IACD;QACE,MAAM,EAAE,iBAAiB;QACzB,OAAO,EAAE,iBAAiB;QAC1B,MAAM,EAAE,24KAA24K;KACp5K;IACD;QACE,MAAM,EAAE,YAAY;QACpB,OAAO,EAAE,oBAAoB;QAC7B,MAAM,EAAE,2hVAA2hV;KACpiV;IACD;QACE,MAAM,EAAE,mBAAmB;QAC3B,OAAO,EAAE,+BAA+B;QACxC,MAAM,EAAE,g2EAAg2E;KACz2E;IACD;QACE,MAAM,EAAE,WAAW;QACnB,OAAO,EAAE,mCAAmC;QAC5C,MAAM,EAAE,8vKAA8vK;KACvwK;IACD;QACE,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,kBAAkB;QAC3B,MAAM,EAAE,itIAAitI;KAC1tI;CACF,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ebitex/content-mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "An MCP server for ebitex Content: build and publish a content model from an AI agent.",
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist"
|
|
9
|
-
],
|
|
10
|
-
"bin": {
|
|
11
|
-
"ebitex-content-mcp": "./dist/main.js"
|
|
12
|
-
},
|
|
13
|
-
"exports": {
|
|
14
|
-
".": {
|
|
15
|
-
"types": "./dist/index.d.ts",
|
|
16
|
-
"import": "./dist/index.js"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"publishConfig": {
|
|
20
|
-
"access": "public"
|
|
21
|
-
},
|
|
22
|
-
"scripts": {
|
|
23
|
-
"prebuild": "node scripts/bundleHelp.mjs",
|
|
24
|
-
"build": "npm run prebuild && tsc -p tsconfig.build.json",
|
|
25
|
-
"lint": "oxlint",
|
|
26
|
-
"pretest": "node scripts/bundleHelp.mjs",
|
|
27
|
-
"test": "vitest run"
|
|
28
|
-
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
31
|
-
"zod": "^3.25.76"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@types/node": "^24.0.0",
|
|
35
|
-
"oxlint": "^1.71.0",
|
|
36
|
-
"typescript": "~6.0.2",
|
|
37
|
-
"vitest": "^4.1.10"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@ebitex/content-mcp",
|
|
3
|
+
"version": "0.1.73",
|
|
4
|
+
"description": "An MCP server for ebitex Content: build and publish a content model from an AI agent.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"bin": {
|
|
11
|
+
"ebitex-content-mcp": "./dist/main.js"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"prebuild": "node scripts/bundleHelp.mjs",
|
|
24
|
+
"build": "npm run prebuild && tsc -p tsconfig.build.json",
|
|
25
|
+
"lint": "oxlint",
|
|
26
|
+
"pretest": "node scripts/bundleHelp.mjs",
|
|
27
|
+
"test": "vitest run"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
31
|
+
"zod": "^3.25.76"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^24.0.0",
|
|
35
|
+
"oxlint": "^1.71.0",
|
|
36
|
+
"typescript": "~6.0.2",
|
|
37
|
+
"vitest": "^4.1.10"
|
|
38
|
+
}
|
|
39
|
+
}
|