Video Production Plan : Video 3 — Structured Content and API Contracts

Text LessonReleased: June 7, 2026

Video 3 — Structured Content and API Contracts

Attribute Details
Course 2 (Content Modeling), Module 2.1
Covers Lessons 2.1.1, 2.1.2, 2.1.3, 2.1.4
Priority Critical
Length 18-25 min
Format Screencast (Contentstack UI + code editor side-by-side)
Status Not started

Why This Video Matters

This is one of the most important videos in the entire series. It shapes how learners think about content from the start. If the model is wrong, the frontend, editorial workflow, and APIs all become harder.

Outline

  1. The mindset shift: stop thinking in pages, start thinking in domain concepts
  2. Three questions for every content type: Does the concept exist independently? Will it appear in multiple contexts? Would editors update it separately?
  3. Veda example: Product, Product Line, Category, Page — four types connected by references instead of one giant page template
  4. Content types as API contracts: every field UID becomes a JSON key in the response
  5. Show a content type definition and the resulting API response side-by-side
  6. Breaking changes: renaming or removing a field UID on published entries breaks frontends immediately. Adding fields is safe (null default)
  7. TypeScript interfaces should mirror content type schemas
  8. Global Fields: define a field group once, reuse across content types, changes propagate everywhere
  9. When to use Global Fields (SEO metadata, addresses, CTAs used by 3+ types) vs when not to
  10. JSON Rich Text Editor: structured document tree, not HTML string — show the raw JSON
  11. Embedded entries/assets as reference nodes; rendering with @contentstack/utils
  12. Custom RTE plugins: extending the editor toolbar via Developer Hub

Key Lines

"If the model is wrong, the frontend, editorial workflow, and APIs all become harder."

"A content model is an agreement between the CMS and downstream consumers."

"The question is not 'can we model this', but 'can we model this in a way that stays healthy over time'."

Detailed Talking Points

1. The mindset shift: stop thinking in pages, start thinking in domain concepts

  • Traditional CMS platforms organize content around pages — one URL, one template, one database row with everything bolted on.
  • That works until someone asks you to show the same product on a mobile app, a marketplace feed, or a store display. The page abstraction falls apart.
  • Structured content separates what the content is from where and how it appears.
  • Instead of a "Product Page" you model the domain concept "Product" — title, price, description, media. No layout, no template assumptions.
  • Authors think in entries, designers think in components, developers think in API consumers. Everyone stops coupling to a single page template.
  • The CMS becomes a content API, not a page factory.

2. Three questions for every content type

  • Before you create a content type, run it through three questions.
  • First: does this concept exist independently? A product line like Digital Dawn exists whether or not it has products yet. That means it deserves its own content type, not a text field inside Product.
  • Second: will this content appear in more than one context? If categories show up on product pages, category landing pages, and navigation menus, they need to be their own type with references — not embedded text you copy-paste.
  • Third: would editors need to update this independently? If changing a product line description should not require opening every product, the product line must be a separate entry.
  • These three questions consistently push you toward smaller, focused content types connected by references.
  • If the answer is "no" to all three, it can stay as a field group or a group field inside the parent type.

3. Veda example: Product, Product Line, Category, Page

  • Walk through the Veda jewelry catalog as a concrete migration from page-oriented to structured content.
  • In a page CMS, a single product page row holds title, description, price, images, product line name, category name, SEO metadata — everything in one record.
  • In Contentstack, decompose into four content types: Product (title, url, short_description, description, price, media, product_line reference, category reference), Product Line (title, url, description, image, products reference), Category (title, url, description, media), Page (title, url, components as modular blocks).
  • Product references Product Line and Category — the product line description exists in exactly one place. Change it once, every product reflects the update.
  • The mobile app fetches Product with include[]=product_line and gets clean JSON. The marketplace feed requests only title, price, and media using field projection. No HTML parsing, no screen-scraping.
  • Show the multi-channel reuse table: Website gets full product with description and images. Mobile app gets product summary, price, media. Marketplace feed gets title, price, short description, first image. Email campaign gets title, short description, hero image, link. Partner API feed gets product data without branding. In-store display gets product images and QR code.
  • One content model, six channels, zero duplication.

4. Content types as API contracts: every field UID becomes a JSON key

  • This is the big mental model shift for developers: a content type is not just a form for editors. It is simultaneously a JSON schema that your frontend codes against.
  • The moment you save a content type with a field UID of short_description, that string becomes a key in every API response. Every frontend component reading entry.short_description depends on it.
  • Field labels like "Short Description" are what editors see — those can change freely. Field UIDs like short_description are the contract — those are locked in once you have published entries.
  • Use snake_case consistently, be descriptive but concise, avoid abbreviations only your team understands.

