multisite-cms-mcp 1.2.4 → 1.3.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.
@@ -37,9 +37,9 @@ A minimal manifest with static pages only:
37
37
  - \`file\` points to the HTML file in pages/ folder
38
38
  - \`editable: true\` allows inline editing in the CMS
39
39
  - \`defaultHeadHtml\` is injected into all pages`,
40
- manifest_custom_paths: `# manifest.json with Custom CMS Paths
40
+ manifest_custom_paths: `# manifest.json with CMS Collections
41
41
 
42
- When your site uses different URLs than the defaults:
42
+ Configure templates for your CMS collections:
43
43
 
44
44
  \`\`\`json
45
45
  {
@@ -58,26 +58,25 @@ When your site uses different URLs than the defaults:
58
58
  }
59
59
  ],
60
60
  "cmsTemplates": {
61
- "blogIndex": "templates/insights_index.html",
62
- "blogIndexPath": "/insights",
63
- "blogPost": "templates/insights_detail.html",
64
- "blogPostPath": "/insights",
65
- "team": "templates/leadership.html",
66
- "teamPath": "/about/leadership",
67
- "downloads": "templates/resources.html",
68
- "downloadsPath": "/resources",
69
- "authorsIndex": "templates/contributors.html",
70
- "authorDetail": "templates/contributor.html",
71
- "authorsPath": "/contributors"
61
+ "postsIndex": "templates/posts_index.html",
62
+ "postsIndexPath": "/blog",
63
+ "postsDetail": "templates/posts_detail.html",
64
+ "postsDetailPath": "/blog",
65
+ "teamIndex": "templates/team.html",
66
+ "teamIndexPath": "/team",
67
+ "servicesIndex": "templates/services.html",
68
+ "servicesIndexPath": "/services",
69
+ "servicesDetail": "templates/service-detail.html",
70
+ "servicesDetailPath": "/services"
72
71
  }
73
72
  }
74
73
  \`\`\`
75
74
 
76
- **Path Configuration:**
77
- - \`blogIndexPath: "/insights"\` Blog listing at /insights
78
- - \`blogPostPath: "/insights"\` Posts at /insights/post-slug
79
- - \`teamPath: "/about/leadership"\` Team page at /about/leadership
80
- - Both blogIndexPath and blogPostPath should usually match`,
75
+ **Path Configuration Pattern:**
76
+ - \`{collectionSlug}Index\` - Template file for listing page
77
+ - \`{collectionSlug}IndexPath\` - URL for listing page
78
+ - \`{collectionSlug}Detail\` - Template file for detail page
79
+ - \`{collectionSlug}DetailPath\` - URL base for detail pages`,
81
80
  manifest_minimal_with_ui: `# Minimal manifest.json (Configure Templates via Settings UI)
82
81
 
83
82
  When you want to upload pages first and configure CMS templates later via the Settings UI:
@@ -115,21 +114,16 @@ When you want to upload pages first and configure CMS templates later via the Se
115
114
 
116
115
  **After uploading this package:**
117
116
  1. Go to Dashboard → Settings → CMS Templates
118
- 2. Select "Blog" (/blog) as the Blog Index Template
119
- 3. Select "Blog Post Template" (/blog-post) as the Blog Post Template
120
- 4. Set the Blog URL Path (e.g., /blog)
121
- 5. Click Save
117
+ 2. Configure template mappings for your collections
118
+ 3. Set URL paths
122
119
 
123
120
  **Benefits of this approach:**
124
121
  - Faster initial upload - no need to configure cmsTemplates in JSON
125
122
  - Visual UI makes it easier to understand the configuration
126
- - Can change template mappings without re-uploading the package
127
- - Good for iterating on template designs
123
+ - Can change template mappings without re-uploading the package`,
124
+ blog_index_template: `# Blog/Posts Index Template
128
125
 
129
- **Note:** The editor will show a warning banner if you visit /blog before configuring the template.`,
130
- blog_index_template: `# Blog Index Template
131
-
132
- Lists all blog posts with filtering options:
126
+ Lists all posts with featured section (using a "posts" collection):
133
127
 
134
128
  \`\`\`html
135
129
  <main class="blog-page">
@@ -139,14 +133,14 @@ Lists all blog posts with filtering options:
139
133
  </header>
140
134
 
141
135
  <!-- Featured Post (first featured article) -->
142
- {{#each blogs featured=true limit=1}}
136
+ {{#each posts featured=true limit=1}}
143
137
  <article class="featured-post">
144
- {{#if mainImage}}
145
- <img src="{{mainImage}}" alt="{{name}}" class="featured-image">
138
+ {{#if image}}
139
+ <img src="{{image}}" alt="{{name}}" class="featured-image">
146
140
  {{/if}}
147
141
  <div class="featured-content">
148
142
  <h2><a href="{{url}}">{{name}}</a></h2>
149
- <p class="excerpt">{{postSummary}}</p>
143
+ <p class="excerpt">{{summary}}</p>
150
144
  <div class="meta">
151
145
  {{#if author}}
152
146
  <span class="author">By {{author.name}}</span>
@@ -159,18 +153,18 @@ Lists all blog posts with filtering options:
159
153
 
160
154
  <!-- All Posts Grid -->
161
155
  <div class="posts-grid">
162
- {{#each blogs limit=12}}
156
+ {{#each posts limit=12 sort="publishedAt" order="desc"}}
163
157
  <article class="post-card">
164
- {{#if thumbnailImage}}
165
- <img src="{{thumbnailImage}}" alt="{{name}}">
158
+ {{#if thumbnail}}
159
+ <img src="{{thumbnail}}" alt="{{name}}">
166
160
  {{else}}
167
- {{#if mainImage}}
168
- <img src="{{mainImage}}" alt="{{name}}">
161
+ {{#if image}}
162
+ <img src="{{image}}" alt="{{name}}">
169
163
  {{/if}}
170
164
  {{/if}}
171
165
  <div class="card-content">
172
166
  <h3><a href="{{url}}">{{name}}</a></h3>
173
- <p>{{postSummary}}</p>
167
+ <p>{{summary}}</p>
174
168
  {{#if author}}
175
169
  <span class="byline">{{author.name}}</span>
176
170
  {{/if}}
@@ -178,22 +172,26 @@ Lists all blog posts with filtering options:
178
172
  </article>
179
173
  {{/each}}
180
174
  </div>
175
+
176
+ {{#unless posts}}
177
+ <p>No posts yet. Check back soon!</p>
178
+ {{/unless}}
181
179
  </main>
182
180
  \`\`\`
183
181
 
184
182
  **Key patterns:**
185
- - \`{{#each blogs featured=true limit=1}}\` for featured post
186
- - \`{{#each blogs limit=12}}\` for post grid
187
- - Always wrap images in \`{{#if mainImage}}\`
183
+ - \`{{#each posts featured=true limit=1}}\` for featured post
184
+ - \`{{#each posts limit=12 sort="publishedAt" order="desc"}}\` for post grid
185
+ - Always wrap images in \`{{#if image}}\`
188
186
  - Use \`{{url}}\` for links to post detail page`,
