[{"data":1,"prerenderedAt":12960},["ShallowReactive",2],{"navigation_docs":3,"docs-navigation-with-icons":419,"-docs-guides-buttercms-migration":561,"-docs-guides-buttercms-migration-surround":12955},[4,66,87,124,405],{"title":5,"path":6,"stem":7,"children":8,"page":65},"Api","\u002Fdocs\u002Fapi","docs\u002Fapi",[9,13,17,21,25,29,33,37,41,45,49,53,57,61],{"title":10,"path":11,"stem":12},"Best Practices","\u002Fdocs\u002Fapi\u002Fbest-practices","docs\u002Fapi\u002Fbest-practices",{"title":14,"path":15,"stem":16},"Blog","\u002Fdocs\u002Fapi\u002Fblog","docs\u002Fapi\u002Fblog",{"title":18,"path":19,"stem":20},"Code Examples","\u002Fdocs\u002Fapi\u002Fcode-examples","docs\u002Fapi\u002Fcode-examples",{"title":22,"path":23,"stem":24},"Collections","\u002Fdocs\u002Fapi\u002Fcollections","docs\u002Fapi\u002Fcollections",{"title":26,"path":27,"stem":28},"Field Types","\u002Fdocs\u002Fapi\u002Ffield-types","docs\u002Fapi\u002Ffield-types",{"title":30,"path":31,"stem":32},"Rate Limits & Errors","\u002Fdocs\u002Fapi\u002Flimits-and-errors","docs\u002Fapi\u002Flimits-and-errors",{"title":34,"path":35,"stem":36},"Media","\u002Fdocs\u002Fapi\u002Fmedia","docs\u002Fapi\u002Fmedia",{"title":38,"path":39,"stem":40},"Overview","\u002Fdocs\u002Fapi\u002Foverview","docs\u002Fapi\u002Foverview",{"title":42,"path":43,"stem":44},"Pages","\u002Fdocs\u002Fapi\u002Fpages","docs\u002Fapi\u002Fpages",{"title":46,"path":47,"stem":48},"Query Parameters","\u002Fdocs\u002Fapi\u002Fquery-parameters","docs\u002Fapi\u002Fquery-parameters",{"title":50,"path":51,"stem":52},"Responses & Caching","\u002Fdocs\u002Fapi\u002Fresponses","docs\u002Fapi\u002Fresponses",{"title":54,"path":55,"stem":56},"Troubleshooting","\u002Fdocs\u002Fapi\u002Ftroubleshooting","docs\u002Fapi\u002Ftroubleshooting",{"title":58,"path":59,"stem":60},"Validation","\u002Fdocs\u002Fapi\u002Fvalidation","docs\u002Fapi\u002Fvalidation",{"title":62,"path":63,"stem":64},"Webhooks","\u002Fdocs\u002Fapi\u002Fwebhooks","docs\u002Fapi\u002Fwebhooks",false,{"title":67,"path":68,"stem":69,"children":70,"page":65},"Getting Started","\u002Fdocs\u002Fgetting-started","docs\u002Fgetting-started",[71,75,79,83],{"title":72,"path":73,"stem":74},"Authentication","\u002Fdocs\u002Fgetting-started\u002Fauthentication","docs\u002Fgetting-started\u002Fauthentication",{"title":76,"path":77,"stem":78},"Introduction","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002Fgetting-started\u002Fintroduction",{"title":80,"path":81,"stem":82},"Quickstart","\u002Fdocs\u002Fgetting-started\u002Fquickstart","docs\u002Fgetting-started\u002Fquickstart",{"title":84,"path":85,"stem":86},"Write Operations","\u002Fdocs\u002Fgetting-started\u002Fwrite-operations","docs\u002Fgetting-started\u002Fwrite-operations",{"title":88,"path":89,"stem":90,"children":91,"page":65},"Guides","\u002Fdocs\u002Fguides","docs\u002Fguides",[92,96,100,104,108,112,116,120],{"title":93,"path":94,"stem":95},"Admin Interface","\u002Fdocs\u002Fguides\u002Fadmin-interface","docs\u002Fguides\u002Fadmin-interface",{"title":97,"path":98,"stem":99},"ButterCMS Migration","\u002Fdocs\u002Fguides\u002Fbuttercms-migration","docs\u002Fguides\u002Fbuttercms-migration",{"title":101,"path":102,"stem":103},"Migrations","\u002Fdocs\u002Fguides\u002Fmigrations","docs\u002Fguides\u002Fmigrations",{"title":105,"path":106,"stem":107},"Page Management","\u002Fdocs\u002Fguides\u002Fpage-management","docs\u002Fguides\u002Fpage-management",{"title":109,"path":110,"stem":111},"Public API Usage","\u002Fdocs\u002Fguides\u002Fpublic-api-usage","docs\u002Fguides\u002Fpublic-api-usage",{"title":113,"path":114,"stem":115},"Schema Builder","\u002Fdocs\u002Fguides\u002Fschema-builder","docs\u002Fguides\u002Fschema-builder",{"title":117,"path":118,"stem":119},"User Management","\u002Fdocs\u002Fguides\u002Fuser-management","docs\u002Fguides\u002Fuser-management",{"title":121,"path":122,"stem":123},"Rich Text Editor","\u002Fdocs\u002Fguides\u002Fwysiwyg-editor","docs\u002Fguides\u002Fwysiwyg-editor",{"title":125,"path":126,"stem":127,"children":128,"page":65},"Sdk","\u002Fdocs\u002Fsdk","docs\u002Fsdk",[129,132,136,140,144],{"title":38,"path":130,"stem":131},"\u002Fdocs\u002Fsdk\u002Freadme","docs\u002Fsdk\u002FREADME",{"title":133,"path":134,"stem":135},"Astro","\u002Fdocs\u002Fsdk\u002Fastro","docs\u002Fsdk\u002Fastro",{"title":137,"path":138,"stem":139},"Next.js","\u002Fdocs\u002Fsdk\u002Fnextjs","docs\u002Fsdk\u002Fnextjs",{"title":141,"path":142,"stem":143},"Nuxt","\u002Fdocs\u002Fsdk\u002Fnuxt","docs\u002Fsdk\u002Fnuxt",{"title":145,"path":146,"stem":147,"children":148,"page":65},"Reference","\u002Fdocs\u002Fsdk\u002Freference","docs\u002Fsdk\u002Freference",[149,153,222,235,380],{"title":150,"path":151,"stem":152},"@tentocms\u002Fclient","\u002Fdocs\u002Fsdk\u002Freference\u002Freadme","docs\u002Fsdk\u002Freference\u002FREADME",{"title":154,"path":155,"stem":156,"children":157,"page":65},"Classes","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses","docs\u002Fsdk\u002Freference\u002Fclasses",[158,162,166,170,174,178,182,186,190,194,198,202,206,210,214,218],{"title":159,"path":160,"stem":161},"Class: BlogCategoriesResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fblogcategoriesresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FBlogCategoriesResource",{"title":163,"path":164,"stem":165},"Class: BlogPostsResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fblogpostsresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FBlogPostsResource",{"title":167,"path":168,"stem":169},"Class: BlogResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fblogresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FBlogResource",{"title":171,"path":172,"stem":173},"Class: BlogTagsResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fblogtagsresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FBlogTagsResource",{"title":175,"path":176,"stem":177},"Class: CollectionsResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fcollectionsresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FCollectionsResource",{"title":179,"path":180,"stem":181},"Class: CollectionsWriteResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fcollectionswriteresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FCollectionsWriteResource",{"title":183,"path":184,"stem":185},"Class: MediaResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fmediaresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FMediaResource",{"title":187,"path":188,"stem":189},"Class: MediaWriteResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fmediawriteresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FMediaWriteResource",{"title":191,"path":192,"stem":193},"Class: PagesResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fpagesresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FPagesResource",{"title":195,"path":196,"stem":197},"Class: PagesWriteResource","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Fpageswriteresource","docs\u002Fsdk\u002Freference\u002Fclasses\u002FPagesWriteResource",{"title":199,"path":200,"stem":201},"Class: TentoAuthenticationError","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Ftentoauthenticationerror","docs\u002Fsdk\u002Freference\u002Fclasses\u002FTentoAuthenticationError",{"title":203,"path":204,"stem":205},"Class: TentoClient","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Ftentoclient","docs\u002Fsdk\u002Freference\u002Fclasses\u002FTentoClient",{"title":207,"path":208,"stem":209},"Class: TentoError","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Ftentoerror","docs\u002Fsdk\u002Freference\u002Fclasses\u002FTentoError",{"title":211,"path":212,"stem":213},"Class: TentoNetworkError","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Ftentonetworkerror","docs\u002Fsdk\u002Freference\u002Fclasses\u002FTentoNetworkError",{"title":215,"path":216,"stem":217},"Class: TentoNotFoundError","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Ftentonotfounderror","docs\u002Fsdk\u002Freference\u002Fclasses\u002FTentoNotFoundError",{"title":219,"path":220,"stem":221},"Class: TentoRateLimitError","\u002Fdocs\u002Fsdk\u002Freference\u002Fclasses\u002Ftentoratelimiterror","docs\u002Fsdk\u002Freference\u002Fclasses\u002FTentoRateLimitError",{"title":223,"path":224,"stem":225,"children":226,"page":65},"Functions","\u002Fdocs\u002Fsdk\u002Freference\u002Ffunctions","docs\u002Fsdk\u002Freference\u002Ffunctions",[227,231],{"title":228,"path":229,"stem":230},"Function: getUrl()","\u002Fdocs\u002Fsdk\u002Freference\u002Ffunctions\u002Fgeturl","docs\u002Fsdk\u002Freference\u002Ffunctions\u002FgetUrl",{"title":232,"path":233,"stem":234},"Function: mockMedia()","\u002Fdocs\u002Fsdk\u002Freference\u002Ffunctions\u002Fmockmedia","docs\u002Fsdk\u002Freference\u002Ffunctions\u002FmockMedia",{"title":236,"path":237,"stem":238,"children":239,"page":65},"Interfaces","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces","docs\u002Fsdk\u002Freference\u002Finterfaces",[240,244,248,252,256,260,264,268,272,276,280,284,288,292,296,300,304,308,312,316,320,324,328,332,336,340,344,348,352,356,360,364,368,372,376],{"title":241,"path":242,"stem":243},"Interface: BlogAuthor","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fblogauthor","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FBlogAuthor",{"title":245,"path":246,"stem":247},"Interface: BlogCategory","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fblogcategory","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FBlogCategory",{"title":249,"path":250,"stem":251},"Interface: BlogPost","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fblogpost","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FBlogPost",{"title":253,"path":254,"stem":255},"Interface: BlogPostListOptions","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fblogpostlistoptions","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FBlogPostListOptions",{"title":257,"path":258,"stem":259},"Interface: BlogTag","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fblogtag","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FBlogTag",{"title":261,"path":262,"stem":263},"Interface: CollectionItemResponse\u003CT>","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fcollectionitemresponse","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FCollectionItemResponse",{"title":265,"path":266,"stem":267},"Interface: CollectionListOptions","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fcollectionlistoptions","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FCollectionListOptions",{"title":269,"path":270,"stem":271},"Interface: CollectionListResponse\u003CT>","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fcollectionlistresponse","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FCollectionListResponse",{"title":273,"path":274,"stem":275},"Interface: CollectionTypeMeta","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fcollectiontypemeta","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FCollectionTypeMeta",{"title":277,"path":278,"stem":279},"Interface: CreateCollectionItemInput","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fcreatecollectioniteminput","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FCreateCollectionItemInput",{"title":281,"path":282,"stem":283},"Interface: CreatePageInput","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fcreatepageinput","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FCreatePageInput",{"title":285,"path":286,"stem":287},"Interface: DryRunResult","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fdryrunresult","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FDryRunResult",{"title":289,"path":290,"stem":291},"Interface: FilterCondition","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Ffiltercondition","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FFilterCondition",{"title":293,"path":294,"stem":295},"Interface: ItemResponse\u003CT>","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fitemresponse","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FItemResponse",{"title":297,"path":298,"stem":299},"Interface: ListResponse\u003CT>","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Flistresponse","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FListResponse",{"title":301,"path":302,"stem":303},"Interface: Media","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fmedia","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FMedia",{"title":305,"path":306,"stem":307},"Interface: MediaItem","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fmediaitem","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FMediaItem",{"title":309,"path":310,"stem":311},"Interface: MediaOption","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fmediaoption","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FMediaOption",{"title":313,"path":314,"stem":315},"Interface: MediaTransformOptions","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fmediatransformoptions","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FMediaTransformOptions",{"title":317,"path":318,"stem":319},"Interface: Page\u003CTContent>","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fpage","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FPage",{"title":321,"path":322,"stem":323},"Interface: PageGetOptions","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fpagegetoptions","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FPageGetOptions",{"title":325,"path":326,"stem":327},"Interface: PageListOptions","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fpagelistoptions","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FPageListOptions",{"title":329,"path":330,"stem":331},"Interface: PageWithRedirect\u003CTContent>","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fpagewithredirect","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FPageWithRedirect",{"title":333,"path":334,"stem":335},"Interface: PaginationMeta","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fpaginationmeta","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FPaginationMeta",{"title":337,"path":338,"stem":339},"Interface: PreviewOption","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fpreviewoption","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FPreviewOption",{"title":341,"path":342,"stem":343},"Interface: RedirectInfo","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fredirectinfo","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FRedirectInfo",{"title":345,"path":346,"stem":347},"Interface: RefTypesOption","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Freftypesoption","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FRefTypesOption",{"title":349,"path":350,"stem":351},"Interface: SeoMetadata","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fseometadata","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FSeoMetadata",{"title":353,"path":354,"stem":355},"Interface: TentoClientConfig","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Ftentoclientconfig","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FTentoClientConfig",{"title":357,"path":358,"stem":359},"Interface: TentoMedia","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Ftentomedia","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FTentoMedia",{"title":361,"path":362,"stem":363},"Interface: UpdateCollectionItemInput","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fupdatecollectioniteminput","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FUpdateCollectionItemInput",{"title":365,"path":366,"stem":367},"Interface: UpdatePageInput","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fupdatepageinput","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FUpdatePageInput",{"title":369,"path":370,"stem":371},"Interface: UploadMediaInput","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fuploadmediainput","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FUploadMediaInput",{"title":373,"path":374,"stem":375},"Interface: WriteCollectionItem","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fwritecollectionitem","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FWriteCollectionItem",{"title":377,"path":378,"stem":379},"Interface: WritePage","\u002Fdocs\u002Fsdk\u002Freference\u002Finterfaces\u002Fwritepage","docs\u002Fsdk\u002Freference\u002Finterfaces\u002FWritePage",{"title":381,"path":382,"stem":383,"children":384,"page":65},"Type Aliases","\u002Fdocs\u002Fsdk\u002Freference\u002Ftype-aliases","docs\u002Fsdk\u002Freference\u002Ftype-aliases",[385,389,393,397,401],{"title":386,"path":387,"stem":388},"Type Alias: BlogPostListItem","\u002Fdocs\u002Fsdk\u002Freference\u002Ftype-aliases\u002Fblogpostlistitem","docs\u002Fsdk\u002Freference\u002Ftype-aliases\u002FBlogPostListItem",{"title":390,"path":391,"stem":392},"Type Alias: BlogPostListResponse","\u002Fdocs\u002Fsdk\u002Freference\u002Ftype-aliases\u002Fblogpostlistresponse","docs\u002Fsdk\u002Freference\u002Ftype-aliases\u002FBlogPostListResponse",{"title":394,"path":395,"stem":396},"Type Alias: CollectionItem\u003CTContent>","\u002Fdocs\u002Fsdk\u002Freference\u002Ftype-aliases\u002Fcollectionitem","docs\u002Fsdk\u002Freference\u002Ftype-aliases\u002FCollectionItem",{"title":398,"path":399,"stem":400},"Type Alias: FilterOperator","\u002Fdocs\u002Fsdk\u002Freference\u002Ftype-aliases\u002Ffilteroperator","docs\u002Fsdk\u002Freference\u002Ftype-aliases\u002FFilterOperator",{"title":402,"path":403,"stem":404},"Type Alias: MediaMode","\u002Fdocs\u002Fsdk\u002Freference\u002Ftype-aliases\u002Fmediamode","docs\u002Fsdk\u002Freference\u002Ftype-aliases\u002FMediaMode",{"title":406,"path":407,"stem":408,"children":409,"page":65},"Typegen","\u002Fdocs\u002Ftypegen","docs\u002Ftypegen",[410,413,416],{"title":133,"path":411,"stem":412},"\u002Fdocs\u002Ftypegen\u002Fastro","docs\u002Ftypegen\u002Fastro",{"title":137,"path":414,"stem":415},"\u002Fdocs\u002Ftypegen\u002Fnextjs","docs\u002Ftypegen\u002Fnextjs",{"title":141,"path":417,"stem":418},"\u002Fdocs\u002Ftypegen\u002Fnuxt","docs\u002Ftypegen\u002Fnuxt",[420,450,460,478,556],{"title":5,"path":6,"stem":7,"children":421,"page":65},[422,424,426,428,430,432,434,436,438,440,442,444,446,448],{"title":10,"path":11,"stem":12,"icon":423},"i-lucide-sparkles",{"title":14,"path":15,"stem":16,"icon":425},"i-lucide-newspaper",{"title":18,"path":19,"stem":20,"icon":427},"i-lucide-terminal",{"title":22,"path":23,"stem":24,"icon":429},"i-lucide-layers",{"title":26,"path":27,"stem":28,"icon":431},"i-lucide-type",{"title":30,"path":31,"stem":32,"icon":433},"i-lucide-triangle-alert",{"title":34,"path":35,"stem":36,"icon":435},"i-lucide-image",{"title":38,"path":39,"stem":40,"icon":437},"i-lucide-map",{"title":42,"path":43,"stem":44,"icon":439},"i-lucide-file-text",{"title":46,"path":47,"stem":48,"icon":441},"i-lucide-sliders-horizontal",{"title":50,"path":51,"stem":52,"icon":443},"i-lucide-braces",{"title":54,"path":55,"stem":56,"icon":445},"i-lucide-wrench",{"title":58,"path":59,"stem":60,"icon":447},"i-lucide-shield-check",{"title":62,"path":63,"stem":64,"icon":449},"i-lucide-webhook",{"title":67,"path":68,"stem":69,"children":451,"page":65},[452,454,456,458],{"title":72,"path":73,"stem":74,"icon":453},"i-lucide-key-round",{"title":76,"path":77,"stem":78,"icon":455},"i-lucide-compass",{"title":80,"path":81,"stem":82,"icon":457},"i-lucide-rocket",{"title":84,"path":85,"stem":86,"icon":459},"i-lucide-pencil",{"title":88,"path":89,"stem":90,"children":461,"page":65},[462,464,466,468,470,472,474,476],{"title":93,"path":94,"stem":95,"icon":463},"i-lucide-layout-dashboard",{"title":97,"path":98,"stem":99,"icon":465},"i-lucide-package",{"title":101,"path":102,"stem":103,"icon":467},"i-lucide-git-branch",{"title":105,"path":106,"stem":107,"icon":469},"i-lucide-files",{"title":109,"path":110,"stem":111,"icon":471},"i-lucide-plug-zap",{"title":113,"path":114,"stem":115,"icon":473},"i-lucide-wand",{"title":117,"path":118,"stem":119,"icon":475},"i-lucide-users",{"title":121,"path":122,"stem":123,"icon":477},"i-lucide-pilcrow",{"title":125,"path":126,"stem":127,"children":479,"page":65},[480,481,482,484,486],{"title":38,"path":130,"stem":131,"icon":465},{"title":133,"path":134,"stem":135,"icon":457},{"title":137,"path":138,"stem":139,"icon":483},"i-lucide-triangle",{"title":141,"path":142,"stem":143,"icon":485},"i-lucide-hexagon",{"title":145,"path":146,"stem":147,"children":487,"page":65},[488,490,508,512,549],{"title":150,"path":151,"stem":152,"icon":489},null,{"title":154,"path":155,"stem":156,"children":491,"page":65},[492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507],{"title":159,"path":160,"stem":161,"icon":489},{"title":163,"path":164,"stem":165,"icon":489},{"title":167,"path":168,"stem":169,"icon":489},{"title":171,"path":172,"stem":173,"icon":489},{"title":175,"path":176,"stem":177,"icon":489},{"title":179,"path":180,"stem":181,"icon":489},{"title":183,"path":184,"stem":185,"icon":489},{"title":187,"path":188,"stem":189,"icon":489},{"title":191,"path":192,"stem":193,"icon":489},{"title":195,"path":196,"stem":197,"icon":489},{"title":199,"path":200,"stem":201,"icon":489},{"title":203,"path":204,"stem":205,"icon":489},{"title":207,"path":208,"stem":209,"icon":489},{"title":211,"path":212,"stem":213,"icon":489},{"title":215,"path":216,"stem":217,"icon":489},{"title":219,"path":220,"stem":221,"icon":489},{"title":223,"path":224,"stem":225,"children":509,"page":65},[510,511],{"title":228,"path":229,"stem":230,"icon":489},{"title":232,"path":233,"stem":234,"icon":489},{"title":236,"path":237,"stem":238,"children":513,"page":65},[514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548],{"title":241,"path":242,"stem":243,"icon":489},{"title":245,"path":246,"stem":247,"icon":489},{"title":249,"path":250,"stem":251,"icon":489},{"title":253,"path":254,"stem":255,"icon":489},{"title":257,"path":258,"stem":259,"icon":489},{"title":261,"path":262,"stem":263,"icon":489},{"title":265,"path":266,"stem":267,"icon":489},{"title":269,"path":270,"stem":271,"icon":489},{"title":273,"path":274,"stem":275,"icon":489},{"title":277,"path":278,"stem":279,"icon":489},{"title":281,"path":282,"stem":283,"icon":489},{"title":285,"path":286,"stem":287,"icon":489},{"title":289,"path":290,"stem":291,"icon":489},{"title":293,"path":294,"stem":295,"icon":489},{"title":297,"path":298,"stem":299,"icon":489},{"title":301,"path":302,"stem":303,"icon":489},{"title":305,"path":306,"stem":307,"icon":489},{"title":309,"path":310,"stem":311,"icon":489},{"title":313,"path":314,"stem":315,"icon":489},{"title":317,"path":318,"stem":319,"icon":489},{"title":321,"path":322,"stem":323,"icon":489},{"title":325,"path":326,"stem":327,"icon":489},{"title":329,"path":330,"stem":331,"icon":489},{"title":333,"path":334,"stem":335,"icon":489},{"title":337,"path":338,"stem":339,"icon":489},{"title":341,"path":342,"stem":343,"icon":489},{"title":345,"path":346,"stem":347,"icon":489},{"title":349,"path":350,"stem":351,"icon":489},{"title":353,"path":354,"stem":355,"icon":489},{"title":357,"path":358,"stem":359,"icon":489},{"title":361,"path":362,"stem":363,"icon":489},{"title":365,"path":366,"stem":367,"icon":489},{"title":369,"path":370,"stem":371,"icon":489},{"title":373,"path":374,"stem":375,"icon":489},{"title":377,"path":378,"stem":379,"icon":489},{"title":381,"path":382,"stem":383,"children":550,"page":65},[551,552,553,554,555],{"title":386,"path":387,"stem":388,"icon":489},{"title":390,"path":391,"stem":392,"icon":489},{"title":394,"path":395,"stem":396,"icon":489},{"title":398,"path":399,"stem":400,"icon":489},{"title":402,"path":403,"stem":404,"icon":489},{"title":406,"path":407,"stem":408,"children":557,"page":65},[558,559,560],{"title":133,"path":411,"stem":412,"icon":457},{"title":137,"path":414,"stem":415,"icon":483},{"title":141,"path":417,"stem":418,"icon":485},{"id":562,"title":97,"body":563,"description":12950,"extension":12951,"icon":465,"links":489,"meta":12952,"navigation":1105,"path":98,"seo":12953,"stem":99,"__hash__":12954},"docs\u002Fdocs\u002Fguides\u002Fbuttercms-migration.md",{"type":564,"value":565,"toc":12905},"minimark",[566,584,592,597,602,610,642,649,677,697,701,749,753,756,776,779,783,786,831,839,841,845,848,899,902,947,949,952,955,971,987,1034,1203,1206,1306,1346,1348,1352,1358,1377,1387,1689,1887,1889,1893,1895,1905,2352,2355,2391,2426,2485,3013,3021,3028,3061,3182,3185,3244,3248,3446,3449,3458,3461,3467,3487,3508,3526,3543,3585,3588,3591,3789,3826,3829,3888,3916,4118,4124,4196,4251,4282,4286,4292,4297,4311,4324,4329,4530,4535,4677,4680,5059,5392,5394,5398,5407,5472,5482,5747,5764,5803,5805,5809,5918,5933,6099,6174,6308,6382,6507,6632,6655,6657,6661,6667,6734,6737,6831,6835,6849,7060,7072,7160,7453,7572,7661,8002,8026,8031,8039,8042,8200,8207,8209,8213,8217,8252,8285,8346,8349,8410,8417,8421,8440,8507,8514,8570,8573,8579,8675,8678,8684,8687,8718,8770,8852,8888,8918,8945,8966,9047,9069,9081,9164,9189,9193,9198,9386,9393,9396,9412,9418,9428,9444,9493,9497,9520,9748,9751,9775,9778,9802,9854,9860,9915,9922,9926,9932,9978,10048,10090,10207,10337,10342,10365,10373,10377,10418,10447,10451,10459,10479,10483,10517,10530,10544,10567,10772,10780,10805,10818,10847,10851,10861,10883,10885,10889,10892,11072,11074,11078,11081,12534,12537,12849,12851,12855,12901],[567,568,569],"p",{},[570,571,572,573,577,578,583],"em",{},"Verified against commit ",[574,575,576],"code",{},"6dbe274"," (2026-07-21). See ",[579,580,582],"a",{"href":581},"#step-0-verify-the-deployment-matches-this-guide","Step 0"," to confirm the live deployment matches.",[567,585,586,587,591],{},"This guide covers the application-level code changes required when migrating a frontend codebase from ButterCMS to TentoCMS. It does not cover content import — use the ",[579,588,590],{"href":589},".\u002Fmigrations","TentoCMS migration wizard"," for that step.",[593,594,596],"h2",{"id":595},"before-you-start","Before You Start",[598,599,601],"h3",{"id":600},"step-0-verify-the-deployment-matches-this-guide","Step 0: Verify the deployment matches this guide",[567,603,604,605,609],{},"Every behaviour documented below was verified against the commit in the line above. A\ndeployment running an ",[606,607,608],"strong",{},"older"," build may not have those fixes — the symptom is a\ndocumented flag or field silently doing nothing, with no error to explain why. Before you\nrely on anything in this guide, confirm what is actually live:",[611,612,617],"pre",{"className":613,"code":614,"language":615,"meta":616,"style":616},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","curl -s https:\u002F\u002Ftento-api.intelligentlending.co.uk\u002Fhealth\n# → { \"status\": \"healthy\", \"version\": \"0.0.1\", \"commit\": \"\u003Csha>\", \"environment\": \"production\" }\n","bash","",[574,618,619,635],{"__ignoreMap":616},[620,621,624,628,632],"span",{"class":622,"line":623},"line",1,[620,625,627],{"class":626},"sBMFI","curl",[620,629,631],{"class":630},"sfazB"," -s",[620,633,634],{"class":630}," https:\u002F\u002Ftento-api.intelligentlending.co.uk\u002Fhealth\n",[620,636,638],{"class":622,"line":637},2,[620,639,641],{"class":640},"sHwdD","# → { \"status\": \"healthy\", \"version\": \"0.0.1\", \"commit\": \"\u003Csha>\", \"environment\": \"production\" }\n",[567,643,644,645,648],{},"Check the ",[574,646,647],{},"commit"," value against the \"Verified against\" commit at the top of this guide:",[650,651,652,661],"ul",{},[653,654,655,660],"li",{},[606,656,657,659],{},[574,658,647],{}," is at or after the verified SHA"," → you're good; proceed.",[653,662,663,668,669,672,673,676],{},[606,664,665,667],{},[574,666,647],{}," is behind it"," (or reports ",[574,670,671],{},"\"unknown\"",") → the deployment predates this guide.\nRequest a redeploy of ",[574,674,675],{},"@tentocms\u002Fapi"," before migrating, or expect discrepancies. If in\ndoubt, ask the Tento team which commit is live and whether it includes the fixes you need.",[678,679,680],"blockquote",{},[567,681,682,685,686,689,690,693,694,696],{},[606,683,684],{},"Why this matters:"," the guide pins a commit, but only the running worker knows what it\nactually serves. A fix merged to ",[574,687,688],{},"main"," is not live until that environment is redeployed.\nThe ",[574,691,692],{},"\u002Fhealth"," ",[574,695,647],{}," field is the authoritative answer.",[598,698,700],{"id":699},"prerequisites","Prerequisites",[650,702,703,706,711,722,737],{},[653,704,705],{},"Content already imported via the TentoCMS migration wizard (run a dry run first)",[653,707,708,710],{},[574,709,150],{}," installed in your project",[653,712,713,714,717,718,721],{},"TentoCMS API key available (public key ",[574,715,716],{},"tento_pk_*"," for read-only, secret key ",[574,719,720],{},"tento_sk_*"," for writes)",[653,723,724,727,728,731,732,736],{},[606,725,726],{},"Your TentoCMS API base URL."," For this deployment it is ",[574,729,730],{},"https:\u002F\u002Ftento-api.intelligentlending.co.uk",". You must pass it to the client — see ",[579,733,735],{"href":734},"#sdk-setup","SDK Setup",".",[653,738,739,740,743,744,748],{},"Optionally: generated TypeScript types via ",[574,741,742],{},"@tentocms\u002Ftypegen"," (see ",[579,745,747],{"href":746},"#generating-types-with-tentocmstypegen","Generating types",")",[598,750,752],{"id":751},"scope","Scope",[567,754,755],{},"This guide covers:",[650,757,758,761,764,767,770,773],{},[653,759,760],{},"SDK setup and client initialisation",[653,762,763],{},"Data shape differences between ButterCMS and TentoCMS responses",[653,765,766],{},"Component identification changes (snake_case to kebab-case)",[653,768,769],{},"SDK method mapping",[653,771,772],{},"Image handling",[653,774,775],{},"Common gotchas discovered during real migrations",[777,778],"hr",{},[593,780,782],{"id":781},"migration-order","Migration Order",[567,784,785],{},"Follow this sequence to avoid breaking your application mid-migration:",[787,788,789,795,801,810,819,825],"ol",{},[653,790,791,794],{},[606,792,793],{},"Schema"," — Create all component schemas in TentoCMS (verify completeness before proceeding)",[653,796,797,800],{},[606,798,799],{},"Content"," — Import content via the migration wizard (dry run, then full migration)",[653,802,803,806,807,809],{},[606,804,805],{},"Types"," — Generate TypeScript types with ",[574,808,742],{}," if using TypeScript",[653,811,812,815,816,818],{},[606,813,814],{},"SDK install"," — Install ",[574,817,150],{}," and initialise the client",[653,820,821,824],{},[606,822,823],{},"Application code"," — Update data fetching calls, field references, and component renderers",[653,826,827,830],{},[606,828,829],{},"Testing"," — Verify all pages render correctly and preview mode works",[567,832,833,834,838],{},"Starting application code changes before schema and content are complete leads to hybrid rendering complexity. See ",[579,835,837],{"href":836},"#partial-schema-migration","Partial schema migration"," in the gotchas section.",[777,840],{},[593,842,844],{"id":843},"migration-strategies","Migration Strategies",[567,846,847],{},"There are two ways to absorb the TentoCMS data shape; the right one depends on your app:",[650,849,850,868],{},[653,851,852,855,856,859,860,863,864,867],{},[606,853,854],{},"Propagate the new shape through your components"," — update renderers to read ",[574,857,858],{},"_type",",\n",[574,861,862],{},"page.seo",", ",[574,865,866],{},"TentoMedia",", etc. directly. This guide's examples take this approach. Best when\nyou have few components or want the new shape end-to-end.",[653,869,870,873,874,877,878,881,882,884,885,887,888,890,891,894,895,898],{},[606,871,872],{},"Adapt at your data-access boundary"," — if your app already centralises CMS access (e.g.\nNuxt\u002FNext server routes or a ",[574,875,876],{},"server\u002Futils"," layer), translate the TentoCMS response back into\nthe shape your components already consume, in ",[606,879,880],{},"one"," place. A real migration touched ~10\nfiles instead of dozens, kept every component and its tests unchanged, and still handled every\nshape difference (kebab ",[574,883,858],{},"→snake, ",[574,886,862],{},"→custom meta, ",[574,889,866],{},"→URL string,\n",[574,892,893],{},"_sortOrder"," ordering, name-filtering, ",[574,896,897],{},"pageType"," filtering). Best for apps with a CMS proxy\nlayer and many components.",[567,900,901],{},"Both are valid — choose based on how your frontend already consumes content.",[678,903,904],{},[567,905,906,916,917,919,920,923,924,926,927,930,931,933,934,937,938,941,942,944,945,736],{},[606,907,908,909,912,913,736],{},"Adapting back to Butter's ",[574,910,911],{},"{ type, fields }"," shape? Don't clobber a component's variant\n",[574,914,915],{},"type"," A component's own ",[574,918,915],{}," field (e.g. a trustpilot block's ",[574,921,922],{},"type: 'carousel'",") sits\nalongside the kebab-case ",[574,925,858],{}," discriminant. If you naively set ",[574,928,929],{},"type = snakeCase(_type)",",\nyou overwrite the variant. Instead, move every non-",[574,932,858],{}," field into a nested ",[574,935,936],{},"fields"," object\nfirst (so the variant lands at ",[574,939,940],{},"fields.type","), then derive ",[574,943,915],{}," from ",[574,946,858],{},[777,948],{},[593,950,735],{"id":951},"sdk-setup",[567,953,954],{},"Replace the ButterCMS client initialisation with the TentoCMS client. Install the SDK from npm:",[611,956,958],{"className":613,"code":957,"language":615,"meta":616,"style":616},"npm install @tentocms\u002Fclient\n",[574,959,960],{"__ignoreMap":616},[620,961,962,965,968],{"class":622,"line":623},[620,963,964],{"class":626},"npm",[620,966,967],{"class":630}," install",[620,969,970],{"class":630}," @tentocms\u002Fclient\n",[678,972,973],{},[567,974,975,978,979,983,984,986],{},[606,976,977],{},"Exact method signatures, options and return types"," live in the generated ",[579,980,982],{"href":981},"..\u002Fsdk\u002Freference\u002Freadme","SDK API\nReference"," — the canonical, always-in-sync surface for\n",[574,985,150],{},". Reach for it whenever you need to confirm a method's shape while porting.",[678,988,989,1016],{},[567,990,991,998,999,1001,1002,1005,1006,1012,1013,1015],{},[606,992,993,994,997],{},"⚠️ Always set ",[574,995,996],{},"baseUrl"," explicitly."," The SDK ships with a default host, but you\nshould pass your deployment's API base URL so your app never depends on the packaged\ndefault. For this deployment it is ",[574,1000,730],{},". A\n",[574,1003,1004],{},"TentoClient"," pointed at the wrong host fails with connection errors or 401s. Note that\nbecause a default exists, ",[606,1007,1008,1009,1011],{},"a missing ",[574,1010,996],{}," does not fail on this deployment"," —\nthe packaged default URL equals this deployment's URL\n(",[574,1014,730],{},"), so a missing config is a silent no-op\nhere. The misconfig only surfaces when porting to another environment where the\npackaged default is wrong. Set it explicitly so an absent env var surfaces immediately.",[567,1017,1018,1021,1022,1025,1026,1029,1030,1033],{},[606,1019,1020],{},"A note on naming."," The npm packages are published under the ",[574,1023,1024],{},"@tentocms\u002F*"," scope, the\nproduct is branded ",[606,1027,1028],{},"TentoCMS",", and the live API for this deployment is\n",[574,1031,1032],{},"tento-api.intelligentlending.co.uk",". These all refer to the same system.",[611,1035,1039],{"className":1036,"code":1037,"language":1038,"meta":616,"style":616},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F ButterCMS\nimport Butter from 'buttercms'\nconst butter = Butter('your_api_token')\n\n\u002F\u002F TentoCMS\nimport { TentoClient } from '@tentocms\u002Fclient'\nconst tento = new TentoClient({\n  apiKey: 'tento_pk_...',\n  baseUrl: 'https:\u002F\u002Ftento-api.intelligentlending.co.uk', \u002F\u002F set this explicitly\n})\n","typescript",[574,1040,1041,1046,1069,1100,1107,1113,1136,1156,1175,1195],{"__ignoreMap":616},[620,1042,1043],{"class":622,"line":623},[620,1044,1045],{"class":640},"\u002F\u002F ButterCMS\n",[620,1047,1048,1052,1056,1059,1063,1066],{"class":622,"line":637},[620,1049,1051],{"class":1050},"s7zQu","import",[620,1053,1055],{"class":1054},"sTEyZ"," Butter ",[620,1057,1058],{"class":1050},"from",[620,1060,1062],{"class":1061},"sMK4o"," '",[620,1064,1065],{"class":630},"buttercms",[620,1067,1068],{"class":1061},"'\n",[620,1070,1072,1076,1079,1082,1086,1089,1092,1095,1097],{"class":622,"line":1071},3,[620,1073,1075],{"class":1074},"spNyl","const",[620,1077,1078],{"class":1054}," butter ",[620,1080,1081],{"class":1061},"=",[620,1083,1085],{"class":1084},"s2Zo4"," Butter",[620,1087,1088],{"class":1054},"(",[620,1090,1091],{"class":1061},"'",[620,1093,1094],{"class":630},"your_api_token",[620,1096,1091],{"class":1061},[620,1098,1099],{"class":1054},")\n",[620,1101,1103],{"class":622,"line":1102},4,[620,1104,1106],{"emptyLinePlaceholder":1105},true,"\n",[620,1108,1110],{"class":622,"line":1109},5,[620,1111,1112],{"class":640},"\u002F\u002F TentoCMS\n",[620,1114,1116,1118,1121,1124,1127,1130,1132,1134],{"class":622,"line":1115},6,[620,1117,1051],{"class":1050},[620,1119,1120],{"class":1061}," {",[620,1122,1123],{"class":1054}," TentoClient",[620,1125,1126],{"class":1061}," }",[620,1128,1129],{"class":1050}," from",[620,1131,1062],{"class":1061},[620,1133,150],{"class":630},[620,1135,1068],{"class":1061},[620,1137,1139,1141,1144,1146,1149,1151,1153],{"class":622,"line":1138},7,[620,1140,1075],{"class":1074},[620,1142,1143],{"class":1054}," tento ",[620,1145,1081],{"class":1061},[620,1147,1148],{"class":1061}," new",[620,1150,1123],{"class":1084},[620,1152,1088],{"class":1054},[620,1154,1155],{"class":1061},"{\n",[620,1157,1159,1163,1166,1168,1171,1173],{"class":622,"line":1158},8,[620,1160,1162],{"class":1161},"swJcz","  apiKey",[620,1164,1165],{"class":1061},":",[620,1167,1062],{"class":1061},[620,1169,1170],{"class":630},"tento_pk_...",[620,1172,1091],{"class":1061},[620,1174,859],{"class":1061},[620,1176,1178,1181,1183,1185,1187,1189,1192],{"class":622,"line":1177},9,[620,1179,1180],{"class":1161},"  baseUrl",[620,1182,1165],{"class":1061},[620,1184,1062],{"class":1061},[620,1186,730],{"class":630},[620,1188,1091],{"class":1061},[620,1190,1191],{"class":1061},",",[620,1193,1194],{"class":640}," \u002F\u002F set this explicitly\n",[620,1196,1198,1201],{"class":622,"line":1197},10,[620,1199,1200],{"class":1061},"}",[620,1202,1099],{"class":1054},[567,1204,1205],{},"Store the API key and base URL in environment variables, not hardcoded in source:",[611,1207,1209],{"className":1036,"code":1208,"language":1038,"meta":616,"style":616},"\u002F\u002F Recommended\nconst tento = new TentoClient({\n  apiKey: process.env.TENTO_API_KEY!,\n  baseUrl: process.env.TENTO_BASE_URL!,      \u002F\u002F https:\u002F\u002Ftento-api.intelligentlending.co.uk\n  previewKey: process.env.TENTO_PREVIEW_KEY, \u002F\u002F optional, for draft content\n})\n",[574,1210,1211,1216,1232,1254,1277,1300],{"__ignoreMap":616},[620,1212,1213],{"class":622,"line":623},[620,1214,1215],{"class":640},"\u002F\u002F Recommended\n",[620,1217,1218,1220,1222,1224,1226,1228,1230],{"class":622,"line":637},[620,1219,1075],{"class":1074},[620,1221,1143],{"class":1054},[620,1223,1081],{"class":1061},[620,1225,1148],{"class":1061},[620,1227,1123],{"class":1084},[620,1229,1088],{"class":1054},[620,1231,1155],{"class":1061},[620,1233,1234,1236,1238,1241,1243,1246,1248,1251],{"class":622,"line":1071},[620,1235,1162],{"class":1161},[620,1237,1165],{"class":1061},[620,1239,1240],{"class":1054}," process",[620,1242,736],{"class":1061},[620,1244,1245],{"class":1054},"env",[620,1247,736],{"class":1061},[620,1249,1250],{"class":1054},"TENTO_API_KEY",[620,1252,1253],{"class":1061},"!,\n",[620,1255,1256,1258,1260,1262,1264,1266,1268,1271,1274],{"class":622,"line":1102},[620,1257,1180],{"class":1161},[620,1259,1165],{"class":1061},[620,1261,1240],{"class":1054},[620,1263,736],{"class":1061},[620,1265,1245],{"class":1054},[620,1267,736],{"class":1061},[620,1269,1270],{"class":1054},"TENTO_BASE_URL",[620,1272,1273],{"class":1061},"!,",[620,1275,1276],{"class":640},"      \u002F\u002F https:\u002F\u002Ftento-api.intelligentlending.co.uk\n",[620,1278,1279,1282,1284,1286,1288,1290,1292,1295,1297],{"class":622,"line":1109},[620,1280,1281],{"class":1161},"  previewKey",[620,1283,1165],{"class":1061},[620,1285,1240],{"class":1054},[620,1287,736],{"class":1061},[620,1289,1245],{"class":1054},[620,1291,736],{"class":1061},[620,1293,1294],{"class":1054},"TENTO_PREVIEW_KEY",[620,1296,1191],{"class":1061},[620,1298,1299],{"class":640}," \u002F\u002F optional, for draft content\n",[620,1301,1302,1304],{"class":622,"line":1115},[620,1303,1200],{"class":1061},[620,1305,1099],{"class":1054},[611,1307,1309],{"className":613,"code":1308,"language":615,"meta":616,"style":616},"# .env\nTENTO_API_KEY=tento_pk_...\nTENTO_BASE_URL=https:\u002F\u002Ftento-api.intelligentlending.co.uk\nTENTO_PREVIEW_KEY=preview_...   # optional, only for previewing drafts\n",[574,1310,1311,1316,1325,1334],{"__ignoreMap":616},[620,1312,1313],{"class":622,"line":623},[620,1314,1315],{"class":640},"# .env\n",[620,1317,1318,1320,1322],{"class":622,"line":637},[620,1319,1250],{"class":1054},[620,1321,1081],{"class":1061},[620,1323,1324],{"class":630},"tento_pk_...\n",[620,1326,1327,1329,1331],{"class":622,"line":1071},[620,1328,1270],{"class":1054},[620,1330,1081],{"class":1061},[620,1332,1333],{"class":630},"https:\u002F\u002Ftento-api.intelligentlending.co.uk\n",[620,1335,1336,1338,1340,1343],{"class":622,"line":1102},[620,1337,1294],{"class":1054},[620,1339,1081],{"class":1061},[620,1341,1342],{"class":630},"preview_...",[620,1344,1345],{"class":640},"   # optional, only for previewing drafts\n",[777,1347],{},[593,1349,1351],{"id":1350},"framework-setup","Framework Setup",[567,1353,1354,1355,1357],{},"The examples in this guide use React\u002FJSX for brevity, but ",[574,1356,150],{}," is\nframework-agnostic. For framework-specific setup (typed composables, server utilities,\ncaching, and preview wiring) see the dedicated guides:",[650,1359,1360,1366,1372],{},[653,1361,1362],{},[579,1363,1365],{"href":1364},"..\u002Fsdk\u002Fnuxt","Nuxt \u002F Vue",[653,1367,1368],{},[579,1369,1371],{"href":1370},"..\u002Fsdk\u002Fnextjs","Next.js \u002F React",[653,1373,1374],{},[579,1375,133],{"href":1376},"..\u002Fsdk\u002Fastro",[567,1378,1379,1382,1383,1386],{},[606,1380,1381],{},"Keep your key server-side."," For SSR frameworks the secure default is to call the SDK\nfrom a ",[606,1384,1385],{},"server route or server utility"," and expose only the rendered data to the\nbrowser — not to register the client as a universal plugin that also runs client-side\n(which leaks the key into the client bundle).",[611,1388,1392],{"className":1389,"code":1390,"language":1391,"meta":616,"style":616},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Nuxt — a server route keeps the key server-side: server\u002Fapi\u002Fcms\u002Fpage.get.ts\nimport { TentoClient, TentoNotFoundError } from '@tentocms\u002Fclient'\n\nconst tento = new TentoClient({\n  apiKey: process.env.TENTO_API_KEY!,\n  baseUrl: process.env.TENTO_BASE_URL!,\n})\n\nexport default defineEventHandler(async (event) => {\n  const { slug, preview } = getQuery(event)\n  \u002F\u002F getBySlug throws TentoNotFoundError on 404 — catch it or a missing slug becomes a 500\n  try {\n    return await tento.pages.getBySlug(String(slug), { preview: preview === '1' })\n  } catch (e) {\n    if (!(e instanceof TentoNotFoundError)) throw e\n    return null\n  }\n})\n","ts",[574,1393,1394,1399,1422,1426,1442,1460,1478,1484,1488,1519,1548,1554,1562,1620,1639,1668,1676,1682],{"__ignoreMap":616},[620,1395,1396],{"class":622,"line":623},[620,1397,1398],{"class":640},"\u002F\u002F Nuxt — a server route keeps the key server-side: server\u002Fapi\u002Fcms\u002Fpage.get.ts\n",[620,1400,1401,1403,1405,1407,1409,1412,1414,1416,1418,1420],{"class":622,"line":637},[620,1402,1051],{"class":1050},[620,1404,1120],{"class":1061},[620,1406,1123],{"class":1054},[620,1408,1191],{"class":1061},[620,1410,1411],{"class":1054}," TentoNotFoundError",[620,1413,1126],{"class":1061},[620,1415,1129],{"class":1050},[620,1417,1062],{"class":1061},[620,1419,150],{"class":630},[620,1421,1068],{"class":1061},[620,1423,1424],{"class":622,"line":1071},[620,1425,1106],{"emptyLinePlaceholder":1105},[620,1427,1428,1430,1432,1434,1436,1438,1440],{"class":622,"line":1102},[620,1429,1075],{"class":1074},[620,1431,1143],{"class":1054},[620,1433,1081],{"class":1061},[620,1435,1148],{"class":1061},[620,1437,1123],{"class":1084},[620,1439,1088],{"class":1054},[620,1441,1155],{"class":1061},[620,1443,1444,1446,1448,1450,1452,1454,1456,1458],{"class":622,"line":1109},[620,1445,1162],{"class":1161},[620,1447,1165],{"class":1061},[620,1449,1240],{"class":1054},[620,1451,736],{"class":1061},[620,1453,1245],{"class":1054},[620,1455,736],{"class":1061},[620,1457,1250],{"class":1054},[620,1459,1253],{"class":1061},[620,1461,1462,1464,1466,1468,1470,1472,1474,1476],{"class":622,"line":1115},[620,1463,1180],{"class":1161},[620,1465,1165],{"class":1061},[620,1467,1240],{"class":1054},[620,1469,736],{"class":1061},[620,1471,1245],{"class":1054},[620,1473,736],{"class":1061},[620,1475,1270],{"class":1054},[620,1477,1253],{"class":1061},[620,1479,1480,1482],{"class":622,"line":1138},[620,1481,1200],{"class":1061},[620,1483,1099],{"class":1054},[620,1485,1486],{"class":622,"line":1158},[620,1487,1106],{"emptyLinePlaceholder":1105},[620,1489,1490,1493,1496,1499,1501,1504,1507,1511,1513,1516],{"class":622,"line":1177},[620,1491,1492],{"class":1050},"export",[620,1494,1495],{"class":1050}," default",[620,1497,1498],{"class":1084}," defineEventHandler",[620,1500,1088],{"class":1054},[620,1502,1503],{"class":1074},"async",[620,1505,1506],{"class":1061}," (",[620,1508,1510],{"class":1509},"sHdIc","event",[620,1512,748],{"class":1061},[620,1514,1515],{"class":1074}," =>",[620,1517,1518],{"class":1061}," {\n",[620,1520,1521,1524,1526,1529,1531,1534,1536,1539,1542,1544,1546],{"class":622,"line":1197},[620,1522,1523],{"class":1074},"  const",[620,1525,1120],{"class":1061},[620,1527,1528],{"class":1054}," slug",[620,1530,1191],{"class":1061},[620,1532,1533],{"class":1054}," preview",[620,1535,1126],{"class":1061},[620,1537,1538],{"class":1061}," =",[620,1540,1541],{"class":1084}," getQuery",[620,1543,1088],{"class":1161},[620,1545,1510],{"class":1054},[620,1547,1099],{"class":1161},[620,1549,1551],{"class":622,"line":1550},11,[620,1552,1553],{"class":640},"  \u002F\u002F getBySlug throws TentoNotFoundError on 404 — catch it or a missing slug becomes a 500\n",[620,1555,1557,1560],{"class":622,"line":1556},12,[620,1558,1559],{"class":1050},"  try",[620,1561,1518],{"class":1061},[620,1563,1565,1568,1571,1574,1576,1579,1581,1584,1586,1589,1591,1594,1596,1598,1600,1602,1604,1606,1609,1611,1614,1616,1618],{"class":622,"line":1564},13,[620,1566,1567],{"class":1050},"    return",[620,1569,1570],{"class":1050}," await",[620,1572,1573],{"class":1054}," tento",[620,1575,736],{"class":1061},[620,1577,1578],{"class":1054},"pages",[620,1580,736],{"class":1061},[620,1582,1583],{"class":1084},"getBySlug",[620,1585,1088],{"class":1161},[620,1587,1588],{"class":1084},"String",[620,1590,1088],{"class":1161},[620,1592,1593],{"class":1054},"slug",[620,1595,748],{"class":1161},[620,1597,1191],{"class":1061},[620,1599,1120],{"class":1061},[620,1601,1533],{"class":1161},[620,1603,1165],{"class":1061},[620,1605,1533],{"class":1054},[620,1607,1608],{"class":1061}," ===",[620,1610,1062],{"class":1061},[620,1612,1613],{"class":630},"1",[620,1615,1091],{"class":1061},[620,1617,1126],{"class":1061},[620,1619,1099],{"class":1161},[620,1621,1623,1626,1629,1631,1634,1637],{"class":622,"line":1622},14,[620,1624,1625],{"class":1061},"  }",[620,1627,1628],{"class":1050}," catch",[620,1630,1506],{"class":1161},[620,1632,1633],{"class":1054},"e",[620,1635,1636],{"class":1161},") ",[620,1638,1155],{"class":1061},[620,1640,1642,1645,1647,1650,1652,1654,1657,1659,1662,1665],{"class":622,"line":1641},15,[620,1643,1644],{"class":1050},"    if",[620,1646,1506],{"class":1161},[620,1648,1649],{"class":1061},"!",[620,1651,1088],{"class":1161},[620,1653,1633],{"class":1054},[620,1655,1656],{"class":1061}," instanceof",[620,1658,1411],{"class":626},[620,1660,1661],{"class":1161},")) ",[620,1663,1664],{"class":1050},"throw",[620,1666,1667],{"class":1054}," e\n",[620,1669,1671,1673],{"class":622,"line":1670},16,[620,1672,1567],{"class":1050},[620,1674,1675],{"class":1061}," null\n",[620,1677,1679],{"class":622,"line":1678},17,[620,1680,1681],{"class":1061},"  }\n",[620,1683,1685,1687],{"class":622,"line":1684},18,[620,1686,1200],{"class":1061},[620,1688,1099],{"class":1054},[611,1690,1694],{"className":1691,"code":1692,"language":1693,"meta":616,"style":616},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- Vue renderer: flatten component fields, match on the kebab-case _type -->\n\u003Cscript setup lang=\"ts\">\ndefineProps\u003C{ components: Array\u003CRecord\u003Cstring, unknown> & { _type: string }> }>()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003C!-- v-bind=\"c\" spreads the component itself, NOT c.fields -->\n  \u003Ccomponent\n    :is=\"componentFor(c._type)\"\n    v-for=\"(c, i) in components\"\n    :key=\"i\"\n    v-bind=\"c\"\n  \u002F>\n\u003C\u002Ftemplate>\n","vue",[574,1695,1696,1701,1727,1782,1791,1795,1804,1809,1817,1832,1846,1860,1874,1879],{"__ignoreMap":616},[620,1697,1698],{"class":622,"line":623},[620,1699,1700],{"class":640},"\u003C!-- Vue renderer: flatten component fields, match on the kebab-case _type -->\n",[620,1702,1703,1706,1709,1712,1715,1717,1720,1722,1724],{"class":622,"line":637},[620,1704,1705],{"class":1061},"\u003C",[620,1707,1708],{"class":1161},"script",[620,1710,1711],{"class":1074}," setup",[620,1713,1714],{"class":1074}," lang",[620,1716,1081],{"class":1061},[620,1718,1719],{"class":1061},"\"",[620,1721,1391],{"class":630},[620,1723,1719],{"class":1061},[620,1725,1726],{"class":1061},">\n",[620,1728,1729,1732,1735,1738,1740,1743,1745,1748,1750,1753,1755,1758,1761,1764,1766,1769,1771,1774,1777,1779],{"class":622,"line":1071},[620,1730,1731],{"class":1084},"defineProps",[620,1733,1734],{"class":1061},"\u003C{",[620,1736,1737],{"class":1161}," components",[620,1739,1165],{"class":1061},[620,1741,1742],{"class":626}," Array",[620,1744,1705],{"class":1061},[620,1746,1747],{"class":626},"Record",[620,1749,1705],{"class":1061},[620,1751,1752],{"class":626},"string",[620,1754,1191],{"class":1061},[620,1756,1757],{"class":626}," unknown",[620,1759,1760],{"class":1061},">",[620,1762,1763],{"class":1061}," &",[620,1765,1120],{"class":1061},[620,1767,1768],{"class":1161}," _type",[620,1770,1165],{"class":1061},[620,1772,1773],{"class":626}," string",[620,1775,1776],{"class":1061}," }>",[620,1778,1776],{"class":1061},[620,1780,1781],{"class":1054},"()\n",[620,1783,1784,1787,1789],{"class":622,"line":1102},[620,1785,1786],{"class":1061},"\u003C\u002F",[620,1788,1708],{"class":1161},[620,1790,1726],{"class":1061},[620,1792,1793],{"class":622,"line":1109},[620,1794,1106],{"emptyLinePlaceholder":1105},[620,1796,1797,1799,1802],{"class":622,"line":1115},[620,1798,1705],{"class":1061},[620,1800,1801],{"class":1161},"template",[620,1803,1726],{"class":1061},[620,1805,1806],{"class":622,"line":1138},[620,1807,1808],{"class":640},"  \u003C!-- v-bind=\"c\" spreads the component itself, NOT c.fields -->\n",[620,1810,1811,1814],{"class":622,"line":1158},[620,1812,1813],{"class":1061},"  \u003C",[620,1815,1816],{"class":1161},"component\n",[620,1818,1819,1822,1824,1826,1829],{"class":622,"line":1177},[620,1820,1821],{"class":1074},"    :is",[620,1823,1081],{"class":1061},[620,1825,1719],{"class":1061},[620,1827,1828],{"class":630},"componentFor(c._type)",[620,1830,1831],{"class":1061},"\"\n",[620,1833,1834,1837,1839,1841,1844],{"class":622,"line":1197},[620,1835,1836],{"class":1074},"    v-for",[620,1838,1081],{"class":1061},[620,1840,1719],{"class":1061},[620,1842,1843],{"class":630},"(c, i) in components",[620,1845,1831],{"class":1061},[620,1847,1848,1851,1853,1855,1858],{"class":622,"line":1550},[620,1849,1850],{"class":1074},"    :key",[620,1852,1081],{"class":1061},[620,1854,1719],{"class":1061},[620,1856,1857],{"class":630},"i",[620,1859,1831],{"class":1061},[620,1861,1862,1865,1867,1869,1872],{"class":622,"line":1556},[620,1863,1864],{"class":1074},"    v-bind",[620,1866,1081],{"class":1061},[620,1868,1719],{"class":1061},[620,1870,1871],{"class":630},"c",[620,1873,1831],{"class":1061},[620,1875,1876],{"class":622,"line":1564},[620,1877,1878],{"class":1061},"  \u002F>\n",[620,1880,1881,1883,1885],{"class":622,"line":1622},[620,1882,1786],{"class":1061},[620,1884,1801],{"class":1161},[620,1886,1726],{"class":1061},[777,1888],{},[593,1890,1892],{"id":1891},"data-shape-differences","Data Shape Differences",[598,1894,42],{"id":1578},[567,1896,1897,1898,1901,1902,1904],{},"ButterCMS returns all page content inside ",[574,1899,1900],{},"page.fields",". TentoCMS also uses ",[574,1903,1900],{}," for custom content, but promotes several identifiers to top-level properties.",[611,1906,1908],{"className":1036,"code":1907,"language":1038,"meta":616,"style":616},"\u002F\u002F ButterCMS page response\n{\n  data: {\n    slug: 'about-us',\n    page_type: 'about-page',\n    fields: {\n      seo_title: 'About Us',\n      meta_description: 'Learn about our company',\n      headline: 'Welcome to Our Company',\n      body: '\u003Cp>...\u003C\u002Fp>',\n    }\n  }\n}\n\n\u002F\u002F TentoCMS page response\n{\n  id: '123e4567-e89b-12d3-a456-426614174000',\n  name: 'About Us',            \u002F\u002F clean title — Butter's admin name field never included a prefix\n  slug: 'about-us',\n  type: 'about-page',          \u002F\u002F page type slug, top-level\n  fields: {                    \u002F\u002F custom content only\n    headline: 'Welcome to Our Company',\n    body: '\u003Cp>...\u003C\u002Fp>',\n  },\n  seo: {                       \u002F\u002F SEO fields nested under seo object\n    metaTitle: 'About Us',\n    metaDescription: 'Learn about our company',\n    metaRobots: 'index',\n    ogTitle: 'About Us',\n    ogDescription: 'Learn about our company',\n    ogImage: 'https:\u002F\u002Ftento-api.intelligentlending.co.uk\u002Fapi\u002Fv1\u002Fmedia\u002F\u003Cid>\u002Fog.png',\n    canonicalUrl: 'https:\u002F\u002Fexample.com\u002Fabout-us',\n  },\n  publishedAt: '2026-01-15T10:00:00Z',\n  updatedAt: '2026-02-01T14:30:00Z',\n}\n",[574,1909,1910,1915,1919,1928,1944,1960,1969,1985,2001,2017,2033,2038,2042,2047,2051,2056,2060,2076,2094,2110,2129,2142,2158,2174,2180,2193,2209,2225,2242,2258,2274,2291,2308,2313,2330,2347],{"__ignoreMap":616},[620,1911,1912],{"class":622,"line":623},[620,1913,1914],{"class":640},"\u002F\u002F ButterCMS page response\n",[620,1916,1917],{"class":622,"line":637},[620,1918,1155],{"class":1061},[620,1920,1921,1924,1926],{"class":622,"line":1071},[620,1922,1923],{"class":626},"  data",[620,1925,1165],{"class":1061},[620,1927,1518],{"class":1061},[620,1929,1930,1933,1935,1937,1940,1942],{"class":622,"line":1102},[620,1931,1932],{"class":626},"    slug",[620,1934,1165],{"class":1061},[620,1936,1062],{"class":1061},[620,1938,1939],{"class":630},"about-us",[620,1941,1091],{"class":1061},[620,1943,859],{"class":1061},[620,1945,1946,1949,1951,1953,1956,1958],{"class":622,"line":1109},[620,1947,1948],{"class":626},"    page_type",[620,1950,1165],{"class":1061},[620,1952,1062],{"class":1061},[620,1954,1955],{"class":630},"about-page",[620,1957,1091],{"class":1061},[620,1959,859],{"class":1061},[620,1961,1962,1965,1967],{"class":622,"line":1115},[620,1963,1964],{"class":626},"    fields",[620,1966,1165],{"class":1061},[620,1968,1518],{"class":1061},[620,1970,1971,1974,1976,1978,1981,1983],{"class":622,"line":1138},[620,1972,1973],{"class":626},"      seo_title",[620,1975,1165],{"class":1061},[620,1977,1062],{"class":1061},[620,1979,1980],{"class":630},"About Us",[620,1982,1091],{"class":1061},[620,1984,859],{"class":1061},[620,1986,1987,1990,1992,1994,1997,1999],{"class":622,"line":1158},[620,1988,1989],{"class":626},"      meta_description",[620,1991,1165],{"class":1061},[620,1993,1062],{"class":1061},[620,1995,1996],{"class":630},"Learn about our company",[620,1998,1091],{"class":1061},[620,2000,859],{"class":1061},[620,2002,2003,2006,2008,2010,2013,2015],{"class":622,"line":1177},[620,2004,2005],{"class":626},"      headline",[620,2007,1165],{"class":1061},[620,2009,1062],{"class":1061},[620,2011,2012],{"class":630},"Welcome to Our Company",[620,2014,1091],{"class":1061},[620,2016,859],{"class":1061},[620,2018,2019,2022,2024,2026,2029,2031],{"class":622,"line":1197},[620,2020,2021],{"class":626},"      body",[620,2023,1165],{"class":1061},[620,2025,1062],{"class":1061},[620,2027,2028],{"class":630},"\u003Cp>...\u003C\u002Fp>",[620,2030,1091],{"class":1061},[620,2032,859],{"class":1061},[620,2034,2035],{"class":622,"line":1550},[620,2036,2037],{"class":1061},"    }\n",[620,2039,2040],{"class":622,"line":1556},[620,2041,1681],{"class":1061},[620,2043,2044],{"class":622,"line":1564},[620,2045,2046],{"class":1061},"}\n",[620,2048,2049],{"class":622,"line":1622},[620,2050,1106],{"emptyLinePlaceholder":1105},[620,2052,2053],{"class":622,"line":1641},[620,2054,2055],{"class":640},"\u002F\u002F TentoCMS page response\n",[620,2057,2058],{"class":622,"line":1670},[620,2059,1155],{"class":1061},[620,2061,2062,2065,2067,2069,2072,2074],{"class":622,"line":1678},[620,2063,2064],{"class":626},"  id",[620,2066,1165],{"class":1061},[620,2068,1062],{"class":1061},[620,2070,2071],{"class":630},"123e4567-e89b-12d3-a456-426614174000",[620,2073,1091],{"class":1061},[620,2075,859],{"class":1061},[620,2077,2078,2081,2083,2085,2087,2089,2091],{"class":622,"line":1684},[620,2079,2080],{"class":626},"  name",[620,2082,1165],{"class":1061},[620,2084,1062],{"class":1061},[620,2086,1980],{"class":630},[620,2088,1091],{"class":1061},[620,2090,1191],{"class":1061},[620,2092,2093],{"class":640},"            \u002F\u002F clean title — Butter's admin name field never included a prefix\n",[620,2095,2097,2100,2102,2104,2106,2108],{"class":622,"line":2096},19,[620,2098,2099],{"class":626},"  slug",[620,2101,1165],{"class":1061},[620,2103,1062],{"class":1061},[620,2105,1939],{"class":630},[620,2107,1091],{"class":1061},[620,2109,859],{"class":1061},[620,2111,2113,2116,2118,2120,2122,2124,2126],{"class":622,"line":2112},20,[620,2114,2115],{"class":626},"  type",[620,2117,1165],{"class":1061},[620,2119,1062],{"class":1061},[620,2121,1955],{"class":630},[620,2123,1091],{"class":1061},[620,2125,1191],{"class":1061},[620,2127,2128],{"class":640},"          \u002F\u002F page type slug, top-level\n",[620,2130,2132,2135,2137,2139],{"class":622,"line":2131},21,[620,2133,2134],{"class":626},"  fields",[620,2136,1165],{"class":1061},[620,2138,1120],{"class":1061},[620,2140,2141],{"class":640},"                    \u002F\u002F custom content only\n",[620,2143,2145,2148,2150,2152,2154,2156],{"class":622,"line":2144},22,[620,2146,2147],{"class":626},"    headline",[620,2149,1165],{"class":1061},[620,2151,1062],{"class":1061},[620,2153,2012],{"class":630},[620,2155,1091],{"class":1061},[620,2157,859],{"class":1061},[620,2159,2161,2164,2166,2168,2170,2172],{"class":622,"line":2160},23,[620,2162,2163],{"class":626},"    body",[620,2165,1165],{"class":1061},[620,2167,1062],{"class":1061},[620,2169,2028],{"class":630},[620,2171,1091],{"class":1061},[620,2173,859],{"class":1061},[620,2175,2177],{"class":622,"line":2176},24,[620,2178,2179],{"class":1061},"  },\n",[620,2181,2183,2186,2188,2190],{"class":622,"line":2182},25,[620,2184,2185],{"class":626},"  seo",[620,2187,1165],{"class":1061},[620,2189,1120],{"class":1061},[620,2191,2192],{"class":640},"                       \u002F\u002F SEO fields nested under seo object\n",[620,2194,2196,2199,2201,2203,2205,2207],{"class":622,"line":2195},26,[620,2197,2198],{"class":626},"    metaTitle",[620,2200,1165],{"class":1061},[620,2202,1062],{"class":1061},[620,2204,1980],{"class":630},[620,2206,1091],{"class":1061},[620,2208,859],{"class":1061},[620,2210,2212,2215,2217,2219,2221,2223],{"class":622,"line":2211},27,[620,2213,2214],{"class":626},"    metaDescription",[620,2216,1165],{"class":1061},[620,2218,1062],{"class":1061},[620,2220,1996],{"class":630},[620,2222,1091],{"class":1061},[620,2224,859],{"class":1061},[620,2226,2228,2231,2233,2235,2238,2240],{"class":622,"line":2227},28,[620,2229,2230],{"class":626},"    metaRobots",[620,2232,1165],{"class":1061},[620,2234,1062],{"class":1061},[620,2236,2237],{"class":630},"index",[620,2239,1091],{"class":1061},[620,2241,859],{"class":1061},[620,2243,2245,2248,2250,2252,2254,2256],{"class":622,"line":2244},29,[620,2246,2247],{"class":626},"    ogTitle",[620,2249,1165],{"class":1061},[620,2251,1062],{"class":1061},[620,2253,1980],{"class":630},[620,2255,1091],{"class":1061},[620,2257,859],{"class":1061},[620,2259,2261,2264,2266,2268,2270,2272],{"class":622,"line":2260},30,[620,2262,2263],{"class":626},"    ogDescription",[620,2265,1165],{"class":1061},[620,2267,1062],{"class":1061},[620,2269,1996],{"class":630},[620,2271,1091],{"class":1061},[620,2273,859],{"class":1061},[620,2275,2277,2280,2282,2284,2287,2289],{"class":622,"line":2276},31,[620,2278,2279],{"class":626},"    ogImage",[620,2281,1165],{"class":1061},[620,2283,1062],{"class":1061},[620,2285,2286],{"class":630},"https:\u002F\u002Ftento-api.intelligentlending.co.uk\u002Fapi\u002Fv1\u002Fmedia\u002F\u003Cid>\u002Fog.png",[620,2288,1091],{"class":1061},[620,2290,859],{"class":1061},[620,2292,2294,2297,2299,2301,2304,2306],{"class":622,"line":2293},32,[620,2295,2296],{"class":626},"    canonicalUrl",[620,2298,1165],{"class":1061},[620,2300,1062],{"class":1061},[620,2302,2303],{"class":630},"https:\u002F\u002Fexample.com\u002Fabout-us",[620,2305,1091],{"class":1061},[620,2307,859],{"class":1061},[620,2309,2311],{"class":622,"line":2310},33,[620,2312,2179],{"class":1061},[620,2314,2316,2319,2321,2323,2326,2328],{"class":622,"line":2315},34,[620,2317,2318],{"class":626},"  publishedAt",[620,2320,1165],{"class":1061},[620,2322,1062],{"class":1061},[620,2324,2325],{"class":630},"2026-01-15T10:00:00Z",[620,2327,1091],{"class":1061},[620,2329,859],{"class":1061},[620,2331,2333,2336,2338,2340,2343,2345],{"class":622,"line":2332},35,[620,2334,2335],{"class":626},"  updatedAt",[620,2337,1165],{"class":1061},[620,2339,1062],{"class":1061},[620,2341,2342],{"class":630},"2026-02-01T14:30:00Z",[620,2344,1091],{"class":1061},[620,2346,859],{"class":1061},[620,2348,2350],{"class":622,"line":2349},36,[620,2351,2046],{"class":1061},[567,2353,2354],{},"Key differences:",[650,2356,2357,2362,2370,2378],{},[653,2358,2359,2361],{},[574,2360,1900],{}," contains only your custom content fields — no SEO, no identifiers",[653,2363,2364,2367,2368,748],{},[574,2365,2366],{},"page.type"," is the page type slug (top-level, not inside ",[574,2369,936],{},[653,2371,2372,2373,2375,2376],{},"SEO metadata is under ",[574,2374,862],{},", not scattered through ",[574,2377,1900],{},[653,2379,2380,863,2383,2386,2387,2390],{},[574,2381,2382],{},"id",[574,2384,2385],{},"publishedAt",", and ",[574,2388,2389],{},"updatedAt"," are always top-level",[567,2392,2393,2395,2396,2399,2400,863,2403,863,2406,859,2409,863,2412,863,2415,2386,2418,2421,2422,2425],{},[574,2394,862],{}," exposes ",[606,2397,2398],{},"seven"," fields: ",[574,2401,2402],{},"metaTitle",[574,2404,2405],{},"metaDescription",[574,2407,2408],{},"metaRobots",[574,2410,2411],{},"ogTitle",[574,2413,2414],{},"ogDescription",[574,2416,2417],{},"ogImage",[574,2419,2420],{},"canonicalUrl",". Any of them may be ",[574,2423,2424],{},"undefined","\nif unset on the content.",[678,2427,2428],{},[567,2429,2430,2441,2442,2445,2446,2448,2449,2451,2452,2455,2456,2459,2460,2463,2464,2466,2467,2469,2470,2473,2474,2476,2477,2480,2481,2484],{},[606,2431,2432,2433,2436,2437,2440],{},"⚠️ ",[574,2434,2435],{},"seo.metaTitle"," may contain a leading type prefix — ",[574,2438,2439],{},"page.name"," will not."," The\nimporter copies ButterCMS's ",[574,2443,2444],{},"meta_title"," value verbatim into ",[574,2447,2435],{},". ButterCMS\nusers frequently set ",[574,2450,2444],{}," to strings like ",[574,2453,2454],{},"\"Product Page: My Title\""," in the\nadmin — that prefix is ",[606,2457,2458],{},"not"," stripped on import, so ",[574,2461,2462],{},"page.seo.metaTitle"," can come\nthrough as ",[574,2465,2454],{}," while ",[574,2468,2439],{}," is the clean ",[574,2471,2472],{},"\"My Title\"",". If\nyou render ",[574,2475,2435],{}," as your ",[574,2478,2479],{},"\u003Ctitle>"," tag, strip any leading ",[574,2482,2483],{},"\"\u003CType>: \""," prefix\nin your adapter (or clean it in the TentoCMS admin post-import) — the importer does not\ndo this automatically.",[678,2486,2487,2509,2609,2642,2692,2824,2829,2913],{},[567,2488,2489,2492,2493,2495,2496,2499,2500,2508],{},[606,2490,2491],{},"🔴 Custom SEO components are reshaped on import — audit before you migrate."," Only the seven\nstandard fields above populate ",[574,2494,862],{},". If your ButterCMS content stored SEO in a custom\ncomponent (e.g. ",[574,2497,2498],{},"fields.meta = { meta_title, meta_description, meta_canonical, page_path }","), the\nmigration ",[606,2501,2502,2503,2505,2506],{},"moves the recognised SEO fields out of ",[574,2504,936],{}," into ",[574,2507,862],{}," and leaves anything\nelse behind. After import:",[650,2510,2511,2540,2567],{},[653,2512,2513,2514,863,2516,863,2519,2522,2523,2529,2530,2532,2533,2535,2536,2539],{},"Recognised SEO fields (",[574,2515,2444],{},[574,2517,2518],{},"meta_description",[574,2520,2521],{},"meta_canonical",", …) are ",[606,2524,2525,2526],{},"gone from\n",[574,2527,2528],{},"fields.meta"," (now under ",[574,2531,862],{},"), and ",[574,2534,862],{}," is often only ",[570,2537,2538],{},"partially"," populated.",[653,2541,2542,2543,2546,2547,2550,2551,2553,2554,2557,2558,2560,2561,2563,2564,736],{},"A non-SEO field (e.g. ",[574,2544,2545],{},"page_path",") that is a ",[606,2548,2549],{},"real field in your Butter schema survives"," in\n",[574,2552,2528],{}," — even when empty. ButterCMS sends defined fields as ",[574,2555,2556],{},"\"\""," when unpopulated (it\ndoes ",[570,2559,2458],{}," omit them), and the importer keeps non-SEO sub-fields regardless of value, so a\ndefined-but-empty ",[574,2562,2545],{}," comes through as ",[574,2565,2566],{},"fields.meta = { page_path: \"\" }",[653,2568,2569,2572,2573,2575,2576,2579,2580,2582,2583,2585,2586,2588,2589,2591,2592,2601,2602,2605,2606,2608],{},[606,2570,2571],{},"A container holding only recognised SEO fields is dropped entirely"," — ",[574,2574,2528],{}," becomes\n",[574,2577,2578],{},"null","\u002F",[574,2581,2424],{},". So if you expected ",[574,2584,2545],{}," but ",[574,2587,2528],{}," is ",[574,2590,2578],{},", the cause is\nthat ",[606,2593,2594,2596,2597,2600],{},[574,2595,2545],{}," is not actually a defined field in your Butter ",[574,2598,2599],{},"meta"," schema"," — commonly a\nleftover\u002Fredundant property in your app's types that the CMS never sent (this is what BINQ hit;\nverify against your real source schema). To recover a routing path, use ",[574,2603,2604],{},"seo.canonicalUrl","\n(preserved when ",[574,2607,2521],{}," was set) — BINQ used exactly this fallback.",[567,2610,2611,2612,2615,2616,2618,2619,693,2622,2625,2626,2629,2630,2632,2633,2635,2636,2638,2639,2641],{},"This still bites: ",[574,2613,2614],{},"data.fields.meta"," being ",[574,2617,2424],{}," means reading\n",[574,2620,2621],{},"data.fields.meta.meta_canonical",[570,2623,2624],{},"without"," optional chaining ",[606,2627,2628],{},"throws at SSR and 500s the\npage",". (Even if ",[574,2631,2528],{}," were to survive on some pages, a moved field like ",[574,2634,2521],{},"\njust reads ",[574,2637,2424],{}," there — no crash, but no value either: read it from ",[574,2640,862],{},".)",[567,2643,2644,2647,2648,2651,2652,2655,2656,2658,2659,2661,2662,2664,2665,2667,2668,2671,2672,2675,2676,863,2679,2682,2683,2685,2686,2689,2690,1165],{},[606,2645,2646],{},"Before migrating:"," audit every ",[574,2649,2650],{},"fields.\u003Cx>"," your app reads, and check it against your ",[606,2653,2654],{},"actual\nButter schema"," — a field your app references but the CMS doesn't define (a redundant type) won't\narrive, and if it was the container's only non-SEO field, ",[574,2657,2528],{}," will be ",[574,2660,2578],{},". Read SEO\nfrom ",[574,2663,862],{}," (or rebuild the old shape in a boundary adapter), and re-derive routing paths from\n",[574,2666,2604],{}," rather than relying on ",[574,2669,2670],{},"fields.meta.page_path",". Also audit ",[606,2673,2674],{},"reference fields","\n(e.g. ",[574,2677,2678],{},"author",[574,2680,2681],{},"category","): these are frequently unset on real imported content — an unset\nsingle-reference resolves to ",[574,2684,2424],{},", so reading ",[574,2687,2688],{},"fields.author.name"," without optional\nchaining SSR-500s. Apply the same optional-chaining \u002F adapter-default discipline to reference\nreads as you would to ",[574,2691,2528],{},[611,2693,2695],{"className":1036,"code":2694,"language":1038,"meta":616,"style":616},"fields.meta = {\n  meta_title: page.seo?.metaTitle,\n  meta_description: page.seo?.metaDescription,\n  meta_canonical: page.seo?.canonicalUrl,        \u002F\u002F often empty after import\n  \u002F\u002F page_path: prefer seo.canonicalUrl. fields.meta?.page_path only exists if page_path is a\n  \u002F\u002F real (defined) field in your Butter meta schema — if it's a redundant app-side type, fields.meta is null.\n  page_path: page.seo?.canonicalUrl ?? page.fields?.meta?.page_path,\n}\n",[574,2696,2697,2710,2732,2751,2773,2778,2783,2820],{"__ignoreMap":616},[620,2698,2699,2701,2703,2706,2708],{"class":622,"line":623},[620,2700,936],{"class":1054},[620,2702,736],{"class":1061},[620,2704,2705],{"class":1054},"meta ",[620,2707,1081],{"class":1061},[620,2709,1518],{"class":1061},[620,2711,2712,2715,2717,2720,2722,2725,2728,2730],{"class":622,"line":637},[620,2713,2714],{"class":1161},"  meta_title",[620,2716,1165],{"class":1061},[620,2718,2719],{"class":1054}," page",[620,2721,736],{"class":1061},[620,2723,2724],{"class":1054},"seo",[620,2726,2727],{"class":1061},"?.",[620,2729,2402],{"class":1054},[620,2731,859],{"class":1061},[620,2733,2734,2737,2739,2741,2743,2745,2747,2749],{"class":622,"line":1071},[620,2735,2736],{"class":1161},"  meta_description",[620,2738,1165],{"class":1061},[620,2740,2719],{"class":1054},[620,2742,736],{"class":1061},[620,2744,2724],{"class":1054},[620,2746,2727],{"class":1061},[620,2748,2405],{"class":1054},[620,2750,859],{"class":1061},[620,2752,2753,2756,2758,2760,2762,2764,2766,2768,2770],{"class":622,"line":1102},[620,2754,2755],{"class":1161},"  meta_canonical",[620,2757,1165],{"class":1061},[620,2759,2719],{"class":1054},[620,2761,736],{"class":1061},[620,2763,2724],{"class":1054},[620,2765,2727],{"class":1061},[620,2767,2420],{"class":1054},[620,2769,1191],{"class":1061},[620,2771,2772],{"class":640},"        \u002F\u002F often empty after import\n",[620,2774,2775],{"class":622,"line":1109},[620,2776,2777],{"class":640},"  \u002F\u002F page_path: prefer seo.canonicalUrl. fields.meta?.page_path only exists if page_path is a\n",[620,2779,2780],{"class":622,"line":1115},[620,2781,2782],{"class":640},"  \u002F\u002F real (defined) field in your Butter meta schema — if it's a redundant app-side type, fields.meta is null.\n",[620,2784,2785,2788,2790,2792,2794,2796,2798,2801,2804,2806,2808,2810,2812,2814,2816,2818],{"class":622,"line":1138},[620,2786,2787],{"class":1161},"  page_path",[620,2789,1165],{"class":1061},[620,2791,2719],{"class":1054},[620,2793,736],{"class":1061},[620,2795,2724],{"class":1054},[620,2797,2727],{"class":1061},[620,2799,2800],{"class":1054},"canonicalUrl ",[620,2802,2803],{"class":1061},"??",[620,2805,2719],{"class":1054},[620,2807,736],{"class":1061},[620,2809,936],{"class":1054},[620,2811,2727],{"class":1061},[620,2813,2599],{"class":1054},[620,2815,2727],{"class":1061},[620,2817,2545],{"class":1054},[620,2819,859],{"class":1061},[620,2821,2822],{"class":622,"line":1158},[620,2823,2046],{"class":1061},[567,2825,2826,2827,1165],{},"For reference fields in a boundary adapter, coerce unset references to empty-string objects\nrather than leaving them ",[574,2828,2424],{},[611,2830,2832],{"className":1036,"code":2831,"language":1038,"meta":616,"style":616},"\u002F\u002F Unset reference → undefined; provide a safe default so deep reads don't throw\nconst author = resolvedPage.fields.author ?? { name: '', bio: '' }\n\u002F\u002F Or use optional chaining everywhere the reference is read directly:\nconst authorName = resolvedPage.fields.author?.name\n",[574,2833,2834,2839,2884,2889],{"__ignoreMap":616},[620,2835,2836],{"class":622,"line":623},[620,2837,2838],{"class":640},"\u002F\u002F Unset reference → undefined; provide a safe default so deep reads don't throw\n",[620,2840,2841,2843,2846,2848,2851,2853,2855,2857,2860,2862,2864,2867,2869,2872,2874,2877,2879,2881],{"class":622,"line":637},[620,2842,1075],{"class":1074},[620,2844,2845],{"class":1054}," author ",[620,2847,1081],{"class":1061},[620,2849,2850],{"class":1054}," resolvedPage",[620,2852,736],{"class":1061},[620,2854,936],{"class":1054},[620,2856,736],{"class":1061},[620,2858,2859],{"class":1054},"author ",[620,2861,2803],{"class":1061},[620,2863,1120],{"class":1061},[620,2865,2866],{"class":1161}," name",[620,2868,1165],{"class":1061},[620,2870,2871],{"class":1061}," ''",[620,2873,1191],{"class":1061},[620,2875,2876],{"class":1161}," bio",[620,2878,1165],{"class":1061},[620,2880,2871],{"class":1061},[620,2882,2883],{"class":1061}," }\n",[620,2885,2886],{"class":622,"line":1071},[620,2887,2888],{"class":640},"\u002F\u002F Or use optional chaining everywhere the reference is read directly:\n",[620,2890,2891,2893,2896,2898,2900,2902,2904,2906,2908,2910],{"class":622,"line":1102},[620,2892,1075],{"class":1074},[620,2894,2895],{"class":1054}," authorName ",[620,2897,1081],{"class":1061},[620,2899,2850],{"class":1054},[620,2901,736],{"class":1061},[620,2903,936],{"class":1054},[620,2905,736],{"class":1061},[620,2907,2678],{"class":1054},[620,2909,2727],{"class":1061},[620,2911,2912],{"class":1054},"name\n",[567,2914,2915,2921,2922,2924,2925,2927,2928,2931,2932,2943,2944,2947,2948,2950,2951,2954,2955,2958,2959,2962,2963,2965,2966,2572,2968,2970,2971,2973,2974,2976,2977,2979,2980,2983,2984,2986,2987,2989,2990,2993,2994,2996,2997,2999,3000,693,3003,3006,3007,3009,3010,3012],{},[606,2916,2917,2918,2920],{},"If you adopt the boundary-adapter strategy, always synthesise a non-null ",[574,2919,2528],{},"\nobject"," (rebuilt from ",[574,2923,862],{},"), even when all values are ",[574,2926,2424],{}," — so consumers that\nread ",[574,2929,2930],{},"fields.meta.meta_title"," without optional chaining don't SSR-500 on pages where the\ncontainer was dropped entirely. Also ",[606,2933,2934,2935,2938,2939,2579,2941],{},"default the individual SEO field values to ",[574,2936,2937],{},"''","\n(empty string), not ",[574,2940,2578],{},[574,2942,2424],{},", to match ButterCMS's empty-string semantics.\nTento returns ",[574,2945,2946],{},"seo.canonicalUrl: null"," (which becomes ",[574,2949,2424],{}," after ",[574,2952,2953],{},"stripNulls",") when\nunset; ButterCMS returned ",[574,2956,2957],{},"meta_canonical: \"\"",". Code like ",[574,2960,2961],{},"meta_canonical ?? '\u002F'"," behaves\ndifferently on ",[574,2964,2424],{}," vs ",[574,2967,2556],{},[574,2969,2803],{}," fires on ",[574,2972,2424],{}," but not on ",[574,2975,2556],{},". So a page that\nreturned ",[574,2978,2556],{}," under ButterCMS (where ",[574,2981,2982],{},"?? '\u002F'"," left it unchanged) can silently start emitting\n",[574,2985,2579],{}," as its canonical under Tento (which returns ",[574,2988,2424],{},"). (Note ",[574,2991,2992],{},"||"," and ",[574,2995,2803],{}," differ here:\n",[574,2998,2556],{}," is falsy, so ",[574,3001,3002],{},"|| '\u002F'",[570,3004,3005],{},"would"," fire, but ",[574,3008,2982],{}," does not.) Normalise to ",[574,3011,2937],{}," in the\nadapter for parity.",[678,3014,3015],{},[567,3016,3017,3020],{},[606,3018,3019],{},"Field presence in the target is driven by content, not the source schema."," A field that\nexists in your ButterCMS schema but is empty (or absent) across every source page is simply\nnot created in TentoCMS — there is no placeholder or empty field for it. Do not assume that\nevery source field will appear in the imported schema; verify the live schema after import and\nrely on optional chaining for any field that may be absent.",[3022,3023,3025,3026],"h4",{"id":3024},"how-buttercms-seo-fields-map-to-pageseo","How ButterCMS SEO fields map to ",[574,3027,862],{},[567,3029,3030,3031,3034,3036,3037,3040,3041,3044,3045,863,3047,863,3049,863,3052,863,3055,863,3058,736],{},"The importer auto-detects SEO in two places and maps a fixed set of names onto ",[606,3032,3033],{},"six of the seven",[574,3035,862],{}," fields. ",[606,3038,3039],{},"Top-level"," fields must carry a prefix; fields ",[606,3042,3043],{},"inside a recognised SEO\ncontainer"," may use bare names. Recognised container keys (underscores optional, case-insensitive):\n",[574,3046,2599],{},[574,3048,2724],{},[574,3050,3051],{},"seo_meta",[574,3053,3054],{},"meta_data",[574,3056,3057],{},"seo_data",[574,3059,3060],{},"seo_fields",[3062,3063,3064,3079],"table",{},[3065,3066,3067],"thead",{},[3068,3069,3070,3074],"tr",{},[3071,3072,3073],"th",{},"Source field name(s)",[3071,3075,3076,3077],{},"→ ",[574,3078,862],{},[3080,3081,3082,3102,3118,3134,3148,3162],"tbody",{},[3068,3083,3084,3098],{},[3085,3086,3087,3089,3090,3093,3094,3097],"td",{},[574,3088,2444],{}," \u002F ",[574,3091,3092],{},"seo_title"," — or bare ",[574,3095,3096],{},"title"," inside a container",[3085,3099,3100],{},[574,3101,2402],{},[3068,3103,3104,3114],{},[3085,3105,3106,3089,3108,3093,3111],{},[574,3107,2518],{},[574,3109,3110],{},"seo_description",[574,3112,3113],{},"description",[3085,3115,3116],{},[574,3117,2405],{},[3068,3119,3120,3130],{},[3085,3121,3122,3089,3124,3093,3127],{},[574,3123,2521],{},[574,3125,3126],{},"canonical_url",[574,3128,3129],{},"canonical",[3085,3131,3132],{},[574,3133,2420],{},[3068,3135,3136,3144],{},[3085,3137,3138,3089,3141],{},[574,3139,3140],{},"og_title",[574,3142,3143],{},"open_graph_title",[3085,3145,3146],{},[574,3147,2411],{},[3068,3149,3150,3158],{},[3085,3151,3152,3089,3155],{},[574,3153,3154],{},"og_description",[574,3156,3157],{},"open_graph_description",[3085,3159,3160],{},[574,3161,2414],{},[3068,3163,3164,3178],{},[3085,3165,3166,3089,3169,3089,3172,3093,3175],{},[574,3167,3168],{},"social_media_image",[574,3170,3171],{},"og_image",[574,3173,3174],{},"open_graph_image",[574,3176,3177],{},"image",[3085,3179,3180],{},[574,3181,2417],{},[567,3183,3184],{},"Notes:",[650,3186,3187,3193,3204,3215,3221],{},[653,3188,3189,3192],{},[606,3190,3191],{},"Top-level wins:"," if a top-level field and a container sub-field both map to the same target, the top-level value is used.",[653,3194,3195,3200,3201,3203],{},[606,3196,3197,3199],{},[574,3198,2408],{}," is never auto-mapped"," — pages default to ",[574,3202,2237],{},"; set robots in TentoCMS after import if you need something else.",[653,3205,3206,1506,3211,3214],{},[606,3207,3208,3210],{},[574,3209,2417],{}," only captures ButterCMS CDN URLs",[574,3212,3213],{},"https:\u002F\u002Fcdn.buttercms.com\u002F...",") so they resolve to migrated media; other image URLs are skipped.",[653,3216,3217,3220],{},[606,3218,3219],{},"Strings only"," — empty or non-string values are ignored.",[653,3222,3223,3226,3227,3230,3231,3233,3234,3236,3237,3240,3241,3243],{},[606,3224,3225],{},"Anything not in this table is not treated as SEO"," — it stays in your content as long as it's a ",[606,3228,3229],{},"real field in your Butter schema"," (it survives even when empty, since ButterCMS sends defined fields as ",[574,3232,2556],{},"). But a ",[574,3235,2599],{}," container left with ",[570,3238,3239],{},"only"," recognised SEO fields is dropped entirely, so a ",[574,3242,2545],{}," that isn't actually defined in your schema (e.g. a redundant app-side type) won't be there — see the warning above. There's no auto-mapping for Twitter-card tags or arbitrary custom meta fields.",[3022,3245,3247],{"id":3246},"other-page-shape-changes-to-verify","Other page-shape changes to verify",[650,3249,3250,3276],{},[653,3251,3252,3255,3256,3259,3260,863,3263,3266,3267,863,3270,3259,3273,736],{},[606,3253,3254],{},"Page-type slugs are kebab-case",": ",[574,3257,3258],{},"main_page"," → ",[574,3261,3262],{},"main-page",[574,3264,3265],{},"legal_page"," →\n",[574,3268,3269],{},"legal-page",[574,3271,3272],{},"campaign_page",[574,3274,3275],{},"campaign-page",[653,3277,3278,3281,3282,3285,3286,3289,3290,3293,3294,3297,3298,3301,3302,3305,3306,2993,3308,693,3311,3314,3315,3318,3319,3321,3322,3325,3326,2641,3330,3386,3403],{},[606,3279,3280],{},"Verify the final page-type slugs after import."," Slug normalisation and the typeless-pages\ncatch-all (see below) both produce slugs that differ from your ButterCMS source names. A\nmismatched slug silently breaks any ",[574,3283,3284],{},"page.type === '...'"," discriminant, so confirm the\nimported slugs before wiring up routing. ",[606,3287,3288],{},"To enumerate the imported page types",", call the\npublic ",[574,3291,3292],{},"GET \u002Fapi\u002Fv1\u002Fschemas"," endpoint (API-key auth). The response is wrapped:\n",[574,3295,3296],{},"{ data: { pageTypes, components, collections } }"," — read ",[574,3299,3300],{},".data"," to reach the arrays.\n",[574,3303,3304],{},"data.pageTypes[]"," lists every page type's ",[574,3307,1593],{},[574,3309,3310],{},"name",[570,3312,3313],{},"regardless of publish state",". (Listing pages —\n",[574,3316,3317],{},"GET \u002Fapi\u002Fv1\u002Fpages?limit=100"," — also surfaces each page's ",[574,3320,915],{},", but only for types that\nhave at least one ",[606,3323,3324],{},"published"," page, so it under-reports types whose content imported as\ndrafts; see ",[579,3327,3329],{"href":3328},"#publish-status-of-imported-content","Publish status of imported content",[678,3331,3332,3359,3377],{},[567,3333,3334,3341,3342,3345,3346,3349,3350,3352,3353,3355,3356,3358],{},[606,3335,3336,3337,3340],{},"🔴 Typeless ButterCMS pages land in a synthetic ",[574,3338,3339],{},"legacy-pages"," type — not a rename.","\nButterCMS allows pages to have no ",[574,3343,3344],{},"page_type"," (an empty string). TentoCMS requires every\npage to belong to a page type, so the importer buckets all such typeless pages into a\nsynthetic page type named ",[606,3347,3348],{},"\"Legacy Pages\""," (slug ",[574,3351,3339],{},"). A page appearing under\n",[574,3354,3339],{}," was not renamed from something else — it simply had no ",[574,3357,3344],{}," set in\nButterCMS and landed in this catch-all.",[567,3360,3361,3364,3365,3367,3368,3370,3371,3373,3374,3376],{},[606,3362,3363],{},"Watch out for heterogeneous typeless pages."," The ",[574,3366,3339],{}," type's field schema is\ninferred from a single sample typeless page. If your typeless pages don't all share the same\nfield structure, some fields will be missing or ",[574,3369,2424],{}," on pages that don't match the\nsample. Discriminate on the top-level ",[574,3372,915],{}," field of the source content and treat\n",[574,3375,3339],{}," as a mixed bucket — don't assume all items in it have the same shape.",[567,3378,3379,3380,3382,3383,3385],{},"Use ",[574,3381,3292],{}," to confirm the final slugs for every imported type, including\n",[574,3384,3339],{}," if typeless pages were present in the source.",[678,3387,3388],{},[567,3389,3390,3393,3394,3397,3398,3402],{},[606,3391,3392],{},"💡 Use the Import report to see typeless-page counts and slug mappings."," After running an\nimport, the migration detail screen shows an ",[606,3395,3396],{},"Import report"," — see\n",[579,3399,3401],{"href":3400},"#verifying-completeness-with-the-import-report","Verifying completeness with the Import report","\nbelow.",[678,3404,3405],{},[567,3406,3407,3417,3418,3423,3424,3427,3428,3431,3432,3434,3435,3438,3439,3441,3442,3445],{},[606,3408,3409,3410,3413,3414,736],{},"💡 Raw REST auth — use ",[574,3411,3412],{},"X-API-Key",", not ",[574,3415,3416],{},"Authorization: Bearer"," Any time this\nguide suggests calling an endpoint directly with curl or fetch, pass the key as the\n",[606,3419,3420],{},[574,3421,3422],{},"X-API-Key: \u003Ckey>"," header (or ",[574,3425,3426],{},"?api_key=\u003Ckey>"," query param). The ",[574,3429,3430],{},"Authorization: Bearer \u003Ckey>"," header is ",[606,3433,2458],{}," recognised and returns\n",[574,3436,3437],{},"401 {\"error\":{\"code\":\"UNAUTHORIZED\",\"message\":\"API key required. Provide via X-API-Key header or api_key query param.\"}}",".\nA read-only public key (",[574,3440,716],{},") is sufficient for schema enumeration and all\nother ",[574,3443,3444],{},"GET"," calls mentioned in this guide. This same header applies to the redirect\nhandling and WAF-debugging calls described later.",[3022,3447,3401],{"id":3448},"verifying-completeness-with-the-import-report",[567,3450,3451,3452,3454,3455,736],{},"After running an import, open the migration detail screen in the TentoCMS admin UI. A new\n",[606,3453,3396],{}," panel summarises what was discovered, created, and skipped — check it\nbefore wiring up any application code so nothing is silently missed. The same data is\navailable on the migration record at ",[574,3456,3457],{},"stats.report",[567,3459,3460],{},"The report has three parts:",[567,3462,3463,3466],{},[606,3464,3465],{},"Schema completeness diff"," — broken down by kind (page types, components, collections):",[650,3468,3469,3475,3481],{},[653,3470,3471,3474],{},[606,3472,3473],{},"Discovered"," — schemas found in the ButterCMS source.",[653,3476,3477,3480],{},[606,3478,3479],{},"Created"," — schemas imported into Tento.",[653,3482,3483,3486],{},[606,3484,3485],{},"Skipped"," — discovered in ButterCMS but not imported (e.g. a type you deselected, or one\nthe importer could not map). Anything in this column represents a source type that is present\nin ButterCMS but absent in the target — worth reviewing before going live.",[567,3488,3489,3492,3493,859,3496,3499,3500,863,3502,2386,3505,736],{},[606,3490,3491],{},"Slug mappings"," — every created schema's original source name alongside its final Tento slug.\nEntries where the slug was normalised (e.g. ",[574,3494,3495],{},"guide_page → guide-page",[574,3497,3498],{},"Legacy Pages → legacy-pages",") are flagged explicitly. Use this table to learn the exact slugs\nto pass to ",[574,3501,3292],{},[574,3503,3504],{},"pages.list({ pageType })",[574,3506,3507],{},"collections.list()",[567,3509,3510,3513,3514,3516,3517,3519,3520,3522,3523,3525],{},[606,3511,3512],{},"Typeless pages"," — a count of ButterCMS pages that had no ",[574,3515,3344],{}," and were imported\nunder the ",[574,3518,3339],{}," catch-all bucket (see the ",[574,3521,3339],{}," note above). A non-zero\ncount means ",[574,3524,3339],{}," is a mixed bucket — check its shape before consuming it.",[678,3527,3528],{},[567,3529,3530,693,3533,3535,3536,3539,3540,3542],{},[606,3531,3532],{},"💡 The Import report is the authoritative source for final slugs.",[574,3534,3292],{},"\ntells you what types exist now; the report tells you ",[570,3537,3538],{},"how each source name became that slug",",\nwhich is what you need when updating discriminants like ",[574,3541,3284],{}," in application code.",[650,3544,3545,3561],{},[653,3546,3547,3552,3553,3556,3557,3560],{},[606,3548,3549,3550],{},"Top-level ButterCMS fields may move into ",[574,3551,936],{}," (e.g. a ",[574,3554,3555],{},"brand"," field becomes\n",[574,3558,3559],{},"fields.brand",").",[653,3562,3563,3569,3570,3572,3573,3575,3576,3579,3580,3582,3583,736],{},[606,3564,3565,3566,736],{},"Watch for a redundant ",[574,3567,3568],{},"fields.page_type"," A custom ",[574,3571,3344],{}," content field can\nsurvive alongside the real top-level ",[574,3574,2366],{},", with a ",[570,3577,3578],{},"different"," value. Always\ndiscriminate on ",[574,3581,2366],{},", never ",[574,3584,3568],{},[598,3586,22],{"id":3587},"collections",[567,3589,3590],{},"TentoCMS uses kebab-case slugs for collection types (not snake_case). System fields are prefixed with an underscore to avoid collisions with your content fields.",[611,3592,3594],{"className":1036,"code":3593,"language":1038,"meta":616,"style":616},"\u002F\u002F ButterCMS collection item\n{\n  meta: {\n    id: 42,\n    slug: 'footer-nav',\n  },\n  label: 'Footer',\n  url: '\u002Ffooter',\n}\n\n\u002F\u002F TentoCMS collection item\n{\n  _sortOrder: 0,\n  _publishedAt: '2026-01-15T10:00:00Z',\n  _updatedAt: '2026-02-01T14:30:00Z',\n  \u002F\u002F Your content fields are merged at the root level\n  name: 'Footer Navigation',\n  label: 'Footer',\n  url: '\u002Ffooter',\n}\n",[574,3595,3596,3601,3605,3614,3627,3642,3646,3662,3678,3682,3686,3691,3695,3707,3722,3737,3742,3757,3771,3785],{"__ignoreMap":616},[620,3597,3598],{"class":622,"line":623},[620,3599,3600],{"class":640},"\u002F\u002F ButterCMS collection item\n",[620,3602,3603],{"class":622,"line":637},[620,3604,1155],{"class":1061},[620,3606,3607,3610,3612],{"class":622,"line":1071},[620,3608,3609],{"class":626},"  meta",[620,3611,1165],{"class":1061},[620,3613,1518],{"class":1061},[620,3615,3616,3619,3621,3625],{"class":622,"line":1102},[620,3617,3618],{"class":626},"    id",[620,3620,1165],{"class":1061},[620,3622,3624],{"class":3623},"sbssI"," 42",[620,3626,859],{"class":1061},[620,3628,3629,3631,3633,3635,3638,3640],{"class":622,"line":1109},[620,3630,1932],{"class":626},[620,3632,1165],{"class":1061},[620,3634,1062],{"class":1061},[620,3636,3637],{"class":630},"footer-nav",[620,3639,1091],{"class":1061},[620,3641,859],{"class":1061},[620,3643,3644],{"class":622,"line":1115},[620,3645,2179],{"class":1061},[620,3647,3648,3651,3653,3655,3658,3660],{"class":622,"line":1138},[620,3649,3650],{"class":626},"  label",[620,3652,1165],{"class":1061},[620,3654,1062],{"class":1061},[620,3656,3657],{"class":630},"Footer",[620,3659,1091],{"class":1061},[620,3661,859],{"class":1061},[620,3663,3664,3667,3669,3671,3674,3676],{"class":622,"line":1158},[620,3665,3666],{"class":626},"  url",[620,3668,1165],{"class":1061},[620,3670,1062],{"class":1061},[620,3672,3673],{"class":630},"\u002Ffooter",[620,3675,1091],{"class":1061},[620,3677,859],{"class":1061},[620,3679,3680],{"class":622,"line":1177},[620,3681,2046],{"class":1061},[620,3683,3684],{"class":622,"line":1197},[620,3685,1106],{"emptyLinePlaceholder":1105},[620,3687,3688],{"class":622,"line":1550},[620,3689,3690],{"class":640},"\u002F\u002F TentoCMS collection item\n",[620,3692,3693],{"class":622,"line":1556},[620,3694,1155],{"class":1061},[620,3696,3697,3700,3702,3705],{"class":622,"line":1564},[620,3698,3699],{"class":626},"  _sortOrder",[620,3701,1165],{"class":1061},[620,3703,3704],{"class":3623}," 0",[620,3706,859],{"class":1061},[620,3708,3709,3712,3714,3716,3718,3720],{"class":622,"line":1622},[620,3710,3711],{"class":626},"  _publishedAt",[620,3713,1165],{"class":1061},[620,3715,1062],{"class":1061},[620,3717,2325],{"class":630},[620,3719,1091],{"class":1061},[620,3721,859],{"class":1061},[620,3723,3724,3727,3729,3731,3733,3735],{"class":622,"line":1641},[620,3725,3726],{"class":626},"  _updatedAt",[620,3728,1165],{"class":1061},[620,3730,1062],{"class":1061},[620,3732,2342],{"class":630},[620,3734,1091],{"class":1061},[620,3736,859],{"class":1061},[620,3738,3739],{"class":622,"line":1670},[620,3740,3741],{"class":640},"  \u002F\u002F Your content fields are merged at the root level\n",[620,3743,3744,3746,3748,3750,3753,3755],{"class":622,"line":1678},[620,3745,2080],{"class":626},[620,3747,1165],{"class":1061},[620,3749,1062],{"class":1061},[620,3751,3752],{"class":630},"Footer Navigation",[620,3754,1091],{"class":1061},[620,3756,859],{"class":1061},[620,3758,3759,3761,3763,3765,3767,3769],{"class":622,"line":1684},[620,3760,3650],{"class":626},[620,3762,1165],{"class":1061},[620,3764,1062],{"class":1061},[620,3766,3657],{"class":630},[620,3768,1091],{"class":1061},[620,3770,859],{"class":1061},[620,3772,3773,3775,3777,3779,3781,3783],{"class":622,"line":2096},[620,3774,3666],{"class":626},[620,3776,1165],{"class":1061},[620,3778,1062],{"class":1061},[620,3780,3673],{"class":630},[620,3782,1091],{"class":1061},[620,3784,859],{"class":1061},[620,3786,3787],{"class":622,"line":2112},[620,3788,2046],{"class":1061},[678,3790,3791],{},[567,3792,3793,3806,3807,3810,3811,3814,3815,3818,3819,3821,3822,3825],{},[606,3794,3795,3796,863,3799,3802,3803,736],{},"⚠️ Collection items have only three system fields — there is no ",[574,3797,3798],{},"_id",[574,3800,3801],{},"_slug",", or\n",[574,3804,3805],{},"_name"," An item is ",[574,3808,3809],{},"{ _sortOrder, _publishedAt, _updatedAt }"," merged with your\ncontent fields at the root. Reading ",[574,3812,3813],{},"item._slug"," or ",[574,3816,3817],{},"item._name"," returns ",[574,3820,2424],{},".\nTo find an item by a human-readable name, filter on a ",[606,3823,3824],{},"content field"," (below), not on\na system slug.",[567,3827,3828],{},"System fields on collection items:",[3062,3830,3831,3844],{},[3065,3832,3833],{},[3068,3834,3835,3838,3841],{},[3071,3836,3837],{},"Field",[3071,3839,3840],{},"Type",[3071,3842,3843],{},"Description",[3080,3845,3846,3860,3874],{},[3068,3847,3848,3852,3857],{},[3085,3849,3850],{},[574,3851,893],{},[3085,3853,3854],{},[574,3855,3856],{},"number",[3085,3858,3859],{},"Sort position",[3068,3861,3862,3867,3871],{},[3085,3863,3864],{},[574,3865,3866],{},"_publishedAt",[3085,3868,3869],{},[574,3870,1752],{},[3085,3872,3873],{},"Publication timestamp",[3068,3875,3876,3881,3885],{},[3085,3877,3878],{},[574,3879,3880],{},"_updatedAt",[3085,3882,3883],{},[574,3884,1752],{},[3085,3886,3887],{},"Last update timestamp",[567,3889,3890,693,3893,3895,3896,693,3898,3901,3902,3904,3905,3907,3908,3911,3912,3915],{},[606,3891,3892],{},"Fetching collection items — treat the result as an unordered set.",[574,3894,3507],{},"\nreturns items in ",[574,3897,3866],{},[606,3899,3900],{},"descending"," order by default — ",[570,3903,2458],{}," the authored\n",[574,3906,893],{}," order. So the primary rule is: ",[606,3909,3910],{},"select the item you want by a content field,\nnever by array position."," Positional access (",[574,3913,3914],{},"items[0]",") is the natural thing a migrating\nButter app already does, and it silently returns the wrong item — a quiet content regression,\nnot a crash:",[611,3917,3919],{"className":1036,"code":3918,"language":1038,"meta":616,"style":616},"\u002F\u002F ✅ Do this — order-independent and intent-explicit\nconst header = await tento.collections.list('navigation-bar', {\n  filters: [{ field: 'name', operator: 'eq', value: 'Header Navigation' }],\n})\nconst headerNav = header.data[0] \u002F\u002F exactly the item you asked for\n\n\u002F\u002F ❌ Not this — `navigation-bar` comes back as [Footer, Header], so items[0] is the FOOTER\n\u002F\u002F    (which has an empty navigation_links array) → an empty header menu, no error.\nconst navItems = await tento.collections.list('navigation-bar')\nconst wrong = navItems.data[0]\n",[574,3920,3921,3926,3961,4020,4026,4052,4056,4061,4066,4097],{"__ignoreMap":616},[620,3922,3923],{"class":622,"line":623},[620,3924,3925],{"class":640},"\u002F\u002F ✅ Do this — order-independent and intent-explicit\n",[620,3927,3928,3930,3933,3935,3937,3939,3941,3943,3945,3948,3950,3952,3955,3957,3959],{"class":622,"line":637},[620,3929,1075],{"class":1074},[620,3931,3932],{"class":1054}," header ",[620,3934,1081],{"class":1061},[620,3936,1570],{"class":1050},[620,3938,1573],{"class":1054},[620,3940,736],{"class":1061},[620,3942,3587],{"class":1054},[620,3944,736],{"class":1061},[620,3946,3947],{"class":1084},"list",[620,3949,1088],{"class":1054},[620,3951,1091],{"class":1061},[620,3953,3954],{"class":630},"navigation-bar",[620,3956,1091],{"class":1061},[620,3958,1191],{"class":1061},[620,3960,1518],{"class":1061},[620,3962,3963,3966,3968,3971,3974,3977,3979,3981,3983,3985,3987,3990,3992,3994,3997,3999,4001,4004,4006,4008,4011,4013,4015,4018],{"class":622,"line":1071},[620,3964,3965],{"class":1161},"  filters",[620,3967,1165],{"class":1061},[620,3969,3970],{"class":1054}," [",[620,3972,3973],{"class":1061},"{",[620,3975,3976],{"class":1161}," field",[620,3978,1165],{"class":1061},[620,3980,1062],{"class":1061},[620,3982,3310],{"class":630},[620,3984,1091],{"class":1061},[620,3986,1191],{"class":1061},[620,3988,3989],{"class":1161}," operator",[620,3991,1165],{"class":1061},[620,3993,1062],{"class":1061},[620,3995,3996],{"class":630},"eq",[620,3998,1091],{"class":1061},[620,4000,1191],{"class":1061},[620,4002,4003],{"class":1161}," value",[620,4005,1165],{"class":1061},[620,4007,1062],{"class":1061},[620,4009,4010],{"class":630},"Header Navigation",[620,4012,1091],{"class":1061},[620,4014,1126],{"class":1061},[620,4016,4017],{"class":1054},"]",[620,4019,859],{"class":1061},[620,4021,4022,4024],{"class":622,"line":1102},[620,4023,1200],{"class":1061},[620,4025,1099],{"class":1054},[620,4027,4028,4030,4033,4035,4038,4040,4043,4046,4049],{"class":622,"line":1109},[620,4029,1075],{"class":1074},[620,4031,4032],{"class":1054}," headerNav ",[620,4034,1081],{"class":1061},[620,4036,4037],{"class":1054}," header",[620,4039,736],{"class":1061},[620,4041,4042],{"class":1054},"data[",[620,4044,4045],{"class":3623},"0",[620,4047,4048],{"class":1054},"] ",[620,4050,4051],{"class":640},"\u002F\u002F exactly the item you asked for\n",[620,4053,4054],{"class":622,"line":1115},[620,4055,1106],{"emptyLinePlaceholder":1105},[620,4057,4058],{"class":622,"line":1138},[620,4059,4060],{"class":640},"\u002F\u002F ❌ Not this — `navigation-bar` comes back as [Footer, Header], so items[0] is the FOOTER\n",[620,4062,4063],{"class":622,"line":1158},[620,4064,4065],{"class":640},"\u002F\u002F    (which has an empty navigation_links array) → an empty header menu, no error.\n",[620,4067,4068,4070,4073,4075,4077,4079,4081,4083,4085,4087,4089,4091,4093,4095],{"class":622,"line":1177},[620,4069,1075],{"class":1074},[620,4071,4072],{"class":1054}," navItems ",[620,4074,1081],{"class":1061},[620,4076,1570],{"class":1050},[620,4078,1573],{"class":1054},[620,4080,736],{"class":1061},[620,4082,3587],{"class":1054},[620,4084,736],{"class":1061},[620,4086,3947],{"class":1084},[620,4088,1088],{"class":1054},[620,4090,1091],{"class":1061},[620,4092,3954],{"class":630},[620,4094,1091],{"class":1061},[620,4096,1099],{"class":1054},[620,4098,4099,4101,4104,4106,4109,4111,4113,4115],{"class":622,"line":1197},[620,4100,1075],{"class":1074},[620,4102,4103],{"class":1054}," wrong ",[620,4105,1081],{"class":1061},[620,4107,4108],{"class":1054}," navItems",[620,4110,736],{"class":1061},[620,4112,4042],{"class":1054},[620,4114,4045],{"class":3623},[620,4116,4117],{"class":1054},"]\n",[567,4119,4120,4121,4123],{},"If you genuinely need authored order (e.g. positional rendering of a list), request it\nexplicitly with ",[574,4122,893],{}," ascending rather than re-sorting client-side:",[611,4125,4127],{"className":1036,"code":4126,"language":1038,"meta":616,"style":616},"const ordered = await tento.collections.list('navigation-bar', {\n  sort: '_sortOrder',   \u002F\u002F the system field shown on items; maps to the source order\n})\n\u002F\u002F REST equivalent: GET \u002Fapi\u002Fv1\u002Fcollections\u002Fnavigation-bar?sort=_sortOrder\n\u002F\u002F Descending: GET \u002Fapi\u002Fv1\u002Fcollections\u002Fnavigation-bar?sort=-_sortOrder\n",[574,4128,4129,4162,4180,4186,4191],{"__ignoreMap":616},[620,4130,4131,4133,4136,4138,4140,4142,4144,4146,4148,4150,4152,4154,4156,4158,4160],{"class":622,"line":623},[620,4132,1075],{"class":1074},[620,4134,4135],{"class":1054}," ordered ",[620,4137,1081],{"class":1061},[620,4139,1570],{"class":1050},[620,4141,1573],{"class":1054},[620,4143,736],{"class":1061},[620,4145,3587],{"class":1054},[620,4147,736],{"class":1061},[620,4149,3947],{"class":1084},[620,4151,1088],{"class":1054},[620,4153,1091],{"class":1061},[620,4155,3954],{"class":630},[620,4157,1091],{"class":1061},[620,4159,1191],{"class":1061},[620,4161,1518],{"class":1061},[620,4163,4164,4167,4169,4171,4173,4175,4177],{"class":622,"line":637},[620,4165,4166],{"class":1161},"  sort",[620,4168,1165],{"class":1061},[620,4170,1062],{"class":1061},[620,4172,893],{"class":630},[620,4174,1091],{"class":1061},[620,4176,1191],{"class":1061},[620,4178,4179],{"class":640},"   \u002F\u002F the system field shown on items; maps to the source order\n",[620,4181,4182,4184],{"class":622,"line":1071},[620,4183,1200],{"class":1061},[620,4185,1099],{"class":1054},[620,4187,4188],{"class":622,"line":1102},[620,4189,4190],{"class":640},"\u002F\u002F REST equivalent: GET \u002Fapi\u002Fv1\u002Fcollections\u002Fnavigation-bar?sort=_sortOrder\n",[620,4192,4193],{"class":622,"line":1109},[620,4194,4195],{"class":640},"\u002F\u002F Descending: GET \u002Fapi\u002Fv1\u002Fcollections\u002Fnavigation-bar?sort=-_sortOrder\n",[567,4197,4198,4201,4202,4205,4206,4209,4210,863,4212,863,4215,863,4218,863,4221,863,4224,863,4227,863,4230,4233,4234,4237,4238,4241,4242,4246,4247,4250],{},[606,4199,4200],{},"Filtering by content fields"," uses ",[574,4203,4204],{},"filter[field][op]=value"," query params (or the SDK\n",[574,4207,4208],{},"filters"," array). Operators: ",[574,4211,3996],{},[574,4213,4214],{},"ne",[574,4216,4217],{},"gt",[574,4219,4220],{},"gte",[574,4222,4223],{},"lt",[574,4225,4226],{},"lte",[574,4228,4229],{},"in",[574,4231,4232],{},"contains"," — e.g.\n",[574,4235,4236],{},"?filter[name][eq]=Header Navigation",". See ",[579,4239,67],{"href":4240},"..\u002Fgetting-started\u002Fintroduction"," and the\n",[579,4243,4245],{"href":4244},"..\u002Fapi\u002Foverview","Public API Reference"," for the full filter, sort (",[574,4248,4249],{},"-field"," prefix form), and pagination syntax.",[678,4252,4253],{},[567,4254,4255,4258,4259,4262,4263,4266,4267,4270,4271,4274,4275,4278,4279,736],{},[606,4256,4257],{},"⚠️ Singleton collections return a single object, not an array."," For a singleton\ncollection, ",[574,4260,4261],{},"list()","'s ",[574,4264,4265],{},"data"," is the item itself (",[574,4268,4269],{},"T","), not ",[574,4272,4273],{},"T[]"," — the type is ",[574,4276,4277],{},"T[] | T",".\nGuard before iterating: ",[574,4280,4281],{},"const items = Array.isArray(res.data) ? res.data : [res.data]",[598,4283,4285],{"id":4284},"blog-posts","Blog posts",[567,4287,4288,4289,4291],{},"Blog posts have a fixed schema in TentoCMS, not a flexible ",[574,4290,936],{}," object. Access all blog post properties directly on the post object.",[567,4293,4294],{},[606,4295,4296],{},"For complete blog API documentation, including:",[650,4298,4299,4302,4305,4308],{},[653,4300,4301],{},"Full response shapes with all fields",[653,4303,4304],{},"Admin and public endpoints",[653,4306,4307],{},"Publishing workflows",[653,4309,4310],{},"Category and tag management",[567,4312,4313,4314,4318,4319,4323],{},"See: ",[579,4315,4317],{"href":4316},"..\u002Fapi\u002Fblog","Blog API Reference"," for the public read API, and the ",[579,4320,4322],{"href":4321},"..\u002F..\u002Fadmin-api\u002Fblog","Blog Admin API"," for admin endpoints, publishing workflows, and category\u002Ftag management.",[567,4325,4326],{},[606,4327,4328],{},"Field mapping:",[3062,4330,4331,4344],{},[3065,4332,4333],{},[3068,4334,4335,4338,4341],{},[3071,4336,4337],{},"ButterCMS field",[3071,4339,4340],{},"TentoCMS field",[3071,4342,4343],{},"Notes",[3080,4345,4346,4361,4376,4396,4411,4445,4461,4476,4490,4509],{},[3068,4347,4348,4353,4358],{},[3085,4349,4350],{},[574,4351,4352],{},"body",[3085,4354,4355],{},[574,4356,4357],{},"content",[3085,4359,4360],{},"HTML or markdown",[3068,4362,4363,4368,4373],{},[3085,4364,4365],{},[574,4366,4367],{},"summary",[3085,4369,4370],{},[574,4371,4372],{},"excerpt",[3085,4374,4375],{},"Short description",[3068,4377,4378,4384,4390],{},[3085,4379,4380,4383],{},[574,4381,4382],{},"featured_image"," (string URL)",[3085,4385,4386,4389],{},[574,4387,4388],{},"featuredImage"," (TentoMedia object)",[3085,4391,4392,4393],{},"See ",[579,4394,772],{"href":4395},"#image-handling",[3068,4397,4398,4403,4408],{},[3085,4399,4400],{},[574,4401,4402],{},"author.first_name + author.last_name",[3085,4404,4405],{},[574,4406,4407],{},"author.name",[3085,4409,4410],{},"Combined into single field",[3068,4412,4413,4418,4423],{},[3085,4414,4415],{},[574,4416,4417],{},"author.profile_image",[3085,4419,4420],{},[574,4421,4422],{},"author.avatarUrl",[3085,4424,4425,4426,4429,4430,4433,4434,4441,4442,4444],{},"Rehosted to TentoCMS media on import on a ",[606,4427,4428],{},"best-effort"," basis — usually a Tento ",[574,4431,4432],{},"\u002Fmedia\u002F…"," URL. On any rehost failure (or when the author was deduplicated from an earlier post) the ",[606,4435,4436,4437,4440],{},"original ",[574,4438,4439],{},"cdn.buttercms.com"," URL is kept"," and a warning is recorded in the Import report. See the note below — verify after import and keep ",[574,4443,4439],{}," allow-listed until confirmed.",[3068,4446,4447,4451,4455],{},[3085,4448,4449],{},[574,4450,3092],{},[3085,4452,4453],{},[574,4454,2435],{},[3085,4456,4457,4458,4460],{},"Nested under ",[574,4459,2724],{}," object",[3068,4462,4463,4467,4472],{},[3085,4464,4465],{},[574,4466,2518],{},[3085,4468,4469],{},[574,4470,4471],{},"seo.metaDescription",[3085,4473,4457,4474,4460],{},[574,4475,2724],{},[3068,4477,4478,4483,4487],{},[3085,4479,4480],{},[574,4481,4482],{},"status",[3085,4484,4485],{},[574,4486,4482],{},[3085,4488,4489],{},"Same concept",[3068,4491,4492,4500,4506],{},[3085,4493,4494,863,4497],{},[574,4495,4496],{},"tags[].name",[574,4498,4499],{},"tags[].slug",[3085,4501,4502,863,4504],{},[574,4503,4496],{},[574,4505,4499],{},[3085,4507,4508],{},"Same structure",[3068,4510,4511,4519,4527],{},[3085,4512,4513,863,4516],{},[574,4514,4515],{},"categories[].name",[574,4517,4518],{},"categories[].slug",[3085,4520,4521,863,4524],{},[574,4522,4523],{},"category.name",[574,4525,4526],{},"category.slug",[3085,4528,4529],{},"Single category, not an array",[567,4531,4532],{},[606,4533,4534],{},"Fields with no Tento equivalent — compute or stub in your adapter:",[3062,4536,4537,4549],{},[3065,4538,4539],{},[3068,4540,4541,4543,4546],{},[3071,4542,4337],{},[3071,4544,4545],{},"Tento equivalent",[3071,4547,4548],{},"What to do",[3080,4550,4551,4570,4592,4624,4658],{},[3068,4552,4553,4558,4561],{},[3085,4554,4555],{},[574,4556,4557],{},"read_time",[3085,4559,4560],{},"— (absent)",[3085,4562,4563,4564,4566,4567],{},"Compute from ",[574,4565,4357],{}," word count: ",[574,4568,4569],{},"Math.ceil(wordCount \u002F 200)",[3068,4571,4572,4579,4583],{},[3085,4573,4574,3089,4577],{},[574,4575,4576],{},"updated",[574,4578,2389],{},[3085,4580,4581],{},[574,4582,2389],{},[3085,4584,4585,4586,4588,4589,3560],{},"Mapped directly — public blog posts expose ",[574,4587,2389],{}," (use it for JSON-LD ",[574,4590,4591],{},"dateModified",[3068,4593,4594,4600,4603],{},[3085,4595,4596,4599],{},[574,4597,4598],{},"featured_image_alt"," (separate string)",[3085,4601,4602],{},"— (dropped on import)",[3085,4604,4605,4606,4608,4609,4612,4613,4616,4617,4620,4621,4623],{},"The migration adapter reads ",[574,4607,4598],{}," from the source post but ",[606,4610,4611],{},"never writes it anywhere"," — it isn't propagated to the created media record's ",[574,4614,4615],{},"altText",", or to any other field. Every migrated blog post's ",[574,4618,4619],{},"featuredImage._altText"," will be empty\u002F",[574,4622,2578],{}," regardless of what was set in ButterCMS. If you need alt text preserved, re-populate it manually (or via a one-off script against the Media API) after import — don't rely on the importer for this field today.",[3068,4625,4626,4635,4640],{},[3085,4627,4628,4631,4632],{},[574,4629,4630],{},"author.first_name"," + ",[574,4633,4634],{},"author.last_name",[3085,4636,4637,4638],{},"single ",[574,4639,4407],{},[3085,4641,4642,4643,4646,4647,4650,4651,4653,4654,4657],{},"Store the full name in ",[574,4644,4645],{},"first_name"," and set ",[574,4648,4649],{},"last_name"," to ",[574,4652,2937],{},", or split on the first space — consumers that concatenate ",[574,4655,4656],{},"first_name + ' ' + last_name"," still get the full name",[3068,4659,4660,4666,4670],{},[3085,4661,4662,4665],{},[574,4663,4664],{},"categories[]"," (array)",[3085,4667,4637,4668],{},[574,4669,2681],{},[3085,4671,4672,4673,4676],{},"Wrap as ",[574,4674,4675],{},"post.category ? [post.category] : []"," wherever an array is expected",[567,4678,4679],{},"Example update for a blog post component:",[611,4681,4683],{"className":1036,"code":4682,"language":1038,"meta":616,"style":616},"\u002F\u002F ButterCMS\nfunction BlogPost({ post }) {\n  return (\n    \u003Carticle>\n      \u003Ch1>{post.title}\u003C\u002Fh1>\n      \u003Cp>{post.summary}\u003C\u002Fp>\n      \u003Cimg src={post.featured_image} \u002F>\n      \u003Cp>By {post.author.first_name} {post.author.last_name}\u003C\u002Fp>\n      \u003Cdiv dangerouslySetInnerHTML={{ __html: post.body }} \u002F>\n    \u003C\u002Farticle>\n  )\n}\n\n\u002F\u002F TentoCMS\nfunction BlogPost({ post }) {\n  return (\n    \u003Carticle>\n      \u003Ch1>{post.title}\u003C\u002Fh1>\n      \u003Cp>{post.excerpt}\u003C\u002Fp>\n      \u003Cimg src={post.featuredImage?._url} alt={post.featuredImage?._altText ?? ''} \u002F>\n      \u003Cp>By {post.author?.name}\u003C\u002Fp>\n      \u003Cdiv dangerouslySetInnerHTML={{ __html: post.content }} \u002F>\n    \u003C\u002Farticle>\n  )\n}\n",[574,4684,4685,4689,4708,4716,4726,4750,4770,4792,4836,4865,4874,4879,4883,4887,4891,4905,4911,4919,4939,4959,4991,5019,5043,5051,5055],{"__ignoreMap":616},[620,4686,4687],{"class":622,"line":623},[620,4688,1045],{"class":640},[620,4690,4691,4694,4697,4700,4703,4706],{"class":622,"line":637},[620,4692,4693],{"class":1074},"function",[620,4695,4696],{"class":1084}," BlogPost",[620,4698,4699],{"class":1061},"({",[620,4701,4702],{"class":1509}," post",[620,4704,4705],{"class":1061}," })",[620,4707,1518],{"class":1061},[620,4709,4710,4713],{"class":622,"line":1071},[620,4711,4712],{"class":1050},"  return",[620,4714,4715],{"class":1161}," (\n",[620,4717,4718,4721,4724],{"class":622,"line":1102},[620,4719,4720],{"class":1161},"    \u003C",[620,4722,4723],{"class":626},"article",[620,4725,1726],{"class":1161},[620,4727,4728,4731,4734,4736,4738,4741,4743,4746,4748],{"class":622,"line":1109},[620,4729,4730],{"class":1161},"      \u003C",[620,4732,4733],{"class":626},"h1",[620,4735,1760],{"class":1161},[620,4737,3973],{"class":1061},[620,4739,4740],{"class":1161},"post.",[620,4742,3096],{"class":1054},[620,4744,4745],{"class":1061},"}\u003C\u002F",[620,4747,4733],{"class":1054},[620,4749,1726],{"class":1061},[620,4751,4752,4754,4756,4758,4760,4762,4764,4766,4768],{"class":622,"line":1115},[620,4753,4730],{"class":1161},[620,4755,567],{"class":626},[620,4757,1760],{"class":1161},[620,4759,3973],{"class":1061},[620,4761,4740],{"class":1161},[620,4763,4367],{"class":1054},[620,4765,4745],{"class":1061},[620,4767,567],{"class":1054},[620,4769,1726],{"class":1061},[620,4771,4772,4774,4777,4780,4783,4785,4787,4789],{"class":622,"line":1138},[620,4773,4730],{"class":1061},[620,4775,4776],{"class":1054},"img",[620,4778,4779],{"class":1054}," src",[620,4781,4782],{"class":1061},"={",[620,4784,4740],{"class":1161},[620,4786,4382],{"class":1054},[620,4788,1200],{"class":1061},[620,4790,4791],{"class":1061}," \u002F>\n",[620,4793,4794,4796,4798,4800,4803,4805,4808,4810,4812,4814,4816,4818,4820,4822,4824,4826,4828,4830,4832,4834],{"class":622,"line":1158},[620,4795,4730],{"class":1161},[620,4797,567],{"class":626},[620,4799,1760],{"class":1161},[620,4801,4802],{"class":1054},"By",[620,4804,1120],{"class":1061},[620,4806,4807],{"class":1509},"post",[620,4809,736],{"class":1161},[620,4811,2678],{"class":1509},[620,4813,736],{"class":1161},[620,4815,4645],{"class":1509},[620,4817,1200],{"class":1061},[620,4819,1120],{"class":1061},[620,4821,4807],{"class":1509},[620,4823,736],{"class":1161},[620,4825,2678],{"class":1509},[620,4827,736],{"class":1161},[620,4829,4649],{"class":1509},[620,4831,4745],{"class":1061},[620,4833,567],{"class":1054},[620,4835,1726],{"class":1061},[620,4837,4838,4840,4843,4846,4849,4852,4854,4856,4858,4860,4863],{"class":622,"line":1177},[620,4839,4730],{"class":1061},[620,4841,4842],{"class":1054},"div",[620,4844,4845],{"class":1054}," dangerouslySetInnerHTML",[620,4847,4848],{"class":1061},"={{",[620,4850,4851],{"class":626}," __html",[620,4853,1165],{"class":1061},[620,4855,4702],{"class":1054},[620,4857,736],{"class":1061},[620,4859,4352],{"class":1054},[620,4861,4862],{"class":1061}," }}",[620,4864,4791],{"class":1061},[620,4866,4867,4870,4872],{"class":622,"line":1197},[620,4868,4869],{"class":1061},"    \u003C\u002F",[620,4871,4723],{"class":1054},[620,4873,1726],{"class":1061},[620,4875,4876],{"class":622,"line":1550},[620,4877,4878],{"class":1161},"  )\n",[620,4880,4881],{"class":622,"line":1556},[620,4882,2046],{"class":1061},[620,4884,4885],{"class":622,"line":1564},[620,4886,1106],{"emptyLinePlaceholder":1105},[620,4888,4889],{"class":622,"line":1622},[620,4890,1112],{"class":640},[620,4892,4893,4895,4897,4899,4901,4903],{"class":622,"line":1641},[620,4894,4693],{"class":1074},[620,4896,4696],{"class":1084},[620,4898,4699],{"class":1061},[620,4900,4702],{"class":1509},[620,4902,4705],{"class":1061},[620,4904,1518],{"class":1061},[620,4906,4907,4909],{"class":622,"line":1670},[620,4908,4712],{"class":1050},[620,4910,4715],{"class":1161},[620,4912,4913,4915,4917],{"class":622,"line":1678},[620,4914,4720],{"class":1161},[620,4916,4723],{"class":626},[620,4918,1726],{"class":1161},[620,4920,4921,4923,4925,4927,4929,4931,4933,4935,4937],{"class":622,"line":1684},[620,4922,4730],{"class":1161},[620,4924,4733],{"class":626},[620,4926,1760],{"class":1161},[620,4928,3973],{"class":1061},[620,4930,4740],{"class":1161},[620,4932,3096],{"class":1054},[620,4934,4745],{"class":1061},[620,4936,4733],{"class":1054},[620,4938,1726],{"class":1061},[620,4940,4941,4943,4945,4947,4949,4951,4953,4955,4957],{"class":622,"line":2096},[620,4942,4730],{"class":1161},[620,4944,567],{"class":626},[620,4946,1760],{"class":1161},[620,4948,3973],{"class":1061},[620,4950,4740],{"class":1161},[620,4952,4372],{"class":1054},[620,4954,4745],{"class":1061},[620,4956,567],{"class":1054},[620,4958,1726],{"class":1061},[620,4960,4961,4963,4965,4967,4969,4972,4975,4977,4980,4982,4985,4987,4989],{"class":622,"line":2112},[620,4962,4730],{"class":1061},[620,4964,4776],{"class":1054},[620,4966,4779],{"class":1054},[620,4968,4782],{"class":1061},[620,4970,4971],{"class":1161},"post.featuredImage?.",[620,4973,4974],{"class":1054},"_url",[620,4976,1200],{"class":1061},[620,4978,4979],{"class":1054}," alt",[620,4981,4782],{"class":1061},[620,4983,4984],{"class":1161},"post.featuredImage?._altText ?? ",[620,4986,2937],{"class":1061},[620,4988,1200],{"class":1061},[620,4990,4791],{"class":1061},[620,4992,4993,4995,4997,4999,5001,5003,5005,5007,5009,5011,5013,5015,5017],{"class":622,"line":2131},[620,4994,4730],{"class":1161},[620,4996,567],{"class":626},[620,4998,1760],{"class":1161},[620,5000,4802],{"class":1054},[620,5002,1120],{"class":1061},[620,5004,4807],{"class":1509},[620,5006,736],{"class":1161},[620,5008,2678],{"class":1509},[620,5010,2727],{"class":1161},[620,5012,3310],{"class":1509},[620,5014,4745],{"class":1061},[620,5016,567],{"class":1054},[620,5018,1726],{"class":1061},[620,5020,5021,5023,5025,5027,5029,5031,5033,5035,5037,5039,5041],{"class":622,"line":2144},[620,5022,4730],{"class":1061},[620,5024,4842],{"class":1054},[620,5026,4845],{"class":1054},[620,5028,4848],{"class":1061},[620,5030,4851],{"class":626},[620,5032,1165],{"class":1061},[620,5034,4702],{"class":1054},[620,5036,736],{"class":1061},[620,5038,4357],{"class":1054},[620,5040,4862],{"class":1061},[620,5042,4791],{"class":1061},[620,5044,5045,5047,5049],{"class":622,"line":2160},[620,5046,4869],{"class":1061},[620,5048,4723],{"class":1054},[620,5050,1726],{"class":1061},[620,5052,5053],{"class":622,"line":2176},[620,5054,4878],{"class":1161},[620,5056,5057],{"class":622,"line":2182},[620,5058,2046],{"class":1061},[678,5060,5061,5066,5077,5095,5103,5386],{},[567,5062,5063],{},[606,5064,5065],{},"⚠️ Security Warning: XSS Risk",[567,5067,5068,5069,5072,5073,5076],{},"The examples above use ",[574,5070,5071],{},"dangerouslySetInnerHTML"," to render HTML content directly from the CMS. This approach is ",[606,5074,5075],{},"vulnerable to cross-site scripting (XSS) attacks"," if malicious HTML or JavaScript is stored in content fields (for example, through a compromised CMS account).",[567,5078,5079,5080,5083,5084,5087,5088,863,5091,5094],{},"Anyone with editor access who can modify ",[574,5081,5082],{},"post.content"," or other rich text fields could inject ",[574,5085,5086],{},"\u003Cscript>"," tags or event handlers (",[574,5089,5090],{},"onclick",[574,5092,5093],{},"onerror",", etc.) that execute in end-user browsers.",[567,5096,5097],{},[606,5098,5099,5100,5102],{},"Before using ",[574,5101,5071],{}," in production:",[787,5104,5105,5260,5376],{},[653,5106,5107,5110,5111,5117,5118,5123,5124],{},[606,5108,5109],{},"Sanitize HTML server-side"," using a trusted library like ",[579,5112,5116],{"href":5113,"rel":5114},"https:\u002F\u002Fgithub.com\u002Fcure53\u002FDOMPurify",[5115],"nofollow","DOMPurify"," (Node.js\u002Fbrowser) or ",[579,5119,5122],{"href":5120,"rel":5121},"https:\u002F\u002Fgithub.com\u002Fapostrophecms\u002Fsanitize-html",[5115],"sanitize-html"," (Node.js):",[611,5125,5127],{"className":1036,"code":5126,"language":1038,"meta":616,"style":616},"import DOMPurify from 'isomorphic-dompurify'\n\nfunction BlogPost({ post }) {\n  const sanitizedContent = DOMPurify.sanitize(post.content)\n  return (\n    \u003Carticle>\n      \u003Ch1>{post.title}\u003C\u002Fh1>\n      \u003Cdiv dangerouslySetInnerHTML={{ __html: sanitizedContent }} \u002F>\n    \u003C\u002Farticle>\n  )\n}\n",[574,5128,5129,5145,5149,5163,5190,5196,5204,5224,5244,5252,5256],{"__ignoreMap":616},[620,5130,5131,5133,5136,5138,5140,5143],{"class":622,"line":623},[620,5132,1051],{"class":1050},[620,5134,5135],{"class":1054}," DOMPurify ",[620,5137,1058],{"class":1050},[620,5139,1062],{"class":1061},[620,5141,5142],{"class":630},"isomorphic-dompurify",[620,5144,1068],{"class":1061},[620,5146,5147],{"class":622,"line":637},[620,5148,1106],{"emptyLinePlaceholder":1105},[620,5150,5151,5153,5155,5157,5159,5161],{"class":622,"line":1071},[620,5152,4693],{"class":1074},[620,5154,4696],{"class":1084},[620,5156,4699],{"class":1061},[620,5158,4702],{"class":1509},[620,5160,4705],{"class":1061},[620,5162,1518],{"class":1061},[620,5164,5165,5167,5170,5172,5175,5177,5180,5182,5184,5186,5188],{"class":622,"line":1102},[620,5166,1523],{"class":1074},[620,5168,5169],{"class":1054}," sanitizedContent",[620,5171,1538],{"class":1061},[620,5173,5174],{"class":1054}," DOMPurify",[620,5176,736],{"class":1061},[620,5178,5179],{"class":1084},"sanitize",[620,5181,1088],{"class":1161},[620,5183,4807],{"class":1054},[620,5185,736],{"class":1061},[620,5187,4357],{"class":1054},[620,5189,1099],{"class":1161},[620,5191,5192,5194],{"class":622,"line":1109},[620,5193,4712],{"class":1050},[620,5195,4715],{"class":1161},[620,5197,5198,5200,5202],{"class":622,"line":1115},[620,5199,4720],{"class":1161},[620,5201,4723],{"class":626},[620,5203,1726],{"class":1161},[620,5205,5206,5208,5210,5212,5214,5216,5218,5220,5222],{"class":622,"line":1138},[620,5207,4730],{"class":1161},[620,5209,4733],{"class":626},[620,5211,1760],{"class":1161},[620,5213,3973],{"class":1061},[620,5215,4740],{"class":1161},[620,5217,3096],{"class":1054},[620,5219,4745],{"class":1061},[620,5221,4733],{"class":1054},[620,5223,1726],{"class":1061},[620,5225,5226,5228,5230,5232,5234,5236,5238,5240,5242],{"class":622,"line":1158},[620,5227,4730],{"class":1061},[620,5229,4842],{"class":1054},[620,5231,4845],{"class":1054},[620,5233,4848],{"class":1061},[620,5235,4851],{"class":626},[620,5237,1165],{"class":1061},[620,5239,5169],{"class":1054},[620,5241,4862],{"class":1061},[620,5243,4791],{"class":1061},[620,5245,5246,5248,5250],{"class":622,"line":1177},[620,5247,4869],{"class":1061},[620,5249,4723],{"class":1054},[620,5251,1726],{"class":1061},[620,5253,5254],{"class":622,"line":1197},[620,5255,4878],{"class":1161},[620,5257,5258],{"class":622,"line":1550},[620,5259,2046],{"class":1061},[653,5261,5262,5265,5266],{},[606,5263,5264],{},"Or use a safe-by-default rich text renderer"," that parses and renders content without raw HTML injection:",[611,5267,5269],{"className":1036,"code":5268,"language":1038,"meta":616,"style":616},"import Markdown from 'react-markdown'\n\nfunction BlogPost({ post }) {\n  return (\n    \u003Carticle>\n      \u003Ch1>{post.title}\u003C\u002Fh1>\n      \u003CMarkdown>{post.content}\u003C\u002FMarkdown>\n    \u003C\u002Farticle>\n  )\n}\n",[574,5270,5271,5287,5291,5305,5311,5319,5339,5360,5368,5372],{"__ignoreMap":616},[620,5272,5273,5275,5278,5280,5282,5285],{"class":622,"line":623},[620,5274,1051],{"class":1050},[620,5276,5277],{"class":1054}," Markdown ",[620,5279,1058],{"class":1050},[620,5281,1062],{"class":1061},[620,5283,5284],{"class":630},"react-markdown",[620,5286,1068],{"class":1061},[620,5288,5289],{"class":622,"line":637},[620,5290,1106],{"emptyLinePlaceholder":1105},[620,5292,5293,5295,5297,5299,5301,5303],{"class":622,"line":1071},[620,5294,4693],{"class":1074},[620,5296,4696],{"class":1084},[620,5298,4699],{"class":1061},[620,5300,4702],{"class":1509},[620,5302,4705],{"class":1061},[620,5304,1518],{"class":1061},[620,5306,5307,5309],{"class":622,"line":1102},[620,5308,4712],{"class":1050},[620,5310,4715],{"class":1161},[620,5312,5313,5315,5317],{"class":622,"line":1109},[620,5314,4720],{"class":1161},[620,5316,4723],{"class":626},[620,5318,1726],{"class":1161},[620,5320,5321,5323,5325,5327,5329,5331,5333,5335,5337],{"class":622,"line":1115},[620,5322,4730],{"class":1161},[620,5324,4733],{"class":626},[620,5326,1760],{"class":1161},[620,5328,3973],{"class":1061},[620,5330,4740],{"class":1161},[620,5332,3096],{"class":1054},[620,5334,4745],{"class":1061},[620,5336,4733],{"class":1054},[620,5338,1726],{"class":1061},[620,5340,5341,5343,5346,5348,5350,5352,5354,5356,5358],{"class":622,"line":1138},[620,5342,4730],{"class":1161},[620,5344,5345],{"class":626},"Markdown",[620,5347,1760],{"class":1161},[620,5349,3973],{"class":1061},[620,5351,4740],{"class":1161},[620,5353,4357],{"class":1054},[620,5355,4745],{"class":1061},[620,5357,5345],{"class":1054},[620,5359,1726],{"class":1061},[620,5361,5362,5364,5366],{"class":622,"line":1158},[620,5363,4869],{"class":1061},[620,5365,4723],{"class":1054},[620,5367,1726],{"class":1061},[620,5369,5370],{"class":622,"line":1177},[620,5371,4878],{"class":1161},[620,5373,5374],{"class":622,"line":1197},[620,5375,2046],{"class":1061},[653,5377,5378,5381,5382,5385],{},[606,5379,5380],{},"Content Security Policy (CSP)",": Deploy a strict CSP header (",[574,5383,5384],{},"Content-Security-Policy: default-src 'self'; script-src 'self'",") to prevent inline script execution even if malicious content bypasses sanitization.",[567,5387,5388,5391],{},[606,5389,5390],{},"Do not render untrusted HTML without sanitization."," Even if you trust your CMS editors today, account compromises, supply chain attacks, or future team changes introduce risk.",[777,5393],{},[593,5395,5397],{"id":5396},"component-identification","Component Identification",[567,5399,5400,5401,5403,5404,5406],{},"ButterCMS identifies components with a ",[574,5402,915],{}," property using snake_case. TentoCMS uses a ",[574,5405,858],{}," property with kebab-case.",[3062,5408,5409,5423],{},[3065,5410,5411],{},[3068,5412,5413,5418],{},[3071,5414,5415,5416],{},"ButterCMS ",[574,5417,915],{},[3071,5419,5420,5421],{},"TentoCMS ",[574,5422,858],{},[3080,5424,5425,5436,5448,5460],{},[3068,5426,5427,5432],{},[3085,5428,5429],{},[574,5430,5431],{},"hero",[3085,5433,5434],{},[574,5435,5431],{},[3068,5437,5438,5443],{},[3085,5439,5440],{},[574,5441,5442],{},"cta_banner",[3085,5444,5445],{},[574,5446,5447],{},"cta-banner",[3068,5449,5450,5455],{},[3085,5451,5452],{},[574,5453,5454],{},"quote_section",[3085,5456,5457],{},[574,5458,5459],{},"quote-section",[3068,5461,5462,5467],{},[3085,5463,5464],{},[574,5465,5466],{},"text_block",[3085,5468,5469],{},[574,5470,5471],{},"text-block",[567,5473,5474,5475,3814,5478,5481],{},"Update every ",[574,5476,5477],{},"switch",[574,5479,5480],{},"if\u002Felse"," block that inspects the component type identifier:",[611,5483,5485],{"className":1036,"code":5484,"language":1038,"meta":616,"style":616},"\u002F\u002F Before (ButterCMS)\nfunction renderComponent(component) {\n  switch (component.type) {\n    case 'hero':        return \u003CHero {...component.fields} \u002F>\n    case 'cta_banner':  return \u003CCtaBanner {...component.fields} \u002F>\n    case 'quote_section': return \u003CQuoteSection {...component.fields} \u002F>\n    default: return null\n  }\n}\n\n\u002F\u002F After (TentoCMS)\nfunction renderComponent(component) {\n  switch (component._type) {\n    case 'hero':         return \u003CHero {...component} \u002F>\n    case 'cta-banner':   return \u003CCtaBanner {...component} \u002F>\n    case 'quote-section': return \u003CQuoteSection {...component} \u002F>\n    default:             return null\n  }\n}\n",[574,5486,5487,5492,5508,5525,5563,5587,5611,5616,5620,5624,5628,5637,5651,5667,5696,5715,5734,5739,5743],{"__ignoreMap":616},[620,5488,5489],{"class":622,"line":623},[620,5490,5491],{"class":640},"\u002F\u002F Before (ButterCMS)\n",[620,5493,5494,5496,5499,5501,5504,5506],{"class":622,"line":637},[620,5495,4693],{"class":1074},[620,5497,5498],{"class":1084}," renderComponent",[620,5500,1088],{"class":1061},[620,5502,5503],{"class":1509},"component",[620,5505,748],{"class":1061},[620,5507,1518],{"class":1061},[620,5509,5510,5513,5515,5517,5519,5521,5523],{"class":622,"line":1071},[620,5511,5512],{"class":1050},"  switch",[620,5514,1506],{"class":1161},[620,5516,5503],{"class":1054},[620,5518,736],{"class":1061},[620,5520,915],{"class":1054},[620,5522,1636],{"class":1161},[620,5524,1155],{"class":1061},[620,5526,5527,5530,5532,5534,5536,5538,5541,5544,5547,5550,5552,5554,5556,5558,5561],{"class":622,"line":1102},[620,5528,5529],{"class":1050},"    case",[620,5531,1062],{"class":1061},[620,5533,5431],{"class":630},[620,5535,1091],{"class":1061},[620,5537,1165],{"class":1061},[620,5539,5540],{"class":1050},"        return",[620,5542,5543],{"class":1061}," \u003C",[620,5545,5546],{"class":626},"Hero",[620,5548,5549],{"class":1061}," {...",[620,5551,5503],{"class":626},[620,5553,736],{"class":1061},[620,5555,936],{"class":626},[620,5557,1200],{"class":1061},[620,5559,5560],{"class":1161}," \u002F",[620,5562,1726],{"class":1061},[620,5564,5565,5568,5570,5573,5575,5577,5579,5581,5583,5585],{"class":622,"line":1109},[620,5566,5567],{"class":1161},"    case 'cta_banner':  return ",[620,5569,1705],{"class":1061},[620,5571,5572],{"class":626},"CtaBanner",[620,5574,5549],{"class":1061},[620,5576,5503],{"class":626},[620,5578,736],{"class":1061},[620,5580,936],{"class":626},[620,5582,1200],{"class":1061},[620,5584,5560],{"class":1161},[620,5586,1726],{"class":1061},[620,5588,5589,5592,5594,5597,5599,5601,5603,5605,5607,5609],{"class":622,"line":1115},[620,5590,5591],{"class":1161},"    case 'quote_section': return ",[620,5593,1705],{"class":1061},[620,5595,5596],{"class":626},"QuoteSection",[620,5598,5549],{"class":1061},[620,5600,5503],{"class":626},[620,5602,736],{"class":1061},[620,5604,936],{"class":626},[620,5606,1200],{"class":1061},[620,5608,5560],{"class":1161},[620,5610,1726],{"class":1061},[620,5612,5613],{"class":622,"line":1138},[620,5614,5615],{"class":1161},"    default: return null\n",[620,5617,5618],{"class":622,"line":1158},[620,5619,1681],{"class":1161},[620,5621,5622],{"class":622,"line":1177},[620,5623,2046],{"class":1161},[620,5625,5626],{"class":622,"line":1197},[620,5627,1106],{"emptyLinePlaceholder":1105},[620,5629,5630,5633,5635],{"class":622,"line":1550},[620,5631,5632],{"class":1161},"\u002F\u002F After (",[620,5634,1028],{"class":1054},[620,5636,1099],{"class":1161},[620,5638,5639,5641,5643,5645,5647,5649],{"class":622,"line":1556},[620,5640,4693],{"class":1074},[620,5642,5498],{"class":1084},[620,5644,1088],{"class":1061},[620,5646,5503],{"class":1509},[620,5648,748],{"class":1061},[620,5650,1518],{"class":1061},[620,5652,5653,5655,5657,5659,5661,5663,5665],{"class":622,"line":1564},[620,5654,5512],{"class":1050},[620,5656,1506],{"class":1161},[620,5658,5503],{"class":1054},[620,5660,736],{"class":1061},[620,5662,858],{"class":1054},[620,5664,1636],{"class":1161},[620,5666,1155],{"class":1061},[620,5668,5669,5671,5673,5675,5677,5679,5682,5684,5686,5688,5690,5692,5694],{"class":622,"line":1622},[620,5670,5529],{"class":1050},[620,5672,1062],{"class":1061},[620,5674,5431],{"class":630},[620,5676,1091],{"class":1061},[620,5678,1165],{"class":1061},[620,5680,5681],{"class":1050},"         return",[620,5683,5543],{"class":1061},[620,5685,5546],{"class":626},[620,5687,5549],{"class":1061},[620,5689,5503],{"class":626},[620,5691,1200],{"class":1061},[620,5693,5560],{"class":1161},[620,5695,1726],{"class":1061},[620,5697,5698,5701,5703,5705,5707,5709,5711,5713],{"class":622,"line":1641},[620,5699,5700],{"class":1161},"    case 'cta-banner':   return ",[620,5702,1705],{"class":1061},[620,5704,5572],{"class":626},[620,5706,5549],{"class":1061},[620,5708,5503],{"class":626},[620,5710,1200],{"class":1061},[620,5712,5560],{"class":1161},[620,5714,1726],{"class":1061},[620,5716,5717,5720,5722,5724,5726,5728,5730,5732],{"class":622,"line":1670},[620,5718,5719],{"class":1161},"    case 'quote-section': return ",[620,5721,1705],{"class":1061},[620,5723,5596],{"class":626},[620,5725,5549],{"class":1061},[620,5727,5503],{"class":626},[620,5729,1200],{"class":1061},[620,5731,5560],{"class":1161},[620,5733,1726],{"class":1061},[620,5735,5736],{"class":622,"line":1678},[620,5737,5738],{"class":1161},"    default:             return null\n",[620,5740,5741],{"class":622,"line":1684},[620,5742,1681],{"class":1161},[620,5744,5745],{"class":622,"line":2096},[620,5746,2046],{"class":1161},[567,5748,5749,5750,5753,5754,5756,5757,5759,5760,5763],{},"Do not confuse ",[574,5751,5752],{},"component._type"," with ",[574,5755,2366],{},". The ",[574,5758,2366],{}," property is the page type slug (e.g. ",[574,5761,5762],{},"landing-page","), not a component identifier.",[678,5765,5766],{},[567,5767,5768,5775,5776,5778,5779,5781,5782,5784,5785,3413,5788,5791,5792,3413,5795,5798,5799,5802],{},[606,5769,5770,5771,5774],{},"Components expose their content fields at the root — there is no ",[574,5772,5773],{},".fields","\nsub-object."," Each nested\u002Frepeater component item is its content fields plus a\nkebab-case ",[574,5777,858],{}," discriminant; a component's own variant field (e.g. ",[574,5780,915],{},") sits\nalongside ",[574,5783,858],{},", so read it as ",[574,5786,5787],{},"component.type",[574,5789,5790],{},"component.fields.type",". That is\nwhy the renderer above spreads ",[574,5793,5794],{},"{...component}",[574,5796,5797],{},"{...component.fields}",". If older\ndocs or examples show ",[574,5800,5801],{},"component.fields",", they are wrong for the current API.",[777,5804],{},[593,5806,5808],{"id":5807},"sdk-method-mapping","SDK Method Mapping",[3062,5810,5811,5820],{},[3065,5812,5813],{},[3068,5814,5815,5818],{},[3071,5816,5817],{},"ButterCMS",[3071,5819,1028],{},[3080,5821,5822,5834,5846,5858,5870,5882,5894,5906],{},[3068,5823,5824,5829],{},[3085,5825,5826],{},[574,5827,5828],{},"butter.page.retrieve('*', 'slug')",[3085,5830,5831],{},[574,5832,5833],{},"tento.pages.getBySlug('slug')",[3068,5835,5836,5841],{},[3085,5837,5838],{},[574,5839,5840],{},"butter.page.list('page_type')",[3085,5842,5843],{},[574,5844,5845],{},"tento.pages.list({ pageType: 'page-type' })",[3068,5847,5848,5853],{},[3085,5849,5850],{},[574,5851,5852],{},"butter.content.retrieve(['collection_key'])",[3085,5854,5855],{},[574,5856,5857],{},"tento.collections.list('collection-key')",[3068,5859,5860,5865],{},[3085,5861,5862],{},[574,5863,5864],{},"butter.content.retrieve(['collection_key'], { ... })",[3085,5866,5867],{},[574,5868,5869],{},"tento.collections.getBySlug('collection-key', 'item-slug')",[3068,5871,5872,5877],{},[3085,5873,5874],{},[574,5875,5876],{},"butter.post.retrieve('slug')",[3085,5878,5879],{},[574,5880,5881],{},"tento.blog.posts.getBySlug('slug')",[3068,5883,5884,5889],{},[3085,5885,5886],{},[574,5887,5888],{},"butter.post.list()",[3085,5890,5891],{},[574,5892,5893],{},"tento.blog.posts.list()",[3068,5895,5896,5901],{},[3085,5897,5898],{},[574,5899,5900],{},"butter.category.list()",[3085,5902,5903],{},[574,5904,5905],{},"tento.blog.categories.list()",[3068,5907,5908,5913],{},[3085,5909,5910],{},[574,5911,5912],{},"butter.tag.list()",[3085,5914,5915],{},[574,5916,5917],{},"tento.blog.tags.list()",[567,5919,5920,5921,3818,5923,5926,5927,5930,5931,736],{},"Note that ",[574,5922,5905],{},[574,5924,5925],{},"BlogCategory[]"," directly, not a ",[574,5928,5929],{},"ListResponse"," wrapper. The same applies to ",[574,5932,5917],{},[678,5934,5935,5964,5977],{},[567,5936,5937,5949,5950,863,5953,5956,5957,5960,5961,5963],{},[606,5938,2432,5939,5942,5943,5946,5947,736],{},[574,5940,5941],{},"getBySlug()"," throws ",[574,5944,5945],{},"TentoNotFoundError"," on 404 — it does not return ",[574,5948,2578],{},"\nThis applies to ",[574,5951,5952],{},"tento.pages.getBySlug()",[574,5954,5955],{},"tento.collections.getBySlug()",", and\n",[574,5958,5959],{},"tento.blog.posts.getBySlug()",". ButterCMS effectively returned empty\u002F",[574,5962,2578],{}," for a missing\nslug; the SDK throws instead. A direct port therefore 500s on every not-found URL if callers\nexpect a nullable result.",[567,5965,5966,5967,5970,5971,5973,5974,1165],{},"If your Nuxt\u002FNext pages do ",[574,5968,5969],{},"if (!data) throw createError({ statusCode: 404 })",", catch the\nerror and return ",[574,5972,2578],{}," at the call site — otherwise a missing slug becomes a ",[606,5975,5976],{},"500 instead\nof a 404",[611,5978,5980],{"className":1389,"code":5979,"language":1391,"meta":616,"style":616},"import { TentoNotFoundError } from '@tentocms\u002Fclient'\n\nlet page = null\ntry {\n  page = await tento.pages.getBySlug(slug, { preview })\n} catch (e) {\n  if (!(e instanceof TentoNotFoundError)) throw e\n  \u002F\u002F 404 → null, matching ButterCMS's empty response\n}\n",[574,5981,5982,6000,6004,6016,6023,6056,6067,6090,6095],{"__ignoreMap":616},[620,5983,5984,5986,5988,5990,5992,5994,5996,5998],{"class":622,"line":623},[620,5985,1051],{"class":1050},[620,5987,1120],{"class":1061},[620,5989,1411],{"class":1054},[620,5991,1126],{"class":1061},[620,5993,1129],{"class":1050},[620,5995,1062],{"class":1061},[620,5997,150],{"class":630},[620,5999,1068],{"class":1061},[620,6001,6002],{"class":622,"line":637},[620,6003,1106],{"emptyLinePlaceholder":1105},[620,6005,6006,6009,6012,6014],{"class":622,"line":1071},[620,6007,6008],{"class":1074},"let",[620,6010,6011],{"class":1054}," page ",[620,6013,1081],{"class":1061},[620,6015,1675],{"class":1061},[620,6017,6018,6021],{"class":622,"line":1102},[620,6019,6020],{"class":1050},"try",[620,6022,1518],{"class":1061},[620,6024,6025,6028,6030,6032,6034,6036,6038,6040,6042,6044,6046,6048,6050,6052,6054],{"class":622,"line":1109},[620,6026,6027],{"class":1054},"  page",[620,6029,1538],{"class":1061},[620,6031,1570],{"class":1050},[620,6033,1573],{"class":1054},[620,6035,736],{"class":1061},[620,6037,1578],{"class":1054},[620,6039,736],{"class":1061},[620,6041,1583],{"class":1084},[620,6043,1088],{"class":1161},[620,6045,1593],{"class":1054},[620,6047,1191],{"class":1061},[620,6049,1120],{"class":1061},[620,6051,1533],{"class":1054},[620,6053,1126],{"class":1061},[620,6055,1099],{"class":1161},[620,6057,6058,6060,6062,6065],{"class":622,"line":1115},[620,6059,1200],{"class":1061},[620,6061,1628],{"class":1050},[620,6063,6064],{"class":1054}," (e) ",[620,6066,1155],{"class":1061},[620,6068,6069,6072,6074,6076,6078,6080,6082,6084,6086,6088],{"class":622,"line":1138},[620,6070,6071],{"class":1050},"  if",[620,6073,1506],{"class":1161},[620,6075,1649],{"class":1061},[620,6077,1088],{"class":1161},[620,6079,1633],{"class":1054},[620,6081,1656],{"class":1061},[620,6083,1411],{"class":626},[620,6085,1661],{"class":1161},[620,6087,1664],{"class":1050},[620,6089,1667],{"class":1054},[620,6091,6092],{"class":622,"line":1158},[620,6093,6094],{"class":640},"  \u002F\u002F 404 → null, matching ButterCMS's empty response\n",[620,6096,6097],{"class":622,"line":1177},[620,6098,2046],{"class":1061},[678,6100,6101,6127],{},[567,6102,6103,6109,6112,6113,6115,6116,6119,6120,6122,6123,6126],{},[606,6104,6105,6106,6108],{},"⚠️ Slugs are unique per page type — always pass ",[574,6107,897],{}," if slugs can collide.",[574,6110,6111],{},"pages.getBySlug(slug)"," without a ",[574,6114,897],{}," option is ambiguous when the same slug\nexists under multiple page types (e.g. ",[574,6117,6118],{},"banking"," as both a ",[574,6121,3262],{}," and a\n",[574,6124,6125],{},"guides-index","). The API silently returns one of them — a quiet content regression, not\nan error. Whenever your routing can produce such collisions, pass the type explicitly:",[611,6128,6130],{"className":1389,"code":6129,"language":1391,"meta":616,"style":616},"const page = await tento.pages.getBySlug(slug, { pageType: 'main-page' })\n",[574,6131,6132],{"__ignoreMap":616},[620,6133,6134,6136,6138,6140,6142,6144,6146,6148,6150,6152,6155,6157,6159,6162,6164,6166,6168,6170,6172],{"class":622,"line":623},[620,6135,1075],{"class":1074},[620,6137,6011],{"class":1054},[620,6139,1081],{"class":1061},[620,6141,1570],{"class":1050},[620,6143,1573],{"class":1054},[620,6145,736],{"class":1061},[620,6147,1578],{"class":1054},[620,6149,736],{"class":1061},[620,6151,1583],{"class":1084},[620,6153,6154],{"class":1054},"(slug",[620,6156,1191],{"class":1061},[620,6158,1120],{"class":1061},[620,6160,6161],{"class":1161}," pageType",[620,6163,1165],{"class":1061},[620,6165,1062],{"class":1061},[620,6167,3262],{"class":630},[620,6169,1091],{"class":1061},[620,6171,1126],{"class":1061},[620,6173,1099],{"class":1054},[678,6175,6176,6194],{},[567,6177,6178,6186,6187,6189,6190,6193],{},[606,6179,2432,6180,6182,6183,6185],{},[574,6181,3507],{}," also throws ",[574,6184,5945],{}," on an unknown type slug.","\nThe not-found guidance above focuses on ",[574,6188,1583],{},", but ",[574,6191,6192],{},"collections.list('unknown-type')","\nthrows the same error on a 404. This matters most for navigation fetches that run on every\npage render — an unhandled throw there degrades every page. Wrap those calls in the same\ntry\u002Fcatch:",[611,6195,6197],{"className":1389,"code":6196,"language":1391,"meta":616,"style":616},"import { TentoNotFoundError } from '@tentocms\u002Fclient'\n\nlet nav = null\ntry {\n  nav = await tento.collections.list('navigation-bar')\n} catch (e) {\n  if (!(e instanceof TentoNotFoundError)) throw e\n  \u002F\u002F collection type not found — safe fallback\n}\n",[574,6198,6199,6217,6221,6232,6238,6267,6277,6299,6304],{"__ignoreMap":616},[620,6200,6201,6203,6205,6207,6209,6211,6213,6215],{"class":622,"line":623},[620,6202,1051],{"class":1050},[620,6204,1120],{"class":1061},[620,6206,1411],{"class":1054},[620,6208,1126],{"class":1061},[620,6210,1129],{"class":1050},[620,6212,1062],{"class":1061},[620,6214,150],{"class":630},[620,6216,1068],{"class":1061},[620,6218,6219],{"class":622,"line":637},[620,6220,1106],{"emptyLinePlaceholder":1105},[620,6222,6223,6225,6228,6230],{"class":622,"line":1071},[620,6224,6008],{"class":1074},[620,6226,6227],{"class":1054}," nav ",[620,6229,1081],{"class":1061},[620,6231,1675],{"class":1061},[620,6233,6234,6236],{"class":622,"line":1102},[620,6235,6020],{"class":1050},[620,6237,1518],{"class":1061},[620,6239,6240,6243,6245,6247,6249,6251,6253,6255,6257,6259,6261,6263,6265],{"class":622,"line":1109},[620,6241,6242],{"class":1054},"  nav",[620,6244,1538],{"class":1061},[620,6246,1570],{"class":1050},[620,6248,1573],{"class":1054},[620,6250,736],{"class":1061},[620,6252,3587],{"class":1054},[620,6254,736],{"class":1061},[620,6256,3947],{"class":1084},[620,6258,1088],{"class":1161},[620,6260,1091],{"class":1061},[620,6262,3954],{"class":630},[620,6264,1091],{"class":1061},[620,6266,1099],{"class":1161},[620,6268,6269,6271,6273,6275],{"class":622,"line":1115},[620,6270,1200],{"class":1061},[620,6272,1628],{"class":1050},[620,6274,6064],{"class":1054},[620,6276,1155],{"class":1061},[620,6278,6279,6281,6283,6285,6287,6289,6291,6293,6295,6297],{"class":622,"line":1138},[620,6280,6071],{"class":1050},[620,6282,1506],{"class":1161},[620,6284,1649],{"class":1061},[620,6286,1088],{"class":1161},[620,6288,1633],{"class":1054},[620,6290,1656],{"class":1061},[620,6292,1411],{"class":626},[620,6294,1661],{"class":1161},[620,6296,1664],{"class":1050},[620,6298,1667],{"class":1054},[620,6300,6301],{"class":622,"line":1158},[620,6302,6303],{"class":640},"  \u002F\u002F collection type not found — safe fallback\n",[620,6305,6306],{"class":622,"line":1177},[620,6307,2046],{"class":1061},[567,6309,6310,2572,6313,6315,6316,6318,6319,6321,6322,6329,6330,6332,6333,2572,6340,6342,6343,6346,6347,6350,6351,6353,6354,6357,6358,2532,6361,6364,6365,6368,6369,6371,6372,6375,6376,2579,6379,6381],{},[606,6311,6312],{},"Listing by page type",[574,6314,5845],{}," filters\nserver-side by the page-type ",[606,6317,1593],{}," and returns a correctly paginated result, so you do\n",[606,6320,2458],{}," need to over-fetch and filter in application code. Each page in the list response\nincludes its ",[606,6323,6324,6325,2993,6327],{},"full ",[574,6326,936],{},[574,6328,2724],{}," — not just metadata — so a single list call per page\ntype is enough to build a sitemap or index without N follow-up ",[574,6331,5941],{}," calls (which also\navoids tripping the WAF burst rule documented later in this guide). ",[606,6334,6335,6336,6339],{},"References in\n",[574,6337,6338],{},"pages.list()"," responses are already resolved",[574,6341,936],{}," contains the full resolved object\n(e.g. ",[574,6344,6345],{},"fields.category.name","), not raw ",[574,6348,6349],{},"_ref"," tokens. This is what makes the\nreference-sub-field-filtering workaround described below possible: you can read and filter on\n",[574,6352,6345],{}," client-side because the list response has already resolved the\nreference. List responses include a\n",[574,6355,6356],{},"pagination"," object (",[574,6359,6360],{},"{ total, page, limit, totalPages }",[574,6362,6363],{},"limit"," maxes out at ",[606,6366,6367],{},"100","\n(",[574,6370,6363],{}," > 100 returns ",[574,6373,6374],{},"400 INVALID_PAGINATION",") — page through large types with\n",[574,6377,6378],{},"page",[574,6380,6363],{}," rather than requesting everything at once.",[678,6383,6384,6434],{},[567,6385,6386,693,6392,6394,6395,6397,6398,6401,6402,6408,6409,6411,6412,6415,6416,6421,6422,6425,6426,6429,6430,6433],{},[606,6387,6388,6389,6391],{},"⚠️ Default page ordering is ",[574,6390,2385],{}," descending — non-deterministic for bulk-imported\ncontent.",[574,6393,6338],{}," defaults to ",[574,6396,2385],{}," descending. When content is bulk-imported,\ntimestamps are often near-identical, making the order effectively arbitrary. Rendering \"the\nfirst N pages of a type\" then picks an ",[606,6399,6400],{},"unstable subset"," that may differ from the source CMS\norder and vary between requests. For any \"top N\" or ordered display, pass an explicit, stable\nsort field — ",[606,6403,6404,3814,6406],{},[574,6405,1593],{},[574,6407,3310],{}," order page lists reliably (prefix form: ",[574,6410,3310],{}," = ascending,\n",[574,6413,6414],{},"-name"," = descending). (",[606,6417,6418,6420],{},[574,6419,893],{}," is collections-only — not a valid page sort field.",")\nThis applies equally to ",[606,6423,6424],{},"fixed-count embedded sections"," (e.g. a \"render the first 6 guides\"\ngrid inside a page): bulk-imported content with near-identical timestamps makes the ",[570,6427,6428],{},"set"," shown\nvary run-to-run, not just the order. BINQ fixed this by passing ",[574,6431,6432],{},"sort: 'slug'"," on the embedded\nlist call. Apply the same stable-sort fix wherever you take a count-limited slice of a page\nlist, not only on paginated index pages.",[611,6435,6437],{"className":1036,"code":6436,"language":1038,"meta":616,"style":616},"\u002F\u002F Stable alphabetical order — deterministic even with identical timestamps.\n\u002F\u002F All resources use the same prefix sort form: 'name' = ascending, '-name' = descending.\nconst pages = await tento.pages.list({ pageType: 'case-study', sort: 'name' })\n\u002F\u002F REST: GET \u002Fapi\u002Fv1\u002Fpages?pageType=case-study&sort=name   (descending: sort=-name)\n",[574,6438,6439,6444,6449,6502],{"__ignoreMap":616},[620,6440,6441],{"class":622,"line":623},[620,6442,6443],{"class":640},"\u002F\u002F Stable alphabetical order — deterministic even with identical timestamps.\n",[620,6445,6446],{"class":622,"line":637},[620,6447,6448],{"class":640},"\u002F\u002F All resources use the same prefix sort form: 'name' = ascending, '-name' = descending.\n",[620,6450,6451,6453,6456,6458,6460,6462,6464,6466,6468,6470,6472,6474,6476,6478,6480,6483,6485,6487,6490,6492,6494,6496,6498,6500],{"class":622,"line":1071},[620,6452,1075],{"class":1074},[620,6454,6455],{"class":1054}," pages ",[620,6457,1081],{"class":1061},[620,6459,1570],{"class":1050},[620,6461,1573],{"class":1054},[620,6463,736],{"class":1061},[620,6465,1578],{"class":1054},[620,6467,736],{"class":1061},[620,6469,3947],{"class":1084},[620,6471,1088],{"class":1054},[620,6473,3973],{"class":1061},[620,6475,6161],{"class":1161},[620,6477,1165],{"class":1061},[620,6479,1062],{"class":1061},[620,6481,6482],{"class":630},"case-study",[620,6484,1091],{"class":1061},[620,6486,1191],{"class":1061},[620,6488,6489],{"class":1161}," sort",[620,6491,1165],{"class":1061},[620,6493,1062],{"class":1061},[620,6495,3310],{"class":630},[620,6497,1091],{"class":1061},[620,6499,1126],{"class":1061},[620,6501,1099],{"class":1054},[620,6503,6504],{"class":622,"line":1102},[620,6505,6506],{"class":640},"\u002F\u002F REST: GET \u002Fapi\u002Fv1\u002Fpages?pageType=case-study&sort=name   (descending: sort=-name)\n",[678,6508,6509,6576],{},[567,6510,6511,6514,6515,6517,6518,6521,6522,6525,6526,6529,6530,6532,6533,6536,6537,6540,6541,6544,6545,6547,6548,863,6550,863,6552,863,6554,863,6556,863,6558,863,6560,859,6562,6564,6565,6568,6569,6572,6573,2641],{},[606,6512,6513],{},"💡 Filtering pages by content fields (server-side)."," Beyond the page-type filter,\n",[574,6516,6338],{}," supports ",[606,6519,6520],{},"content-field filters"," — the same ",[574,6523,6524],{},"json_extract"," engine as\ncollections. Pass ",[574,6527,6528],{},"filter[fields.\u003Cpath>][\u003Cop>]=\u003Cvalue>"," query params (or the SDK ",[574,6531,4208],{},"\narray), e.g. ",[574,6534,6535],{},"filter[fields.tier][eq]=pro"," (nested ",[606,6538,6539],{},"scalar"," paths like\n",[574,6542,6543],{},"fields.hero.headline"," work too — but ",[606,6546,2458],{}," resolved-reference sub-fields; see the caveat\nbelow). Operators: ",[574,6549,3996],{},[574,6551,4214],{},[574,6553,4217],{},[574,6555,4220],{},[574,6557,4223],{},[574,6559,4226],{},[574,6561,4229],{},[574,6563,4232],{},". Scope by type with ",[574,6566,6567],{},"?pageType=\u003Cslug>"," and layer content filters on top — no need\nto fetch every page and filter in application code. The returned ",[574,6570,6571],{},"pagination.total"," reflects\nthe filtered set. (Collections filter the same way via ",[574,6574,6575],{},"filters: [{ field, operator, value }]",[567,6577,6578,6581,6582,6584,6585,6587,6588,6591,6592,6594,6595,6598,6599,6601,6602,6604,6605,6607,6608,6611,6612,6614,6615,6618,6619,6621,6622,6624,6625,6627,6628,6631],{},[606,6579,6580],{},"Caveat: server-side filters run on the raw stored content, not resolved references.","\nThe ",[574,6583,6524],{}," filter reads the value as it exists in the database — an unresolved ",[574,6586,6349],{},"\nstring — not the object that the response builder resolves it into. This means scalar content\nfields (strings, numbers, booleans) filter correctly, but you ",[606,6589,6590],{},"cannot"," filter on a\nresolved-reference sub-field. For example, if ",[574,6593,2681],{}," is a reference field that resolves to\n",[574,6596,6597],{},"{ name, slug, _type }"," in the response, a filter on ",[574,6600,6345],{}," matches against the\nraw ",[574,6603,6349],{}," token, not the resolved ",[574,6606,3310],{}," — it will silently return no results. For those cases,\nfetch the page type without a content filter and apply the filter client-side on the adapted shape.\n",[606,6609,6610],{},"When filtering client-side, be aware of the list page-size cap."," A single ",[574,6613,4261],{}," call\nreturns at most 100 items (",[574,6616,6617],{},"limit: 100","). If a page type exceeds 100 published pages you must\npaginate. Pagination is page-number based: the response's ",[574,6620,6356],{}," object has\n",[574,6623,6360],{}," (there is no cursor). Loop by incrementing the ",[574,6626,6378],{},"\nquery param while ",[574,6629,6630],{},"page \u003C totalPages"," — rather than assuming one request returns the full set.",[678,6633,6634],{},[567,6635,6636,6643,6644,6647,6648,6651,6652,6654],{},[606,6637,6638,6639,6642],{},"✔ ",[574,6640,6641],{},"filter[slug][eq]=\u003Cslug>"," on the pages list now works."," Filtering the public page list\nby slug (",[574,6645,6646],{},"GET \u002Fapi\u002Fv1\u002Fpages?filter[slug][eq]=about-us",") correctly returns only the matching\npage(s). ",[574,6649,6650],{},"getBySlug(slug, { pageType })"," remains the idiomatic single-page fetch (it returns\none page directly and throws ",[574,6653,5945],{}," on miss), but a slug filter on the list\nendpoint is now functional if you need the list wrapper, pagination metadata, or to combine\nslug with other filters.",[777,6656],{},[593,6658,6660],{"id":6659},"image-handling","Image Handling",[567,6662,6663,6664,6666],{},"ButterCMS returns images as plain URL strings. TentoCMS returns images as ",[574,6665,866],{}," objects with underscore-prefixed fields.",[611,6668,6670],{"className":1036,"code":6669,"language":1038,"meta":616,"style":616},"interface TentoMedia {\n  _url: string\n  _mimeType: string\n  _width?: number\n  _height?: number\n  _altText?: string\n}\n",[574,6671,6672,6682,6692,6701,6712,6721,6730],{"__ignoreMap":616},[620,6673,6674,6677,6680],{"class":622,"line":623},[620,6675,6676],{"class":1074},"interface",[620,6678,6679],{"class":626}," TentoMedia",[620,6681,1518],{"class":1061},[620,6683,6684,6687,6689],{"class":622,"line":637},[620,6685,6686],{"class":1161},"  _url",[620,6688,1165],{"class":1061},[620,6690,6691],{"class":626}," string\n",[620,6693,6694,6697,6699],{"class":622,"line":1071},[620,6695,6696],{"class":1161},"  _mimeType",[620,6698,1165],{"class":1061},[620,6700,6691],{"class":626},[620,6702,6703,6706,6709],{"class":622,"line":1102},[620,6704,6705],{"class":1161},"  _width",[620,6707,6708],{"class":1061},"?:",[620,6710,6711],{"class":626}," number\n",[620,6713,6714,6717,6719],{"class":622,"line":1109},[620,6715,6716],{"class":1161},"  _height",[620,6718,6708],{"class":1061},[620,6720,6711],{"class":626},[620,6722,6723,6726,6728],{"class":622,"line":1115},[620,6724,6725],{"class":1161},"  _altText",[620,6727,6708],{"class":1061},[620,6729,6691],{"class":626},[620,6731,6732],{"class":622,"line":1138},[620,6733,2046],{"class":1061},[567,6735,6736],{},"Update image references in templates:",[611,6738,6740],{"className":1036,"code":6739,"language":1038,"meta":616,"style":616},"\u002F\u002F ButterCMS\n\u003Cimg src={post.featured_image} \u002F>\n\n\u002F\u002F TentoCMS\n\u003Cimg\n  src={post.featuredImage?._url}\n  alt={post.featuredImage?._altText ?? ''}\n  width={post.featuredImage?._width}\n  height={post.featuredImage?._height}\n\u002F>\n",[574,6741,6742,6746,6762,6766,6770,6777,6789,6802,6814,6826],{"__ignoreMap":616},[620,6743,6744],{"class":622,"line":623},[620,6745,1045],{"class":640},[620,6747,6748,6750,6753,6755,6758,6760],{"class":622,"line":637},[620,6749,1705],{"class":1061},[620,6751,6752],{"class":1054},"img src",[620,6754,4782],{"class":1061},[620,6756,6757],{"class":1054},"post.featured_image",[620,6759,1200],{"class":1061},[620,6761,4791],{"class":1061},[620,6763,6764],{"class":622,"line":1071},[620,6765,1106],{"emptyLinePlaceholder":1105},[620,6767,6768],{"class":622,"line":1102},[620,6769,1112],{"class":640},[620,6771,6772,6774],{"class":622,"line":1109},[620,6773,1705],{"class":1061},[620,6775,6776],{"class":1054},"img\n",[620,6778,6779,6782,6784,6787],{"class":622,"line":1115},[620,6780,6781],{"class":1054},"  src",[620,6783,4782],{"class":1061},[620,6785,6786],{"class":1054},"post.featuredImage?._url",[620,6788,2046],{"class":1061},[620,6790,6791,6794,6796,6798,6800],{"class":622,"line":1138},[620,6792,6793],{"class":1054},"  alt",[620,6795,4782],{"class":1061},[620,6797,4984],{"class":1054},[620,6799,2937],{"class":1061},[620,6801,2046],{"class":1061},[620,6803,6804,6807,6809,6812],{"class":622,"line":1158},[620,6805,6806],{"class":1054},"  width",[620,6808,4782],{"class":1061},[620,6810,6811],{"class":1054},"post.featuredImage?._width",[620,6813,2046],{"class":1061},[620,6815,6816,6819,6821,6824],{"class":622,"line":1177},[620,6817,6818],{"class":1054},"  height",[620,6820,4782],{"class":1061},[620,6822,6823],{"class":1054},"post.featuredImage?._height",[620,6825,2046],{"class":1061},[620,6827,6828],{"class":622,"line":1197},[620,6829,6830],{"class":1061},"\u002F>\n",[598,6832,6834],{"id":6833},"image-transformation-api","Image transformation API",[567,6836,6837,6838,6841,6842,6845,6846,1165],{},"TentoCMS applies image transforms via ",[606,6839,6840],{},"query parameters on the media URL",". The SDK's\n",[574,6843,6844],{},"tento.media.*"," helpers build those URLs for you when you hold a ",[606,6847,6848],{},"media id",[611,6850,6852],{"className":1036,"code":6851,"language":1038,"meta":616,"style":616},"\u002F\u002F Simple transformed URL (requires a media id)\nconst url = tento.media.getImageUrl('media-id', {\n  width: 800,\n  height: 600,\n  fit: 'cover',\n  quality: 85,\n  format: 'webp',\n})\n\n\u002F\u002F Convenience thumbnail (400x400, cover, quality 80)\nconst thumb = tento.media.getThumbnailUrl('media-id')\n\n\u002F\u002F Responsive srcset\nconst srcset = tento.media.getSrcSet('media-id', [400, 800, 1200])\n",[574,6853,6854,6859,6893,6904,6915,6931,6943,6959,6965,6969,6974,7004,7008,7013],{"__ignoreMap":616},[620,6855,6856],{"class":622,"line":623},[620,6857,6858],{"class":640},"\u002F\u002F Simple transformed URL (requires a media id)\n",[620,6860,6861,6863,6866,6868,6870,6872,6875,6877,6880,6882,6884,6887,6889,6891],{"class":622,"line":637},[620,6862,1075],{"class":1074},[620,6864,6865],{"class":1054}," url ",[620,6867,1081],{"class":1061},[620,6869,1573],{"class":1054},[620,6871,736],{"class":1061},[620,6873,6874],{"class":1054},"media",[620,6876,736],{"class":1061},[620,6878,6879],{"class":1084},"getImageUrl",[620,6881,1088],{"class":1054},[620,6883,1091],{"class":1061},[620,6885,6886],{"class":630},"media-id",[620,6888,1091],{"class":1061},[620,6890,1191],{"class":1061},[620,6892,1518],{"class":1061},[620,6894,6895,6897,6899,6902],{"class":622,"line":1071},[620,6896,6806],{"class":1161},[620,6898,1165],{"class":1061},[620,6900,6901],{"class":3623}," 800",[620,6903,859],{"class":1061},[620,6905,6906,6908,6910,6913],{"class":622,"line":1102},[620,6907,6818],{"class":1161},[620,6909,1165],{"class":1061},[620,6911,6912],{"class":3623}," 600",[620,6914,859],{"class":1061},[620,6916,6917,6920,6922,6924,6927,6929],{"class":622,"line":1109},[620,6918,6919],{"class":1161},"  fit",[620,6921,1165],{"class":1061},[620,6923,1062],{"class":1061},[620,6925,6926],{"class":630},"cover",[620,6928,1091],{"class":1061},[620,6930,859],{"class":1061},[620,6932,6933,6936,6938,6941],{"class":622,"line":1115},[620,6934,6935],{"class":1161},"  quality",[620,6937,1165],{"class":1061},[620,6939,6940],{"class":3623}," 85",[620,6942,859],{"class":1061},[620,6944,6945,6948,6950,6952,6955,6957],{"class":622,"line":1138},[620,6946,6947],{"class":1161},"  format",[620,6949,1165],{"class":1061},[620,6951,1062],{"class":1061},[620,6953,6954],{"class":630},"webp",[620,6956,1091],{"class":1061},[620,6958,859],{"class":1061},[620,6960,6961,6963],{"class":622,"line":1158},[620,6962,1200],{"class":1061},[620,6964,1099],{"class":1054},[620,6966,6967],{"class":622,"line":1177},[620,6968,1106],{"emptyLinePlaceholder":1105},[620,6970,6971],{"class":622,"line":1197},[620,6972,6973],{"class":640},"\u002F\u002F Convenience thumbnail (400x400, cover, quality 80)\n",[620,6975,6976,6978,6981,6983,6985,6987,6989,6991,6994,6996,6998,7000,7002],{"class":622,"line":1550},[620,6977,1075],{"class":1074},[620,6979,6980],{"class":1054}," thumb ",[620,6982,1081],{"class":1061},[620,6984,1573],{"class":1054},[620,6986,736],{"class":1061},[620,6988,6874],{"class":1054},[620,6990,736],{"class":1061},[620,6992,6993],{"class":1084},"getThumbnailUrl",[620,6995,1088],{"class":1054},[620,6997,1091],{"class":1061},[620,6999,6886],{"class":630},[620,7001,1091],{"class":1061},[620,7003,1099],{"class":1054},[620,7005,7006],{"class":622,"line":1556},[620,7007,1106],{"emptyLinePlaceholder":1105},[620,7009,7010],{"class":622,"line":1564},[620,7011,7012],{"class":640},"\u002F\u002F Responsive srcset\n",[620,7014,7015,7017,7020,7022,7024,7026,7028,7030,7033,7035,7037,7039,7041,7043,7045,7048,7050,7052,7054,7057],{"class":622,"line":1622},[620,7016,1075],{"class":1074},[620,7018,7019],{"class":1054}," srcset ",[620,7021,1081],{"class":1061},[620,7023,1573],{"class":1054},[620,7025,736],{"class":1061},[620,7027,6874],{"class":1054},[620,7029,736],{"class":1061},[620,7031,7032],{"class":1084},"getSrcSet",[620,7034,1088],{"class":1054},[620,7036,1091],{"class":1061},[620,7038,6886],{"class":630},[620,7040,1091],{"class":1061},[620,7042,1191],{"class":1061},[620,7044,3970],{"class":1054},[620,7046,7047],{"class":3623},"400",[620,7049,1191],{"class":1061},[620,7051,6901],{"class":3623},[620,7053,1191],{"class":1061},[620,7055,7056],{"class":3623}," 1200",[620,7058,7059],{"class":1054},"])\n",[567,7061,7062,863,7065,2386,7068,7071],{},[574,7063,7064],{},"getImageUrl()",[574,7066,7067],{},"getThumbnailUrl()",[574,7069,7070],{},"getSrcSet()"," are synchronous — they build a URL string and make no HTTP request.",[678,7073,7074,7116,7147],{},[567,7075,7076,7089,7090,7092,7093,863,7095,863,7098,863,7101,863,7104,7107,7108,2579,7110,7112,7113,7115],{},[606,7077,7078,7079,7081,7082,7084,7085,7088],{},"⚠️ Inline ",[574,7080,866],{}," has no ",[574,7083,2382],{},", so ",[574,7086,7087],{},"getImageUrl(id, …)"," cannot be used on images\nalready resolved in page\u002Fcollection content."," A resolved ",[574,7091,866],{}," object exposes\nonly ",[574,7094,4974],{},[574,7096,7097],{},"_mimeType",[574,7099,7100],{},"_width",[574,7102,7103],{},"_height",[574,7105,7106],{},"_altText"," — there is no\n",[574,7109,2382],{},[574,7111,3798],{},". For inline media, append transform params to ",[574,7114,4974],{}," directly:",[611,7117,7119],{"className":1036,"code":7118,"language":1038,"meta":616,"style":616},"const src = `${media._url}?width=800&fit=cover&format=webp`\n",[574,7120,7121],{"__ignoreMap":616},[620,7122,7123,7125,7128,7130,7133,7135,7137,7139,7141,7144],{"class":622,"line":623},[620,7124,1075],{"class":1074},[620,7126,7127],{"class":1054}," src ",[620,7129,1081],{"class":1061},[620,7131,7132],{"class":1061}," `${",[620,7134,6874],{"class":1054},[620,7136,736],{"class":1061},[620,7138,4974],{"class":1054},[620,7140,1200],{"class":1061},[620,7142,7143],{"class":630},"?width=800&fit=cover&format=webp",[620,7145,7146],{"class":1061},"`\n",[567,7148,7149,7150,7152,7153,7156,7157,7159],{},"Use the ",[574,7151,7087],{}," helpers only where you actually hold a media id (e.g. from\nthe media API), not for media embedded in content. (The SDK's ",[574,7154,7155],{},"media.transformUrl(url, opts)","\nappends transform params to a ",[574,7158,4974],{}," string for you.)",[678,7161,7162,7186,7214,7388,7404],{},[567,7163,7164,7173,7174,7176,7177,7179,7180,7182,7183,7185],{},[606,7165,7166,7167,7170,7171,736],{},"✔ Optional media is ",[574,7168,7169],{},"TentoMedia | undefined"," — never ",[574,7172,2556],{}," As of the current API version,\nan unset MEDIA or IMAGE field returns ",[574,7175,2578],{}," from the API, which the SDK's ",[574,7178,2953],{},"\nconverts to ",[574,7181,2424],{},". You will never receive ",[574,7184,2556],{}," for an unset media field on a current\ndeployment.",[567,7187,7188,7189,7191,7192,7194,7195,7198,7199,7202,7203,7205,7206,7209,7210,7213],{},"A set image is a ",[574,7190,866],{}," object; an unset one is ",[574,7193,2424],{},". Passing the object straight\nto ",[574,7196,7197],{},"\u003Cimg :src>"," still renders ",[574,7200,7201],{},"[object Object]",", so with the boundary-adapter strategy,\ncollapse any ",[574,7204,4974],{},"-bearing object to its string ",[606,7207,7208],{},"once"," at the boundary rather than\n",[574,7211,7212],{},"?._url"," at every call site:",[611,7215,7217],{"className":1036,"code":7216,"language":1038,"meta":616,"style":616},"const flattenMedia = (v: any): any =>\n  Array.isArray(v) ? v.map(flattenMedia)\n    : v && typeof v === 'object'\n      ? ('_url' in v ? v._url : Object.fromEntries(Object.entries(v).map(([k, x]) => [k, flattenMedia(x)])))\n      : v\n",[574,7218,7219,7246,7273,7299,7380],{"__ignoreMap":616},[620,7220,7221,7223,7226,7228,7230,7233,7235,7238,7241,7243],{"class":622,"line":623},[620,7222,1075],{"class":1074},[620,7224,7225],{"class":1054}," flattenMedia ",[620,7227,1081],{"class":1061},[620,7229,1506],{"class":1061},[620,7231,7232],{"class":1509},"v",[620,7234,1165],{"class":1061},[620,7236,7237],{"class":626}," any",[620,7239,7240],{"class":1061},"):",[620,7242,7237],{"class":626},[620,7244,7245],{"class":1074}," =>\n",[620,7247,7248,7251,7253,7256,7259,7262,7265,7267,7270],{"class":622,"line":637},[620,7249,7250],{"class":1054},"  Array",[620,7252,736],{"class":1061},[620,7254,7255],{"class":1084},"isArray",[620,7257,7258],{"class":1054},"(v) ",[620,7260,7261],{"class":1061},"?",[620,7263,7264],{"class":1054}," v",[620,7266,736],{"class":1061},[620,7268,7269],{"class":1084},"map",[620,7271,7272],{"class":1054},"(flattenMedia)\n",[620,7274,7275,7278,7281,7284,7287,7289,7292,7294,7297],{"class":622,"line":1071},[620,7276,7277],{"class":1061},"    :",[620,7279,7280],{"class":1054}," v ",[620,7282,7283],{"class":1061},"&&",[620,7285,7286],{"class":1061}," typeof",[620,7288,7280],{"class":1054},[620,7290,7291],{"class":1061},"===",[620,7293,1062],{"class":1061},[620,7295,7296],{"class":630},"object",[620,7298,1068],{"class":1061},[620,7300,7301,7304,7306,7308,7310,7312,7315,7317,7319,7321,7323,7326,7328,7331,7333,7336,7339,7341,7344,7347,7349,7351,7353,7356,7359,7361,7364,7367,7369,7372,7374,7377],{"class":622,"line":1102},[620,7302,7303],{"class":1061},"      ?",[620,7305,1506],{"class":1054},[620,7307,1091],{"class":1061},[620,7309,4974],{"class":630},[620,7311,1091],{"class":1061},[620,7313,7314],{"class":1061}," in",[620,7316,7280],{"class":1054},[620,7318,7261],{"class":1061},[620,7320,7264],{"class":1054},[620,7322,736],{"class":1061},[620,7324,7325],{"class":1054},"_url ",[620,7327,1165],{"class":1061},[620,7329,7330],{"class":1054}," Object",[620,7332,736],{"class":1061},[620,7334,7335],{"class":1084},"fromEntries",[620,7337,7338],{"class":1054},"(Object",[620,7340,736],{"class":1061},[620,7342,7343],{"class":1084},"entries",[620,7345,7346],{"class":1054},"(v)",[620,7348,736],{"class":1061},[620,7350,7269],{"class":1084},[620,7352,1088],{"class":1054},[620,7354,7355],{"class":1061},"([",[620,7357,7358],{"class":1509},"k",[620,7360,1191],{"class":1061},[620,7362,7363],{"class":1509}," x",[620,7365,7366],{"class":1061},"])",[620,7368,1515],{"class":1074},[620,7370,7371],{"class":1054}," [k",[620,7373,1191],{"class":1061},[620,7375,7376],{"class":1084}," flattenMedia",[620,7378,7379],{"class":1054},"(x)])))\n",[620,7381,7382,7385],{"class":622,"line":1109},[620,7383,7384],{"class":1061},"      :",[620,7386,7387],{"class":1054}," v\n",[567,7389,7390,7396,7397,7400,7401,7403],{},[606,7391,7392,7393,7395],{},"If your adapter already treats ",[574,7394,2556],{}," as a no-op"," (i.e. the pass-through branch in\n",[574,7398,7399],{},"flattenMedia"," above handles it as a plain value), that is harmless — but on a current\ndeployment ",[574,7402,2556],{}," will never arrive for a media field, so any special-casing for it is dead\ncode. Keep it only if the app must also run against an older API deployment that predates\nthis normalisation.",[567,7405,7406,7413,7414,7416,7417,7419,7420,7422,7423,7425,7426,693,7428,7431,7432,7435,7436,7438,7439,7442,7443,7445,7446,7448,7449,7452],{},[606,7407,2432,7408,7410,7411,736],{},[574,7409,7399],{}," discards ",[574,7412,7106],{}," Collapsing a ",[574,7415,866],{}," object to its ",[574,7418,4974],{},"\nstring drops every other field, including ",[574,7421,7106],{},". Where a separate alt field is\nexpected — for example, blog ",[574,7424,4598],{}," — capture ",[574,7427,7106],{},[606,7429,7430],{},"before","\nflattening: ",[574,7433,7434],{},"const alt = post.featuredImage?._altText ?? ''"," then call ",[574,7437,7399],{},". This\npattern is correct in general, but be aware that for ",[606,7440,7441],{},"imported"," blog posts specifically,\n",[574,7444,7106],{}," will be empty regardless — see the ",[574,7447,4598],{}," note in\n",[579,7450,4285],{"href":7451},"#blog-posts"," above; the migration adapter never propagates the source alt text.",[678,7454,7455,7481],{},[567,7456,7457,7464,7465,7467,7468,859,7470,2386,7472,7474,7475,7477,7478,7480],{},[606,7458,7459,7460,7463],{},"💡 Strip all ",[574,7461,7462],{},"_","-prefixed fields when building your clean Butter shape."," The example\ncleaners above only strip ",[574,7466,858],{},". Collection items also carry ",[574,7469,893],{},[574,7471,3866],{},[574,7473,3880],{}," at the root, which will leak into the adapted shape if\nnot removed. Tento reserves the ",[574,7476,7462],{}," prefix for system fields, so dropping every key that\nstarts with ",[574,7479,7462],{}," is safe and gives you a predictable, clean output:",[611,7482,7484],{"className":1036,"code":7483,"language":1038,"meta":616,"style":616},"const stripSystemFields = (obj: Record\u003Cstring, unknown>) =>\n  Object.fromEntries(Object.entries(obj).filter(([k]) => !k.startsWith('_')))\n",[574,7485,7486,7518],{"__ignoreMap":616},[620,7487,7488,7490,7493,7495,7497,7500,7502,7505,7507,7509,7511,7513,7516],{"class":622,"line":623},[620,7489,1075],{"class":1074},[620,7491,7492],{"class":1054}," stripSystemFields ",[620,7494,1081],{"class":1061},[620,7496,1506],{"class":1061},[620,7498,7499],{"class":1509},"obj",[620,7501,1165],{"class":1061},[620,7503,7504],{"class":626}," Record",[620,7506,1705],{"class":1061},[620,7508,1752],{"class":626},[620,7510,1191],{"class":1061},[620,7512,1757],{"class":626},[620,7514,7515],{"class":1061},">)",[620,7517,7245],{"class":1074},[620,7519,7520,7523,7525,7527,7529,7531,7533,7536,7538,7541,7543,7545,7547,7549,7551,7554,7556,7558,7561,7563,7565,7567,7569],{"class":622,"line":637},[620,7521,7522],{"class":1054},"  Object",[620,7524,736],{"class":1061},[620,7526,7335],{"class":1084},[620,7528,7338],{"class":1054},[620,7530,736],{"class":1061},[620,7532,7343],{"class":1084},[620,7534,7535],{"class":1054},"(obj)",[620,7537,736],{"class":1061},[620,7539,7540],{"class":1084},"filter",[620,7542,1088],{"class":1054},[620,7544,7355],{"class":1061},[620,7546,7358],{"class":1509},[620,7548,7366],{"class":1061},[620,7550,1515],{"class":1074},[620,7552,7553],{"class":1061}," !",[620,7555,7358],{"class":1054},[620,7557,736],{"class":1061},[620,7559,7560],{"class":1084},"startsWith",[620,7562,1088],{"class":1054},[620,7564,1091],{"class":1061},[620,7566,7462],{"class":630},[620,7568,1091],{"class":1061},[620,7570,7571],{"class":1054},")))\n",[678,7573,7574,7584,7618],{},[567,7575,7576,7579,7580,7583],{},[606,7577,7578],{},"Note: transforms have two states — disabled or working.","\nTransform params are always accepted (the request succeeds). If Image Resizing is not\nactive on the deployment, the API returns the ",[606,7581,7582],{},"original"," image unchanged — the params\nbecome a graceful no-op. If width\u002Fheight\u002Fquality appear to have no effect, the resizing\nlayer is likely disabled on that domain rather than the params being wrong.",[567,7585,7586,7591,7592,7595,7596,2572,7607,7609,7610,7613,7614,7617],{},[606,7587,7588,7589],{},"The media route now defaults transforms to ",[574,7590,6954],{}," (paired with the ",[574,7593,7594],{},"quality=85","\ndefault), so the old lossless-VP8L re-encoding path that produced files larger than the\nsource is gone. For clarity and forward compatibility, ",[606,7597,7598,7599,7602,7603,7606],{},"always append ",[574,7600,7601],{},"format=auto"," or\n",[574,7604,7605],{},"format=webp"," explicitly",[574,7608,7601],{}," is mapped to webp and is the recommended\nform. BINQ measured a 289 KB source image coming back as ",[606,7611,7612],{},"52 KB"," at\n",[574,7615,7616],{},"?width=640&height=256&fit=cover&format=auto",". Still measure byte sizes on your own\nassets; gains vary with content.",[567,7619,7620,7632,7633,2579,7636,2579,7639,7642,7643,7645,7646,7648,7649,7652,7653,7656,7657,7660],{},[606,7621,7622,2993,7625,7628,7629,7631],{},[574,7623,7624],{},"@nuxt\u002Fimage",[574,7626,7627],{},"next\u002Fimage"," consumers: use the bare ",[574,7630,4974],{},", not a pre-built transform\nURL."," These components strip the query string and re-derive their own transforms from the\ncomponent's ",[574,7634,7635],{},"width",[574,7637,7638],{},"height",[574,7640,7641],{},"quality"," props — any params you append to ",[574,7644,4974],{}," are dropped\nbefore the request is made. Pass ",[574,7647,4974],{}," directly as the ",[574,7650,7651],{},"src"," and let the image component\nmanage transforms. (For remote images without intrinsic dimensions, NuxtImg may emit a\ndegenerate ",[574,7654,7655],{},"s_1x1"," placeholder src while the real ",[574,7658,7659],{},"srcset"," entries resolve fine — this is\nexpected behaviour, not an error.)",[678,7662,7663,7690,7705,7999],{},[567,7664,7665,7668,7669,7672,7673,7675,7676,7679,7680,7683,7684,7687,7688,736],{},[606,7666,7667],{},"Porting an existing image-optimisation helper?"," If your app had a Butter-specific URL\nrewriter (e.g. a Filestack-style ",[574,7670,7671],{},"resize=width:…,height:…"," keyed to ",[574,7674,4439],{},"), it\n",[606,7677,7678],{},"silently no-ops"," on TentoCMS URLs — different host ",[570,7681,7682],{},"and"," different transform syntax. Images\nstill render, but optimisation is lost. Re-point the helper to append TentoCMS params\n(",[574,7685,7686],{},"?width=&height=&fit=&format=",") to ",[574,7689,4974],{},[567,7691,7692,7695,7696,7698,7699,7704],{},[606,7693,7694],{},"Detect TentoCMS media by path, not by host."," Keying your rewriter off the hostname\n(e.g. ",[574,7697,1032],{},") is brittle: the host differs across\nenvironments and may be proxied or aliased locally. Instead, detect TentoCMS media URLs by\nthe path segment ",[606,7700,7701],{},[574,7702,7703],{},"\u002Fapi\u002Fv1\u002Fmedia\u002F"," — present in every media URL regardless of host — and\npass all other URLs through unchanged:",[611,7706,7708],{"className":1036,"code":7707,"language":1038,"meta":616,"style":616},"function appendTentoTransforms(url: string, opts: { width?: number; format?: string } = {}): string {\n  if (!url.includes('\u002Fapi\u002Fv1\u002Fmedia\u002F')) return url  \u002F\u002F not a Tento media URL — leave it alone\n  \u002F\u002F Build the query string without `new URL()` — that throws on RELATIVE URLs\n  \u002F\u002F (e.g. \"\u002Fapi\u002Fv1\u002Fmedia\u002F...\"), which the API can emit. String-append instead so\n  \u002F\u002F both absolute and relative media URLs work.\n  const params = new URLSearchParams()\n  if (opts.width)  params.set('width', String(opts.width))\n  if (opts.format) params.set('format', opts.format)\n  const qs = params.toString()\n  return qs ? url + (url.includes('?') ? '&' : '?') + qs : url\n}\n",[574,7709,7710,7764,7798,7803,7808,7813,7829,7876,7915,7933,7995],{"__ignoreMap":616},[620,7711,7712,7714,7717,7719,7722,7724,7726,7728,7731,7733,7735,7738,7740,7743,7746,7749,7751,7753,7755,7757,7760,7762],{"class":622,"line":623},[620,7713,4693],{"class":1074},[620,7715,7716],{"class":1084}," appendTentoTransforms",[620,7718,1088],{"class":1061},[620,7720,7721],{"class":1509},"url",[620,7723,1165],{"class":1061},[620,7725,1773],{"class":626},[620,7727,1191],{"class":1061},[620,7729,7730],{"class":1509}," opts",[620,7732,1165],{"class":1061},[620,7734,1120],{"class":1061},[620,7736,7737],{"class":1161}," width",[620,7739,6708],{"class":1061},[620,7741,7742],{"class":626}," number",[620,7744,7745],{"class":1061},";",[620,7747,7748],{"class":1161}," format",[620,7750,6708],{"class":1061},[620,7752,1773],{"class":626},[620,7754,1126],{"class":1061},[620,7756,1538],{"class":1061},[620,7758,7759],{"class":1061}," {}):",[620,7761,1773],{"class":626},[620,7763,1518],{"class":1061},[620,7765,7766,7768,7770,7772,7774,7776,7779,7781,7783,7785,7787,7789,7792,7795],{"class":622,"line":637},[620,7767,6071],{"class":1050},[620,7769,1506],{"class":1161},[620,7771,1649],{"class":1061},[620,7773,7721],{"class":1054},[620,7775,736],{"class":1061},[620,7777,7778],{"class":1084},"includes",[620,7780,1088],{"class":1161},[620,7782,1091],{"class":1061},[620,7784,7703],{"class":630},[620,7786,1091],{"class":1061},[620,7788,1661],{"class":1161},[620,7790,7791],{"class":1050},"return",[620,7793,7794],{"class":1054}," url",[620,7796,7797],{"class":640},"  \u002F\u002F not a Tento media URL — leave it alone\n",[620,7799,7800],{"class":622,"line":1071},[620,7801,7802],{"class":640},"  \u002F\u002F Build the query string without `new URL()` — that throws on RELATIVE URLs\n",[620,7804,7805],{"class":622,"line":1102},[620,7806,7807],{"class":640},"  \u002F\u002F (e.g. \"\u002Fapi\u002Fv1\u002Fmedia\u002F...\"), which the API can emit. String-append instead so\n",[620,7809,7810],{"class":622,"line":1109},[620,7811,7812],{"class":640},"  \u002F\u002F both absolute and relative media URLs work.\n",[620,7814,7815,7817,7820,7822,7824,7827],{"class":622,"line":1115},[620,7816,1523],{"class":1074},[620,7818,7819],{"class":1054}," params",[620,7821,1538],{"class":1061},[620,7823,1148],{"class":1061},[620,7825,7826],{"class":1084}," URLSearchParams",[620,7828,1781],{"class":1161},[620,7830,7831,7833,7835,7838,7840,7842,7845,7848,7850,7852,7854,7856,7858,7860,7862,7865,7867,7869,7871,7873],{"class":622,"line":1138},[620,7832,6071],{"class":1050},[620,7834,1506],{"class":1161},[620,7836,7837],{"class":1054},"opts",[620,7839,736],{"class":1061},[620,7841,7635],{"class":1054},[620,7843,7844],{"class":1161},")  ",[620,7846,7847],{"class":1054},"params",[620,7849,736],{"class":1061},[620,7851,6428],{"class":1084},[620,7853,1088],{"class":1161},[620,7855,1091],{"class":1061},[620,7857,7635],{"class":630},[620,7859,1091],{"class":1061},[620,7861,1191],{"class":1061},[620,7863,7864],{"class":1084}," String",[620,7866,1088],{"class":1161},[620,7868,7837],{"class":1054},[620,7870,736],{"class":1061},[620,7872,7635],{"class":1054},[620,7874,7875],{"class":1161},"))\n",[620,7877,7878,7880,7882,7884,7886,7889,7891,7893,7895,7897,7899,7901,7903,7905,7907,7909,7911,7913],{"class":622,"line":1158},[620,7879,6071],{"class":1050},[620,7881,1506],{"class":1161},[620,7883,7837],{"class":1054},[620,7885,736],{"class":1061},[620,7887,7888],{"class":1054},"format",[620,7890,1636],{"class":1161},[620,7892,7847],{"class":1054},[620,7894,736],{"class":1061},[620,7896,6428],{"class":1084},[620,7898,1088],{"class":1161},[620,7900,1091],{"class":1061},[620,7902,7888],{"class":630},[620,7904,1091],{"class":1061},[620,7906,1191],{"class":1061},[620,7908,7730],{"class":1054},[620,7910,736],{"class":1061},[620,7912,7888],{"class":1054},[620,7914,1099],{"class":1161},[620,7916,7917,7919,7922,7924,7926,7928,7931],{"class":622,"line":1177},[620,7918,1523],{"class":1074},[620,7920,7921],{"class":1054}," qs",[620,7923,1538],{"class":1061},[620,7925,7819],{"class":1054},[620,7927,736],{"class":1061},[620,7929,7930],{"class":1084},"toString",[620,7932,1781],{"class":1161},[620,7934,7935,7937,7939,7942,7944,7947,7949,7951,7953,7955,7957,7959,7961,7963,7965,7967,7969,7972,7974,7977,7979,7981,7983,7985,7988,7990,7992],{"class":622,"line":1197},[620,7936,4712],{"class":1050},[620,7938,7921],{"class":1054},[620,7940,7941],{"class":1061}," ?",[620,7943,7794],{"class":1054},[620,7945,7946],{"class":1061}," +",[620,7948,1506],{"class":1161},[620,7950,7721],{"class":1054},[620,7952,736],{"class":1061},[620,7954,7778],{"class":1084},[620,7956,1088],{"class":1161},[620,7958,1091],{"class":1061},[620,7960,7261],{"class":630},[620,7962,1091],{"class":1061},[620,7964,1636],{"class":1161},[620,7966,7261],{"class":1061},[620,7968,1062],{"class":1061},[620,7970,7971],{"class":630},"&",[620,7973,1091],{"class":1061},[620,7975,7976],{"class":1061}," :",[620,7978,1062],{"class":1061},[620,7980,7261],{"class":630},[620,7982,1091],{"class":1061},[620,7984,1636],{"class":1161},[620,7986,7987],{"class":1061},"+",[620,7989,7921],{"class":1054},[620,7991,7976],{"class":1061},[620,7993,7994],{"class":1054}," url\n",[620,7996,7997],{"class":622,"line":1550},[620,7998,2046],{"class":1061},[567,8000,8001],{},"If your boundary-adapter recipe has a media helper, apply the same path-based guard there.",[678,8003,8004],{},[567,8005,8006,8009,8010,8012,8013,8015,8016,693,8018,8021,8022,8025],{},[606,8007,8008],{},"Allow-list the TentoCMS media host."," Media moved off ",[574,8011,4439],{}," to the TentoCMS\nAPI host (e.g. ",[574,8014,1032],{},"). Frameworks that proxy\u002Foptimise images\nmust add it — ",[574,8017,7624],{},[574,8019,8020],{},"image.domains",", Next.js ",[574,8023,8024],{},"images.remotePatterns",", etc. — otherwise\nthe image component rejects the URL.",[567,8027,8028],{},[606,8029,8030],{},"For complete documentation on image transformations, see:",[650,8032,8033],{},[653,8034,8035],{},[579,8036,8038],{"href":8037},"..\u002Fapi\u002Fmedia","Media API Reference",[567,8040,8041],{},"Supported transform parameters:",[3062,8043,8044,8057],{},[3065,8045,8046],{},[3068,8047,8048,8051,8054],{},[3071,8049,8050],{},"Parameter",[3071,8052,8053],{},"Values",[3071,8055,8056],{},"Default",[3080,8058,8059,8070,8080,8109,8121,8158,8188],{},[3068,8060,8061,8065,8068],{},[3085,8062,8063],{},[574,8064,7635],{},[3085,8066,8067],{},"1–4000",[3085,8069,7582],{},[3068,8071,8072,8076,8078],{},[3085,8073,8074],{},[574,8075,7638],{},[3085,8077,8067],{},[3085,8079,7582],{},[3068,8081,8082,8087,8105],{},[3085,8083,8084],{},[574,8085,8086],{},"fit",[3085,8088,8089,863,8092,863,8095,863,8097,8100,8101,8104],{},[574,8090,8091],{},"scale-down",[574,8093,8094],{},"contain",[574,8096,6926],{},[574,8098,8099],{},"crop"," (no ",[574,8102,8103],{},"pad"," mode)",[3085,8106,8107],{},[574,8108,8091],{},[3068,8110,8111,8115,8118],{},[3085,8112,8113],{},[574,8114,7641],{},[3085,8116,8117],{},"1–100",[3085,8119,8120],{},"85",[3068,8122,8123,8127,8147],{},[3085,8124,8125],{},[574,8126,7888],{},[3085,8128,8129,863,8131,863,8134,863,8137,8100,8140,2579,8143,8146],{},[574,8130,6954],{},[574,8132,8133],{},"avif",[574,8135,8136],{},"auto",[574,8138,8139],{},"json",[574,8141,8142],{},"jpeg",[574,8144,8145],{},"png"," values)",[3085,8148,8149,8151,8152,8154,8155,8157],{},[574,8150,6954],{}," — supplying any other transform param without an explicit ",[574,8153,7888],{}," still re-encodes to ",[574,8156,6954],{},", it does not pass through the original format",[3068,8159,8160,8165,8184],{},[3085,8161,8162],{},[574,8163,8164],{},"gravity",[3085,8166,8167,863,8169,863,8172,863,8175,863,8178,863,8181],{},[574,8168,8136],{},[574,8170,8171],{},"center",[574,8173,8174],{},"top",[574,8176,8177],{},"bottom",[574,8179,8180],{},"left",[574,8182,8183],{},"right",[3085,8185,8186],{},[574,8187,8171],{},[3068,8189,8190,8195,8198],{},[3085,8191,8192],{},[574,8193,8194],{},"dpr",[3085,8196,8197],{},"1–3",[3085,8199,1613],{},[567,8201,8202,8203,8206],{},"Total pixels after DPR scaling (",[574,8204,8205],{},"width × height × dpr²",") also can't exceed 25 megapixels — a validation error rejects requests beyond that, independent of the individual width\u002Fheight caps above.",[777,8208],{},[593,8210,8212],{"id":8211},"common-gotchas","Common Gotchas",[598,8214,8216],{"id":8215},"null-vs-undefined","null vs undefined",[567,8218,8219,8220,8222,8223,8225,8226,8229,8230,2993,8233,8236,8237,863,8240,863,8242,8244,8245,3814,8248,8251],{},"The client SDK automatically converts all ",[574,8221,2578],{}," values to ",[574,8224,2424],{}," via the ",[574,8227,8228],{},"stripNulls()"," utility function called in ",[574,8231,8232],{},"makeRequest()",[574,8234,8235],{},"makeWriteRequest()",". This ensures that optional fields in responses (such as ",[574,8238,8239],{},"TentoMedia._altText",[574,8241,7100],{},[574,8243,7103],{},") are typed and behave as ",[574,8246,8247],{},"string | undefined",[574,8249,8250],{},"number | undefined",", matching framework expectations for component props.",[678,8253,8254],{},[567,8255,8256,8263,8264,8266,8267,2505,8269,8271,8272,8274,8275,8277,8278,8281,8282,736],{},[606,8257,8258,8259,3582,8261,736],{},"⚠️ Optional fields are missing or ",[574,8260,2424],{},[574,8262,2578],{}," The SDK's ",[574,8265,2953],{}," turns any\nexplicit ",[574,8268,2578],{},[574,8270,2424],{}," (the key stays, valued ",[574,8273,2424],{},"), and fields the API or\nmigration omits aren't present at all — e.g. a custom ",[574,8276,2599],{}," SEO container removed on import (see\nthe SEO warning above). Either way, reach through with optional chaining\n(",[574,8279,8280],{},"data.fields.meta?.meta_canonical","); never test ",[574,8283,8284],{},"=== null",[678,8286,8287],{},[567,8288,8289,693,8295,8297,8298,4650,8300,6189,8302,8305,8306,8308,8309,8312,8313,8316,8317,8319,8320,8319,8322,8324,8325,3814,8328,8331,8332,8334,8335,8338,8339,859,8342,8345],{},[606,8290,8291,8292,8294],{},"⚠️ A Nitro\u002FJSON proxy route drops ",[574,8293,2424],{},"-valued keys entirely from the serialised\nresponse.",[574,8296,2953],{}," converts ",[574,8299,2578],{},[574,8301,2424],{},[574,8303,8304],{},"JSON.stringify"," omits keys whose\nvalue is ",[574,8307,2424],{}," — so when a TentoCMS response passes through a Nuxt server route or any\nother JSON proxy, an unset field such as ",[574,8310,8311],{},"hero_section.image"," arrives at the browser ",[606,8314,8315],{},"absent\nfrom the object entirely"," (not as ",[574,8318,2578],{},", not as ",[574,8321,2424],{},[574,8323,2556],{},"). Consumers using\n",[574,8326,8327],{},"x.image || fallback",[574,8329,8330],{},"x.image ?? fallback"," are fine — both handle an absent key the same\nas ",[574,8333,2424],{},". Consumers that check key ",[570,8336,8337],{},"presence"," (e.g. ",[574,8340,8341],{},"'image' in x",[574,8343,8344],{},"Object.hasOwn(x, 'image')",") will not find it and should treat absence as unset.",[567,8347,8348],{},"You can pass optional fields directly to component props without any conversion:",[611,8350,8352],{"className":1036,"code":8351,"language":1038,"meta":616,"style":616},"\u002F\u002F Correct - SDK already returns undefined for absent fields\n\u003Cimg\n  src={post.featuredImage?._url}\n  alt={post.featuredImage?._altText}\n  width={post.featuredImage?._width}\n  height={post.featuredImage?._height}\n\u002F>\n",[574,8353,8354,8359,8365,8375,8386,8396,8406],{"__ignoreMap":616},[620,8355,8356],{"class":622,"line":623},[620,8357,8358],{"class":640},"\u002F\u002F Correct - SDK already returns undefined for absent fields\n",[620,8360,8361,8363],{"class":622,"line":637},[620,8362,1705],{"class":1061},[620,8364,6776],{"class":1054},[620,8366,8367,8369,8371,8373],{"class":622,"line":1071},[620,8368,6781],{"class":1054},[620,8370,4782],{"class":1061},[620,8372,6786],{"class":1054},[620,8374,2046],{"class":1061},[620,8376,8377,8379,8381,8384],{"class":622,"line":1102},[620,8378,6793],{"class":1054},[620,8380,4782],{"class":1061},[620,8382,8383],{"class":1054},"post.featuredImage?._altText",[620,8385,2046],{"class":1061},[620,8387,8388,8390,8392,8394],{"class":622,"line":1109},[620,8389,6806],{"class":1054},[620,8391,4782],{"class":1061},[620,8393,6811],{"class":1054},[620,8395,2046],{"class":1061},[620,8397,8398,8400,8402,8404],{"class":622,"line":1115},[620,8399,6818],{"class":1054},[620,8401,4782],{"class":1061},[620,8403,6823],{"class":1054},[620,8405,2046],{"class":1061},[620,8407,8408],{"class":622,"line":1138},[620,8409,6830],{"class":1061},[567,8411,8412,8413,8416],{},"The SDK handles this sanitization transparently, so you don't need ",[574,8414,8415],{},"?? undefined"," coalescing patterns.",[598,8418,8420],{"id":8419},"collection-key-format","Collection key format",[567,8422,8423,8424,2993,8426,8428,8429,8432,8433,2993,8436,8439],{},"Kebab-case is the canonical form for collection slugs in TentoCMS. ",[574,8425,3292],{},[574,8427,742],{}," both emit kebab-case slugs, and kebab-case is what you should use in new code. That said, the public collection list endpoint intentionally normalizes underscores to hyphens — ",[574,8430,8431],{},"c.req.param('type').replace(\u002F_\u002Fg, '-')"," — so ",[574,8434,8435],{},"GET \u002Fcollections\u002Fguide_category",[574,8437,8438],{},"GET \u002Fcollections\u002Fguide-category"," return identical data. A snake_case slug resolving successfully is not a bug to chase down; it is expected behaviour. Use kebab-case as canonical; just don't be alarmed if a legacy snake_case call unexpectedly succeeds.",[611,8441,8443],{"className":1036,"code":8442,"language":1038,"meta":616,"style":616},"\u002F\u002F ButterCMS (snake_case)\nbutter.content.retrieve(['navigation_bar'])\n\n\u002F\u002F TentoCMS (kebab-case — canonical form)\ntento.collections.list('navigation-bar')\n",[574,8444,8445,8450,8475,8479,8484],{"__ignoreMap":616},[620,8446,8447],{"class":622,"line":623},[620,8448,8449],{"class":640},"\u002F\u002F ButterCMS (snake_case)\n",[620,8451,8452,8455,8457,8459,8461,8464,8466,8468,8471,8473],{"class":622,"line":637},[620,8453,8454],{"class":1054},"butter",[620,8456,736],{"class":1061},[620,8458,4357],{"class":1054},[620,8460,736],{"class":1061},[620,8462,8463],{"class":1084},"retrieve",[620,8465,7355],{"class":1054},[620,8467,1091],{"class":1061},[620,8469,8470],{"class":630},"navigation_bar",[620,8472,1091],{"class":1061},[620,8474,7059],{"class":1054},[620,8476,8477],{"class":622,"line":1071},[620,8478,1106],{"emptyLinePlaceholder":1105},[620,8480,8481],{"class":622,"line":1102},[620,8482,8483],{"class":640},"\u002F\u002F TentoCMS (kebab-case — canonical form)\n",[620,8485,8486,8489,8491,8493,8495,8497,8499,8501,8503,8505],{"class":622,"line":1109},[620,8487,8488],{"class":1054},"tento",[620,8490,736],{"class":1061},[620,8492,3587],{"class":1054},[620,8494,736],{"class":1061},[620,8496,3947],{"class":1084},[620,8498,1088],{"class":1054},[620,8500,1091],{"class":1061},[620,8502,3954],{"class":630},[620,8504,1091],{"class":1061},[620,8506,1099],{"class":1054},[567,8508,8509,8510,8513],{},"Grep your codebase for every ",[574,8511,8512],{},"content.retrieve"," call and update each key to kebab-case.",[678,8515,8516],{},[567,8517,8518,8523,8524,8526,8527,8530,8531,859,8534,863,8537,863,8540,8543,8544,8546,8547,3814,8549,8552,8553,863,8556,863,8559,859,8562,8565,8566,8569],{},[606,8519,2432,8520,8522],{},[574,8521,3292],{}," reports reference options in snake_case — but the list endpoint\nwants kebab-case."," When you inspect a component schema via ",[574,8525,3292],{},", reference\nfield options list their ",[574,8528,8529],{},"collectionType"," in snake_case (e.g. ",[574,8532,8533],{},"\"guide_category\"",[574,8535,8536],{},"\"background_colours\"",[574,8538,8539],{},"\"success_story\"",[574,8541,8542],{},"\"authors\"","). These are ",[606,8545,2458],{}," the slugs you pass\nto ",[574,8548,3507],{},[574,8550,8551],{},"GET \u002Fapi\u002Fv1\u002Fcollections\u002F:slug",". The public collection endpoints\nexpect the kebab-case slug (",[574,8554,8555],{},"guide-category",[574,8557,8558],{},"background-colours",[574,8560,8561],{},"success-story",[574,8563,8564],{},"authors","). Convert snake_case → kebab-case (",[574,8567,8568],{},"collectionType.replaceAll('_', '-')",") whenever\nyou programmatically turn a schema reference option into a collection list call. (The\nunderscore-normalization above means both forms resolve, but kebab-case is the canonical slug\nreported by the schema and generated by typegen.)",[598,8571,837],{"id":8572},"partial-schema-migration",[567,8574,8575,8576,8578],{},"If not all component schemas were created in TentoCMS before starting the application migration, you will encounter components that still use the ButterCMS ",[574,8577,911],{}," format alongside components in the TentoCMS format. This requires a hybrid rendering approach, for example:",[611,8580,8582],{"className":1036,"code":8581,"language":1038,"meta":616,"style":616},"function getComponentType(component) {\n  \u002F\u002F TentoCMS components use _type (kebab-case)\n  if ('_type' in component) return component._type\n  \u002F\u002F Legacy ButterCMS components (snake_case to kebab-case)\n  return component.type.replaceAll('_', '-')\n}\n",[574,8583,8584,8599,8604,8632,8637,8671],{"__ignoreMap":616},[620,8585,8586,8588,8591,8593,8595,8597],{"class":622,"line":623},[620,8587,4693],{"class":1074},[620,8589,8590],{"class":1084}," getComponentType",[620,8592,1088],{"class":1061},[620,8594,5503],{"class":1509},[620,8596,748],{"class":1061},[620,8598,1518],{"class":1061},[620,8600,8601],{"class":622,"line":637},[620,8602,8603],{"class":640},"  \u002F\u002F TentoCMS components use _type (kebab-case)\n",[620,8605,8606,8608,8610,8612,8614,8616,8618,8621,8623,8625,8627,8629],{"class":622,"line":1071},[620,8607,6071],{"class":1050},[620,8609,1506],{"class":1161},[620,8611,1091],{"class":1061},[620,8613,858],{"class":630},[620,8615,1091],{"class":1061},[620,8617,7314],{"class":1061},[620,8619,8620],{"class":1054}," component",[620,8622,1636],{"class":1161},[620,8624,7791],{"class":1050},[620,8626,8620],{"class":1054},[620,8628,736],{"class":1061},[620,8630,8631],{"class":1054},"_type\n",[620,8633,8634],{"class":622,"line":1102},[620,8635,8636],{"class":640},"  \u002F\u002F Legacy ButterCMS components (snake_case to kebab-case)\n",[620,8638,8639,8641,8643,8645,8647,8649,8652,8654,8656,8658,8660,8662,8664,8667,8669],{"class":622,"line":1109},[620,8640,4712],{"class":1050},[620,8642,8620],{"class":1054},[620,8644,736],{"class":1061},[620,8646,915],{"class":1054},[620,8648,736],{"class":1061},[620,8650,8651],{"class":1084},"replaceAll",[620,8653,1088],{"class":1161},[620,8655,1091],{"class":1061},[620,8657,7462],{"class":630},[620,8659,1091],{"class":1061},[620,8661,1191],{"class":1061},[620,8663,1062],{"class":1061},[620,8665,8666],{"class":630},"-",[620,8668,1091],{"class":1061},[620,8670,1099],{"class":1161},[620,8672,8673],{"class":622,"line":1115},[620,8674,2046],{"class":1061},[567,8676,8677],{},"This adds ongoing maintenance burden. Verify all component schemas exist in TentoCMS before beginning application changes.",[598,8679,8681,8682],{"id":8680},"generating-types-with-tentocmstypegen","Generating types with ",[574,8683,742],{},[567,8685,8686],{},"If you use TypeScript, generate types from your live schema rather than hand-writing them:",[611,8688,8690],{"className":613,"code":8689,"language":615,"meta":616,"style":616},"npm install -D @tentocms\u002Ftypegen\nnpx tentocms-typegen init      # creates tentocms.config.js\n",[574,8691,8692,8704],{"__ignoreMap":616},[620,8693,8694,8696,8698,8701],{"class":622,"line":623},[620,8695,964],{"class":626},[620,8697,967],{"class":630},[620,8699,8700],{"class":630}," -D",[620,8702,8703],{"class":630}," @tentocms\u002Ftypegen\n",[620,8705,8706,8709,8712,8715],{"class":622,"line":637},[620,8707,8708],{"class":626},"npx",[620,8710,8711],{"class":630}," tentocms-typegen",[620,8713,8714],{"class":630}," init",[620,8716,8717],{"class":640},"      # creates tentocms.config.js\n",[567,8719,8720,8723,8724,8727,8728,8731,8732,8735,8736,8739,8740,8743,8744,8747,8748,8751,8752,3255,8755,8758,8759,8762,8763,8766,8767,8769],{},[574,8721,8722],{},"tentocms-typegen init"," writes an ",[606,8725,8726],{},"ESM"," config (",[574,8729,8730],{},"export default","), which is correct for any\n",[574,8733,8734],{},"\"type\": \"module\""," project (Nuxt 4, etc.). A ",[574,8737,8738],{},"module.exports"," form will fail to load there — if\nyou must use CommonJS, name the file ",[574,8741,8742],{},"tentocms.config.cjs",". ",[574,8745,8746],{},"init"," also ",[606,8749,8750],{},"detects your project\nlayout"," and writes a sensible ",[574,8753,8754],{},"output",[574,8756,8757],{},".\u002Fsrc\u002Ftypes\u002Fcms.ts"," when a ",[574,8760,8761],{},"src\u002F"," directory exists,\notherwise ",[574,8764,8765],{},".\u002Ftypes\u002Fcms.ts"," (correct for Nuxt 4, which has no ",[574,8768,8761],{},"). Adjust it if your layout\ndiffers.",[611,8771,8775],{"className":8772,"code":8773,"language":8774,"meta":616,"style":616},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F tentocms.config.js — reuse the same env vars as the SDK setup above\nexport default {\n  apiKey: process.env.TENTO_API_KEY,\n  apiUrl: process.env.TENTO_BASE_URL, \u002F\u002F https:\u002F\u002Ftento-api.intelligentlending.co.uk\n  output: '.\u002Ftypes\u002Fcms.ts',          \u002F\u002F init writes .\u002Fsrc\u002Ftypes\u002Fcms.ts when a src\u002F dir exists\n}\n","js",[574,8776,8777,8782,8790,8808,8830,8848],{"__ignoreMap":616},[620,8778,8779],{"class":622,"line":623},[620,8780,8781],{"class":640},"\u002F\u002F tentocms.config.js — reuse the same env vars as the SDK setup above\n",[620,8783,8784,8786,8788],{"class":622,"line":637},[620,8785,1492],{"class":1050},[620,8787,1495],{"class":1050},[620,8789,1518],{"class":1061},[620,8791,8792,8794,8796,8798,8800,8802,8804,8806],{"class":622,"line":1071},[620,8793,1162],{"class":1161},[620,8795,1165],{"class":1061},[620,8797,1240],{"class":1054},[620,8799,736],{"class":1061},[620,8801,1245],{"class":1054},[620,8803,736],{"class":1061},[620,8805,1250],{"class":1054},[620,8807,859],{"class":1061},[620,8809,8810,8813,8815,8817,8819,8821,8823,8825,8827],{"class":622,"line":1102},[620,8811,8812],{"class":1161},"  apiUrl",[620,8814,1165],{"class":1061},[620,8816,1240],{"class":1054},[620,8818,736],{"class":1061},[620,8820,1245],{"class":1054},[620,8822,736],{"class":1061},[620,8824,1270],{"class":1054},[620,8826,1191],{"class":1061},[620,8828,8829],{"class":640}," \u002F\u002F https:\u002F\u002Ftento-api.intelligentlending.co.uk\n",[620,8831,8832,8835,8837,8839,8841,8843,8845],{"class":622,"line":1109},[620,8833,8834],{"class":1161},"  output",[620,8836,1165],{"class":1061},[620,8838,1062],{"class":1061},[620,8840,8765],{"class":630},[620,8842,1091],{"class":1061},[620,8844,1191],{"class":1061},[620,8846,8847],{"class":640},"          \u002F\u002F init writes .\u002Fsrc\u002Ftypes\u002Fcms.ts when a src\u002F dir exists\n",[620,8849,8850],{"class":622,"line":1115},[620,8851,2046],{"class":1061},[611,8853,8855],{"className":613,"code":8854,"language":615,"meta":616,"style":616},"# generate auto-loads .\u002F.env when present — or point it at any file with -e\u002F--env:\ntentocms-typegen generate                # auto-loads .\u002F.env if it exists\ntentocms-typegen generate -e .env.local  # explicit env file\n",[574,8856,8857,8862,8873],{"__ignoreMap":616},[620,8858,8859],{"class":622,"line":623},[620,8860,8861],{"class":640},"# generate auto-loads .\u002F.env when present — or point it at any file with -e\u002F--env:\n",[620,8863,8864,8867,8870],{"class":622,"line":637},[620,8865,8866],{"class":626},"tentocms-typegen",[620,8868,8869],{"class":630}," generate",[620,8871,8872],{"class":640},"                # auto-loads .\u002F.env if it exists\n",[620,8874,8875,8877,8879,8882,8885],{"class":622,"line":1071},[620,8876,8866],{"class":626},[620,8878,8869],{"class":630},[620,8880,8881],{"class":630}," -e",[620,8883,8884],{"class":630}," .env.local",[620,8886,8887],{"class":640},"  # explicit env file\n",[567,8889,8890,8891,8894,8895,8898,8899],{},"Real environment variables always take precedence over the ",[574,8892,8893],{},".env"," file (it never overrides them),\nso this is safe in CI. The older ",[574,8896,8897],{},"node --env-file=.env node_modules\u002F.bin\u002Ftentocms-typegen generate","\nform still works. ",[570,8900,8901,8902,2579,8905,863,8908,8910,8911,8914,8915,8917],{},"(Use ",[574,8903,8904],{},"-e",[574,8906,8907],{},"--env",[606,8909,2458],{}," a bare ",[574,8912,8913],{},"--env-file",": Node ≥22 reserves ",[574,8916,8913],{},"\nfor its own loader and swallows it before the CLI sees it.)",[567,8919,8920,8921,8924,8925,8930,8931,8937,8938,8941,8942,8944],{},"Add a ",[574,8922,8923],{},"types:generate"," npm script for this and wire it into ",[606,8926,8927],{},[574,8928,8929],{},"prebuild"," so types stay in sync\nwith the schema. ",[606,8932,8933,8934],{},"Avoid ",[574,8935,8936],{},"postinstall"," — it runs on every ",[574,8939,8940],{},"npm install"," (CI, fresh clones),\nwhere ",[574,8943,1250],{}," is absent, and would break the install.",[567,8946,8947,8948,8950,8951,8954,8955,8957,8958,8961,8962,8965],{},"Make the ",[574,8949,8929],{}," step ",[606,8952,8953],{},"tolerant"," so a CI or deploy environment without ",[574,8956,1250],{}," falls\nback to the committed ",[574,8959,8960],{},"types\u002Fcms.ts"," rather than failing the build — while still letting real\ntypegen failures fail the build when the key ",[570,8963,8964],{},"is"," present:",[611,8967,8970],{"className":8968,"code":8969,"language":8139,"meta":616,"style":616},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"scripts\": {\n    \"types:generate\": \"if [ -n \\\"$TENTO_API_KEY\\\" ]; then tentocms-typegen generate; else echo 'TENTO_API_KEY absent — skipping typegen, using committed types'; fi\",\n    \"prebuild\": \"npm run types:generate\"\n  }\n}\n",[574,8971,8972,8976,8990,9022,9039,9043],{"__ignoreMap":616},[620,8973,8974],{"class":622,"line":623},[620,8975,1155],{"class":1061},[620,8977,8978,8981,8984,8986,8988],{"class":622,"line":637},[620,8979,8980],{"class":1061},"  \"",[620,8982,8983],{"class":1074},"scripts",[620,8985,1719],{"class":1061},[620,8987,1165],{"class":1061},[620,8989,1518],{"class":1061},[620,8991,8992,8995,8997,8999,9001,9004,9007,9010,9013,9015,9018,9020],{"class":622,"line":1071},[620,8993,8994],{"class":1061},"    \"",[620,8996,8923],{"class":626},[620,8998,1719],{"class":1061},[620,9000,1165],{"class":1061},[620,9002,9003],{"class":1061}," \"",[620,9005,9006],{"class":630},"if [ -n ",[620,9008,9009],{"class":1054},"\\\"",[620,9011,9012],{"class":630},"$TENTO_API_KEY",[620,9014,9009],{"class":1054},[620,9016,9017],{"class":630}," ]; then tentocms-typegen generate; else echo 'TENTO_API_KEY absent — skipping typegen, using committed types'; fi",[620,9019,1719],{"class":1061},[620,9021,859],{"class":1061},[620,9023,9024,9026,9028,9030,9032,9034,9037],{"class":622,"line":1102},[620,9025,8994],{"class":1061},[620,9027,8929],{"class":626},[620,9029,1719],{"class":1061},[620,9031,1165],{"class":1061},[620,9033,9003],{"class":1061},[620,9035,9036],{"class":630},"npm run types:generate",[620,9038,1831],{"class":1061},[620,9040,9041],{"class":622,"line":1109},[620,9042,1681],{"class":1061},[620,9044,9045],{"class":622,"line":1115},[620,9046,2046],{"class":1061},[567,9048,9049,9050,9053,9054,9056,9057,9060,9061,9064,9065,9068],{},"Gate the skip on the key, not on the exit code: a ",[606,9051,9052],{},"missing key"," → skip regeneration and use the\ncommitted ",[574,9055,8960],{},"; ",[606,9058,9059],{},"key present but typegen fails"," (a real regression, or a schema\u002FAPI\u002Fconfig\nerror) → the build fails. Avoid the blanket ",[574,9062,9063],{},"tentocms-typegen generate || echo …"," form — it swallows\n",[570,9066,9067],{},"all"," failures, silently shipping stale committed types when something is genuinely broken.\nRegeneration still runs locally and in any pipeline that has the key, but a deploy without it stays green.",[567,9070,9071,9072,863,9075,863,9078,736],{},"The framework guides cover end-to-end setup:\n",[579,9073,141],{"href":9074},"..\u002Ftypegen\u002Fnuxt",[579,9076,137],{"href":9077},"..\u002Ftypegen\u002Fnextjs",[579,9079,133],{"href":9080},"..\u002Ftypegen\u002Fastro",[567,9082,9083,9091,9092,9095,9096,9099,9100,2579,9102,2579,9104,2579,9106,9108,9109,9112,9113,9117,9118,9120,9121,9123,9124,9126,9127,9129,9130,9132,9133,9136,9137,863,9139,863,9141,9143,9144,9146,9147,9150,9151,9153,9154,9156,9157,9159,9160,9163],{},[606,9084,9085,2965,9088,736],{},[574,9086,9087],{},"*Content",[574,9089,9090],{},"*Fields"," For each page type the generator emits ",[606,9093,9094],{},"two"," interfaces:\n",[574,9097,9098],{},"\u003CName>Content"," (includes the base ",[574,9101,2382],{},[574,9103,1593],{},[574,9105,3096],{},[574,9107,2724],{},") and ",[574,9110,9111],{},"\u003CName>Fields"," (your custom\nfields only). Use ",[606,9114,9115],{},[574,9116,9090],{}," to type the runtime ",[574,9119,1900],{}," object — ",[574,9122,1900],{}," does\n",[570,9125,2458],{}," contain the system id\u002Fslug\u002Ftitle\u002Fseo (those are top-level and under ",[574,9128,862],{},"), so\n",[574,9131,9087],{}," won't match it directly. ",[606,9134,9135],{},"Exception:"," if a page type declares its own content field\nnamed ",[574,9138,3096],{},[574,9140,1593],{},[574,9142,2382],{},", or ",[574,9145,2724],{},", that field ",[570,9148,9149],{},"does"," appear in ",[574,9152,1900],{}," at runtime — and\n",[574,9155,9090],{}," includes it accordingly. This is rare but intentional: ",[574,9158,9090],{}," excludes the ",[570,9161,9162],{},"system","\nbase fields, not any same-named content field the page type itself declares.",[678,9165,9166],{},[567,9167,9168,693,9171,9173,9174,2579,9176,859,9178,863,9181,9184,9185,9188],{},[606,9169,9170],{},"Note for boundary-adapter apps.",[574,9172,742],{}," output (",[574,9175,9087],{},[574,9177,9090],{},[574,9179,9180],{},"AnyComponent[]",[574,9182,9183],{},"ResolvedReference",") describes the ",[606,9186,9187],{},"raw TentoCMS response shape — not"," the\nadapted object your components receive after translation. If you translate TentoCMS responses\nback into your old CMS shape at the data boundary, the generated types are reference material\nonly; keep your existing hand-written component prop types for the adapted shape.",[598,9190,9192],{"id":9191},"typegen-limitations","Typegen limitations",[567,9194,9195,9197],{},[574,9196,742],{}," has a few known limitations:",[650,9199,9200,9214,9297,9360,9366],{},[653,9201,9202,9205,9206,9209,9210,9213],{},[606,9203,9204],{},"Repeater\u002Flist fields"," are typed as ",[574,9207,9208],{},"Record\u003Cstring, unknown>[]"," when the source schema declares no sub-fields for them (repeaters ",[570,9211,9212],{},"with"," declared sub-fields get a precise item type). Declare the sub-fields in the source schema for a typed array, or cast at the consumption point.",[653,9215,9216,9219,9220,9223,9224,9226,9227,9230,9231,9234,9235],{},[606,9217,9218],{},"Reference fields"," can be typed differently across components when the ",[606,9221,9222],{},"source schema models the same field differently"," — e.g. a ",[574,9225,8558],{}," reference typed as a reference in one component (→ ",[574,9228,9229],{},"ResolvedReference & …",") but as a plain object in another (→ ",[574,9232,9233],{},"Record\u003Cstring, unknown>","). Align the field's definition across components for consistent output, or create a manual intersection type, for example:",[611,9236,9238],{"className":1036,"code":9237,"language":1038,"meta":616,"style":616},"import type { BackgroundColoursItem } from '.\u002Fcms'\ntype BackgroundRef = { _id: string; _slug: string } & BackgroundColoursItem\n",[574,9239,9240,9263],{"__ignoreMap":616},[620,9241,9242,9244,9247,9249,9252,9254,9256,9258,9261],{"class":622,"line":623},[620,9243,1051],{"class":1050},[620,9245,9246],{"class":1050}," type",[620,9248,1120],{"class":1061},[620,9250,9251],{"class":1054}," BackgroundColoursItem",[620,9253,1126],{"class":1061},[620,9255,1129],{"class":1050},[620,9257,1062],{"class":1061},[620,9259,9260],{"class":630},".\u002Fcms",[620,9262,1068],{"class":1061},[620,9264,9265,9267,9270,9272,9274,9277,9279,9281,9283,9286,9288,9290,9292,9294],{"class":622,"line":637},[620,9266,915],{"class":1074},[620,9268,9269],{"class":626}," BackgroundRef",[620,9271,1538],{"class":1061},[620,9273,1120],{"class":1061},[620,9275,9276],{"class":1161}," _id",[620,9278,1165],{"class":1061},[620,9280,1773],{"class":626},[620,9282,7745],{"class":1061},[620,9284,9285],{"class":1161}," _slug",[620,9287,1165],{"class":1061},[620,9289,1773],{"class":626},[620,9291,1126],{"class":1061},[620,9293,1763],{"class":1061},[620,9295,9296],{"class":626}," BackgroundColoursItem\n",[653,9298,9299,3413,9307,9309,9310,9312,9313],{},[606,9300,9301,9303,9304],{},[574,9302,8139],{}," fields are typed ",[574,9305,9306],{},"unknown",[574,9308,9233],{},", because a ",[574,9311,8139],{}," field may hold an array or an object. You must narrow or cast at the use site before accessing properties:",[611,9314,9316],{"className":1036,"code":9315,"language":1038,"meta":616,"style":616},"const stats = fields.stats as Array\u003C{ metric: string; value: number }>\n",[574,9317,9318],{"__ignoreMap":616},[620,9319,9320,9322,9325,9327,9330,9332,9335,9338,9340,9342,9345,9347,9349,9351,9353,9355,9357],{"class":622,"line":623},[620,9321,1075],{"class":1074},[620,9323,9324],{"class":1054}," stats ",[620,9326,1081],{"class":1061},[620,9328,9329],{"class":1054}," fields",[620,9331,736],{"class":1061},[620,9333,9334],{"class":1054},"stats ",[620,9336,9337],{"class":1050},"as",[620,9339,1742],{"class":626},[620,9341,1734],{"class":1061},[620,9343,9344],{"class":1161}," metric",[620,9346,1165],{"class":1061},[620,9348,1773],{"class":626},[620,9350,7745],{"class":1061},[620,9352,4003],{"class":1161},[620,9354,1165],{"class":1061},[620,9356,7742],{"class":626},[620,9358,9359],{"class":1061}," }>\n",[653,9361,9362,9365],{},[606,9363,9364],{},"Duplicate collection\u002Fcomponent slugs"," are de-duplicated by keeping the definition with the most fields (a warning is printed). If two definitions genuinely differ, fix the source schema, since the smaller one is dropped.",[653,9367,9368,9373,9374,9376,9377,9379,9380,9382,9383,9385],{},[606,9369,9370,9371,3976],{},"A stale ",[574,9372,2599],{}," can appear on collection interfaces if your content was imported by an ",[570,9375,608],{}," migration (before the adapter stripped ButterCMS's system ",[574,9378,2599],{},"). The live API never returns ",[574,9381,2599],{}," on collection items — re-run the migration, or remove the ",[574,9384,2599],{}," field from the collection type, to clear it.",[598,9387,9389,2965,9391],{"id":9388},"pagetype-vs-component_type",[574,9390,2366],{},[574,9392,5752],{},[567,9394,9395],{},"These are different things:",[650,9397,9398,9405],{},[653,9399,9400,9402,9403,748],{},[574,9401,2366],{}," — the slug of the page type this page belongs to (e.g. ",[574,9404,5762],{},[653,9406,9407,9409,9410,748],{},[574,9408,5752],{}," — the slug of a component within a page's content (e.g. ",[574,9411,5447],{},[567,9413,9414,9415,9417],{},"Do not use ",[574,9416,2366],{}," to identify components.",[598,9419,9421,9422,9425,9426],{"id":9420},"blog-posts-use-content-not-fields","Blog posts use ",[574,9423,9424],{},".content"," not ",[574,9427,5773],{},[567,9429,9430,9431,9433,9434,9436,9437,9440,9441,3560],{},"Blog posts do not have a ",[574,9432,5773],{}," object. All blog post properties are top-level on the post object. The full body content is under ",[574,9435,9424],{}," (not ",[574,9438,9439],{},".body"," as in ButterCMS, and not ",[574,9442,9443],{},".fields.body",[611,9445,9447],{"className":1036,"code":9446,"language":1038,"meta":616,"style":616},"\u002F\u002F Wrong\npost.fields.body\npost.body\n\n\u002F\u002F Correct\npost.content\n",[574,9448,9449,9454,9467,9475,9479,9484],{"__ignoreMap":616},[620,9450,9451],{"class":622,"line":623},[620,9452,9453],{"class":640},"\u002F\u002F Wrong\n",[620,9455,9456,9458,9460,9462,9464],{"class":622,"line":637},[620,9457,4807],{"class":1054},[620,9459,736],{"class":1061},[620,9461,936],{"class":1054},[620,9463,736],{"class":1061},[620,9465,9466],{"class":1054},"body\n",[620,9468,9469,9471,9473],{"class":622,"line":1071},[620,9470,4807],{"class":1054},[620,9472,736],{"class":1061},[620,9474,9466],{"class":1054},[620,9476,9477],{"class":622,"line":1102},[620,9478,1106],{"emptyLinePlaceholder":1105},[620,9480,9481],{"class":622,"line":1109},[620,9482,9483],{"class":640},"\u002F\u002F Correct\n",[620,9485,9486,9488,9490],{"class":622,"line":1115},[620,9487,4807],{"class":1054},[620,9489,736],{"class":1061},[620,9491,9492],{"class":1054},"content\n",[598,9494,9496],{"id":9495},"per-request-preview-toggle","Per-request preview toggle",[567,9498,9499,9500,9503,9504,9507,9508,9511,9512,9519],{},"ButterCMS requires a separate client instance configured with a preview token. TentoCMS toggles preview per request on the same client. ",[606,9501,9502],{},"Preview is opt-in:"," content is live unless you explicitly pass ",[574,9505,9506],{},"{ preview: true }",". Omitting the flag (or passing ",[574,9509,9510],{},"{ preview: false }",") always returns published content — ",[606,9513,9514,9515,9518],{},"even when a ",[574,9516,9517],{},"previewKey"," is configured"," — so you can share one client between live and preview rendering without ever serving drafts by accident.",[611,9521,9523],{"className":1036,"code":9522,"language":1038,"meta":616,"style":616},"const tento = new TentoClient({\n  apiKey: process.env.TENTO_API_KEY!,\n  baseUrl: process.env.TENTO_BASE_URL!,\n  previewKey: process.env.TENTO_PREVIEW_KEY,\n})\n\n\u002F\u002F Live content (default — no flag needed, even with a previewKey set)\nconst page = await tento.pages.getBySlug('home')\n\n\u002F\u002F Draft\u002Fpreview content — opt in explicitly\nconst draftPage = await tento.pages.getBySlug('home', { preview: true })\n\n\u002F\u002F Explicitly live (same as omitting the flag)\nconst livePage = await tento.pages.getBySlug('home', { preview: false })\n",[574,9524,9525,9541,9559,9577,9595,9601,9605,9610,9641,9645,9650,9695,9699,9704],{"__ignoreMap":616},[620,9526,9527,9529,9531,9533,9535,9537,9539],{"class":622,"line":623},[620,9528,1075],{"class":1074},[620,9530,1143],{"class":1054},[620,9532,1081],{"class":1061},[620,9534,1148],{"class":1061},[620,9536,1123],{"class":1084},[620,9538,1088],{"class":1054},[620,9540,1155],{"class":1061},[620,9542,9543,9545,9547,9549,9551,9553,9555,9557],{"class":622,"line":637},[620,9544,1162],{"class":1161},[620,9546,1165],{"class":1061},[620,9548,1240],{"class":1054},[620,9550,736],{"class":1061},[620,9552,1245],{"class":1054},[620,9554,736],{"class":1061},[620,9556,1250],{"class":1054},[620,9558,1253],{"class":1061},[620,9560,9561,9563,9565,9567,9569,9571,9573,9575],{"class":622,"line":1071},[620,9562,1180],{"class":1161},[620,9564,1165],{"class":1061},[620,9566,1240],{"class":1054},[620,9568,736],{"class":1061},[620,9570,1245],{"class":1054},[620,9572,736],{"class":1061},[620,9574,1270],{"class":1054},[620,9576,1253],{"class":1061},[620,9578,9579,9581,9583,9585,9587,9589,9591,9593],{"class":622,"line":1102},[620,9580,1281],{"class":1161},[620,9582,1165],{"class":1061},[620,9584,1240],{"class":1054},[620,9586,736],{"class":1061},[620,9588,1245],{"class":1054},[620,9590,736],{"class":1061},[620,9592,1294],{"class":1054},[620,9594,859],{"class":1061},[620,9596,9597,9599],{"class":622,"line":1109},[620,9598,1200],{"class":1061},[620,9600,1099],{"class":1054},[620,9602,9603],{"class":622,"line":1115},[620,9604,1106],{"emptyLinePlaceholder":1105},[620,9606,9607],{"class":622,"line":1138},[620,9608,9609],{"class":640},"\u002F\u002F Live content (default — no flag needed, even with a previewKey set)\n",[620,9611,9612,9614,9616,9618,9620,9622,9624,9626,9628,9630,9632,9634,9637,9639],{"class":622,"line":1158},[620,9613,1075],{"class":1074},[620,9615,6011],{"class":1054},[620,9617,1081],{"class":1061},[620,9619,1570],{"class":1050},[620,9621,1573],{"class":1054},[620,9623,736],{"class":1061},[620,9625,1578],{"class":1054},[620,9627,736],{"class":1061},[620,9629,1583],{"class":1084},[620,9631,1088],{"class":1054},[620,9633,1091],{"class":1061},[620,9635,9636],{"class":630},"home",[620,9638,1091],{"class":1061},[620,9640,1099],{"class":1054},[620,9642,9643],{"class":622,"line":1177},[620,9644,1106],{"emptyLinePlaceholder":1105},[620,9646,9647],{"class":622,"line":1197},[620,9648,9649],{"class":640},"\u002F\u002F Draft\u002Fpreview content — opt in explicitly\n",[620,9651,9652,9654,9657,9659,9661,9663,9665,9667,9669,9671,9673,9675,9677,9679,9681,9683,9685,9687,9691,9693],{"class":622,"line":1550},[620,9653,1075],{"class":1074},[620,9655,9656],{"class":1054}," draftPage ",[620,9658,1081],{"class":1061},[620,9660,1570],{"class":1050},[620,9662,1573],{"class":1054},[620,9664,736],{"class":1061},[620,9666,1578],{"class":1054},[620,9668,736],{"class":1061},[620,9670,1583],{"class":1084},[620,9672,1088],{"class":1054},[620,9674,1091],{"class":1061},[620,9676,9636],{"class":630},[620,9678,1091],{"class":1061},[620,9680,1191],{"class":1061},[620,9682,1120],{"class":1061},[620,9684,1533],{"class":1161},[620,9686,1165],{"class":1061},[620,9688,9690],{"class":9689},"sfNiH"," true",[620,9692,1126],{"class":1061},[620,9694,1099],{"class":1054},[620,9696,9697],{"class":622,"line":1556},[620,9698,1106],{"emptyLinePlaceholder":1105},[620,9700,9701],{"class":622,"line":1564},[620,9702,9703],{"class":640},"\u002F\u002F Explicitly live (same as omitting the flag)\n",[620,9705,9706,9708,9711,9713,9715,9717,9719,9721,9723,9725,9727,9729,9731,9733,9735,9737,9739,9741,9744,9746],{"class":622,"line":1622},[620,9707,1075],{"class":1074},[620,9709,9710],{"class":1054}," livePage ",[620,9712,1081],{"class":1061},[620,9714,1570],{"class":1050},[620,9716,1573],{"class":1054},[620,9718,736],{"class":1061},[620,9720,1578],{"class":1054},[620,9722,736],{"class":1061},[620,9724,1583],{"class":1084},[620,9726,1088],{"class":1054},[620,9728,1091],{"class":1061},[620,9730,9636],{"class":630},[620,9732,1091],{"class":1061},[620,9734,1191],{"class":1061},[620,9736,1120],{"class":1061},[620,9738,1533],{"class":1161},[620,9740,1165],{"class":1061},[620,9742,9743],{"class":9689}," false",[620,9745,1126],{"class":1061},[620,9747,1099],{"class":1054},[567,9749,9750],{},"This removes the need to maintain separate client instances for preview and live rendering.",[678,9752,9753],{},[567,9754,9755,9762,9763,9765,9766,9768,9769,9771,9772,9774],{},[606,9756,9757,9758,9761],{},"💡 ",[574,9759,9760],{},"preview: true"," falls back to published content when no draft exists."," When you request\na page with ",[574,9764,9506],{}," and that page has no unpublished draft, TentoCMS returns the\n",[606,9767,3324],{}," content — it does not 404 or return empty. This is intentional: forcing preview\nlocally (e.g. via a middleware that always sets ",[574,9770,9760],{},") means published-only pages\nstill render correctly. You will not see a blank page or a ",[574,9773,5945],{}," simply because\ncontent has no draft. The returned content is the current published version until a draft is\ncreated.",[598,9776,3329],{"id":9777},"publish-status-of-imported-content",[567,9779,9780,9781,9784,9785,9787,9788,9790,9791,9794,9795,9798,9799,9801],{},"The migration wizard ",[606,9782,9783],{},"preserves each source page's publish state",": a ButterCMS page that\nwas ",[570,9786,3324],{}," imports as a ",[606,9789,3324],{}," TentoCMS page; an ",[570,9792,9793],{},"unpublished"," one imports as a\n",[606,9796,9797],{},"draft",". Collection items always import as ",[606,9800,3324],{}," (ButterCMS collections have no\ndraft workflow). This trips teams up in two ways:",[650,9803,9804,9834],{},[653,9805,9806,693,9809,9812,9813,9815,9816,9819,9820,9822,9823,9826,9827,9830,9831,9833],{},[606,9807,9808],{},"The public API serves published content only.",[574,9810,9811],{},"GET \u002Fapi\u002Fv1\u002Fpages"," and the SDK's\n",[574,9814,6338],{}," — including the ",[574,9817,9818],{},"?pageType="," filter — omit drafts unless you pass\n",[574,9821,9506],{}," with a configured preview key. So a \"verify by listing pages\" step\n",[606,9824,9825],{},"silently under-reports",": a page type whose pages all imported as drafts returns ",[606,9828,9829],{},"zero","\nresults until you preview. (This is also why ",[574,9832,3292],{}," is the reliable way to\nenumerate page-type slugs — it lists every type regardless of publish state.)",[653,9835,9836,693,9839,693,9841,9846,9847,9849,9850,9853],{},[606,9837,9838],{},"Draft pages are treated as not-found in production.",[574,9840,5941],{},[606,9842,9843,9844],{},"throws\n",[574,9845,5945],{}," (it does not return ",[574,9848,2578],{},") when the requested slug resolves to a draft\nand no preview key is active — it only renders locally if your dev setup forces preview on.\nCatch the error as described in the ",[579,9851,5808],{"href":9852},"#sdk-method-mapping"," gotcha.",[567,9855,9856,9859],{},[606,9857,9858],{},"To publish imported drafts in bulk",", use the admin write API with an authenticated session\n(publisher role or higher) — there is no public or SDK publish method:",[3062,9861,9862,9875],{},[3065,9863,9864],{},[3068,9865,9866,9869,9872],{},[3071,9867,9868],{},"Endpoint",[3071,9870,9871],{},"Body",[3071,9873,9874],{},"Result",[3080,9876,9877,9900],{},[3068,9878,9879,9884,9890],{},[3085,9880,9881],{},[574,9882,9883],{},"POST \u002Fapi\u002Fv1\u002Fadmin\u002Fpages\u002Fbulk-publish",[3085,9885,9886,9889],{},[574,9887,9888],{},"{ \"ids\": [\"\u003CpageId>\", …] }"," (1–100)",[3085,9891,9892,9895,9896,9899],{},[574,9893,9894],{},"{ published, failed }","; fires ",[574,9897,9898],{},"page.published"," webhooks",[3068,9901,9902,9907,9912],{},[3085,9903,9904],{},[574,9905,9906],{},"POST \u002Fapi\u002Fv1\u002Fadmin\u002Fcollection-items\u002Fbulk-publish",[3085,9908,9909,9889],{},[574,9910,9911],{},"{ \"ids\": [\"\u003CitemId>\", …] }",[3085,9913,9914],{},"publishes the listed items",[567,9916,9917,9918,9921],{},"You can also publish pages individually from the TentoCMS admin UI. Either way, after import\n",[606,9919,9920],{},"confirm the pages you expect to be live are published"," (or deliberately left as drafts)\nbefore pointing traffic at them.",[598,9923,9925],{"id":9924},"reference-resolution","Reference Resolution",[567,9927,9928,9929,9931],{},"TentoCMS automatically resolves ",[574,9930,6349],{}," objects in your content. When a page or collection item contains references to other content (users, pages, blog posts, media), they are automatically resolved and replaced with the referenced data. Note that different reference types return different levels of detail: collection items return full content, while page and blog post references return metadata only.",[678,9933,9934],{},[567,9935,9936,9941,9942,9944,9945,3259,9948,9951,9952,863,9955,9958,9959,859,9962,9958,9965,859,9968,9958,9971,9974,9975,9977],{},[606,9937,9938,9940],{},[574,9939,858],{}," is on every nested object — including repeater\u002Farray items."," It isn't only\ntop-level components — every resolved nested group, reference object, and\nrepeater\u002Farray item carries its own kebab-case ",[574,9943,858],{}," discriminant. Examples:\n",[574,9946,9947],{},"background",[574,9949,9950],{},"{ colour, name, _type: \"background-colours\" }",", a CTA URL →\n",[574,9953,9954],{},"{ url, _type: \"urls\" }",[574,9956,9957],{},"products_list[]"," items → ",[574,9960,9961],{},"{ …, _type: \"products\" }",[574,9963,9964],{},"navigation_links[]",[574,9966,9967],{},"{ …, _type: \"navigation-item\" }",[574,9969,9970],{},"children[]",[574,9972,9973],{},"{ …, _type: \"navigation-child-item\" }",". Reading named fields is\nunaffected, but code that deep-compares or spreads nested items onto props should expect\nthe extra ",[574,9976,858],{}," key everywhere.",[678,9979,9980,9986,10023,10037],{},[567,9981,9982,9985],{},[606,9983,9984],{},"✔ Empty references now have consistent, predictable shapes."," As of the current API version,\nunset reference fields are normalised by their declared schema type:",[650,9987,9988,10011],{},[653,9989,9990,9999,10000,8297,10002,4650,10004,10006,10007,10010],{},[606,9991,9992,9993,9995,9996,9998],{},"Unset single reference field → ",[574,9994,2578],{}," from the API → ",[574,9997,2424],{}," via the SDK"," (the SDK's\n",[574,10001,2953],{},[574,10003,2578],{},[574,10005,2424],{},"). You will never see ",[574,10008,10009],{},"{}"," for an unset single\nreference on a current deployment.",[653,10012,10013,10019,10020,10022],{},[606,10014,10015,10016],{},"Unset multiple\u002Farray reference field → ",[574,10017,10018],{},"[]"," (always an empty array). You will never see\n",[574,10021,10009],{}," for an unset array reference on a current deployment.",[567,10024,10025,10026,2579,10029,10032,10033,10036],{},"This applies to reference fields at the top level of a page or collection item, inside\ncomponents (",[574,10027,10028],{},"nestedComponent",[574,10030,10031],{},"componentPicker","), and inside ",[574,10034,10035],{},"repeater"," items.",[567,10038,10039,8338,10044,10047],{},[606,10040,10041,10042],{},"If you added guards for ",[574,10043,10009],{},[574,10045,10046],{},"background && Object.keys(background).length > 0","),\nthey are harmless and do not need to be removed — but they are no longer necessary against a\ncurrent deployment. Keep them if your app must also run against an older API deployment that\npredates this normalisation.",[678,10049,10050],{},[567,10051,10052,10063,10064,10066,10067,10070,10071,10073,10074,10076,10077,3814,10080,10083,10084,10086,10087,10089],{},[606,10053,10054,10055,10057,10058,3413,10060,10062],{},"📝 Empty ",[574,10056,8139],{},"-typed fields also come back as ",[574,10059,10009],{},[574,10061,2578],{}," or absent."," An unset\n",[574,10065,8139],{}," field (e.g. an unset ",[574,10068,10069],{},"location"," or a card-level ",[574,10072,9947],{},") resolves to ",[574,10075,10009],{},".\nReading ",[574,10078,10079],{},".url",[574,10081,10082],{},".colour"," off it yields ",[574,10085,2424],{}," (safe), but guard accordingly — the\nfield is present as ",[574,10088,10009],{},", not missing.",[678,10091,10092,10146,10185],{},[567,10093,10094,10105,10106,10108,10109,10111,10112,10115,10116,10120,10121,10124,10125,10127,10128,4650,10130,10133,10134,10136,10137,10139,10140,10142,10143,10145],{},[606,10095,10096,10097,10099,10100,3413,10102,736],{},"⚠️ Collection-backed ",[574,10098,2678],{}," reference: the image field is ",[574,10101,3177],{},[574,10103,10104],{},"profile"," When\na guide or page has an ",[574,10107,2678],{}," field that references the ",[574,10110,8564],{}," collection, the resolved\nobject is ",[574,10113,10114],{},"{ name, title, image: TentoMedia, _type: 'authors' }",". ButterCMS exposed the same\nauthor image under ",[606,10117,10118],{},[574,10119,10104],{}," — so code reading ",[574,10122,10123],{},"author.profile"," silently gets ",[574,10126,2424],{},"\nafter migration. Rename all reads of ",[574,10129,10123],{},[574,10131,10132],{},"author.image"," (a ",[574,10135,866],{}," object,\nnot a URL string). Note this is distinct from blog post authors, where the image is\n",[574,10138,4422],{}," — a plain URL string that is ",[606,10141,4428],{}," rehosted to a Tento-hosted\n",[574,10144,4432],{}," URL at import time.",[567,10147,10148,10154,10155,10161,10162,10164,10165,10167,10168,10171,10172,10174,10175,10178,10179,10181,10182,10184],{},[606,10149,10150,10151,10153],{},"⚠️ Blog author avatar rehosting can silently fall back to the source URL — keep\n",[574,10152,4439],{}," allow-listed until you've verified."," Rehosting is attempted per author,\nbut on any failure (network\u002FCDN error fetching the source image, R2 error) — or when the same\nauthor is reused across posts and was created from an earlier one — the importer ",[606,10156,10157,10158,10160],{},"keeps the\noriginal ",[574,10159,4439],{}," URL"," and records a warning in the Import report rather than\nfailing the migration. So ",[574,10163,4422],{}," may come back as either a ",[574,10166,4432],{}," URL ",[606,10169,10170],{},"or"," a\n",[574,10173,4439],{}," URL. Unlike guide\u002Fpage ",[606,10176,10177],{},"collection"," authors (whose ",[574,10180,3177],{}," field is\nreliably Tento-hosted), do ",[606,10183,2458],{}," assume blog author avatars are rehosted:",[787,10186,10187,10193],{},[653,10188,10189,10190,10192],{},"After import, check the Import report for avatar rehost warnings, and spot-check a few\n",[574,10191,4422],{}," values.",[653,10194,10195,10196,10198,10199,693,10201,10203,10204,10206],{},"Keep ",[574,10197,4439],{}," in your ",[574,10200,7624],{},[574,10202,8020],{}," \u002F\n",[574,10205,8024],{}," allow-list until every author avatar is confirmed Tento-hosted —\notherwise avatars that fell back to the source URL fail to load. (This is exactly the\nsafety net a real migration relied on.)",[678,10208,10209,10263,10272,10330],{},[567,10210,10211,10219,10220,10222,10223,10226,10227,10229,10230,10232,10233,10235,10236,10239,10240,10242,10243,10245,10246,10248,10249,10252,10253,863,10256,10258,10259,10262],{},[606,10212,10213,10214,2579,10216,10218],{},"🔴 Any unset reference or nested-component field that ButterCMS returned as an empty-string\nobject now arrives as ",[574,10215,2578],{},[574,10217,2424],{}," in TentoCMS."," This is the same class of problem as\nthe ",[574,10221,2528],{}," container described in ",[579,10224,1892],{"href":10225},"#data-shape-differences",":\nButterCMS sent defined fields as ",[574,10228,2556],{}," (never ",[574,10231,2578],{},"), so an unset ",[574,10234,2678],{}," reference came back\nas ",[574,10237,10238],{},"{ name: '', title: '', profile: '' }"," — something truthy that deep reads could traverse\nsafely. TentoCMS returns ",[574,10241,2578],{}," for unset references, which the SDK's ",[574,10244,2953],{}," converts to\n",[574,10247,2424],{},". An unguarded deep read like ",[574,10250,10251],{},"guidePage.fields.author.name"," (or\n",[574,10254,10255],{},"location.url",[574,10257,2604],{},") therefore ",[606,10260,10261],{},"throws at SSR and 500s the page"," — even if\nit never crashed under ButterCMS.",[567,10264,10265,10268,10269,10271],{},[606,10266,10267],{},"In a boundary adapter, coerce unset references to non-null objects with empty-string defaults","\n— exactly as advised for ",[574,10270,2528],{}," — so existing consumer code that accesses sub-fields\nwithout optional chaining stays safe:",[611,10273,10275],{"className":1036,"code":10274,"language":1038,"meta":616,"style":616},"\u002F\u002F In your adapter, after resolving the page:\nconst author = resolvedPage.fields.author ?? { name: '', title: '', profile: '' }\n",[574,10276,10277,10282],{"__ignoreMap":616},[620,10278,10279],{"class":622,"line":623},[620,10280,10281],{"class":640},"\u002F\u002F In your adapter, after resolving the page:\n",[620,10283,10284,10286,10288,10290,10292,10294,10296,10298,10300,10302,10304,10306,10308,10310,10312,10315,10317,10319,10321,10324,10326,10328],{"class":622,"line":637},[620,10285,1075],{"class":1074},[620,10287,2845],{"class":1054},[620,10289,1081],{"class":1061},[620,10291,2850],{"class":1054},[620,10293,736],{"class":1061},[620,10295,936],{"class":1054},[620,10297,736],{"class":1061},[620,10299,2859],{"class":1054},[620,10301,2803],{"class":1061},[620,10303,1120],{"class":1061},[620,10305,2866],{"class":1161},[620,10307,1165],{"class":1061},[620,10309,2871],{"class":1061},[620,10311,1191],{"class":1061},[620,10313,10314],{"class":1161}," title",[620,10316,1165],{"class":1061},[620,10318,2871],{"class":1061},[620,10320,1191],{"class":1061},[620,10322,10323],{"class":1161}," profile",[620,10325,1165],{"class":1061},[620,10327,2871],{"class":1061},[620,10329,2883],{"class":1061},[567,10331,10332,10333,10336],{},"Use optional chaining (",[574,10334,10335],{},"page.fields.author?.name",") at every call site where you read direct\nTentoCMS responses rather than an adapted shape. Any field that was a non-null object in\nButterCMS but is a reference or nested component in TentoCMS is a candidate for this treatment —\naudit every deep read in your templates before migrating.",[567,10338,10339],{},[606,10340,10341],{},"For complete documentation on reference resolution behavior, including:",[650,10343,10344,10353,10356,10359,10362],{},[653,10345,10346,10347,863,10349,10352],{},"All reference types (",[574,10348,6349],{},[574,10350,10351],{},"_media",", typed references)",[653,10354,10355],{},"What fields are resolved for each type",[653,10357,10358],{},"Nested reference handling",[653,10360,10361],{},"Performance optimizations (batch loading)",[653,10363,10364],{},"Depth limits and error handling",[567,10366,4313,10367,2993,10369,10372],{},[579,10368,67],{"href":4240},[579,10370,26],{"href":10371},"..\u002Fapi\u002Ffield-types"," for reference-resolution behaviour and what each reference type resolves to.",[598,10374,10376],{"id":10375},"slug-redirects-seo","Slug redirects (SEO)",[567,10378,10379,10380,10389,10390,10392,10393,2588,10396,10399,10400,10411,10412,10414,10415,10417],{},"When a page's slug changes, the old slug keeps working. ",[606,10381,10382,10383,3818,10386],{},"The raw REST endpoint wraps the\npage: ",[574,10384,10385],{},"GET \u002Fapi\u002Fv1\u002Fpages\u002F:slug",[574,10387,10388],{},"{ data, redirect }"," — your page is under ",[574,10391,4265],{},",\nand ",[574,10394,10395],{},"redirect",[574,10397,10398],{},"{ from, to, permanent } | null"," (populated when you fetched via an old\nslug, so you can issue a 301). Collection items behave similarly. ",[606,10401,10402,10403,10405,10406,10408,10409],{},"The SDK's ",[574,10404,5941],{},"\nunwraps to ",[574,10407,3300],{}," and drops ",[574,10410,10395],{}," — so anyone hand-rolling REST to honour redirects\nmust read ",[574,10413,3300],{}," for the page and the top-level ",[574,10416,10395],{}," for the 301.",[567,10419,10420,693,10426,10429,10430,10433,10434,10436,10437,10439,10440,10443,10444,10446],{},[606,10421,10422,10423,736],{},"Blog single-post REST also wraps in ",[574,10424,10425],{},"{ data }",[574,10427,10428],{},"GET \u002Fapi\u002Fv1\u002Fblog\u002Fposts\u002F:slug"," returns\n",[574,10431,10432],{},"{ data: BlogPost }"," — the post is under ",[574,10435,3300],{},". There is no ",[574,10438,10395],{}," on blog posts. The\nSDK's ",[574,10441,10442],{},"blog.posts.getBySlug()"," unwraps this for you; if you hand-roll the REST call, read\n",[574,10445,3300],{}," to get the post object.",[598,10448,10450],{"id":10449},"rate-limits-and-the-waf","Rate limits and the WAF",[567,10452,10453,10454,10458],{},"Public reads are rate-limited per API key in the Worker (counted per cache miss), with a coarser\nper-IP Cloudflare WAF ceiling as an anti-DDoS backstop above it. Current figures:\n",[579,10455,10457],{"href":10456},"..\u002Fapi\u002Flimits-and-errors","Limits & Errors",". The read\nlimit is set generously so a full build can fetch all published content in one pass. One\nconsequence for migrations:",[650,10460,10461],{},[653,10462,10463,10464,10466,10467,10470,10471,10474,10475,736],{},"Build-time bulk fetches (SSG looping over many pages — exactly what this guide's list loops\ndo) can exceed the per-key limit on a large catalogue or a highly concurrent build. Prefer the\npaginated list endpoints (",[574,10465,6363],{}," up to 100) over one request per item, cap your fetch\nconcurrency, and on a ",[574,10468,10469],{},"429 RATE_LIMITED"," add backoff and honour ",[574,10472,10473],{},"Retry-After",". See\n",[579,10476,10478],{"href":10477},"..\u002Fapi\u002Flimits-and-errors#static-site-generation--bulk-reads","Static-site generation & bulk reads",[598,10480,10482],{"id":10481},"blog-filtering-slugs-vs-ids","Blog filtering: slugs vs IDs",[567,10484,10485,10486,2572,10489,3089,10492,10495,10496,10499,10500,10503,10504,2675,10506,10509,10510,10512,10513,2993,10515,736],{},"The public blog list filters by category\u002Ftag ",[606,10487,10488],{},"IDs",[574,10490,10491],{},"?categoryId=\u003Cuuid>",[574,10493,10494],{},"?tagId=\u003Cuuid>","\n(SDK: ",[574,10497,10498],{},"list({ categoryId, tagId })","). These are validated as ",[606,10501,10502],{},"UUIDs",", so passing a ",[570,10505,1593],{},[574,10507,10508],{},"?categoryId=my-category",") fails validation. If you only have a slug (from a URL), resolve\nit to its ",[574,10511,2382],{}," first via the categories\u002Ftags list endpoints — each item returns both ",[574,10514,2382],{},[574,10516,1593],{},[567,10518,10519,10525,10526,10529],{},[606,10520,10521,10522,10524],{},"Blog list omits ",[574,10523,4357],{}," by default"," — the list endpoint returns lighter ",[574,10527,10528],{},"BlogPostListItem","\nobjects without the post body. Opt in when you need the full HTML:",[650,10531,10532,10538],{},[653,10533,10534,10535],{},"REST: ",[574,10536,10537],{},"GET \u002Fapi\u002Fv1\u002Fblog\u002Fposts?includeContent=true",[653,10539,10540,10541],{},"SDK: ",[574,10542,10543],{},"tento.blog.posts.list({ includeContent: true })",[567,10545,10546,10547,3089,10549,1636,10551,10554,10555,10557,10558,10560,10561,3818,10563,10566],{},"The single-post endpoint (",[574,10548,10428],{},[574,10550,10442],{},[606,10552,10553],{},"always","\nreturns ",[574,10556,4357],{},". The SDK types reflect this: list items are ",[574,10559,10528],{}," (content\noptional \u002F absent by default) while ",[574,10562,5941],{},[574,10564,10565],{},"BlogPost"," (content always present).",[678,10568,10569,10601,10752],{},[567,10570,10571,10580,10581,10583,10584,10586,10587,10589,10590,10592,10593,10595,10596,4650,10598,1165],{},[606,10572,10573,10574,10576,10577,736],{},"⚠️ Computing ",[574,10575,4557],{}," on list items requires ",[574,10578,10579],{},"includeContent: true"," The field-mapping\ntable above advises computing ",[574,10582,4557],{}," from the post's ",[574,10585,4357],{}," word count\n(",[574,10588,4569],{},"), but ",[574,10591,4357],{}," is absent on list items by default. To reproduce\nButterCMS's behaviour — where the list response included the full post body and allowed\n",[574,10594,4557],{}," to be shown on post cards — pass ",[574,10597,10579],{},[574,10599,10600],{},"blog.posts.list()",[611,10602,10604],{"className":1036,"code":10603,"language":1038,"meta":616,"style":616},"const posts = await tento.blog.posts.list({ includeContent: true })\nconst withReadTime = posts.data.map(post => ({\n  ...post,\n  read_time: Math.ceil((post.content?.split(\u002F\\s+\u002F).length ?? 0) \u002F 200),\n}))\n",[574,10605,10606,10648,10678,10687,10746],{"__ignoreMap":616},[620,10607,10608,10610,10613,10615,10617,10619,10621,10624,10626,10629,10631,10633,10635,10637,10640,10642,10644,10646],{"class":622,"line":623},[620,10609,1075],{"class":1074},[620,10611,10612],{"class":1054}," posts ",[620,10614,1081],{"class":1061},[620,10616,1570],{"class":1050},[620,10618,1573],{"class":1054},[620,10620,736],{"class":1061},[620,10622,10623],{"class":1054},"blog",[620,10625,736],{"class":1061},[620,10627,10628],{"class":1054},"posts",[620,10630,736],{"class":1061},[620,10632,3947],{"class":1084},[620,10634,1088],{"class":1054},[620,10636,3973],{"class":1061},[620,10638,10639],{"class":1161}," includeContent",[620,10641,1165],{"class":1061},[620,10643,9690],{"class":9689},[620,10645,1126],{"class":1061},[620,10647,1099],{"class":1054},[620,10649,10650,10652,10655,10657,10660,10662,10664,10666,10668,10670,10672,10674,10676],{"class":622,"line":637},[620,10651,1075],{"class":1074},[620,10653,10654],{"class":1054}," withReadTime ",[620,10656,1081],{"class":1061},[620,10658,10659],{"class":1054}," posts",[620,10661,736],{"class":1061},[620,10663,4265],{"class":1054},[620,10665,736],{"class":1061},[620,10667,7269],{"class":1084},[620,10669,1088],{"class":1054},[620,10671,4807],{"class":1509},[620,10673,1515],{"class":1074},[620,10675,1506],{"class":1054},[620,10677,1155],{"class":1061},[620,10679,10680,10683,10685],{"class":622,"line":1071},[620,10681,10682],{"class":1061},"  ...",[620,10684,4807],{"class":1054},[620,10686,859],{"class":1061},[620,10688,10689,10692,10694,10697,10699,10702,10705,10707,10709,10711,10714,10716,10718,10721,10724,10726,10728,10731,10733,10735,10737,10739,10742,10744],{"class":622,"line":1102},[620,10690,10691],{"class":1161},"  read_time",[620,10693,1165],{"class":1061},[620,10695,10696],{"class":1054}," Math",[620,10698,736],{"class":1061},[620,10700,10701],{"class":1084},"ceil",[620,10703,10704],{"class":1054},"((post",[620,10706,736],{"class":1061},[620,10708,4357],{"class":1054},[620,10710,2727],{"class":1061},[620,10712,10713],{"class":1084},"split",[620,10715,1088],{"class":1054},[620,10717,2579],{"class":1061},[620,10719,10720],{"class":630},"\\s",[620,10722,10723],{"class":1061},"+\u002F",[620,10725,748],{"class":1054},[620,10727,736],{"class":1061},[620,10729,10730],{"class":1054},"length ",[620,10732,2803],{"class":1061},[620,10734,3704],{"class":3623},[620,10736,1636],{"class":1054},[620,10738,2579],{"class":1061},[620,10740,10741],{"class":3623}," 200",[620,10743,748],{"class":1054},[620,10745,859],{"class":1061},[620,10747,10748,10750],{"class":622,"line":1109},[620,10749,1200],{"class":1061},[620,10751,7875],{"class":1054},[567,10753,10754,10755,10757,10758,10760,10761,10763,10764,3814,10766,10768,10769,10771],{},"Without ",[574,10756,10579],{},", list items have no ",[574,10759,4357],{}," and a computed ",[574,10762,4557],{}," is\nalways ",[574,10765,4045],{},[574,10767,2424],{},". The single-post endpoint always includes ",[574,10770,4357],{},", so this only\naffects post-card or index-page rendering that uses the list call.",[598,10773,10775,10776,10779],{"id":10774},"rich-text-wysiwyg-is-not-sanitized-server-side-migrated-content-bypasses-sanitization-entirely","Rich text (",[574,10777,10778],{},"wysiwyg",") is NOT sanitized server-side — migrated content bypasses sanitization entirely",[567,10781,10782,10783,10785,10786,10788,10789,10792,10793,10796,10797,10800,10801,10804],{},"Unlike what you might expect, ",[574,10784,10778],{}," content is ",[606,10787,2458],{}," sanitized on the server at any point.\nThe DOMPurify allowlist (which does strip dangerous tags and adds ",[574,10790,10791],{},"rel=\"noopener noreferrer\""," —\nnote ",[606,10794,10795],{},"noreferrer",", not just ",[574,10798,10799],{},"noopener"," — to external links) only runs ",[606,10802,10803],{},"client-side",", inside\nthe TentoCMS admin's rich-text editor component, when a human edits that field through the admin\nUI. It's deliberately client-only because the underlying library crashes if it runs during\nserver-side rendering on Cloudflare Workers.",[567,10806,10807,10808,10811,10812,10814,10815,10817],{},"This matters directly for migration: ",[606,10809,10810],{},"content imported by the migration wizard is written\nstraight to the database and never passes through the admin editor",", so it never goes through\nthis (or any) sanitizer. The same is true of any content written via direct API calls. A\nButterCMS ",[574,10813,4352],{}," field containing unescaped ",[574,10816,5086],{}," tags or event-handler attributes will be\nimported and served back byte-for-byte as-is by the public API.",[567,10819,10820,10823,10824,10827,10828,10830,10831,10833,10834,10836,10837,10839,10840,2579,10842,2579,10844,10846],{},[606,10821,10822],{},"Practical implication:"," the ",[579,10825,10826],{"href":7451},"XSS guidance"," above — sanitizing before\n",[574,10829,5071],{}," (or your framework's equivalent) — is not just defence-in-depth for\nmigrated content, it is the ",[570,10832,3239],{}," sanitization migrated ",[574,10835,10778],{},"\u002Fblog ",[574,10838,4357],{}," ever receives.\nDo not skip it on the assumption that TentoCMS has already cleaned the HTML server-side; treat\nevery ",[574,10841,4357],{},[574,10843,4352],{},[574,10845,10778],{}," field as untrusted at render time, regardless of how it got into\nTentoCMS.",[598,10848,10850],{"id":10849},"keeping-content-fresh-webhooks","Keeping content fresh (webhooks)",[567,10852,10853,10854,10856,10857,10860],{},"To replace Butter's publish→rebuild flow, subscribe to a ",[574,10855,9898],{}," webhook\n(HMAC-signed via ",[574,10858,10859],{},"X-Webhook-Signature",") and trigger a revalidate\u002Frebuild. Nothing polls for\nyou, so wire this up if edits need to propagate to the frontend automatically.",[678,10862,10863],{},[567,10864,10865,10871,10872,10875,10876,10879,10880,10882],{},[606,10866,10867,10868,10870],{},"💡 Bulk import stamps every page's ",[574,10869,2389],{}," with the import timestamp."," If you use\n",[574,10873,10874],{},"page.updatedAt"," as the ",[574,10877,10878],{},"lastmod"," value in your sitemap, all imported pages will share the\nsame date — the moment the import ran — rather than their original edit dates. This is expected\nbehaviour, not a bug. Once editors save or re-publish content after the migration, ",[574,10881,2389],{},"\nreflects real edit times again.",[777,10884],{},[593,10886,10888],{"id":10887},"pre-migration-checklist","Pre-Migration Checklist",[567,10890,10891],{},"Work through this list before changing any application code.",[650,10893,10896,10905,10911,10922,10931,10939,10950,10958,10967,10976,10994,11000,11016,11028,11034,11046,11060,11066],{"className":10894},[10895],"contains-task-list",[653,10897,10900,10904],{"className":10898},[10899],"task-list-item",[10901,10902],"input",{"disabled":1105,"type":10903},"checkbox"," All component schemas created in TentoCMS",[653,10906,10908,10910],{"className":10907},[10899],[10901,10909],{"disabled":1105,"type":10903}," Content imported via migration wizard (dry run first, then full migration)",[653,10912,10914,693,10916,10918,10919,748],{"className":10913},[10899],[10901,10915],{"disabled":1105,"type":10903},[574,10917,150],{}," installed (",[574,10920,10921],{},"npm install @tentocms\u002Fclient",[653,10923,10925,10927,10928,10930],{"className":10924},[10899],[10901,10926],{"disabled":1105,"type":10903}," Generated types via ",[574,10929,742],{}," (if using TypeScript)",[653,10932,10934,10936,10937,748],{"className":10933},[10899],[10901,10935],{"disabled":1105,"type":10903}," API key stored in environment variables (",[574,10938,1250],{},[653,10940,10942,10944,10945,10947,10948,748],{"className":10941},[10899],[10901,10943],{"disabled":1105,"type":10903}," API base URL set on the client (",[574,10946,1270],{}," = ",[574,10949,730],{},[653,10951,10953,10955,10956,748],{"className":10952},[10899],[10901,10954],{"disabled":1105,"type":10903}," Preview key configured if using preview mode (",[574,10957,1294],{},[653,10959,10961,10963,10964,10966],{"className":10960},[10899],[10901,10962],{"disabled":1105,"type":10903}," Updated component renderer to use ",[574,10965,858],{}," (kebab-case)",[653,10968,10970,10972,10973,10975],{"className":10969},[10899],[10901,10971],{"disabled":1105,"type":10903}," Updated image references from URL strings to ",[574,10974,866],{}," objects",[653,10977,10979,10981,10982,4650,10984,863,10986,4650,10988,863,10990,4650,10992,748],{"className":10978},[10899],[10901,10980],{"disabled":1105,"type":10903}," Updated blog post field names (",[574,10983,4352],{},[574,10985,4357],{},[574,10987,4367],{},[574,10989,4372],{},[574,10991,4382],{},[574,10993,4388],{},[653,10995,10997,10999],{"className":10996},[10899],[10901,10998],{"disabled":1105,"type":10903}," Updated collection keys from snake_case to kebab-case",[653,11001,11003,11005,11006,4650,11009,11011,11012,11015],{"className":11002},[10899],[10901,11004],{"disabled":1105,"type":10903}," Updated SEO field access from ",[574,11007,11008],{},"page.fields.seo_title",[574,11010,2462],{}," (all seven ",[574,11013,11014],{},"seo.*"," fields)",[653,11017,11019,11021,11022,11024,11025,11027],{"className":11018},[10899],[10901,11020],{"disabled":1105,"type":10903}," Verified final page-type slugs after import — check the Import report for slug mappings and typeless-page counts; enumerate live types via ",[574,11023,3292],{}," (typeless ButterCMS pages land in the ",[574,11026,3339],{}," catch-all, not a renamed type)",[653,11029,11031,11033],{"className":11030},[10899],[10901,11032],{"disabled":1105,"type":10903}," Confirmed imported pages are published (drafts are hidden from the public API — bulk-publish or publish them in admin)",[653,11035,11037,11039,11040,4650,11043],{"className":11036},[10899],[10901,11038],{"disabled":1105,"type":10903}," Updated author access from ",[574,11041,11042],{},"post.author.first_name",[574,11044,11045],{},"post.author?.name",[653,11047,11049,11051,11052,3089,11054,5753,11056,3089,11058],{"className":11048},[10899],[10901,11050],{"disabled":1105,"type":10903}," Replaced ",[574,11053,5900],{},[574,11055,5912],{},[574,11057,5905],{},[574,11059,5917],{},[653,11061,11063,11065],{"className":11062},[10899],[10901,11064],{"disabled":1105,"type":10903}," Tested all pages render correctly",[653,11067,11069,11071],{"className":11068},[10899],[10901,11070],{"disabled":1105,"type":10903}," Verified preview mode works",[777,11073],{},[593,11075,11077],{"id":11076},"appendix-boundary-adapter-recipe","Appendix: boundary-adapter recipe",[567,11079,11080],{},"If you centralise CMS access in a server route or utility layer, the transforms below cover every\nshape difference documented in this guide. Copy them into your adapter module and compose them in\nsequence. See the relevant sections above for the reasoning behind each transform.",[611,11082,11084],{"className":1036,"code":11083,"language":1038,"meta":616,"style":616},"import { TentoNotFoundError } from '@tentocms\u002Fclient'\nimport type { Page, TentoMedia } from '@tentocms\u002Fclient'\n\n\u002F\u002F ─── 1. Recursive media flatten ────────────────────────────────────────────\n\u002F\u002F Collapses any { _url, … } object to its _url string.\n\u002F\u002F Leaves undefined (and \"\" if present) as-is. Does NOT touch nested objects that lack _url.\n\u002F\u002F NOTE: as of the current API, unset MEDIA\u002FIMAGE fields arrive as undefined (null → undefined\n\u002F\u002F via stripNulls), never as \"\". The \"\" pass-through below is harmless and is kept for\n\u002F\u002F compatibility with older API deployments that predate this normalisation.\n\u002F\u002F ⚠️ Capture _altText BEFORE calling this wherever a separate alt field is needed\n\u002F\u002F    (e.g. blog featured_image_alt). Example:\n\u002F\u002F      const featuredImageAlt = post.featuredImage?._altText ?? ''\n\u002F\u002F      const flat = flattenMedia(post)\nexport function flattenMedia(v: unknown): unknown {\n  if (Array.isArray(v)) return v.map(flattenMedia)\n  if (v && typeof v === 'object') {\n    if ('_url' in (v as object)) return (v as TentoMedia)._url\n    return Object.fromEntries(\n      Object.entries(v as Record\u003Cstring, unknown>).map(([k, x]) => [k, flattenMedia(x)])\n    )\n  }\n  return v\n}\n\n\u002F\u002F ─── 2. Strip all _-prefixed system fields ──────────────────────────────────\n\u002F\u002F Drops _type, _sortOrder, _publishedAt, _updatedAt, etc. from the top level\n\u002F\u002F of an object. Tento reserves the _ prefix for system fields; stripping them\n\u002F\u002F gives a clean shape that matches the old { type, fields } structure.\n\u002F\u002F Apply after step 3 (so _type is still readable there) and after step 4.\nexport function stripSystemFields\u003CT extends Record\u003Cstring, unknown>>(obj: T): Partial\u003CT> {\n  return Object.fromEntries(\n    Object.entries(obj).filter(([k]) => !k.startsWith('_'))\n  ) as Partial\u003CT>\n}\n\n\u002F\u002F ─── 3. Component _type → type rewrap ──────────────────────────────────────\n\u002F\u002F Converts a TentoCMS component back to Butter's { type, fields } shape.\n\u002F\u002F Moves non-_type fields into fields FIRST so a component's own variant field\n\u002F\u002F (e.g. logo_marquee's `type`) lands at fields.type — not clobbered by step 2.\nexport function rewrapComponent(component: Record\u003Cstring, unknown>): {\n  type: string\n  fields: Record\u003Cstring, unknown>\n} {\n  const { _type, ...rest } = component\n  const fields = stripSystemFields(rest as Record\u003Cstring, unknown>)\n  const type = String(_type ?? '').replaceAll('-', '_') \u002F\u002F kebab → snake\n  return { type, fields: flattenMedia(fields) as Record\u003Cstring, unknown> }\n}\n\n\u002F\u002F ─── 4. seo → fields.meta synthesis ────────────────────────────────────────\n\u002F\u002F Rebuilds the old fields.meta shape from page.seo.\n\u002F\u002F Always returns a non-null object with '' defaults (never undefined\u002Fnull)\n\u002F\u002F so consumers reading fields.meta.meta_canonical without optional chaining\n\u002F\u002F don't SSR-500 on pages where the container was dropped on import.\n\u002F\u002F Re-derives page_path from seo.canonicalUrl (see Data Shape Differences).\nexport function synthesiseMeta(page: Page): Record\u003Cstring, string> {\n  const seo = page.seo ?? {}\n  return {\n    meta_title:       seo.metaTitle       ?? '',\n    meta_description: seo.metaDescription ?? '',\n    meta_canonical:   seo.canonicalUrl    ?? '',\n    og_title:         seo.ogTitle         ?? '',\n    og_description:   seo.ogDescription   ?? '',\n    \u002F\u002F Re-derive page_path from canonicalUrl — fields.meta?.page_path only\n    \u002F\u002F survives import when page_path is a real (defined) field in your Butter\n    \u002F\u002F meta schema; if it was a redundant app-side type, fields.meta is null.\n    page_path: seo.canonicalUrl ?? (page.fields?.meta as Record\u003Cstring, string>)?.page_path ?? '',\n  }\n}\n\n\u002F\u002F ─── 5. Author image remap ──────────────────────────────────────────────────\n\u002F\u002F Collection-backed author resolves the image under `image` (TentoMedia).\n\u002F\u002F ButterCMS exposed it under `profile`. Alias it so existing reads of\n\u002F\u002F author.profile keep working. (Blog post authors use avatarUrl — a plain URL\n\u002F\u002F string rehosted to TentoCMS media at import time, not a cdn.buttercms.com URL.)\nexport function remapAuthor(\n  author: Record\u003Cstring, unknown> | undefined\n): Record\u003Cstring, unknown> | undefined {\n  if (!author) return undefined\n  return { ...author, profile: author.image }\n}\n\n\u002F\u002F ─── 6. getBySlug \u002F list try-catch wrappers ─────────────────────────────────\n\u002F\u002F Catch TentoNotFoundError and return null \u002F [] so existing `if (!data) throw 404`\n\u002F\u002F guards produce 404s, not 500s. Apply to every SDK call at the boundary.\nexport async function safeGetBySlug\u003CT>(\n  fn: () => Promise\u003CT>\n): Promise\u003CT | null> {\n  try { return await fn() }\n  catch (e) {\n    if (e instanceof TentoNotFoundError) return null\n    throw e\n  }\n}\n\nexport async function safeList\u003CT>(\n  fn: () => Promise\u003C{ data: T[] }>\n): Promise\u003CT[]> {\n  try { return (await fn()).data }\n  catch (e) {\n    if (e instanceof TentoNotFoundError) return []\n    throw e\n  }\n}\n",[574,11085,11086,11104,11129,11133,11138,11143,11148,11153,11158,11163,11168,11173,11178,11183,11206,11239,11266,11308,11321,11384,11389,11393,11399,11403,11407,11412,11417,11422,11427,11432,11481,11493,11540,11555,11559,11563,11568,11574,11580,11586,11617,11626,11645,11652,11676,11707,11754,11793,11798,11803,11809,11815,11821,11827,11833,11839,11873,11894,11901,11923,11943,11965,11987,12008,12014,12020,12026,12080,12085,12090,12095,12101,12107,12113,12119,12125,12137,12163,12187,12204,12230,12235,12240,12246,12252,12258,12278,12300,12320,12340,12354,12373,12381,12386,12391,12396,12414,12442,12459,12484,12497,12517,12524,12529],{"__ignoreMap":616},[620,11087,11088,11090,11092,11094,11096,11098,11100,11102],{"class":622,"line":623},[620,11089,1051],{"class":1050},[620,11091,1120],{"class":1061},[620,11093,1411],{"class":1054},[620,11095,1126],{"class":1061},[620,11097,1129],{"class":1050},[620,11099,1062],{"class":1061},[620,11101,150],{"class":630},[620,11103,1068],{"class":1061},[620,11105,11106,11108,11110,11112,11115,11117,11119,11121,11123,11125,11127],{"class":622,"line":637},[620,11107,1051],{"class":1050},[620,11109,9246],{"class":1050},[620,11111,1120],{"class":1061},[620,11113,11114],{"class":1054}," Page",[620,11116,1191],{"class":1061},[620,11118,6679],{"class":1054},[620,11120,1126],{"class":1061},[620,11122,1129],{"class":1050},[620,11124,1062],{"class":1061},[620,11126,150],{"class":630},[620,11128,1068],{"class":1061},[620,11130,11131],{"class":622,"line":1071},[620,11132,1106],{"emptyLinePlaceholder":1105},[620,11134,11135],{"class":622,"line":1102},[620,11136,11137],{"class":640},"\u002F\u002F ─── 1. Recursive media flatten ────────────────────────────────────────────\n",[620,11139,11140],{"class":622,"line":1109},[620,11141,11142],{"class":640},"\u002F\u002F Collapses any { _url, … } object to its _url string.\n",[620,11144,11145],{"class":622,"line":1115},[620,11146,11147],{"class":640},"\u002F\u002F Leaves undefined (and \"\" if present) as-is. Does NOT touch nested objects that lack _url.\n",[620,11149,11150],{"class":622,"line":1138},[620,11151,11152],{"class":640},"\u002F\u002F NOTE: as of the current API, unset MEDIA\u002FIMAGE fields arrive as undefined (null → undefined\n",[620,11154,11155],{"class":622,"line":1158},[620,11156,11157],{"class":640},"\u002F\u002F via stripNulls), never as \"\". The \"\" pass-through below is harmless and is kept for\n",[620,11159,11160],{"class":622,"line":1177},[620,11161,11162],{"class":640},"\u002F\u002F compatibility with older API deployments that predate this normalisation.\n",[620,11164,11165],{"class":622,"line":1197},[620,11166,11167],{"class":640},"\u002F\u002F ⚠️ Capture _altText BEFORE calling this wherever a separate alt field is needed\n",[620,11169,11170],{"class":622,"line":1550},[620,11171,11172],{"class":640},"\u002F\u002F    (e.g. blog featured_image_alt). Example:\n",[620,11174,11175],{"class":622,"line":1556},[620,11176,11177],{"class":640},"\u002F\u002F      const featuredImageAlt = post.featuredImage?._altText ?? ''\n",[620,11179,11180],{"class":622,"line":1564},[620,11181,11182],{"class":640},"\u002F\u002F      const flat = flattenMedia(post)\n",[620,11184,11185,11187,11190,11192,11194,11196,11198,11200,11202,11204],{"class":622,"line":1622},[620,11186,1492],{"class":1050},[620,11188,11189],{"class":1074}," function",[620,11191,7376],{"class":1084},[620,11193,1088],{"class":1061},[620,11195,7232],{"class":1509},[620,11197,1165],{"class":1061},[620,11199,1757],{"class":626},[620,11201,7240],{"class":1061},[620,11203,1757],{"class":626},[620,11205,1518],{"class":1061},[620,11207,11208,11210,11212,11215,11217,11219,11221,11223,11225,11227,11229,11231,11233,11235,11237],{"class":622,"line":1641},[620,11209,6071],{"class":1050},[620,11211,1506],{"class":1161},[620,11213,11214],{"class":1054},"Array",[620,11216,736],{"class":1061},[620,11218,7255],{"class":1084},[620,11220,1088],{"class":1161},[620,11222,7232],{"class":1054},[620,11224,1661],{"class":1161},[620,11226,7791],{"class":1050},[620,11228,7264],{"class":1054},[620,11230,736],{"class":1061},[620,11232,7269],{"class":1084},[620,11234,1088],{"class":1161},[620,11236,7399],{"class":1054},[620,11238,1099],{"class":1161},[620,11240,11241,11243,11245,11247,11250,11252,11254,11256,11258,11260,11262,11264],{"class":622,"line":1670},[620,11242,6071],{"class":1050},[620,11244,1506],{"class":1161},[620,11246,7232],{"class":1054},[620,11248,11249],{"class":1061}," &&",[620,11251,7286],{"class":1061},[620,11253,7264],{"class":1054},[620,11255,1608],{"class":1061},[620,11257,1062],{"class":1061},[620,11259,7296],{"class":630},[620,11261,1091],{"class":1061},[620,11263,1636],{"class":1161},[620,11265,1155],{"class":1061},[620,11267,11268,11270,11272,11274,11276,11278,11280,11282,11284,11287,11289,11291,11293,11295,11297,11299,11301,11303,11305],{"class":622,"line":1678},[620,11269,1644],{"class":1050},[620,11271,1506],{"class":1161},[620,11273,1091],{"class":1061},[620,11275,4974],{"class":630},[620,11277,1091],{"class":1061},[620,11279,7314],{"class":1061},[620,11281,1506],{"class":1161},[620,11283,7232],{"class":1054},[620,11285,11286],{"class":1050}," as",[620,11288,4460],{"class":626},[620,11290,1661],{"class":1161},[620,11292,7791],{"class":1050},[620,11294,1506],{"class":1161},[620,11296,7232],{"class":1054},[620,11298,11286],{"class":1050},[620,11300,6679],{"class":626},[620,11302,748],{"class":1161},[620,11304,736],{"class":1061},[620,11306,11307],{"class":1054},"_url\n",[620,11309,11310,11312,11314,11316,11318],{"class":622,"line":1684},[620,11311,1567],{"class":1050},[620,11313,7330],{"class":1054},[620,11315,736],{"class":1061},[620,11317,7335],{"class":1084},[620,11319,11320],{"class":1161},"(\n",[620,11322,11323,11326,11328,11330,11332,11334,11336,11338,11340,11342,11344,11346,11348,11350,11352,11354,11356,11358,11360,11362,11364,11366,11368,11370,11372,11374,11376,11378,11381],{"class":622,"line":2096},[620,11324,11325],{"class":1054},"      Object",[620,11327,736],{"class":1061},[620,11329,7343],{"class":1084},[620,11331,1088],{"class":1161},[620,11333,7232],{"class":1054},[620,11335,11286],{"class":1050},[620,11337,7504],{"class":626},[620,11339,1705],{"class":1061},[620,11341,1752],{"class":626},[620,11343,1191],{"class":1061},[620,11345,1757],{"class":626},[620,11347,1760],{"class":1061},[620,11349,748],{"class":1161},[620,11351,736],{"class":1061},[620,11353,7269],{"class":1084},[620,11355,1088],{"class":1161},[620,11357,7355],{"class":1061},[620,11359,7358],{"class":1509},[620,11361,1191],{"class":1061},[620,11363,7363],{"class":1509},[620,11365,7366],{"class":1061},[620,11367,1515],{"class":1074},[620,11369,3970],{"class":1161},[620,11371,7358],{"class":1054},[620,11373,1191],{"class":1061},[620,11375,7376],{"class":1084},[620,11377,1088],{"class":1161},[620,11379,11380],{"class":1054},"x",[620,11382,11383],{"class":1161},")])\n",[620,11385,11386],{"class":622,"line":2112},[620,11387,11388],{"class":1161},"    )\n",[620,11390,11391],{"class":622,"line":2131},[620,11392,1681],{"class":1061},[620,11394,11395,11397],{"class":622,"line":2144},[620,11396,4712],{"class":1050},[620,11398,7387],{"class":1054},[620,11400,11401],{"class":622,"line":2160},[620,11402,2046],{"class":1061},[620,11404,11405],{"class":622,"line":2176},[620,11406,1106],{"emptyLinePlaceholder":1105},[620,11408,11409],{"class":622,"line":2182},[620,11410,11411],{"class":640},"\u002F\u002F ─── 2. Strip all _-prefixed system fields ──────────────────────────────────\n",[620,11413,11414],{"class":622,"line":2195},[620,11415,11416],{"class":640},"\u002F\u002F Drops _type, _sortOrder, _publishedAt, _updatedAt, etc. from the top level\n",[620,11418,11419],{"class":622,"line":2211},[620,11420,11421],{"class":640},"\u002F\u002F of an object. Tento reserves the _ prefix for system fields; stripping them\n",[620,11423,11424],{"class":622,"line":2227},[620,11425,11426],{"class":640},"\u002F\u002F gives a clean shape that matches the old { type, fields } structure.\n",[620,11428,11429],{"class":622,"line":2244},[620,11430,11431],{"class":640},"\u002F\u002F Apply after step 3 (so _type is still readable there) and after step 4.\n",[620,11433,11434,11436,11438,11441,11443,11445,11448,11450,11452,11454,11456,11458,11461,11463,11465,11468,11470,11473,11475,11477,11479],{"class":622,"line":2260},[620,11435,1492],{"class":1050},[620,11437,11189],{"class":1074},[620,11439,11440],{"class":1084}," stripSystemFields",[620,11442,1705],{"class":1061},[620,11444,4269],{"class":626},[620,11446,11447],{"class":1074}," extends",[620,11449,7504],{"class":626},[620,11451,1705],{"class":1061},[620,11453,1752],{"class":626},[620,11455,1191],{"class":1061},[620,11457,1757],{"class":626},[620,11459,11460],{"class":1061},">>(",[620,11462,7499],{"class":1509},[620,11464,1165],{"class":1061},[620,11466,11467],{"class":626}," T",[620,11469,7240],{"class":1061},[620,11471,11472],{"class":626}," Partial",[620,11474,1705],{"class":1061},[620,11476,4269],{"class":626},[620,11478,1760],{"class":1061},[620,11480,1518],{"class":1061},[620,11482,11483,11485,11487,11489,11491],{"class":622,"line":2276},[620,11484,4712],{"class":1050},[620,11486,7330],{"class":1054},[620,11488,736],{"class":1061},[620,11490,7335],{"class":1084},[620,11492,11320],{"class":1161},[620,11494,11495,11498,11500,11502,11504,11506,11508,11510,11512,11514,11516,11518,11520,11522,11524,11526,11528,11530,11532,11534,11536,11538],{"class":622,"line":2293},[620,11496,11497],{"class":1054},"    Object",[620,11499,736],{"class":1061},[620,11501,7343],{"class":1084},[620,11503,1088],{"class":1161},[620,11505,7499],{"class":1054},[620,11507,748],{"class":1161},[620,11509,736],{"class":1061},[620,11511,7540],{"class":1084},[620,11513,1088],{"class":1161},[620,11515,7355],{"class":1061},[620,11517,7358],{"class":1509},[620,11519,7366],{"class":1061},[620,11521,1515],{"class":1074},[620,11523,7553],{"class":1061},[620,11525,7358],{"class":1054},[620,11527,736],{"class":1061},[620,11529,7560],{"class":1084},[620,11531,1088],{"class":1161},[620,11533,1091],{"class":1061},[620,11535,7462],{"class":630},[620,11537,1091],{"class":1061},[620,11539,7875],{"class":1161},[620,11541,11542,11545,11547,11549,11551,11553],{"class":622,"line":2310},[620,11543,11544],{"class":1161},"  ) ",[620,11546,9337],{"class":1050},[620,11548,11472],{"class":626},[620,11550,1705],{"class":1061},[620,11552,4269],{"class":626},[620,11554,1726],{"class":1061},[620,11556,11557],{"class":622,"line":2315},[620,11558,2046],{"class":1061},[620,11560,11561],{"class":622,"line":2332},[620,11562,1106],{"emptyLinePlaceholder":1105},[620,11564,11565],{"class":622,"line":2349},[620,11566,11567],{"class":640},"\u002F\u002F ─── 3. Component _type → type rewrap ──────────────────────────────────────\n",[620,11569,11571],{"class":622,"line":11570},37,[620,11572,11573],{"class":640},"\u002F\u002F Converts a TentoCMS component back to Butter's { type, fields } shape.\n",[620,11575,11577],{"class":622,"line":11576},38,[620,11578,11579],{"class":640},"\u002F\u002F Moves non-_type fields into fields FIRST so a component's own variant field\n",[620,11581,11583],{"class":622,"line":11582},39,[620,11584,11585],{"class":640},"\u002F\u002F (e.g. logo_marquee's `type`) lands at fields.type — not clobbered by step 2.\n",[620,11587,11589,11591,11593,11596,11598,11600,11602,11604,11606,11608,11610,11612,11615],{"class":622,"line":11588},40,[620,11590,1492],{"class":1050},[620,11592,11189],{"class":1074},[620,11594,11595],{"class":1084}," rewrapComponent",[620,11597,1088],{"class":1061},[620,11599,5503],{"class":1509},[620,11601,1165],{"class":1061},[620,11603,7504],{"class":626},[620,11605,1705],{"class":1061},[620,11607,1752],{"class":626},[620,11609,1191],{"class":1061},[620,11611,1757],{"class":626},[620,11613,11614],{"class":1061},">):",[620,11616,1518],{"class":1061},[620,11618,11620,11622,11624],{"class":622,"line":11619},41,[620,11621,2115],{"class":1161},[620,11623,1165],{"class":1061},[620,11625,6691],{"class":626},[620,11627,11629,11631,11633,11635,11637,11639,11641,11643],{"class":622,"line":11628},42,[620,11630,2134],{"class":1161},[620,11632,1165],{"class":1061},[620,11634,7504],{"class":626},[620,11636,1705],{"class":1061},[620,11638,1752],{"class":626},[620,11640,1191],{"class":1061},[620,11642,1757],{"class":626},[620,11644,1726],{"class":1061},[620,11646,11648,11650],{"class":622,"line":11647},43,[620,11649,1200],{"class":1061},[620,11651,1518],{"class":1061},[620,11653,11655,11657,11659,11661,11663,11666,11669,11671,11673],{"class":622,"line":11654},44,[620,11656,1523],{"class":1074},[620,11658,1120],{"class":1061},[620,11660,1768],{"class":1054},[620,11662,1191],{"class":1061},[620,11664,11665],{"class":1061}," ...",[620,11667,11668],{"class":1054},"rest",[620,11670,1126],{"class":1061},[620,11672,1538],{"class":1061},[620,11674,11675],{"class":1054}," component\n",[620,11677,11679,11681,11683,11685,11687,11689,11691,11693,11695,11697,11699,11701,11703,11705],{"class":622,"line":11678},45,[620,11680,1523],{"class":1074},[620,11682,9329],{"class":1054},[620,11684,1538],{"class":1061},[620,11686,11440],{"class":1084},[620,11688,1088],{"class":1161},[620,11690,11668],{"class":1054},[620,11692,11286],{"class":1050},[620,11694,7504],{"class":626},[620,11696,1705],{"class":1061},[620,11698,1752],{"class":626},[620,11700,1191],{"class":1061},[620,11702,1757],{"class":626},[620,11704,1760],{"class":1061},[620,11706,1099],{"class":1161},[620,11708,11710,11712,11714,11716,11718,11720,11722,11725,11727,11729,11731,11733,11735,11737,11739,11741,11743,11745,11747,11749,11751],{"class":622,"line":11709},46,[620,11711,1523],{"class":1074},[620,11713,9246],{"class":1054},[620,11715,1538],{"class":1061},[620,11717,7864],{"class":1084},[620,11719,1088],{"class":1161},[620,11721,858],{"class":1054},[620,11723,11724],{"class":1061}," ??",[620,11726,2871],{"class":1061},[620,11728,748],{"class":1161},[620,11730,736],{"class":1061},[620,11732,8651],{"class":1084},[620,11734,1088],{"class":1161},[620,11736,1091],{"class":1061},[620,11738,8666],{"class":630},[620,11740,1091],{"class":1061},[620,11742,1191],{"class":1061},[620,11744,1062],{"class":1061},[620,11746,7462],{"class":630},[620,11748,1091],{"class":1061},[620,11750,1636],{"class":1161},[620,11752,11753],{"class":640},"\u002F\u002F kebab → snake\n",[620,11755,11757,11759,11761,11763,11765,11767,11769,11771,11773,11775,11777,11779,11781,11783,11785,11787,11789,11791],{"class":622,"line":11756},47,[620,11758,4712],{"class":1050},[620,11760,1120],{"class":1061},[620,11762,9246],{"class":1054},[620,11764,1191],{"class":1061},[620,11766,9329],{"class":1161},[620,11768,1165],{"class":1061},[620,11770,7376],{"class":1084},[620,11772,1088],{"class":1161},[620,11774,936],{"class":1054},[620,11776,1636],{"class":1161},[620,11778,9337],{"class":1050},[620,11780,7504],{"class":626},[620,11782,1705],{"class":1061},[620,11784,1752],{"class":626},[620,11786,1191],{"class":1061},[620,11788,1757],{"class":626},[620,11790,1760],{"class":1061},[620,11792,2883],{"class":1061},[620,11794,11796],{"class":622,"line":11795},48,[620,11797,2046],{"class":1061},[620,11799,11801],{"class":622,"line":11800},49,[620,11802,1106],{"emptyLinePlaceholder":1105},[620,11804,11806],{"class":622,"line":11805},50,[620,11807,11808],{"class":640},"\u002F\u002F ─── 4. seo → fields.meta synthesis ────────────────────────────────────────\n",[620,11810,11812],{"class":622,"line":11811},51,[620,11813,11814],{"class":640},"\u002F\u002F Rebuilds the old fields.meta shape from page.seo.\n",[620,11816,11818],{"class":622,"line":11817},52,[620,11819,11820],{"class":640},"\u002F\u002F Always returns a non-null object with '' defaults (never undefined\u002Fnull)\n",[620,11822,11824],{"class":622,"line":11823},53,[620,11825,11826],{"class":640},"\u002F\u002F so consumers reading fields.meta.meta_canonical without optional chaining\n",[620,11828,11830],{"class":622,"line":11829},54,[620,11831,11832],{"class":640},"\u002F\u002F don't SSR-500 on pages where the container was dropped on import.\n",[620,11834,11836],{"class":622,"line":11835},55,[620,11837,11838],{"class":640},"\u002F\u002F Re-derives page_path from seo.canonicalUrl (see Data Shape Differences).\n",[620,11840,11842,11844,11846,11849,11851,11853,11855,11857,11859,11861,11863,11865,11867,11869,11871],{"class":622,"line":11841},56,[620,11843,1492],{"class":1050},[620,11845,11189],{"class":1074},[620,11847,11848],{"class":1084}," synthesiseMeta",[620,11850,1088],{"class":1061},[620,11852,6378],{"class":1509},[620,11854,1165],{"class":1061},[620,11856,11114],{"class":626},[620,11858,7240],{"class":1061},[620,11860,7504],{"class":626},[620,11862,1705],{"class":1061},[620,11864,1752],{"class":626},[620,11866,1191],{"class":1061},[620,11868,1773],{"class":626},[620,11870,1760],{"class":1061},[620,11872,1518],{"class":1061},[620,11874,11876,11878,11881,11883,11885,11887,11889,11891],{"class":622,"line":11875},57,[620,11877,1523],{"class":1074},[620,11879,11880],{"class":1054}," seo",[620,11882,1538],{"class":1061},[620,11884,2719],{"class":1054},[620,11886,736],{"class":1061},[620,11888,2724],{"class":1054},[620,11890,11724],{"class":1061},[620,11892,11893],{"class":1061}," {}\n",[620,11895,11897,11899],{"class":622,"line":11896},58,[620,11898,4712],{"class":1050},[620,11900,1518],{"class":1061},[620,11902,11904,11907,11909,11912,11914,11916,11919,11921],{"class":622,"line":11903},59,[620,11905,11906],{"class":1161},"    meta_title",[620,11908,1165],{"class":1061},[620,11910,11911],{"class":1054},"       seo",[620,11913,736],{"class":1061},[620,11915,2402],{"class":1054},[620,11917,11918],{"class":1061},"       ??",[620,11920,2871],{"class":1061},[620,11922,859],{"class":1061},[620,11924,11926,11929,11931,11933,11935,11937,11939,11941],{"class":622,"line":11925},60,[620,11927,11928],{"class":1161},"    meta_description",[620,11930,1165],{"class":1061},[620,11932,11880],{"class":1054},[620,11934,736],{"class":1061},[620,11936,2405],{"class":1054},[620,11938,11724],{"class":1061},[620,11940,2871],{"class":1061},[620,11942,859],{"class":1061},[620,11944,11946,11949,11951,11954,11956,11958,11961,11963],{"class":622,"line":11945},61,[620,11947,11948],{"class":1161},"    meta_canonical",[620,11950,1165],{"class":1061},[620,11952,11953],{"class":1054},"   seo",[620,11955,736],{"class":1061},[620,11957,2420],{"class":1054},[620,11959,11960],{"class":1061},"    ??",[620,11962,2871],{"class":1061},[620,11964,859],{"class":1061},[620,11966,11968,11971,11973,11976,11978,11980,11983,11985],{"class":622,"line":11967},62,[620,11969,11970],{"class":1161},"    og_title",[620,11972,1165],{"class":1061},[620,11974,11975],{"class":1054},"         seo",[620,11977,736],{"class":1061},[620,11979,2411],{"class":1054},[620,11981,11982],{"class":1061},"         ??",[620,11984,2871],{"class":1061},[620,11986,859],{"class":1061},[620,11988,11990,11993,11995,11997,11999,12001,12004,12006],{"class":622,"line":11989},63,[620,11991,11992],{"class":1161},"    og_description",[620,11994,1165],{"class":1061},[620,11996,11953],{"class":1054},[620,11998,736],{"class":1061},[620,12000,2414],{"class":1054},[620,12002,12003],{"class":1061},"   ??",[620,12005,2871],{"class":1061},[620,12007,859],{"class":1061},[620,12009,12011],{"class":622,"line":12010},64,[620,12012,12013],{"class":640},"    \u002F\u002F Re-derive page_path from canonicalUrl — fields.meta?.page_path only\n",[620,12015,12017],{"class":622,"line":12016},65,[620,12018,12019],{"class":640},"    \u002F\u002F survives import when page_path is a real (defined) field in your Butter\n",[620,12021,12023],{"class":622,"line":12022},66,[620,12024,12025],{"class":640},"    \u002F\u002F meta schema; if it was a redundant app-side type, fields.meta is null.\n",[620,12027,12029,12032,12034,12036,12038,12040,12042,12044,12046,12048,12050,12052,12054,12056,12058,12060,12062,12064,12066,12068,12070,12072,12074,12076,12078],{"class":622,"line":12028},67,[620,12030,12031],{"class":1161},"    page_path",[620,12033,1165],{"class":1061},[620,12035,11880],{"class":1054},[620,12037,736],{"class":1061},[620,12039,2420],{"class":1054},[620,12041,11724],{"class":1061},[620,12043,1506],{"class":1161},[620,12045,6378],{"class":1054},[620,12047,736],{"class":1061},[620,12049,936],{"class":1054},[620,12051,2727],{"class":1061},[620,12053,2599],{"class":1054},[620,12055,11286],{"class":1050},[620,12057,7504],{"class":626},[620,12059,1705],{"class":1061},[620,12061,1752],{"class":626},[620,12063,1191],{"class":1061},[620,12065,1773],{"class":626},[620,12067,1760],{"class":1061},[620,12069,748],{"class":1161},[620,12071,2727],{"class":1061},[620,12073,2545],{"class":1054},[620,12075,11724],{"class":1061},[620,12077,2871],{"class":1061},[620,12079,859],{"class":1061},[620,12081,12083],{"class":622,"line":12082},68,[620,12084,1681],{"class":1061},[620,12086,12088],{"class":622,"line":12087},69,[620,12089,2046],{"class":1061},[620,12091,12093],{"class":622,"line":12092},70,[620,12094,1106],{"emptyLinePlaceholder":1105},[620,12096,12098],{"class":622,"line":12097},71,[620,12099,12100],{"class":640},"\u002F\u002F ─── 5. Author image remap ──────────────────────────────────────────────────\n",[620,12102,12104],{"class":622,"line":12103},72,[620,12105,12106],{"class":640},"\u002F\u002F Collection-backed author resolves the image under `image` (TentoMedia).\n",[620,12108,12110],{"class":622,"line":12109},73,[620,12111,12112],{"class":640},"\u002F\u002F ButterCMS exposed it under `profile`. Alias it so existing reads of\n",[620,12114,12116],{"class":622,"line":12115},74,[620,12117,12118],{"class":640},"\u002F\u002F author.profile keep working. (Blog post authors use avatarUrl — a plain URL\n",[620,12120,12122],{"class":622,"line":12121},75,[620,12123,12124],{"class":640},"\u002F\u002F string rehosted to TentoCMS media at import time, not a cdn.buttercms.com URL.)\n",[620,12126,12128,12130,12132,12135],{"class":622,"line":12127},76,[620,12129,1492],{"class":1050},[620,12131,11189],{"class":1074},[620,12133,12134],{"class":1084}," remapAuthor",[620,12136,11320],{"class":1061},[620,12138,12140,12143,12145,12147,12149,12151,12153,12155,12157,12160],{"class":622,"line":12139},77,[620,12141,12142],{"class":1509},"  author",[620,12144,1165],{"class":1061},[620,12146,7504],{"class":626},[620,12148,1705],{"class":1061},[620,12150,1752],{"class":626},[620,12152,1191],{"class":1061},[620,12154,1757],{"class":626},[620,12156,1760],{"class":1061},[620,12158,12159],{"class":1061}," |",[620,12161,12162],{"class":626}," undefined\n",[620,12164,12166,12168,12170,12172,12174,12176,12178,12180,12182,12185],{"class":622,"line":12165},78,[620,12167,7240],{"class":1061},[620,12169,7504],{"class":626},[620,12171,1705],{"class":1061},[620,12173,1752],{"class":626},[620,12175,1191],{"class":1061},[620,12177,1757],{"class":626},[620,12179,1760],{"class":1061},[620,12181,12159],{"class":1061},[620,12183,12184],{"class":626}," undefined",[620,12186,1518],{"class":1061},[620,12188,12190,12192,12194,12196,12198,12200,12202],{"class":622,"line":12189},79,[620,12191,6071],{"class":1050},[620,12193,1506],{"class":1161},[620,12195,1649],{"class":1061},[620,12197,2678],{"class":1054},[620,12199,1636],{"class":1161},[620,12201,7791],{"class":1050},[620,12203,12162],{"class":1061},[620,12205,12207,12209,12211,12213,12215,12217,12219,12221,12224,12226,12228],{"class":622,"line":12206},80,[620,12208,4712],{"class":1050},[620,12210,1120],{"class":1061},[620,12212,11665],{"class":1061},[620,12214,2678],{"class":1054},[620,12216,1191],{"class":1061},[620,12218,10323],{"class":1161},[620,12220,1165],{"class":1061},[620,12222,12223],{"class":1054}," author",[620,12225,736],{"class":1061},[620,12227,3177],{"class":1054},[620,12229,2883],{"class":1061},[620,12231,12233],{"class":622,"line":12232},81,[620,12234,2046],{"class":1061},[620,12236,12238],{"class":622,"line":12237},82,[620,12239,1106],{"emptyLinePlaceholder":1105},[620,12241,12243],{"class":622,"line":12242},83,[620,12244,12245],{"class":640},"\u002F\u002F ─── 6. getBySlug \u002F list try-catch wrappers ─────────────────────────────────\n",[620,12247,12249],{"class":622,"line":12248},84,[620,12250,12251],{"class":640},"\u002F\u002F Catch TentoNotFoundError and return null \u002F [] so existing `if (!data) throw 404`\n",[620,12253,12255],{"class":622,"line":12254},85,[620,12256,12257],{"class":640},"\u002F\u002F guards produce 404s, not 500s. Apply to every SDK call at the boundary.\n",[620,12259,12261,12263,12266,12268,12271,12273,12275],{"class":622,"line":12260},86,[620,12262,1492],{"class":1050},[620,12264,12265],{"class":1074}," async",[620,12267,11189],{"class":1074},[620,12269,12270],{"class":1084}," safeGetBySlug",[620,12272,1705],{"class":1061},[620,12274,4269],{"class":626},[620,12276,12277],{"class":1061},">(\n",[620,12279,12281,12284,12286,12289,12291,12294,12296,12298],{"class":622,"line":12280},87,[620,12282,12283],{"class":1084},"  fn",[620,12285,1165],{"class":1061},[620,12287,12288],{"class":1061}," ()",[620,12290,1515],{"class":1074},[620,12292,12293],{"class":626}," Promise",[620,12295,1705],{"class":1061},[620,12297,4269],{"class":626},[620,12299,1726],{"class":1061},[620,12301,12303,12305,12307,12309,12311,12313,12316,12318],{"class":622,"line":12302},88,[620,12304,7240],{"class":1061},[620,12306,12293],{"class":626},[620,12308,1705],{"class":1061},[620,12310,4269],{"class":626},[620,12312,12159],{"class":1061},[620,12314,12315],{"class":626}," null",[620,12317,1760],{"class":1061},[620,12319,1518],{"class":1061},[620,12321,12323,12325,12327,12330,12332,12335,12338],{"class":622,"line":12322},89,[620,12324,1559],{"class":1050},[620,12326,1120],{"class":1061},[620,12328,12329],{"class":1050}," return",[620,12331,1570],{"class":1050},[620,12333,12334],{"class":1084}," fn",[620,12336,12337],{"class":1161},"() ",[620,12339,2046],{"class":1061},[620,12341,12343,12346,12348,12350,12352],{"class":622,"line":12342},90,[620,12344,12345],{"class":1050},"  catch",[620,12347,1506],{"class":1161},[620,12349,1633],{"class":1054},[620,12351,1636],{"class":1161},[620,12353,1155],{"class":1061},[620,12355,12357,12359,12361,12363,12365,12367,12369,12371],{"class":622,"line":12356},91,[620,12358,1644],{"class":1050},[620,12360,1506],{"class":1161},[620,12362,1633],{"class":1054},[620,12364,1656],{"class":1061},[620,12366,1411],{"class":626},[620,12368,1636],{"class":1161},[620,12370,7791],{"class":1050},[620,12372,1675],{"class":1061},[620,12374,12376,12379],{"class":622,"line":12375},92,[620,12377,12378],{"class":1050},"    throw",[620,12380,1667],{"class":1054},[620,12382,12384],{"class":622,"line":12383},93,[620,12385,1681],{"class":1061},[620,12387,12389],{"class":622,"line":12388},94,[620,12390,2046],{"class":1061},[620,12392,12394],{"class":622,"line":12393},95,[620,12395,1106],{"emptyLinePlaceholder":1105},[620,12397,12399,12401,12403,12405,12408,12410,12412],{"class":622,"line":12398},96,[620,12400,1492],{"class":1050},[620,12402,12265],{"class":1074},[620,12404,11189],{"class":1074},[620,12406,12407],{"class":1084}," safeList",[620,12409,1705],{"class":1061},[620,12411,4269],{"class":626},[620,12413,12277],{"class":1061},[620,12415,12417,12419,12421,12423,12425,12427,12429,12432,12434,12436,12439],{"class":622,"line":12416},97,[620,12418,12283],{"class":1084},[620,12420,1165],{"class":1061},[620,12422,12288],{"class":1061},[620,12424,1515],{"class":1074},[620,12426,12293],{"class":626},[620,12428,1734],{"class":1061},[620,12430,12431],{"class":1161}," data",[620,12433,1165],{"class":1061},[620,12435,11467],{"class":626},[620,12437,12438],{"class":1054},"[] ",[620,12440,12441],{"class":1061},"}>\n",[620,12443,12445,12447,12449,12451,12453,12455,12457],{"class":622,"line":12444},98,[620,12446,7240],{"class":1061},[620,12448,12293],{"class":626},[620,12450,1705],{"class":1061},[620,12452,4269],{"class":626},[620,12454,10018],{"class":1054},[620,12456,1760],{"class":1061},[620,12458,1518],{"class":1061},[620,12460,12462,12464,12466,12468,12470,12473,12475,12478,12480,12482],{"class":622,"line":12461},99,[620,12463,1559],{"class":1050},[620,12465,1120],{"class":1061},[620,12467,12329],{"class":1050},[620,12469,1506],{"class":1161},[620,12471,12472],{"class":1050},"await",[620,12474,12334],{"class":1084},[620,12476,12477],{"class":1161},"())",[620,12479,736],{"class":1061},[620,12481,4265],{"class":1054},[620,12483,2883],{"class":1061},[620,12485,12487,12489,12491,12493,12495],{"class":622,"line":12486},100,[620,12488,12345],{"class":1050},[620,12490,1506],{"class":1161},[620,12492,1633],{"class":1054},[620,12494,1636],{"class":1161},[620,12496,1155],{"class":1061},[620,12498,12500,12502,12504,12506,12508,12510,12512,12514],{"class":622,"line":12499},101,[620,12501,1644],{"class":1050},[620,12503,1506],{"class":1161},[620,12505,1633],{"class":1054},[620,12507,1656],{"class":1061},[620,12509,1411],{"class":626},[620,12511,1636],{"class":1161},[620,12513,7791],{"class":1050},[620,12515,12516],{"class":1161}," []\n",[620,12518,12520,12522],{"class":622,"line":12519},102,[620,12521,12378],{"class":1050},[620,12523,1667],{"class":1054},[620,12525,12527],{"class":622,"line":12526},103,[620,12528,1681],{"class":1061},[620,12530,12532],{"class":622,"line":12531},104,[620,12533,2046],{"class":1061},[567,12535,12536],{},"Compose in your adapter:",[611,12538,12540],{"className":1036,"code":12539,"language":1038,"meta":616,"style":616},"\u002F\u002F Example: adapt a page at the data-access boundary\nexport async function getPage(slug: string, preview = false) {\n  const page = await safeGetBySlug(() =>\n    tento.pages.getBySlug(slug, { preview })\n  )\n  if (!page) return null\n\n  const components = (page.fields?.components as Record\u003Cstring, unknown>[] ?? [])\n    .map(rewrapComponent)\n\n  return {\n    slug: page.slug,\n    type: page.type?.replaceAll('-', '_'),\n    fields: {\n      ...flattenMedia(stripSystemFields(page.fields as Record\u003Cstring, unknown>)),\n      meta: synthesiseMeta(page),\n      components,\n    },\n  }\n}\n",[574,12541,12542,12547,12578,12597,12624,12628,12644,12648,12690,12704,12708,12714,12728,12765,12773,12812,12829,12836,12841,12845],{"__ignoreMap":616},[620,12543,12544],{"class":622,"line":623},[620,12545,12546],{"class":640},"\u002F\u002F Example: adapt a page at the data-access boundary\n",[620,12548,12549,12551,12553,12555,12558,12560,12562,12564,12566,12568,12570,12572,12574,12576],{"class":622,"line":637},[620,12550,1492],{"class":1050},[620,12552,12265],{"class":1074},[620,12554,11189],{"class":1074},[620,12556,12557],{"class":1084}," getPage",[620,12559,1088],{"class":1061},[620,12561,1593],{"class":1509},[620,12563,1165],{"class":1061},[620,12565,1773],{"class":626},[620,12567,1191],{"class":1061},[620,12569,1533],{"class":1509},[620,12571,1538],{"class":1061},[620,12573,9743],{"class":9689},[620,12575,748],{"class":1061},[620,12577,1518],{"class":1061},[620,12579,12580,12582,12584,12586,12588,12590,12592,12595],{"class":622,"line":1071},[620,12581,1523],{"class":1074},[620,12583,2719],{"class":1054},[620,12585,1538],{"class":1061},[620,12587,1570],{"class":1050},[620,12589,12270],{"class":1084},[620,12591,1088],{"class":1161},[620,12593,12594],{"class":1061},"()",[620,12596,7245],{"class":1074},[620,12598,12599,12602,12604,12606,12608,12610,12612,12614,12616,12618,12620,12622],{"class":622,"line":1102},[620,12600,12601],{"class":1054},"    tento",[620,12603,736],{"class":1061},[620,12605,1578],{"class":1054},[620,12607,736],{"class":1061},[620,12609,1583],{"class":1084},[620,12611,1088],{"class":1161},[620,12613,1593],{"class":1054},[620,12615,1191],{"class":1061},[620,12617,1120],{"class":1061},[620,12619,1533],{"class":1054},[620,12621,1126],{"class":1061},[620,12623,1099],{"class":1161},[620,12625,12626],{"class":622,"line":1109},[620,12627,4878],{"class":1161},[620,12629,12630,12632,12634,12636,12638,12640,12642],{"class":622,"line":1115},[620,12631,6071],{"class":1050},[620,12633,1506],{"class":1161},[620,12635,1649],{"class":1061},[620,12637,6378],{"class":1054},[620,12639,1636],{"class":1161},[620,12641,7791],{"class":1050},[620,12643,1675],{"class":1061},[620,12645,12646],{"class":622,"line":1138},[620,12647,1106],{"emptyLinePlaceholder":1105},[620,12649,12650,12652,12654,12656,12658,12660,12662,12664,12666,12669,12671,12673,12675,12677,12679,12681,12683,12685,12687],{"class":622,"line":1158},[620,12651,1523],{"class":1074},[620,12653,1737],{"class":1054},[620,12655,1538],{"class":1061},[620,12657,1506],{"class":1161},[620,12659,6378],{"class":1054},[620,12661,736],{"class":1061},[620,12663,936],{"class":1054},[620,12665,2727],{"class":1061},[620,12667,12668],{"class":1054},"components",[620,12670,11286],{"class":1050},[620,12672,7504],{"class":626},[620,12674,1705],{"class":1061},[620,12676,1752],{"class":626},[620,12678,1191],{"class":1061},[620,12680,1757],{"class":626},[620,12682,1760],{"class":1061},[620,12684,12438],{"class":1161},[620,12686,2803],{"class":1061},[620,12688,12689],{"class":1161}," [])\n",[620,12691,12692,12695,12697,12699,12702],{"class":622,"line":1177},[620,12693,12694],{"class":1061},"    .",[620,12696,7269],{"class":1084},[620,12698,1088],{"class":1161},[620,12700,12701],{"class":1054},"rewrapComponent",[620,12703,1099],{"class":1161},[620,12705,12706],{"class":622,"line":1197},[620,12707,1106],{"emptyLinePlaceholder":1105},[620,12709,12710,12712],{"class":622,"line":1550},[620,12711,4712],{"class":1050},[620,12713,1518],{"class":1061},[620,12715,12716,12718,12720,12722,12724,12726],{"class":622,"line":1556},[620,12717,1932],{"class":1161},[620,12719,1165],{"class":1061},[620,12721,2719],{"class":1054},[620,12723,736],{"class":1061},[620,12725,1593],{"class":1054},[620,12727,859],{"class":1061},[620,12729,12730,12733,12735,12737,12739,12741,12743,12745,12747,12749,12751,12753,12755,12757,12759,12761,12763],{"class":622,"line":1564},[620,12731,12732],{"class":1161},"    type",[620,12734,1165],{"class":1061},[620,12736,2719],{"class":1054},[620,12738,736],{"class":1061},[620,12740,915],{"class":1054},[620,12742,2727],{"class":1061},[620,12744,8651],{"class":1084},[620,12746,1088],{"class":1161},[620,12748,1091],{"class":1061},[620,12750,8666],{"class":630},[620,12752,1091],{"class":1061},[620,12754,1191],{"class":1061},[620,12756,1062],{"class":1061},[620,12758,7462],{"class":630},[620,12760,1091],{"class":1061},[620,12762,748],{"class":1161},[620,12764,859],{"class":1061},[620,12766,12767,12769,12771],{"class":622,"line":1622},[620,12768,1964],{"class":1161},[620,12770,1165],{"class":1061},[620,12772,1518],{"class":1061},[620,12774,12775,12778,12780,12782,12785,12787,12789,12791,12793,12795,12797,12799,12801,12803,12805,12807,12810],{"class":622,"line":1641},[620,12776,12777],{"class":1061},"      ...",[620,12779,7399],{"class":1084},[620,12781,1088],{"class":1161},[620,12783,12784],{"class":1084},"stripSystemFields",[620,12786,1088],{"class":1161},[620,12788,6378],{"class":1054},[620,12790,736],{"class":1061},[620,12792,936],{"class":1054},[620,12794,11286],{"class":1050},[620,12796,7504],{"class":626},[620,12798,1705],{"class":1061},[620,12800,1752],{"class":626},[620,12802,1191],{"class":1061},[620,12804,1757],{"class":626},[620,12806,1760],{"class":1061},[620,12808,12809],{"class":1161},"))",[620,12811,859],{"class":1061},[620,12813,12814,12817,12819,12821,12823,12825,12827],{"class":622,"line":1670},[620,12815,12816],{"class":1161},"      meta",[620,12818,1165],{"class":1061},[620,12820,11848],{"class":1084},[620,12822,1088],{"class":1161},[620,12824,6378],{"class":1054},[620,12826,748],{"class":1161},[620,12828,859],{"class":1061},[620,12830,12831,12834],{"class":622,"line":1678},[620,12832,12833],{"class":1054},"      components",[620,12835,859],{"class":1061},[620,12837,12838],{"class":622,"line":1684},[620,12839,12840],{"class":1061},"    },\n",[620,12842,12843],{"class":622,"line":2096},[620,12844,1681],{"class":1061},[620,12846,12847],{"class":622,"line":2112},[620,12848,2046],{"class":1061},[777,12850],{},[593,12852,12854],{"id":12853},"further-reading","Further Reading",[650,12856,12857,12862,12866,12876,12885,12891,12896],{},[653,12858,12859,12861],{},[579,12860,67],{"href":4240}," — filters, sorting, pagination, references",[653,12863,12864],{},[579,12865,4245],{"href":4244},[653,12867,12868,12869,12871,12872,12871,12874],{},"Framework guides: ",[579,12870,141],{"href":1364}," · ",[579,12873,137],{"href":1370},[579,12875,133],{"href":1376},[653,12877,12878,12879,12871,12881,12871,12883],{},"API references: ",[579,12880,14],{"href":4316},[579,12882,34],{"href":8037},[579,12884,26],{"href":10371},[653,12886,12887],{},[579,12888,12890],{"href":12889},"..\u002Fsdk\u002Freadme","SDK README",[653,12892,12893],{},[579,12894,12895],{"href":589},"Migration Wizard Guide",[653,12897,12898],{},[579,12899,109],{"href":12900},".\u002Fpublic-api-usage",[12902,12903,12904],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":616,"searchDepth":637,"depth":637,"links":12906},[12907,12912,12913,12914,12915,12916,12921,12922,12923,12926,12947,12948,12949],{"id":595,"depth":637,"text":596,"children":12908},[12909,12910,12911],{"id":600,"depth":1071,"text":601},{"id":699,"depth":1071,"text":700},{"id":751,"depth":1071,"text":752},{"id":781,"depth":637,"text":782},{"id":843,"depth":637,"text":844},{"id":951,"depth":637,"text":735},{"id":1350,"depth":637,"text":1351},{"id":1891,"depth":637,"text":1892,"children":12917},[12918,12919,12920],{"id":1578,"depth":1071,"text":42},{"id":3587,"depth":1071,"text":22},{"id":4284,"depth":1071,"text":4285},{"id":5396,"depth":637,"text":5397},{"id":5807,"depth":637,"text":5808},{"id":6659,"depth":637,"text":6660,"children":12924},[12925],{"id":6833,"depth":1071,"text":6834},{"id":8211,"depth":637,"text":8212,"children":12927},[12928,12929,12930,12931,12933,12934,12936,12938,12939,12940,12941,12942,12943,12944,12946],{"id":8215,"depth":1071,"text":8216},{"id":8419,"depth":1071,"text":8420},{"id":8572,"depth":1071,"text":837},{"id":8680,"depth":1071,"text":12932},"Generating types with @tentocms\u002Ftypegen",{"id":9191,"depth":1071,"text":9192},{"id":9388,"depth":1071,"text":12935},"page.type vs component._type",{"id":9420,"depth":1071,"text":12937},"Blog posts use .content not .fields",{"id":9495,"depth":1071,"text":9496},{"id":9777,"depth":1071,"text":3329},{"id":9924,"depth":1071,"text":9925},{"id":10375,"depth":1071,"text":10376},{"id":10449,"depth":1071,"text":10450},{"id":10481,"depth":1071,"text":10482},{"id":10774,"depth":1071,"text":12945},"Rich text (wysiwyg) is NOT sanitized server-side — migrated content bypasses sanitization entirely",{"id":10849,"depth":1071,"text":10850},{"id":10887,"depth":637,"text":10888},{"id":11076,"depth":637,"text":11077},{"id":12853,"depth":637,"text":12854},"Step-by-step migration of a ButterCMS application to TentoCMS.","md",{},{"title":97,"description":12950},"v5893RgAwff4dFyeGVhDeP6npR2yC-DL60btUAJLXMk",[12956,12958],{"title":93,"path":94,"stem":95,"description":12957,"children":-1},"Navigating and using the TentoCMS admin interface.",{"title":101,"path":102,"stem":103,"description":12959,"children":-1},"Migrating content from other CMS platforms using the built-in migration tools.",1787919218823]