5. Show a content type definition and the resulting API response side-by-side

  • Open the Product content type in the Contentstack UI and point out the field labels and UIDs.
  • Switch to the API response JSON for a real Product entry — show how every field UID maps one-to-one to a JSON key.
  • Highlight that reference fields appear as stubs by default (just UIDs), and you add include[]=product_line to resolve them.
  • Highlight that file fields return objects with url, filename, and MIME type.
  • Highlight the system fields that appear automatically: uid, locale, created_at, updated_at.

6. Breaking changes vs. safe changes

  • Adding a new field to a content type is safe. Existing entries return null for the new field. Frontend code should handle null gracefully with optional chaining.
  • Removing a field is a breaking change. If the frontend reads product.media[0].url and you delete the media field, the component throws a runtime error. Remove the frontend dependency first, deploy, then delete the field.
  • Renaming a field UID breaks the contract immediately. The old key vanishes from API responses and the new key appears. Every line of frontend code referencing the old key fails.
  • Changing a field type is risky — converting a Number to Single Line Text changes the API output from 295 to "295". Code calling .toFixed(2) crashes.
  • Reordering fields in the schema has zero API impact — it only changes the editorial UI order.
  • The takeaway: treat field UIDs as public API surface, not internal details.

7. TypeScript interfaces should mirror content type schemas

  • Show a TypeScript interface for the Product content type that matches the field UIDs exactly.
  • This gives you compile-time safety — if someone renames short_description to summary, TypeScript catches the breakage before deployment.
  • Some teams generate TypeScript types directly from the content type schema using the CMA or the Contentstack CLI cs:content-type:get command.
  • The interface is your developer-side contract; the content type schema is your CMS-side contract. They should stay in sync.

8. Global Fields: define once, reuse across content types, changes propagate

  • Global Fields solve the problem of maintaining identical field groups across multiple content types.
  • You create a Global Field under Settings > Global Fields — for example, an SEO Metadata global field with meta_title, meta_description, og_image, and canonical_url.
  • Then you add it to any content type. It appears in the editor as an expandable group, just like a regular Group field.
  • The difference: a Group field is defined inline inside one content type. A Global Field is defined centrally and referenced — changes propagate to every content type that uses it.
  • Think of it like a shared component in a design system. Define a Button once, use it on every page.
  • The API output is identical to a Group field — a nested JSON object. Frontend developers do not need to know whether it came from a Group or Global Field.

9. When to use Global Fields vs. when not to

  • Use Global Fields when the same group of fields appears in three or more content types with identical structure: SEO metadata, address blocks, CTAs, social media links.
  • Do not use Global Fields for data that needs to be independently queryable — that should be a separate content type with references. A Category needs to be listed, filtered, searched — it cannot be a Global Field.
  • Do not use Global Fields when different content types need different variations of the field group. If Products need extra Schema.org fields that Pages do not, create two global fields or use a Group for the extension.
  • Do not use Global Fields for a group used by only one content type. A regular Group field avoids the management overhead.
  • Key distinction: References share content (one Product Line entry used by many products). Global Fields share structure (one field definition used by many content types).

10. JSON Rich Text Editor: structured document tree, not HTML string

  • The JSON RTE is where structured content meets rich text — and it matters because HTML strings are opaque blobs you cannot traverse or transform.
  • An HTML RTE stores <p>Check out our <a href="...">Premium Widget</a></p> as a flat string. You cannot extract the product reference, validate the link, or repurpose the content for a mobile app.
  • The JSON RTE stores the same content as a tree of typed nodes — a root doc node, paragraph nodes, text leaf nodes with formatting flags like bold and italic.
  • Show the raw JSON structure: { "type": "doc", "children": [{ "type": "p", "children": [{ "text": "..." }] }] }.
  • Every node has a type, a UID, optional attrs, and children. Text nodes are leaf nodes with a text property and boolean formatting properties.
  • This tree is fully traversable. A mobile app can extract just the text. A voice assistant skips images. A web app renders every node with custom components.