189
187
  blog_post_template: `# Blog Post Detail Template
190
188
 
191
- Single blog post with author information:
189
+ Single post page with author information (using a "posts" collection with author relation):
192
190
 
193
191
  \`\`\`html
194
192
  <article class="blog-post">
195
- {{#if mainImage}}
196
- <img src="{{mainImage}}" alt="{{name}}" class="hero-image">
193
+ {{#if image}}
194
+ <img src="{{image}}" alt="{{name}}" class="hero-image">
197
195
  {{/if}}
198
196
 
199
197
  <header class="post-header">
@@ -202,8 +200,8 @@ Single blog post with author information:
202
200
  <div class="post-meta">
203
201
  {{#if author}}
204
202
  <div class="author-info">
205
- {{#if author.picture}}
206
- <img src="{{author.picture}}" alt="{{author.name}}" class="author-avatar">
203
+ {{#if author.photo}}
204
+ <img src="{{author.photo}}" alt="{{author.name}}" class="author-avatar">
207
205
  {{/if}}
208
206
  <a href="{{author.url}}" class="author-name">{{author.name}}</a>
209
207
  </div>
@@ -212,30 +210,22 @@ Single blog post with author information:
212
210
  </div>
213
211
  </header>
214
212
 
215
- {{#if postSummary}}
216
- <p class="lead">{{postSummary}}</p>
213
+ {{#if summary}}
214
+ <p class="lead">{{summary}}</p>
217
215
  {{/if}}
218
216
 
219
217
  <div class="post-content">
220
- {{{postBody}}}
218
+ {{{body}}}
221
219
  </div>
222
220
 
223
221
  {{#if author}}
224
222
  <aside class="author-box">
225
- {{#if author.picture}}
226
- <img src="{{author.picture}}" alt="{{author.name}}">
223
+ {{#if author.photo}}
224
+ <img src="{{author.photo}}" alt="{{author.name}}">
227
225
  {{/if}}
228
226
  <div class="author-details">
229
227
  <h3>About <a href="{{author.url}}">{{author.name}}</a></h3>
230
228
  {{{author.bio}}}
231
- <div class="social-links">
232
- {{#if author.twitterProfileLink}}
233
- <a href="{{author.twitterProfileLink}}">Twitter</a>
234
- {{/if}}
235
- {{#if author.linkedinProfileLink}}
236
- <a href="{{author.linkedinProfileLink}}">LinkedIn</a>
237
- {{/if}}
238
- </div>
239
229
  </div>
240
230
  </aside>
241
231
  {{/if}}
@@ -243,11 +233,13 @@ Single blog post with author information:
243
233
  \`\`\`
244
234
 
245
235
  **IMPORTANT:**
246
- - \`{{{postBody}}}\` uses TRIPLE braces (contains HTML)
236
+ - \`{{{body}}}\` uses TRIPLE braces (contains HTML)
247
237
  - \`{{{author.bio}}}\` uses TRIPLE braces (contains HTML)
248
238
  - Everything else uses double braces`,
249
239
  team_template: `# Team Members Template
250
240
 
241
+ Using a "team" collection with name, role, photo, bio, order fields:
242
+
251
243
  \`\`\`html
252
244
  <section class="team-section">
253
245
  <header class="section-header">
@@ -279,9 +271,10 @@ Single blog post with author information:
279
271
 
280
272
  **Key points:**
281
273
  - \`sort="order" order="asc"\` maintains manual ordering
282
- - \`{{{bio}}}\` uses triple braces for HTML content
283
- - \`{{photo}}\` not \`{{picture}}\` (that's for authors)`,
284
- downloads_template: `# Downloads Template
274
+ - \`{{{bio}}}\` uses triple braces for HTML content`,
275
+ downloads_template: `# Downloads/Resources Template
276
+
277
+ Using a "downloads" collection with name, description, fileUrl, category fields:
285
278
 
286
279
  \`\`\`html
287
280
  <section class="downloads-section">
@@ -294,7 +287,7 @@ Single blog post with author information:
294
287
  {{#each downloads sort="order" order="asc"}}
295
288
  <div class="download-item">
296
289
  <div class="download-info">
297
- <h3>{{title}}</h3>
290
+ <h3>{{name}}</h3>
298
291
  {{#if description}}
299
292
  <p>{{description}}</p>
300
293
  {{/if}}
@@ -309,11 +302,11 @@ Single blog post with author information:
309
302
  {{/each}}
310
303
  </div>
311
304
  </section>
312
- \`\`\`
313
-
314
- **Note:** Downloads uses \`{{title}}\` not \`{{name}}\``,
305
+ \`\`\``,
315
306
  authors_template: `# Authors Index Template
316
307
 
308
+ Using an "authors" collection:
309
+
317
310
  \`\`\`html
318
311
  <section class="authors-section">
319
312
  <header class="section-header">
@@ -323,8 +316,8 @@ Single blog post with author information:
323
316
  <div class="authors-grid">
324
317
  {{#each authors}}
325
318
  <a href="{{url}}" class="author-card">
326
- {{#if picture}}
327
- <img src="{{picture}}" alt="{{name}}" class="author-photo">
319
+ {{#if photo}}
320
+ <img src="{{photo}}" alt="{{name}}" class="author-photo">
328
321
  {{/if}}
329
322
  <h3>{{name}}</h3>
330
323
  {{#if bio}}
@@ -337,11 +330,13 @@ Single blog post with author information:
337
330
  \`\`\``,
