@zachariaz/strapi-plugin-content-variants 0.1.0
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 +600 -0
- package/dist/_chunks/Segments-BREqC60L.js +330 -0
- package/dist/_chunks/Segments-BgxnvvtR.mjs +330 -0
- package/dist/_chunks/en-Bnfrhhim.js +62 -0
- package/dist/_chunks/en-e_966kWj.mjs +62 -0
- package/dist/_chunks/index-DVoZM8JU.js +1036 -0
- package/dist/_chunks/index-Dj2sexmk.mjs +1020 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/src/components/Initializer.d.ts +5 -0
- package/dist/admin/src/components/SegmentPickerAction.d.ts +7 -0
- package/dist/admin/src/components/VariantInfoAction.d.ts +8 -0
- package/dist/admin/src/components/VariantPanel.d.ts +13 -0
- package/dist/admin/src/components/VariantPickerAction.d.ts +20 -0
- package/dist/admin/src/contentManagerHooks/editView.d.ts +6 -0
- package/dist/admin/src/contentManagerHooks/listView.d.ts +22 -0
- package/dist/admin/src/hooks/useSegments.d.ts +17 -0
- package/dist/admin/src/hooks/useVariantFamily.d.ts +19 -0
- package/dist/admin/src/hooks/useVariantLinks.d.ts +44 -0
- package/dist/admin/src/index.d.ts +11 -0
- package/dist/admin/src/pages/Settings/Segments.d.ts +2 -0
- package/dist/admin/src/pluginId.d.ts +2 -0
- package/dist/admin/src/utils/batchLinkFetcher.d.ts +11 -0
- package/dist/admin/src/utils/variants.d.ts +13 -0
- package/dist/server/index.js +895 -0
- package/dist/server/index.mjs +896 -0
- package/dist/server/src/bootstrap.d.ts +17 -0
- package/dist/server/src/config/index.d.ts +5 -0
- package/dist/server/src/content-types/index.d.ts +121 -0
- package/dist/server/src/controllers/index.d.ts +24 -0
- package/dist/server/src/controllers/segment.d.ts +11 -0
- package/dist/server/src/controllers/variant-link.d.ts +18 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +244 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/admin.d.ts +12 -0
- package/dist/server/src/routes/content-api.d.ts +19 -0
- package/dist/server/src/routes/index.d.ts +25 -0
- package/dist/server/src/services/index.d.ts +62 -0
- package/dist/server/src/services/segment.d.ts +14 -0
- package/dist/server/src/services/variant-link.d.ts +60 -0
- package/dist/server/src/services/variant-resolver.d.ts +23 -0
- package/package.json +104 -0
package/README.md
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
# @solteq/strapi-plugin-content-variants
|
|
2
|
+
|
|
3
|
+
Strapi v5 plugin for segment-based content personalization. Define audience segments, mark fields as variant-aware, and serve different content to different user groups -- all within Strapi's existing component system.
|
|
4
|
+
|
|
5
|
+
## Current State
|
|
6
|
+
|
|
7
|
+
### Working -- Verified in Browser
|
|
8
|
+
|
|
9
|
+
#### Phase 1: Segment Model -- `plugin::content-variants.segment`
|
|
10
|
+
|
|
11
|
+
A collection type storing audience segment definitions.
|
|
12
|
+
|
|
13
|
+
- **Fields**: `name` (string, unique, required), `slug` (string, unique, required, auto-generated from name), `description` (text, optional), `externalId` (string, optional -- for future CDP integration)
|
|
14
|
+
- **No draft/publish** -- segments are always active
|
|
15
|
+
- **Server**: CRUD service using `strapi.documents()`, controller, admin-only routes at `GET|POST /content-variants/segments`, `GET|PUT|DELETE /content-variants/segments/:id`
|
|
16
|
+
- **Content API**: Read-only `GET /api/content-variants/segments` for frontends
|
|
17
|
+
|
|
18
|
+
**Files**: `server/src/content-types/segment/schema.json`, `server/src/services/segment.ts`, `server/src/controllers/segment.ts`, `server/src/routes/admin.ts`, `server/src/routes/content-api.ts`
|
|
19
|
+
|
|
20
|
+
#### Phase 1: Segments Settings Page
|
|
21
|
+
|
|
22
|
+
Admin page under **Settings > Global Settings > Content Variants** (`/admin/settings/content-variants`).
|
|
23
|
+
|
|
24
|
+
- Table of all segments with Name, Slug, External ID, Description columns
|
|
25
|
+
- Edit and Delete action buttons per row
|
|
26
|
+
- "Add Segment" opens an inline form with auto-slug generation from name
|
|
27
|
+
- Delete shows a confirm dialog
|
|
28
|
+
|
|
29
|
+
**Files**: `admin/src/pages/Settings/Segments.tsx`, `admin/src/hooks/useSegments.ts`
|
|
30
|
+
|
|
31
|
+
#### Phase 2: CTB Content-Type-Level Toggle
|
|
32
|
+
|
|
33
|
+
"Enable content variants" checkbox in the **Content-Type Builder** when editing any content type's Advanced Settings.
|
|
34
|
+
|
|
35
|
+
- Stores `pluginOptions['content-variants'].enabled: true` in the content type schema
|
|
36
|
+
- Appears alongside "Draft & publish" and "Internationalization" checkboxes
|
|
37
|
+
|
|
38
|
+
**Location**: CTB > click content type > Edit > Advanced Settings tab
|
|
39
|
+
|
|
40
|
+
#### Phase 2: CTB Per-Field Variant Checkbox
|
|
41
|
+
|
|
42
|
+
"Enable variants for this field" checkbox in Advanced Settings of **string, text, richtext, media, and blocks** fields within components.
|
|
43
|
+
|
|
44
|
+
- Stores `pluginOptions['content-variants'].variant: true` on the field schema
|
|
45
|
+
- Only appears for fields belonging to **components** (`forTarget === 'component'`), since variants live inside dynamic zone components
|
|
46
|
+
|
|
47
|
+
**Location**: CTB > select component (e.g., Hero) > Edit field > Advanced Settings tab
|
|
48
|
+
|
|
49
|
+
#### Phase 3: Edit View Sparkle Indicators
|
|
50
|
+
|
|
51
|
+
Sparkle icon badge on variant-enabled fields in the Content Manager edit view, so editors can see at a glance which fields have per-segment values.
|
|
52
|
+
|
|
53
|
+
- Registered via `registerHook('Admin/CM/pages/EditView/mutate-edit-view-layout')`
|
|
54
|
+
- Adds a tooltip: "This field has per-segment variants"
|
|
55
|
+
- Only active when the content type has `pluginOptions['content-variants'].enabled: true`
|
|
56
|
+
- **Verified working** -- sparkle icons appear next to Title, Description, Image fields
|
|
57
|
+
|
|
58
|
+
**Files**: `admin/src/contentManagerHooks/editView.tsx`
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
#### Phase 3: Segment Picker Header Action
|
|
63
|
+
|
|
64
|
+
Dropdown in the Content Manager edit view header (next to locale picker) for switching between "Default" and segment-specific variant views.
|
|
65
|
+
|
|
66
|
+
- Shows "Default" option plus all defined segments
|
|
67
|
+
- Segments with existing variants show a checkmark; segments without show "(no variant)"
|
|
68
|
+
- Selecting a segment swaps variant-enabled field values in the form using `setValues()`
|
|
69
|
+
- Stores active segment in URL query params: `?plugins[content-variants][segment]=slug`
|
|
70
|
+
- Preserves default values when switching and writes back edits to the correct variant slot
|
|
71
|
+
- **Depends on**: components having a `variants[]` repeatable component field
|
|
72
|
+
|
|
73
|
+
**Files**: `admin/src/components/SegmentPickerAction.tsx`
|
|
74
|
+
|
|
75
|
+
#### Phase 3: Variant Management Side Panel
|
|
76
|
+
|
|
77
|
+
"VARIANTS" panel in the edit view right sidebar (alongside ENTRY and PREVIEW).
|
|
78
|
+
|
|
79
|
+
- Lists all dynamic zone components that support variants
|
|
80
|
+
- Shows each variant with its segment assignments and priority
|
|
81
|
+
- "Add variant" button creates a new empty variant in the component's `variants[]` array
|
|
82
|
+
- Per-variant: assign/remove segments with priority number input
|
|
83
|
+
- Delete variant action
|
|
84
|
+
|
|
85
|
+
**Files**: `admin/src/components/VariantPanel.tsx`
|
|
86
|
+
|
|
87
|
+
#### Phase 3: List View Variant Count Column
|
|
88
|
+
|
|
89
|
+
"Variants" column in the Content Manager list view showing variant count per document.
|
|
90
|
+
|
|
91
|
+
- Registered via `registerHook('Admin/CM/pages/ListView/inject-column-in-table')`
|
|
92
|
+
- Shows a badge with the count, or "--" if no variants
|
|
93
|
+
- Only injected for content types with variants enabled
|
|
94
|
+
|
|
95
|
+
**Files**: `admin/src/contentManagerHooks/listView.tsx`
|
|
96
|
+
|
|
97
|
+
#### Phase 3: Variant Utilities
|
|
98
|
+
|
|
99
|
+
Core utility functions for admin-side variant detection and value manipulation.
|
|
100
|
+
|
|
101
|
+
- `isVariantEnabledContentType()`, `isVariantField()`, `getVariantFieldNames()`
|
|
102
|
+
- `hasVariantsField()`, `getDynamicZoneFields()`, `getVariantComponentUIDs()`
|
|
103
|
+
- `findVariantForSegment()`, `extractFieldValues()`, `buildSwappedFormValues()`
|
|
104
|
+
- `writeBackToVariant()`, `countVariants()`, `getSegmentSlugsWithVariants()`
|
|
105
|
+
|
|
106
|
+
**Files**: `admin/src/utils/variants.ts`
|
|
107
|
+
|
|
108
|
+
#### Phase 4: Variant Resolver Service
|
|
109
|
+
|
|
110
|
+
Server-side service that resolves variant fields given a segment slug.
|
|
111
|
+
|
|
112
|
+
- Walks all dynamic zone components in a document
|
|
113
|
+
- Finds the variant matching the segment with highest priority (lowest number)
|
|
114
|
+
- Merges variant field values over the defaults
|
|
115
|
+
- Strips the `variants[]` array from resolved output
|
|
116
|
+
|
|
117
|
+
**Files**: `server/src/services/variant-resolver.ts`
|
|
118
|
+
|
|
119
|
+
#### Phase 4: Document Service Middleware
|
|
120
|
+
|
|
121
|
+
Intercepts `findMany`/`findOne` Document Service operations for REST API segment filtering.
|
|
122
|
+
|
|
123
|
+
- When a `segment` parameter is present in the request, resolves variants server-side
|
|
124
|
+
- Returns flat, resolved content (variant fields merged, variants array removed)
|
|
125
|
+
- Handles both single documents and paginated results
|
|
126
|
+
|
|
127
|
+
**Files**: `server/src/bootstrap.ts`
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Variant Data Model
|
|
132
|
+
|
|
133
|
+
Variants are stored as Strapi components embedded inside the main component:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Hero (component in dynamic zone)
|
|
137
|
+
├── title (default value -- fallback, variant:true)
|
|
138
|
+
├── description (default value -- fallback, variant:true)
|
|
139
|
+
├── image (default media -- fallback, variant:true)
|
|
140
|
+
└── variants[] (repeatable component: zone.hero-variant)
|
|
141
|
+
├── title (variant-specific override)
|
|
142
|
+
├── description (variant-specific override)
|
|
143
|
+
├── image (variant-specific media)
|
|
144
|
+
└── segmentAssignments[] (repeatable: shared.segment-assignment)
|
|
145
|
+
├── segment (relation → plugin::content-variants.segment)
|
|
146
|
+
└── priority (integer, lower = higher priority)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The plugin does **not** auto-generate these components. Developers create them following the naming convention, or use the "Scaffold Demo Content" feature (not yet built).
|
|
150
|
+
|
|
151
|
+
### Required shared components (created in host project)
|
|
152
|
+
|
|
153
|
+
**shared.segment-assignment** (`src/components/shared/segment-assignment.json`):
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"collectionName": "components_shared_segment_assignments",
|
|
157
|
+
"info": { "displayName": "Segment Assignment" },
|
|
158
|
+
"attributes": {
|
|
159
|
+
"segment": {
|
|
160
|
+
"type": "relation",
|
|
161
|
+
"relation": "oneToOne",
|
|
162
|
+
"target": "plugin::content-variants.segment"
|
|
163
|
+
},
|
|
164
|
+
"priority": {
|
|
165
|
+
"type": "integer",
|
|
166
|
+
"default": 0,
|
|
167
|
+
"min": 0
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**zone.hero-variant** (`src/components/zone/hero-variant.json`) -- mirrors variant-enabled fields + segmentAssignments:
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"collectionName": "components_zone_hero_variants",
|
|
177
|
+
"info": { "displayName": "Hero Variant" },
|
|
178
|
+
"attributes": {
|
|
179
|
+
"Title": { "type": "string" },
|
|
180
|
+
"Description": { "type": "blocks" },
|
|
181
|
+
"Image": { "type": "media", "multiple": false },
|
|
182
|
+
"segmentAssignments": {
|
|
183
|
+
"type": "component",
|
|
184
|
+
"repeatable": true,
|
|
185
|
+
"component": "shared.segment-assignment"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Then add `variants` field to the parent Hero component:
|
|
192
|
+
```json
|
|
193
|
+
"variants": {
|
|
194
|
+
"type": "component",
|
|
195
|
+
"repeatable": true,
|
|
196
|
+
"component": "zone.hero-variant"
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Plugin Architecture
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
strapi-plugin-content-variants/
|
|
204
|
+
├── package.json
|
|
205
|
+
├── admin/
|
|
206
|
+
│ ├── custom.d.ts
|
|
207
|
+
│ ├── tsconfig.json
|
|
208
|
+
│ ├── tsconfig.build.json
|
|
209
|
+
│ └── src/
|
|
210
|
+
│ ├── index.tsx # register + bootstrap (CTB, CM, hooks)
|
|
211
|
+
│ ├── pluginId.ts
|
|
212
|
+
│ ├── components/
|
|
213
|
+
│ │ ├── Initializer.tsx
|
|
214
|
+
│ │ ├── SegmentPickerAction.tsx # Header dropdown for segment selection
|
|
215
|
+
│ │ └── VariantPanel.tsx # Side panel for variant management
|
|
216
|
+
│ ├── contentManagerHooks/
|
|
217
|
+
│ │ ├── editView.tsx # Variant field indicators (sparkle badge)
|
|
218
|
+
│ │ └── listView.tsx # Variant count column
|
|
219
|
+
│ ├── hooks/
|
|
220
|
+
│ │ └── useSegments.ts # Fetch/manage segments via admin API
|
|
221
|
+
│ ├── pages/
|
|
222
|
+
│ │ └── Settings/
|
|
223
|
+
│ │ └── Segments.tsx # Settings page: segment CRUD table + form
|
|
224
|
+
│ ├── translations/
|
|
225
|
+
│ │ └── en.json
|
|
226
|
+
│ └── utils/
|
|
227
|
+
│ └── variants.ts # Variant detection and value manipulation
|
|
228
|
+
└── server/
|
|
229
|
+
├── tsconfig.json
|
|
230
|
+
├── tsconfig.build.json
|
|
231
|
+
└── src/
|
|
232
|
+
├── index.ts # Exports all server modules
|
|
233
|
+
├── register.ts # Plugin register lifecycle
|
|
234
|
+
├── bootstrap.ts # Document Service middleware registration
|
|
235
|
+
├── destroy.ts # Plugin destroy lifecycle
|
|
236
|
+
├── config/
|
|
237
|
+
│ └── index.ts # Plugin config defaults
|
|
238
|
+
├── content-types/
|
|
239
|
+
│ ├── index.ts # Exports { segment }
|
|
240
|
+
│ └── segment/
|
|
241
|
+
│ └── schema.json # Segment model definition
|
|
242
|
+
├── controllers/
|
|
243
|
+
│ ├── index.ts # Exports { segment }
|
|
244
|
+
│ └── segment.ts # Segment CRUD controller
|
|
245
|
+
├── routes/
|
|
246
|
+
│ ├── index.ts # Exports { admin, 'content-api' }
|
|
247
|
+
│ ├── admin.ts # Admin-only CRUD routes for /segments
|
|
248
|
+
│ └── content-api.ts # Public read-only /segments route
|
|
249
|
+
└── services/
|
|
250
|
+
├── index.ts # Exports { segment, 'variant-resolver' }
|
|
251
|
+
├── segment.ts # Segment CRUD service with auto-slug
|
|
252
|
+
└── variant-resolver.ts # Resolve variants by segment
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Installation
|
|
256
|
+
|
|
257
|
+
Install the plugin in your Strapi v5 project:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm install @zachariaz/strapi-plugin-content-variants
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Enable it in `config/plugins.ts` (or `.js`):
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
export default {
|
|
267
|
+
'content-variants': {
|
|
268
|
+
enabled: true,
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Restart Strapi. The plugin registers its admin pages, Content-Type Builder extensions, and Content Manager hooks on boot.
|
|
274
|
+
|
|
275
|
+
## Development
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# Build plugin
|
|
279
|
+
npm run build # or: npx @strapi/pack-up build
|
|
280
|
+
|
|
281
|
+
# Watch mode
|
|
282
|
+
npm run watch
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## REST API Usage
|
|
286
|
+
|
|
287
|
+
The plugin intercepts standard Strapi Content API calls via Document Service middleware. No special endpoints needed — just add query parameters to your existing API calls.
|
|
288
|
+
|
|
289
|
+
### Authentication
|
|
290
|
+
|
|
291
|
+
Storefronts and frontends authenticate with a **Strapi API Token** (Bearer token). Create one at **Settings > API Tokens** (`/admin/settings/api-tokens`):
|
|
292
|
+
|
|
293
|
+
- **Token type**: Read-only
|
|
294
|
+
- **Permissions**: Enable `find` and `findOne` for each content type the storefront needs (e.g., `hero-banner`, `campaign`) plus `find` for `content-variants` plugin (segments endpoint)
|
|
295
|
+
|
|
296
|
+
All Content API calls require the `Authorization` header:
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
Authorization: Bearer <your-api-token>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Query Parameters
|
|
303
|
+
|
|
304
|
+
| Parameter | Description |
|
|
305
|
+
|-----------|-------------|
|
|
306
|
+
| `segment` | Segment slug. Resolves variant fields server-side, returns flat merged content. |
|
|
307
|
+
| `includeVariants` | Set to `true` to return base content + `_variants[]` array with all variant data. |
|
|
308
|
+
| `locale` | Standard Strapi i18n locale (e.g., `en`, `fi`). |
|
|
309
|
+
| `status` | `draft` or `published`. Default: `published`. Use `draft` to see unpublished content. |
|
|
310
|
+
| `populate` | Standard Strapi populate (e.g., `*`, `heroBanner`, `image`). |
|
|
311
|
+
|
|
312
|
+
### Important: Draft vs Published
|
|
313
|
+
|
|
314
|
+
By default, the Content API returns **published** content only. Variant resolution only works if both the base document and the variant documents have been published. If variant documents are draft-only, the published base content won't be resolved.
|
|
315
|
+
|
|
316
|
+
Use `status=draft` during development to test with unpublished content. In production, make sure to publish both base and variant documents.
|
|
317
|
+
|
|
318
|
+
### API Test Calls (Postman / curl)
|
|
319
|
+
|
|
320
|
+
Below are example calls using the test data. Replace `localhost:1337` with your Strapi host.
|
|
321
|
+
|
|
322
|
+
All calls require the `Authorization: Bearer <token>` header. In curl:
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
curl -H 'Authorization: Bearer <your-api-token>' 'http://localhost:1337/api/...'
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
In Postman, set the Authorization type to "Bearer Token" and paste the token value.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
#### 1. List hero banners — base content (no segment)
|
|
333
|
+
|
|
334
|
+
Returns default field values without variant resolution.
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
GET http://localhost:1337/api/hero-banners?locale=en&status=draft&populate=*
|
|
338
|
+
Authorization: Bearer <token>
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Response (trimmed to first entry):
|
|
342
|
+
```json
|
|
343
|
+
{
|
|
344
|
+
"data": [
|
|
345
|
+
{
|
|
346
|
+
"id": 1,
|
|
347
|
+
"documentId": "c0y1hk1245eats3bret72241",
|
|
348
|
+
"title": "Herobanner ",
|
|
349
|
+
"subtitle": "hero subtitle",
|
|
350
|
+
"ctaLabel": "Click me",
|
|
351
|
+
"ctaUrl": "#",
|
|
352
|
+
"locale": "en",
|
|
353
|
+
"image": null
|
|
354
|
+
}
|
|
355
|
+
],
|
|
356
|
+
"meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 5 } }
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
#### 2. List hero banners — resolved for a segment
|
|
363
|
+
|
|
364
|
+
The `segment` parameter triggers server-side variant resolution. Variant-marked fields are replaced with segment-specific values.
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
GET http://localhost:1337/api/hero-banners?locale=en&status=draft&segment=new-members&populate=*
|
|
368
|
+
Authorization: Bearer <token>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Response — note `title` changed from `"Herobanner "` to `"Herobanner for new members"`:
|
|
372
|
+
```json
|
|
373
|
+
{
|
|
374
|
+
"data": [
|
|
375
|
+
{
|
|
376
|
+
"id": 1,
|
|
377
|
+
"documentId": "c0y1hk1245eats3bret72241",
|
|
378
|
+
"title": "Herobanner for new members",
|
|
379
|
+
"subtitle": "hero subtitle",
|
|
380
|
+
"ctaLabel": "Click me",
|
|
381
|
+
"ctaUrl": "#",
|
|
382
|
+
"locale": "en",
|
|
383
|
+
"image": null
|
|
384
|
+
}
|
|
385
|
+
]
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
#### 3. Single hero banner — resolved for a different segment
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
GET http://localhost:1337/api/hero-banners/c0y1hk1245eats3bret72241?locale=en&status=draft&segment=club-members&populate=*
|
|
395
|
+
Authorization: Bearer <token>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Response — `title` resolved for club-members segment:
|
|
399
|
+
```json
|
|
400
|
+
{
|
|
401
|
+
"data": {
|
|
402
|
+
"id": 1,
|
|
403
|
+
"documentId": "c0y1hk1245eats3bret72241",
|
|
404
|
+
"title": "Herobanner for club",
|
|
405
|
+
"subtitle": "hero subtitle",
|
|
406
|
+
"ctaLabel": "Click me",
|
|
407
|
+
"ctaUrl": "#",
|
|
408
|
+
"locale": "en",
|
|
409
|
+
"image": null
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
#### 4. Single hero banner — enriched mode with all variants
|
|
417
|
+
|
|
418
|
+
The `includeVariants=true` parameter returns base content plus a `_variants[]` array listing every variant with its segment assignments and overridden field values.
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
GET http://localhost:1337/api/hero-banners/c0y1hk1245eats3bret72241?locale=en&status=draft&includeVariants=true&populate=*
|
|
422
|
+
Authorization: Bearer <token>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Response:
|
|
426
|
+
```json
|
|
427
|
+
{
|
|
428
|
+
"data": {
|
|
429
|
+
"id": 1,
|
|
430
|
+
"documentId": "c0y1hk1245eats3bret72241",
|
|
431
|
+
"title": "Herobanner ",
|
|
432
|
+
"subtitle": "hero subtitle",
|
|
433
|
+
"ctaLabel": "Click me",
|
|
434
|
+
"ctaUrl": "#",
|
|
435
|
+
"locale": "en",
|
|
436
|
+
"_variants": [
|
|
437
|
+
{
|
|
438
|
+
"documentId": "j55u6yrwiukbdbz50tsir7g1",
|
|
439
|
+
"segments": [
|
|
440
|
+
{ "name": "New members", "slug": "new-members" }
|
|
441
|
+
],
|
|
442
|
+
"fields": {
|
|
443
|
+
"title": "Herobanner for new members",
|
|
444
|
+
"subtitle": "hero subtitle",
|
|
445
|
+
"ctaLabel": "Click me"
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"documentId": "q65aizvvuneke6lpnxpglsoj",
|
|
450
|
+
"segments": [
|
|
451
|
+
{ "name": "Club Members", "slug": "club-members" }
|
|
452
|
+
],
|
|
453
|
+
"fields": {
|
|
454
|
+
"title": "Herobanner for club",
|
|
455
|
+
"subtitle": "hero subtitle",
|
|
456
|
+
"ctaLabel": "Click me"
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"documentId": "mkjtwxoyc509uedvsbh6uq5b",
|
|
461
|
+
"segments": [
|
|
462
|
+
{ "name": "Superbuyers", "slug": "superbyers" }
|
|
463
|
+
],
|
|
464
|
+
"fields": {
|
|
465
|
+
"title": "Herobanner superbyers",
|
|
466
|
+
"subtitle": "hero subtitle",
|
|
467
|
+
"ctaLabel": "Click me"
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
]
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
#### 5. Campaigns with relation — no segment
|
|
478
|
+
|
|
479
|
+
Campaigns have a `heroBanner` relation. Without `segment`, the related hero banner returns base (default) field values.
|
|
480
|
+
|
|
481
|
+
```
|
|
482
|
+
GET http://localhost:1337/api/campaigns?locale=en&status=draft&populate=heroBanner
|
|
483
|
+
Authorization: Bearer <token>
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Response:
|
|
487
|
+
```json
|
|
488
|
+
{
|
|
489
|
+
"data": [
|
|
490
|
+
{
|
|
491
|
+
"id": 1,
|
|
492
|
+
"documentId": "nwqo1liifvnuz6iv5eb2riu1",
|
|
493
|
+
"title": "Summer campaign",
|
|
494
|
+
"slug": "summercampaign",
|
|
495
|
+
"description": "Description",
|
|
496
|
+
"locale": "en",
|
|
497
|
+
"heroBanner": {
|
|
498
|
+
"id": 1,
|
|
499
|
+
"documentId": "c0y1hk1245eats3bret72241",
|
|
500
|
+
"title": "Herobanner ",
|
|
501
|
+
"subtitle": "hero subtitle",
|
|
502
|
+
"ctaLabel": "Click me",
|
|
503
|
+
"ctaUrl": "#",
|
|
504
|
+
"locale": "en"
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
]
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
#### 6. Campaigns with relation — segment resolution propagates through relations
|
|
514
|
+
|
|
515
|
+
When `segment` is set, variant resolution propagates into populated relations. The hero banner's variant-marked fields are resolved for the segment.
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
GET http://localhost:1337/api/campaigns?locale=en&status=draft&segment=new-members&populate=heroBanner
|
|
519
|
+
Authorization: Bearer <token>
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Response — the `heroBanner.title` is now resolved for `new-members`:
|
|
523
|
+
```json
|
|
524
|
+
{
|
|
525
|
+
"data": [
|
|
526
|
+
{
|
|
527
|
+
"id": 1,
|
|
528
|
+
"documentId": "nwqo1liifvnuz6iv5eb2riu1",
|
|
529
|
+
"title": "Summer campaign",
|
|
530
|
+
"slug": "summercampaign",
|
|
531
|
+
"description": "Description",
|
|
532
|
+
"locale": "en",
|
|
533
|
+
"heroBanner": {
|
|
534
|
+
"id": 1,
|
|
535
|
+
"documentId": "c0y1hk1245eats3bret72241",
|
|
536
|
+
"title": "Herobanner for new members",
|
|
537
|
+
"subtitle": "hero subtitle",
|
|
538
|
+
"ctaLabel": "Click me",
|
|
539
|
+
"ctaUrl": "#",
|
|
540
|
+
"locale": "en"
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
]
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
#### 7. List available segments
|
|
550
|
+
|
|
551
|
+
Returns all defined segments. The API token must have `find` permission for the `content-variants` plugin.
|
|
552
|
+
|
|
553
|
+
```
|
|
554
|
+
GET http://localhost:1337/api/content-variants/segments
|
|
555
|
+
Authorization: Bearer <token>
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Response:
|
|
559
|
+
```json
|
|
560
|
+
[
|
|
561
|
+
{
|
|
562
|
+
"id": 2,
|
|
563
|
+
"documentId": "nlhcms3seykmet4bniz2z794",
|
|
564
|
+
"name": "New members",
|
|
565
|
+
"slug": "new-members",
|
|
566
|
+
"description": null,
|
|
567
|
+
"externalId": null
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
"id": 1,
|
|
571
|
+
"documentId": "qf7hz4xyuo2rlkr74hnfa20c",
|
|
572
|
+
"name": "Club Members",
|
|
573
|
+
"slug": "club-members",
|
|
574
|
+
"description": null,
|
|
575
|
+
"externalId": null
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
"id": 3,
|
|
579
|
+
"documentId": "iww0h4gt6z2wka0o92hg25co",
|
|
580
|
+
"name": "Superbuyers",
|
|
581
|
+
"slug": "superbyers",
|
|
582
|
+
"description": null,
|
|
583
|
+
"externalId": null
|
|
584
|
+
}
|
|
585
|
+
]
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Use the `slug` values from this response as the `segment` query parameter in content API calls.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
### How Variant Resolution Works
|
|
593
|
+
|
|
594
|
+
1. **Without `segment`**: Returns the base document with default field values. No variant data is included unless `includeVariants=true` is set.
|
|
595
|
+
|
|
596
|
+
2. **With `segment=slug`**: The Document Service middleware intercepts the response. For each variant-enabled content type, it finds the variant link matching the segment slug, fetches the variant document, and merges its variant-marked fields over the base document's fields. The response looks identical to a normal Strapi response — the frontend doesn't need to know about variants.
|
|
597
|
+
|
|
598
|
+
3. **With `includeVariants=true`**: Returns the base document as-is, plus a `_variants[]` array. Each entry contains the variant's `documentId`, `segments` (name + slug), and `fields` (the overridden field values). Useful for client-side resolution or preview UIs.
|
|
599
|
+
|
|
600
|
+
4. **Relations**: Segment resolution propagates through `populate`. If a Campaign populates its `heroBanner` relation and `segment=new-members` is set, the heroBanner's fields are resolved for that segment too.
|