11. Embedded entries and assets as reference nodes; rendering with @contentstack/utils

  • Editors can embed entries from other content types directly within JSON RTE content — inline or as blocks.
  • The JSON RTE stores these as reference nodes with entry-uid and content-type-uid attributes. The data is not duplicated; it is referenced.
  • To get the full embedded entry data in the API response, you must add include_embedded_items[]=<field_uid> to your API call. Without it, you get bare UIDs and embedded entries silently disappear from rendered output.
  • On the frontend, install @contentstack/utils and use jsonToHtml to convert the JSON tree to HTML. Pass paths to specify which fields contain JSON RTE data.
  • For custom rendering, use the renderOption parameter with renderNode handlers for each node type — including a reference handler for embedded entries and assets.
  • In React, you can build a component-based renderer where each node type maps to a React component, giving you full control over rendering.

12. Custom RTE plugins: extending the editor toolbar via Developer Hub

  • Custom plugins add toolbar buttons, custom element types, paste behaviors, and keyboard shortcuts to the JSON RTE editor.
  • Plugins are built with @contentstack/app-sdk and deployed through Developer Hub as Contentstack apps with an RTE Plugin location.
  • A plugin inserts custom node types into the JSON tree — for example, a "Callout" button that creates a node of type callout with a style attribute.
  • The custom nodes appear in the API response as regular nodes with your custom type values. Your frontend renderer must handle them — if it does not, they render as blank space.
  • This is a three-way coordination: the plugin developer defines the node type, the content modeler enables the plugin on specific JSON RTE fields, and the frontend developer implements the renderer. Document the custom node schemas the same way you document content type schemas.

Screen: What to Show

Outline item Screen instructions
1. Mindset shift Show a wireframe of a traditional product page with everything in one record. Then switch to the Contentstack Content Models list showing Product, Product Line, Category, Page as separate types.
2. Three questions Display the three questions as a text overlay or slide. Point at each one while explaining.
3. Veda example Open the Product content type in Content Models. Scroll through the field list: title, url, short_description, description, price, media, product_line (reference), category (reference). Then open Product Line and Category to show their schemas. Show the multi-channel reuse table as a slide or overlay.
4. API contracts Split screen: left side shows the Content Type Builder with field labels and UIDs visible. Right side shows a code editor with the equivalent JSON API response. Draw attention to how the UID column maps to JSON keys.
5. Side-by-side definition and response Open the CMA endpoint GET /v3/content_types/product in a REST client (Postman or terminal with curl + jq). Show the schema array. Then fetch a Product entry from the CDA at GET /v3/content_types/product/entries/{uid} and place both responses side by side.
6. Breaking changes In the Content Type Builder, hover over a field UID and demonstrate what happens conceptually if you rename it. Show a terminal or browser console with a "Cannot read property of undefined" error to illustrate the frontend breakage. Show the safe path: adding a new field and showing it returns null on existing entries.
7. TypeScript interfaces Switch to VS Code. Show a TypeScript interface for Product that mirrors the content type UIDs. Show the compiler catching a wrong property name with a red underline.
8. Global Fields Navigate to Settings > Global Fields. Create or open the SEO Metadata global field showing meta_title, meta_description, og_image, canonical_url. Then open a content type (e.g., Page) and show the SEO Metadata global field embedded as an expandable group. Show the API response with the nested seo object.
9. Global Fields decision Show a simple decision table as a slide: "3+ content types with identical shape = Global Field. Independently queryable = Reference. One content type only = Group field."
10. JSON RTE Create or open a JSON RTE field in the entry editor. Type a paragraph with bold text. Then switch to the API response and show the raw JSON document tree — the doc node, p node, text nodes with bold: true. Contrast with an HTML RTE field that returns a flat HTML string.
11. Embedded entries and rendering In the JSON RTE editor, click the Embed Entry toolbar button and insert an entry. Show the API response with the reference node containing entry-uid and content-type-uid. Switch to VS Code and show the @contentstack/utils import and jsonToHtml call with a renderOption that handles the reference node type.
12. Custom RTE plugins Show the Developer Hub > New App screen. Show a plugin code snippet in VS Code that registers a custom toolbar button. In the entry editor, click the custom button and show the custom node appearing. Then show the API response containing the custom node type.