338
331
  author_detail_template: `# Author Detail Template
339
332
 
333
+ Using an "authors" collection with detail pages:
334
+
340
335
  \`\`\`html
341
336
  <article class="author-detail">
342
337
  <header class="author-header">
343
- {{#if picture}}
344
- <img src="{{picture}}" alt="{{name}}" class="author-photo-large">
338
+ {{#if photo}}
339
+ <img src="{{photo}}" alt="{{name}}" class="author-photo-large">
345
340
  {{/if}}
346
341
  <h1>{{name}}</h1>
347
342
 
@@ -349,11 +344,11 @@ Single blog post with author information:
349
344
  {{#if email}}
350
345
  <a href="mailto:{{email}}">Email</a>
351
346
  {{/if}}
352
- {{#if twitterProfileLink}}
353
- <a href="{{twitterProfileLink}}">Twitter</a>
347
+ {{#if twitter}}
348
+ <a href="{{twitter}}">Twitter</a>
354
349
  {{/if}}
355
- {{#if linkedinProfileLink}}
356
- <a href="{{linkedinProfileLink}}">LinkedIn</a>
350
+ {{#if linkedin}}
351
+ <a href="{{linkedin}}">LinkedIn</a>
357
352
  {{/if}}
358
353
  </div>
359
354
  </header>
@@ -362,27 +357,30 @@ Single blog post with author information:
362
357
  <div class="author-bio">{{{bio}}}</div>
363
358
  {{/if}}
364
359
 
360
+ <!-- Show posts by this author (if you have a posts collection with author relation) -->
365
361
  <section class="author-articles">
366
362
  <h2>Articles by {{name}}</h2>
367
363
  <div class="articles-grid">
368
- {{#each blogs where="author.slug:{{slug}}" limit=10}}
369
- <a href="{{url}}" class="article-card">
370
- {{#if thumbnailImage}}
371
- <img src="{{thumbnailImage}}" alt="{{name}}">
364
+ {{#each posts}}
365
+ {{#if (eq author.name ../name)}}
366
+ <a href="{{url}}" class="article-card">
367
+ {{#if thumbnail}}
368
+ <img src="{{thumbnail}}" alt="{{name}}">
369
+ {{/if}}
370
+ <h3>{{name}}</h3>
371
+ <p>{{summary}}</p>
372
+ </a>
372
373
  {{/if}}
373
- <h3>{{name}}</h3>
374
- <p>{{postSummary}}</p>
375
- </a>
376
374
  {{/each}}
377
375
  </div>
378
376
  </section>
379
377
  </article>
380
378
  \`\`\`
381
379
 
382
- **Special:** \`where="author.slug:{{slug}}"\` filters blogs by this author`,
380
+ **Pattern:** \`{{#if (eq author.name ../name)}}\` filters posts by this author`,
383
381
  custom_collection_template: `# Custom Collection Template
384
382
 
385
- For user-defined collections. All custom collections have mandatory \`name\` and \`publishedAt\` fields.
383
+ For any user-defined collection. All collections have built-in \`name\`, \`slug\`, \`url\`, and date fields.
386
384
 
387
385
  ## Index Template
388
386
 
@@ -408,6 +406,10 @@ For user-defined collections. All custom collections have mandatory \`name\` and
408
406
  </article>
409
407
  {{/each}}
410
408
  </div>
409
+
410
+ {{#unless products}}
411
+ <p>No products yet.</p>
412
+ {{/unless}}
411
413
  </section>
412
414
  \`\`\`
413
415
 
@@ -433,38 +435,27 @@ For user-defined collections. All custom collections have mandatory \`name\` and
433
435
  </article>
434
436
  \`\`\`
435
437
 
436
- ## Required Built-in Tokens
438
+ ## Built-in Tokens (Available on ALL items)
437
439
 
438
- Every custom collection item automatically has:
439
- - \`{{name}}\` - Item name/title (REQUIRED)
440
- - \`{{slug}}\` - URL slug for detail pages
440
+ - \`{{name}}\` - Item name/title
441
+ - \`{{slug}}\` - URL slug
441
442
  - \`{{url}}\` - Full URL to detail page
442
- - \`{{publishedAt}}\` - Publish date (REQUIRED)
443
+ - \`{{publishedAt}}\` - Publish date
444
+ - \`{{createdAt}}\` - Creation date
445
+ - \`{{updatedAt}}\` - Last modified date
443
446
 
444
- ## In manifest.json (Flat Format)
447
+ ## In manifest.json
445
448
 
446
449
  \`\`\`json
447
450
  {
448
451
  "cmsTemplates": {
449
- "productIndex": "pages/shop.html",
450
- "productIndexPath": "/shop",
451
- "productDetail": "templates/product-detail.html",
452
- "productDetailPath": "/product"
452
+ "productsIndex": "templates/products_index.html",
453
+ "productsIndexPath": "/shop",
454
+ "productsDetail": "templates/products_detail.html",
455
+ "productsDetailPath": "/products"
453
456
  }
454
457
  }
455
- \`\`\`
456
-
457
- **Path Configuration (flat format):**
458
- - \`{slug}Index\` - Template file for the list page
459
- - \`{slug}IndexPath\` - URL for the list page (e.g., /shop → lists all products)
460
- - \`{slug}Detail\` - Template file for the detail page
461
- - \`{slug}DetailPath\` - Base URL for detail pages (e.g., /product → /product/my-product-slug)
462
-
463
- This serves:
464
- - Collection index at \`/shop\` (lists all products using productIndex template)
465
- - Detail pages at \`/product/product-slug\` (individual product using productDetail template)
466
-
467
- **IMPORTANT:** The manifest's paths are the SINGLE SOURCE OF TRUTH. Collection slug is for database only.`,
458
+ \`\`\``,
468
459
  form_handling: `# Form Handling
469
460
 
470
461
  Forms are automatically captured by the CMS:
@@ -488,37 +479,38 @@ Forms are automatically captured by the CMS:
488
479
 
489
480
  <button type="submit">Send Message</button>
490
481
  </form>
482
+ \`\`\`
483
+
484
+ ## Form Handler Script
491
485
 
492
- <script>
486
+ \`\`\`javascript
493
487
  document.querySelectorAll('form[data-form-name]').forEach(form => {
494
488
  form.addEventListener('submit', async (e) => {
495
489
  e.preventDefault();
496
490
 
491
+ const formName = form.dataset.formName || 'general';
497
492
  const formData = new FormData(form);
498
493
  const data = Object.fromEntries(formData);
499
494
 
500
- const response = await fetch('/api/forms/submit', {
495
+ // IMPORTANT: Endpoint is /_forms/{formName}
496
+ const response = await fetch('/_forms/' + formName, {
501
497
  method: 'POST',
502
498
  headers: { 'Content-Type': 'application/json' },
503
- body: JSON.stringify({
504
- formName: form.dataset.formName,
505
- data: data
506
- })
499
+ body: JSON.stringify(data)
507
500
  });
508
501
 
509
502
  if (response.ok) {
510
503
  form.reset();
511
- alert('Thank you! Your message has been sent.');
504
+ alert(form.dataset.successMessage || 'Thank you!');
512
505
  }
513
506
  });
514
507
  });
515
- </script>
516
508
  \`\`\`
517
509
 
518
510
  **Key points:**
519
511
  - Add \`data-form-name="xxx"\` to identify the form
520
- - All \`name\` attributes are captured as fields
521
- - Tenant context is handled automatically by the system`,
512
+ - Endpoint is \`/_forms/{formName}\` (NOT \`/api/forms/submit\`)
513
+ - All \`name\` attributes are captured as fields`,
522
514
  asset_paths: `# Asset Path Rules
523
515
 
524
516
  **ALL asset paths must use /public/ prefix:**
@@ -569,78 +561,28 @@ Logos, icons, decorative backgrounds, UI elements - these are bundled with the s
569
561
  \`\`\`
570
562
 
571
563
  ### 2. Content Images (Use CMS Tokens)
572
- Blog photos, team headshots, product images - these are managed through the CMS:
564
+ Post images, team photos, product images - these are managed through the CMS:
573
565
  \`\`\`html
574
566
  <!-- USE CMS tokens for content -->
575
- <img src="{{mainImage}}" alt="{{name}}">
576
- <img src="{{thumbnailImage}}" alt="{{name}}">
567
+ <img src="{{image}}" alt="{{name}}">
568
+ <img src="{{thumbnail}}" alt="{{name}}">
577
569
  <img src="{{photo}}" alt="{{name}}">
578
570
  \`\`\`
579
571
 
580
572
  ---
581
573
 
582
- ## How to Decide: Static vs CMS Token
583
-
584
- | Image Type | Use | Example |
585
- |------------|-----|---------|
586
- | Company logo | \`/public/images/logo.png\` | Header/footer logo |
587
- | UI icons | \`/public/images/icons/...\` | Arrows, social icons |
588
- | Decorative backgrounds | \`/public/images/...\` | Hero patterns, textures |
589
- | Favicon | \`/public/images/favicon.ico\` | Browser tab icon |
590
- | Blog post images | \`{{mainImage}}\` | Article hero image |
591
- | Team member photos | \`{{photo}}\` | Staff headshots |
592
- | Product images | \`{{productImage}}\` | E-commerce photos |
593
- | User-uploaded content | \`{{fieldName}}\` | Any CMS-managed media |
594
-
595
- **Rule of thumb:** If the image is part of the site's design/branding → static. If it's content that changes per item → CMS token.
596
-
597
- ---
598
-
599
- ## Content Images: Avoid Hardcoded Examples
600
-
601
- When converting a static site with example blog posts or team members, replace the example content with CMS tokens:
602
-
603
- **WRONG - Hardcoded example content:**
604
- \`\`\`html
605
- <div class="videos-grid">
606
- <article class="video-card">
607
- <img src="/images/example-video.jpg" alt="Example">
608
- <h2>Example Video Title</h2>
609
- <p>Example description text...</p>
610
- </article>
611
- </div>
612
- \`\`\`
613
-
614
- **CORRECT - CMS tokens with loop:**
615
- \`\`\`html
616
- <div class="videos-grid">
617
- {{#each video sort="publishedAt" order="desc"}}
618
- <article class="video-card">
619
- {{#if videothumbnail}}
620
- <img src="{{videothumbnail}}" alt="{{name}}">
621
- {{/if}}
622
- <h2><a href="{{url}}">{{name}}</a></h2>
623
- <p>{{shortdescription}}</p>
624
- </article>
625
- {{/each}}
626
- </div>
627
- \`\`\`
628
-
629
- ---
630
-
631
574
  ## Always Wrap Content Images in Conditionals
632
575
 
633
- Since CMS images may not be uploaded, always check:
634
576
  \`\`\`html
635
- {{#if mainImage}}
636
- <img src="{{mainImage}}" alt="{{name}}" class="hero-image">
577
+ {{#if image}}
578
+ <img src="{{image}}" alt="{{name}}" class="hero-image">
637
579
  {{/if}}
638
580
  \`\`\`
639
581
 
640
- Or provide a CSS fallback (not a hardcoded placeholder image):
582
+ Or provide a CSS fallback:
641
583
  \`\`\`html
642
- {{#if mainImage}}
643
- <img src="{{mainImage}}" alt="{{name}}">
584
+ {{#if image}}
585
+ <img src="{{image}}" alt="{{name}}">
644
586
  {{else}}
645
587
  <div class="placeholder-image"></div>
646
588
  {{/if}}
@@ -648,61 +590,14 @@ Or provide a CSS fallback (not a hardcoded placeholder image):
648
590
 
649
591
  ---
650
592
 
651
- ## Image Field Names by Collection
652
-
653
- **Blog Posts:**
654
- - \`{{mainImage}}\` - Hero/cover image
655
- - \`{{thumbnailImage}}\` - Small preview image
656
-
657
- **Team Members:**
658
- - \`{{photo}}\` - Headshot
659
-
660
- **Authors:**
661
- - \`{{picture}}\` - Author photo
662
-
663
- **Custom Collections:**
664
- - **Token name = field slug** defined when creating the collection
665
- - e.g., field slug \`videothumbnail\` → \`{{videothumbnail}}\`
666
- - e.g., field slug \`ogimage\` → \`{{ogimage}}\`
667
- - e.g., field slug \`productImage\` → \`{{productImage}}\`
668
-
669
- **Use \`get_tenant_schema\` tool to see exact field slugs for a tenant's custom collections.**
670
-
671
- ---
672
-
673
- ## How CMS Images Work
674
-
675
- 1. User creates a custom collection with image fields (e.g., \`videothumbnail\`)
676
- 2. User uploads images via CMS dashboard
677
- 3. Template uses the field slug as the token: \`{{videothumbnail}}\`
678
- 4. CMS automatically provides the correct URL at runtime
679
-
680
- ---
681
-
682
593
  ## Common Mistakes
683
594
 
684
595
  1. **Replacing logos with CMS tokens** - Keep static UI images as \`/public/\` paths
685
- 2. **Hardcoding example blog/product images** - Use \`{{#each}}\` loops with CMS tokens
686
- 3. **Wrong field names** - \`{{photo}}\` for team, \`{{picture}}\` for authors
687
- 4. **Missing conditionals** - Always wrap optional content images in \`{{#if}}\`
688
- 5. **Hardcoded placeholder images** - Use CSS placeholders, not image files`,
596
+ 2. **Hardcoding example content images** - Use \`{{#each}}\` loops with CMS tokens
597
+ 3. **Missing conditionals** - Always wrap optional content images in \`{{#if}}\``,
689
598
  relation_fields: `# Relation Fields - Linking Collections
690
599
 
691
- Relation fields let you link items from one collection to another, like adding tags, categories, or related items to your content.
692
-
693
- ---
694
-
695
- ## Creating a Relation Field
696
-
697
- In the CMS dashboard, when adding a custom field to a collection:
698
- 1. Choose field type: **Relation**
699
- 2. Select the target collection to link to (built-in or custom)
700
- 3. The field will store a reference to an item from that collection
701
-
702
- **Example: Adding a "Category" relation field to a Projects collection**
703
- - Create a "Categories" custom collection with items like "Web Design", "Branding", "Mobile"
704
- - Add a relation field called "category" to Projects, linked to Categories
705
- - Each project can now be assigned a category
600
+ Relation fields let you link items from one collection to another.
706
601
 
707
602
  ---
708
603
 
@@ -730,11 +625,8 @@ When a relation field is set, access the related item's data using dot notation:
730
625
 
731
626
  ## Available Relation Tokens
732
627
 
733
- All relation fields expose these tokens:
734
-
735
628
  | Token | Description |
736
629
  |-------|-------------|
737
- | \`{{relationField.id}}\` | Related item's ID |
738
630
  | \`{{relationField.name}}\` | Related item's name |
739
631
  | \`{{relationField.slug}}\` | Related item's URL slug |
740
632
  | \`{{relationField.url}}\` | Full URL to related item's detail page |
@@ -742,29 +634,11 @@ All relation fields expose these tokens:
742
634
 
743
635
  ---
744
636
 
745
- ## Common Patterns
746
-
747
- ### Tags/Categories on Blog Posts
748
-
749
- \`\`\`html
750
- {{#each blogs limit=10 sort="publishedAt" order="desc"}}
751
- <article class="blog-card">
752
- <h2>{{name}}</h2>
753
- {{#if tag}}
754
- <span class="tag">{{tag.name}}</span>
755
- {{/if}}
756
- <p>{{postSummary}}</p>
757
- <a href="{{url}}">Read more</a>
758
- </article>
759
- {{/each}}
760
- \`\`\`
761
-
762
- ### Category/Tag Detail Pages - Filter by Relation
637
+ ## Filter by Relation on Detail Pages
763
638
 
764
639
  On a category detail page, show only items with that category:
765
640
 
766
641
  \`\`\`html
767
- <!-- templates/category-detail.html -->
768
642
  <h1>{{name}}</h1>
769
643
  <p>Projects in this category:</p>
770
644
 
@@ -780,50 +654,13 @@ On a category detail page, show only items with that category:
780
654
  </div>
781
655
  \`\`\`
782
656
 
783
- ### Related Service on Project
784
-
785
- \`\`\`html
786
- <div class="project-detail">
787
- <h1>{{name}}</h1>
788
- {{{description}}}
789
-
790
- {{#if relatedService}}
791
- <aside class="service-highlight">
792
- <h3>Service Used</h3>
793
- <a href="{{relatedService.url}}">
794
- <strong>{{relatedService.name}}</strong>
795
- </a>
796
- {{#if relatedService.shortDescription}}
797
- <p>{{relatedService.shortDescription}}</p>
798
- {{/if}}
799
- </aside>
800
- {{/if}}
801
- </div>
802
- \`\`\`
803
-
804
- ---
805
-
806
- ## Target Collections
807
-
808
- Relation fields can link to:
809
-
810
- **Built-in Collections:**
811
- - \`blogs\` - Blog posts
812
- - \`authors\` - Content authors
813
- - \`team\` - Team members
814
- - \`downloads\` - Downloadable files
815
-
816
- **Custom Collections:**
817
- - Any custom collection you create (e.g., \`categories\`, \`tags\`, \`services\`, \`clients\`)
818
-
819
657
  ---
820
658
 
821
659
  ## Best Practices
822
660
 
823
661
  1. **Always wrap in {{#if}}** - The relation may not be set for all items
824
662
  2. **Use for categorization** - Tags, categories, content types
825
- 3. **Link related content** - Featured author, related service, parent category
826
- 4. **Filter on detail pages** - Use \`{{#if (eq relation.slug ../slug)}}\` to show related items`,
663
+ 3. **Link related content** - Featured author, related service, parent category`,
827
664
  data_edit_keys: `# Inline Editing with data-edit-key
828
665
 
829
666
  Make text editable in the CMS visual editor:
@@ -854,31 +691,31 @@ Make text editable in the CMS visual editor:
854
691
 
855
692
  **Basic loop:**
856
693
  \`\`\`html
857
- {{#each blogs}}
694
+ {{#each posts}}
858
695
  <article>
859
696
  <h2>{{name}}</h2>
860
- <p>{{postSummary}}</p>
697
+ <p>{{summary}}</p>
861
698
  </article>
862
699
  {{/each}}
863
700
  \`\`\`
864
701
 
865
702
  **With limit:**
866
703
  \`\`\`html
867
- {{#each blogs limit=6}}
704
+ {{#each posts limit=6}}
868
705
  ...
869
706
  {{/each}}
870
707
  \`\`\`
871
708
 
872
- **Featured only:**
709
+ **Featured only (if collection has boolean "featured" field):**
873
710
  \`\`\`html
874
- {{#each blogs featured=true}}
711
+ {{#each posts featured=true}}
875
712
  ...
876
713
  {{/each}}
877
714
  \`\`\`
878
715
 
879
716
  **Combined:**
880
717
  \`\`\`html
881
- {{#each blogs featured=true limit=3}}
718
+ {{#each posts featured=true limit=3}}
882
719
  ...
883
720
  {{/each}}
884
721
  \`\`\`
@@ -892,7 +729,7 @@ Make text editable in the CMS visual editor:
892
729
 
893
730
  **Loop variables:**
894
731
  \`\`\`html
895
- {{#each blogs limit=5}}
732
+ {{#each posts limit=5}}
896
733
  <article class="{{#if @first}}featured{{/if}} {{#if @last}}last{{/if}}">
897
734
  <span class="position">Item {{@index}}</span>
898
735
  <h2>{{name}}</h2>
@@ -907,15 +744,15 @@ Make text editable in the CMS visual editor:
907
744
 
908
745
  **{{#if}} - Render if truthy:**
909
746
  \`\`\`html
910
- {{#if mainImage}}
911
- <img src="{{mainImage}}" alt="{{name}}">
747
+ {{#if image}}
748
+ <img src="{{image}}" alt="{{name}}">
912
749
  {{/if}}
913
750
  \`\`\`
914
751
 
915
752
  **{{#if}} with {{else}}:**
916
753
  \`\`\`html
917
- {{#if thumbnailImage}}
918
- <img src="{{thumbnailImage}}" alt="{{name}}">
754
+ {{#if thumbnail}}
755
+ <img src="{{thumbnail}}" alt="{{name}}">
919
756
  {{else}}
920
757
  <div class="placeholder">No image</div>
921
758
  {{/if}}
@@ -932,8 +769,8 @@ Make text editable in the CMS visual editor:
932
769
  \`\`\`html
933
770
  {{#if author}}
934
771
  <div class="author-info">
935
- {{#if author.picture}}
936
- <img src="{{author.picture}}" alt="{{author.name}}">
772
+ {{#if author.photo}}
773
+ <img src="{{author.photo}}" alt="{{author.name}}">
937
774
  {{/if}}
938
775
  <span>{{author.name}}</span>
939
776
  </div>
@@ -942,24 +779,25 @@ Make text editable in the CMS visual editor:
942
779
 
943
780
  **Multiple conditions (check both):**
944
781
  \`\`\`html
945
- {{#if mainImage}}
782
+ {{#if image}}
946
783
  {{#if featured}}
947
- <img src="{{mainImage}}" class="featured-image">
784
+ <img src="{{image}}" class="featured-image">
948
785
  {{/if}}
949
786
  {{/if}}
950
787
  \`\`\``,