Veda Scenario Thread

  • Open with the problem: Veda currently has a monolithic "Product Page" template in their old CMS. Every product bundles title, description, price, images, product line text, category text, and SEO fields into one record. The marketing team wants to launch a mobile app, a marketplace feed for partners, and an email campaign — all pulling from the same product data.
  • In section 1-2, explain why the page model fails for Veda: the marketplace feed needs title, price, and one image without any HTML. The mobile app needs a product summary without layout artifacts. The email needs a hero image and a link. You cannot serve six channels from one page template.
  • In section 3, walk through the Veda decomposition: Product with its eight fields, Product Line (Digital Dawn, Urban Armor, Heritage Craft), Category (Earrings, Necklaces, Bracelets, Rings), and Page with modular blocks. Show how the Matrix Link Bracelet references Digital Dawn and Bracelets.
  • In section 4-6, use the Product content type as the live example of the API contract. Show the Matrix Link Bracelet's API response. Demonstrate that renaming short_description to summary would break the ProductCard component. Show that adding a sale_price field is safe — existing entries return null.
  • In section 7, show the TypeScript interface for Veda's Product type mirroring the content type schema.
  • In section 8-9, add the SEO Metadata global field to both the Product and Page content types for Veda. Show how one update to the global field (adding a robots_directive field) propagates to both types.
  • In section 10-11, open the Veda Product's description field as a JSON RTE. Show an editor embedding a "Product Comparison" entry within the product description. Show the raw JSON with the reference node. Render it with @contentstack/utils.
  • In section 12, mention that Veda could build a custom "Care Instructions" callout plugin for the JSON RTE, allowing editors to insert standardized care instruction blocks within product descriptions.

Transitions

1 to 2: "So if we are not thinking in pages, how do we decide what gets its own content type? Three questions."

2 to 3: "Let us apply those three questions to the Veda jewelry catalog and see what content types fall out."

3 to 4: "Now that we have our four content types, here is the part most people miss — every field UID you just defined is a public API contract."

4 to 5: "Let me prove that to you by putting the content type definition next to the actual API response."

5 to 6: "So what happens when someone changes that contract? Some changes are safe, and some break everything."

6 to 7: "The best defense against accidental breakage is TypeScript — make the compiler enforce the contract."

7 to 8: "We have been looking at individual field definitions, but some field groups show up on every content type. That is where Global Fields come in."

8 to 9: "Global Fields are powerful, but they are not always the right tool — here is how to decide."

9 to 10: "Now let us talk about the trickiest field type: rich text. Specifically, the JSON Rich Text Editor."

10 to 11: "The real power of the JSON RTE is not just formatting — it is embedding other entries and assets right inside the text."

11 to 12: "And if the built-in toolbar is not enough, you can extend the JSON RTE with custom plugins."

Closing to Video 4: "We have covered how to model domain concepts, lock in API contracts, and handle rich text. In the next video, we will connect these content types together with references, modular blocks, and taxonomies — the relationship layer that makes your content model actually work."

Common Mistakes to Call Out

  1. Recreating page layouts as content types. Building a "Homepage" content type with fields for hero_section, featured_products_carousel, and newsletter_signup locks content into a single layout and throws away all reuse benefits. Use Modular Blocks or references to compose pages from independent content pieces.
  2. Storing structured data inside rich text fields. Putting the product line description inside a JSON RTE as formatted text makes it impossible to query by product line, filter products, or generate collection pages. If data needs to be queried or reused independently, it belongs in its own content type with discrete fields.
  3. Duplicating content instead of referencing it. Copying the product line name and description into every product entry creates maintenance burden and guarantees inconsistency. Use Reference fields to point to a single Product Line entry.
  4. Treating field UIDs as internal details. Field UIDs are public API field names. Choosing a UID like f1 or temp_field makes the API response unreadable and forces frontend developers to guess. Use meaningful, stable UIDs from the start.
  5. Changing field types without coordinating with frontend teams. Converting a Date field to Single Line Text changes the API output from an ISO 8601 string to freeform text. The frontend date formatter breaks. Treat type changes as a contract renegotiation.
  6. Ignoring optional fields in frontend code. When a new field is added, existing entries do not have a value for it. Components that assume every field has a value crash with "Cannot read property of undefined." Always use optional chaining and null checks.
  7. Creating global fields for data that should be references. A "Featured Product Line" global field with line_title, line_description, and line_image embedded in every Product creates data duplication. That data belongs in a Product Line content type with Reference fields. Global fields share structure, not content.
  8. Modifying global fields without checking downstream impact. Removing a field from a global field used by 12 content types simultaneously breaks the API contract for all 12. The blast radius is proportional to reuse. Always audit usage before editing a global field.
  9. Not handling embedded entries in the frontend renderer. When editors embed entries in a JSON RTE, the API response contains reference nodes. If the renderer does not handle the reference node type, those entries silently disappear from the output.
  10. Forgetting include_embedded_items[] in the API call. Without this parameter, embedded entry references contain only UIDs, not actual data. Embedded content vanishes from the rendered page with no error — it just disappears.

Notes

Use this space for recording notes, script drafts, or post-production feedback.