951
- nested_fields: `# Nested Field Access
788
+ nested_fields: `# Nested Field Access (Relation Fields)
789
+
790
+ Access related item fields using dot notation:
952
791
 
953
- **Author fields in blog templates:**
954
792
  \`\`\`html
955
- {{#each blogs}}
793
+ {{#each posts}}
956
794
  <article>
957
795
  <h2>{{name}}</h2>
958
796
 
959
797
  {{#if author}}
960
798
  <div class="author">
961
- {{#if author.picture}}
962
- <img src="{{author.picture}}" alt="{{author.name}}">
799
+ {{#if author.photo}}
800
+ <img src="{{author.photo}}" alt="{{author.name}}">
963
801
  {{/if}}
964
802
  <span>By {{author.name}}</span>
965
803
 
@@ -972,17 +810,14 @@ Make text editable in the CMS visual editor:
972
810
  {{/each}}
973
811
  \`\`\`
974
812
 
975
- **Available nested author fields:**
813
+ **Available nested tokens (for a relation field named "author"):**
976
814
  - \`{{author.name}}\`
977
815
  - \`{{author.slug}}\`
978
- - \`{{author.picture}}\`
979
- - \`{{{author.bio}}}\` (triple braces!)
980
- - \`{{author.email}}\`
981
- - \`{{author.twitterProfileLink}}\`
982
- - \`{{author.linkedinProfileLink}}\`
983
816
  - \`{{author.url}}\`
817
+ - \`{{{author.bio}}}\` (triple braces for richText!)
818
+ - Any other field from the related collection
984
819
 
985
- **Always wrap in {{#if author}}** to handle posts without authors`,
820
+ **Always wrap in {{#if author}}** to handle items without the relation set`,
986
821
  featured_posts: `# Featured Posts Section
987
822
 
988
823
  **Homepage featured posts (3 most recent):**
@@ -990,14 +825,14 @@ Make text editable in the CMS visual editor:
990
825
  <section class="featured-posts">
991
826
  <h2 data-edit-key="home-blog-title">Latest News</h2>
992
827
 
993
- {{#each blogs featured=true limit=3}}
828
+ {{#each posts featured=true limit=3}}
994
829
  <article class="featured-card {{#if @first}}large{{/if}}">
995
- {{#if mainImage}}
996
- <img src="{{mainImage}}" alt="{{name}}">
830
+ {{#if image}}
831
+ <img src="{{image}}" alt="{{name}}">
997
832
  {{/if}}
998
833
  <div class="card-content">
999
834
  <h3><a href="{{url}}">{{name}}</a></h3>
1000
- <p>{{postSummary}}</p>
835
+ <p>{{summary}}</p>
1001
836
  {{#if author}}
1002
837
  <span class="byline">By {{author.name}}</span>
1003
838
  {{/if}}
@@ -1006,28 +841,11 @@ Make text editable in the CMS visual editor:
1006
841
  </article>
1007
842
  {{/each}}
1008
843
 
1009
- <a href="/blog" class="view-all">View All Posts →</a>
844
+ <a href="/blog" class="view-all">View All Posts</a>
1010
845
  </section>
1011
846
  \`\`\`
1012
847
 
1013
- **Hero featured post (single):**
1014
- \`\`\`html
1015
- {{#each blogs featured=true limit=1}}
1016
- <section class="hero-post">
1017
- {{#if mainImage}}
1018
- <div class="hero-image" style="background-image: url('{{mainImage}}')">
1019
- {{/if}}
1020
- <div class="hero-content">
1021
- <span class="label">Featured</span>
1022
- <h1><a href="{{url}}">{{name}}</a></h1>
1023
- <p>{{postSummary}}</p>
1024
- </div>
1025
- {{#if mainImage}}
1026
- </div>
1027
- {{/if}}
1028
- </section>
1029
- {{/each}}
1030
- \`\`\``,
848
+ **Note:** Requires a boolean "featured" field on your collection`,
1031
849
  parent_context: `# Parent Context References (\`../\`)
1032
850
 
1033
851
  Inside loops, access the **parent scope** (the page's current item) using \`../\`:
@@ -1041,11 +859,11 @@ Inside loops, access the **parent scope** (the page's current item) using \`../\
1041
859
  <section class="author-articles">
1042
860
  <h2>Posts by {{name}}</h2>
1043
861
 
1044
- {{#each blogs}}
862
+ {{#each posts}}
1045
863
  {{#if (eq author.name ../name)}}
1046
864
  <article class="post-card">
1047
865
  <h3><a href="{{url}}">{{name}}</a></h3>
1048
- <p>{{postSummary}}</p>
866
+ <p>{{summary}}</p>
1049
867
  <time>{{publishedAt}}</time>
1050
868
  </article>
1051
869
  {{/if}}
@@ -1055,8 +873,8 @@ Inside loops, access the **parent scope** (the page's current item) using \`../\
1055
873
  \`\`\`
1056
874
 
1057
875
  **How It Works:**
1058
- - Inside \`{{#each blogs}}\`, the context is each blog post
1059
- - \`author.name\` = the blog post's author
876
+ - Inside \`{{#each posts}}\`, the context is each post
877
+ - \`author.name\` = the post's author
1060
878
  - \`../name\` = the parent context (the author being displayed on the page)
1061
879
  - Only posts where author.name matches ../name are shown
1062
880
 
@@ -1069,23 +887,12 @@ Inside loops, access the **parent scope** (the page's current item) using \`../\
1069
887
  </a>
1070
888
  {{/each}}
1071
889
  </nav>
1072
-
1073
- <div class="category-content">
1074
- <h1>{{name}}</h1>
1075
-
1076
- {{#each items}}
1077
- {{#if (eq category ../slug)}}
1078
- <div class="item">{{name}}</div>
1079
- {{/if}}
1080
- {{/each}}
1081
- </div>
1082
890
  \`\`\`
1083
891
 
1084
892
  **Available Parent Fields:**
1085
893
  - \`../name\` - Parent item's name
1086
894
  - \`../slug\` - Parent item's slug
1087
- - \`../fieldName\` - Any field from the parent item
1088
- - \`../nested.field\` - Nested field access in parent`,
895
+ - \`../fieldName\` - Any field from the parent item`,
1089
896
  equality_comparison: `# Equality Comparisons
1090
897
 
1091
898
  Compare two values using \`(eq field1 field2)\` helper:
@@ -1093,7 +900,7 @@ Compare two values using \`(eq field1 field2)\` helper:
1093
900
  ## Show When Equal ({{#if (eq ...)}})
1094
901
  \`\`\`html
1095
902
  {{#if (eq author.slug ../slug)}}
1096
- <span class="current-author-badge">✓ Your Post</span>
903
+ <span class="current-author-badge">Your Post</span>
1097
904
  {{/if}}
1098
905
  \`\`\`
1099
906
 
@@ -1101,13 +908,13 @@ Compare two values using \`(eq field1 field2)\` helper:
1101
908
  The most common pattern for "Related Posts" or "Other Items" sections:
1102
909
 
1103
910
  \`\`\`html
1104
- <!-- On a blog post page, show other posts EXCEPT the current one -->
911
+ <!-- On a post page, show other posts EXCEPT the current one -->
1105
912
  <h3>Related Posts</h3>
1106
- {{#each blogs limit=3}}
913
+ {{#each posts limit=3}}
1107
914
  {{#unless (eq slug ../slug)}}
1108
915
  <article>
1109
916
  <a href="{{url}}">{{name}}</a>
1110
- <p>{{postSummary}}</p>
917
+ <p>{{summary}}</p>
1111
918
  </article>
1112
919
  {{/unless}}
1113
920
  {{/each}}
@@ -1126,38 +933,20 @@ The most common pattern for "Related Posts" or "Other Items" sections:
1126
933
  {{/eq}}
1127
934
 
1128
935
  {{#eq category "news"}}
1129
- <span class="news-icon">📰</span>
936
+ <span class="news-icon">News</span>
1130
937
  {{/eq}}
1131
938
  \`\`\`
1132
939
 
1133
- ## Inside Loops with Parent Context
1134
- \`\`\`html
1135
- {{#each blogs}}
1136
- <article class="{{#if (eq author.name ../name)}}highlight{{/if}}">
1137
- <h3>{{name}}</h3>
1138
- {{#if (eq author.name ../name)}}
1139
- <span class="yours">You wrote this!</span>
1140
- {{/if}}
1141
- </article>
1142
- {{/each}}
1143
- \`\`\`
1144
-
1145
940
  ## Summary Table
1146
941
 
1147
942
  | Syntax | Shows content when... |
1148
943
  |--------|----------------------|
1149
944
  | \`{{#if (eq a b)}}\` | a equals b |
1150
945
  | \`{{#unless (eq a b)}}\` | a does NOT equal b |
1151
- | \`{{#eq field "value"}}\` | field equals "value" |
1152
-
1153
- **Common Use Cases:**
1154
- - Related posts (exclude current) - use \`{{#unless (eq slug ../slug)}}\`
1155
- - Filter by current author/category - use \`{{#if (eq author.slug ../slug)}}\`
1156
- - Highlight active items - use \`{{#if (eq ...)}}\`
1157
- - Show badges for specific statuses - use \`{{#eq status "value"}}\``,
946
+ | \`{{#eq field "value"}}\` | field equals "value" |`,
1158
947
  youtube_embed: `# YouTube Video Embeds
1159
948
 
1160
- **IMPORTANT:** YouTube iframes require specific attributes to work correctly. Missing attributes will cause Error 150/153 "Video player configuration error".
949
+ **IMPORTANT:** YouTube iframes require specific attributes to work correctly. Missing attributes will cause Error 150/153.
1161
950
 
1162
951
  ## Correct YouTube Embed Format
1163
952
 
@@ -1182,20 +971,6 @@ The most common pattern for "Related Posts" or "Other Items" sections:
1182
971
  | \`frameborder="0"\` | Recommended | Removes border |
1183
972
  | \`title="..."\` | Recommended | Accessibility for screen readers |
1184
973
 
1185
- ## Common Mistake (Causes Error 153)
1186
-
1187
- \`\`\`html
1188
- <!-- ❌ WRONG - Missing referrerpolicy -->
1189
- <iframe src="{{videoUrl}}" allowfullscreen></iframe>
1190
-
1191
- <!-- ✅ CORRECT - All required attributes -->
1192
- <iframe
1193
- src="{{videoUrl}}"
1194
- referrerpolicy="strict-origin-when-cross-origin"
1195
- allowfullscreen
1196
- ></iframe>
1197
- \`\`\`
1198
-
1199
974
  ## Video URL Format
1200
975
 
1201
976
  Store YouTube URLs in embed format:
@@ -1205,7 +980,7 @@ https://www.youtube.com/embed/VIDEO_ID
1205
980
 
1206
981
  **NOT** watch format:
1207
982
  \`\`\`
1208
- https://www.youtube.com/watch?v=VIDEO_ID
983
+ https://www.youtube.com/watch?v=VIDEO_ID
1209
984
  \`\`\`
1210
985
 
1211
986
  ## Full Example with Conditional
@@ -1227,24 +1002,6 @@ https://www.youtube.com/watch?v=VIDEO_ID ❌
1227
1002
  </div>
1228
1003
  {{/if}}
1229
1004
  </div>
1230
- \`\`\`
1231
-
1232
- ## Responsive Video CSS
1233
-
1234
- \`\`\`css
1235
- .video-container {
1236
- position: relative;
1237
- width: 100%;
1238
- padding-bottom: 56.25%; /* 16:9 aspect ratio */
1239
- }
1240
-
1241
- .video-container iframe {
1242
- position: absolute;
1243
- top: 0;
1244
- left: 0;
1245
- width: 100%;
1246
- height: 100%;
1247
- }
1248
1005
  \`\`\``,
1249
1006
  };
1250
1007
  /**