[{"data":1,"prerenderedAt":584},["ShallowReactive",2],{"bootstrap":3,"blog-build-your-first-custom-dashboard-app-in-minutes":277},{"navigation":4,"globalConfig":196,"forms":218},{"headerSections":5,"headerFooterLinks":67,"footer":76,"legalLinks":153,"socialLinks":176},[6,39,44,50,57,62],{"hasSubMenu":7,"title":8,"links":9,"columns":38},true,"Product",[10,20,26,32],{"showTag":11,"_modelApiKey":12,"text":13,"subtitle":14,"icon":15,"isExternal":11,"link":16,"openForm":11},false,"navigation_link","Universal checkout","Sell anywhere","globe-simple",{"_modelApiKey":17,"slug":18,"title":19},"page","universal-checkout","Universal Checkout",{"showTag":11,"_modelApiKey":12,"text":21,"subtitle":22,"icon":23,"isExternal":11,"link":24,"openForm":11},"Distributed OMS","Manage orders","package",{"_modelApiKey":17,"slug":25,"title":21},"distributed-oms",{"showTag":11,"_modelApiKey":12,"text":27,"subtitle":28,"icon":29,"isExternal":11,"link":30,"openForm":11},"Promotion engine","Automated discounts","seal-percent",{"_modelApiKey":17,"slug":31,"title":27},"promotion-engine",{"showTag":11,"_modelApiKey":12,"text":33,"subtitle":34,"icon":35,"isExternal":11,"link":36,"openForm":11},"POS system","Operate stores","devices",{"_modelApiKey":17,"slug":37,"title":33},"pos-system",2,{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":40,"isExternal":11,"link":41,"openForm":11,"title":40},"Pricing",{"_modelApiKey":42,"slug":43},"pricing_page","pricing",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":45,"isExternal":11,"link":46,"openForm":11,"title":45},"Customers",{"_modelApiKey":47,"slug":48,"title":49},"customer_landing_page","customers","Customers page",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":51,"isExternal":11,"link":52,"openForm":11,"form":54,"title":51},"Partners",{"_modelApiKey":17,"slug":53,"title":51},"partners",{"_modelApiKey":55,"slug":53,"name":56},"form","PartnersForm",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":58,"label":59,"isExternal":7,"url":60,"openForm":11,"title":61},"Docs Hub","Learn more","https:\u002F\u002Fdocs.commercelayer.io\u002F","Docs",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":63,"isExternal":11,"link":64,"openForm":11,"title":63},"Blog",{"_modelApiKey":65,"slug":66},"blog_landing_page","blog",[68,72],{"text":69,"link":70},"Sign in",{"_modelApiKey":12,"text":69,"isExternal":7,"url":71,"openForm":11},"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_in",{"text":73,"link":74},"Sign up for free",{"_modelApiKey":12,"text":73,"isExternal":7,"url":75,"openForm":11},"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_up",[77,89,109,132],{"hasSubMenu":7,"title":8,"links":78},[79,81,83,85,87],{"showTag":11,"_modelApiKey":12,"text":13,"subtitle":14,"icon":15,"isExternal":11,"link":80,"openForm":11},{"_modelApiKey":17,"slug":18,"title":19},{"showTag":11,"_modelApiKey":12,"text":21,"subtitle":22,"icon":23,"isExternal":11,"link":82,"openForm":11},{"_modelApiKey":17,"slug":25,"title":21},{"showTag":11,"_modelApiKey":12,"text":27,"subtitle":28,"icon":29,"isExternal":11,"link":84,"openForm":11},{"_modelApiKey":17,"slug":31,"title":27},{"showTag":11,"_modelApiKey":12,"text":33,"subtitle":34,"icon":35,"isExternal":11,"link":86,"openForm":11},{"_modelApiKey":17,"slug":37,"title":33},{"showTag":11,"_modelApiKey":12,"text":40,"isExternal":11,"link":88,"openForm":11},{"_modelApiKey":42,"slug":43},{"hasSubMenu":7,"title":90,"links":91},"Resources",[92,94,99,104,107],{"showTag":11,"_modelApiKey":12,"text":93,"isExternal":7,"url":60,"openForm":11},"Documentation",{"showTag":11,"_modelApiKey":12,"text":95,"label":95,"isExternal":11,"link":96,"openForm":11},"Agentic Commerce",{"_modelApiKey":17,"slug":97,"title":98},"agentic-commerce-whitepaper","Agentic Commerce Whitepaper",{"showTag":11,"_modelApiKey":12,"text":100,"isExternal":11,"link":101,"openForm":11},"Core concepts",{"_modelApiKey":17,"slug":102,"title":103},"headless-commerce","Headless commerce",{"showTag":11,"_modelApiKey":12,"text":105,"isExternal":7,"url":106,"openForm":11},"Changelog","https:\u002F\u002Fdocs.commercelayer.io\u002Fchangelog\u002F",{"showTag":11,"_modelApiKey":12,"text":63,"isExternal":11,"link":108,"openForm":11},{"_modelApiKey":65,"slug":66},{"hasSubMenu":7,"title":110,"links":111},"Company",[112,116,118,121,126],{"showTag":11,"_modelApiKey":12,"text":113,"isExternal":11,"link":114,"openForm":11},"Vision",{"_modelApiKey":17,"slug":115,"title":113},"vision",{"showTag":11,"_modelApiKey":12,"text":45,"isExternal":11,"link":117,"openForm":11},{"_modelApiKey":47,"slug":48,"title":49},{"showTag":11,"_modelApiKey":12,"text":51,"isExternal":11,"link":119,"openForm":11,"form":120},{"_modelApiKey":17,"slug":53,"title":51},{"_modelApiKey":55,"slug":53,"name":56},{"showTag":11,"_modelApiKey":12,"text":122,"isExternal":11,"link":123,"openForm":11},"Security",{"_modelApiKey":17,"slug":124,"title":125},"security","Security at Commerce Layer",{"showTag":11,"_modelApiKey":12,"text":127,"label":128,"isExternal":11,"openForm":7,"form":129},"Giveback","Giveback program",{"_modelApiKey":55,"slug":130,"name":131},"giveback","GivebackForm",{"hasSubMenu":7,"title":133,"links":134},"Get in touch",[135,140,145,149],{"showTag":11,"_modelApiKey":12,"text":136,"isExternal":11,"openForm":7,"form":137},"Contact",{"_modelApiKey":55,"slug":138,"name":139},"contact","ContactUsForm",{"showTag":11,"_modelApiKey":12,"text":141,"isExternal":11,"openForm":7,"form":142},"Book a demo",{"_modelApiKey":55,"slug":143,"name":144},"demo","RequestDemoForm",{"showTag":11,"_modelApiKey":12,"text":146,"label":147,"isExternal":7,"url":148,"openForm":11},"Support","Email support","mailto:support@commercelayer.io",{"showTag":11,"_modelApiKey":12,"text":150,"label":151,"isExternal":7,"url":152,"openForm":11},"Community","Discord community","https:\u002F\u002Fdiscord.gg\u002Fcommercelayer",{"hasSubMenu":7,"title":154,"links":155},"Legal",[156,161,165,169,173],{"showTag":11,"_modelApiKey":12,"text":157,"label":158,"isExternal":7,"url":159,"openForm":11,"htmlClassNames":160},"Terms","Terms and conditions","\u002Flegal-embed\u002Fterms-of-service","iubenda-nostyle no-brand iubenda-noiframe iubenda-embed",{"showTag":11,"_modelApiKey":12,"text":162,"label":163,"isExternal":7,"url":164,"openForm":11,"htmlClassNames":160},"Privacy","Privacy policy","\u002Flegal-embed\u002Fprivacy-policy",{"showTag":11,"_modelApiKey":12,"text":166,"label":167,"isExternal":7,"url":168,"openForm":11,"htmlClassNames":160},"Cookies","Cookie policy","\u002Flegal-embed\u002Fcookie-policy",{"showTag":11,"_modelApiKey":12,"text":170,"isExternal":7,"url":171,"openForm":11,"htmlClassNames":172},"Notice at collection","#","iubenda-cs-uspr-link",{"showTag":11,"_modelApiKey":12,"text":174,"isExternal":7,"url":171,"openForm":11,"htmlClassNames":175},"Privacy preferences","iubenda-cs-preferences-link",[177,180,184,188,192],{"title":178,"type":179,"url":152,"isExternal":7},"Discord","discord-logo",{"title":181,"type":182,"url":183,"isExternal":7},"Github","github","https:\u002F\u002Fgithub.com\u002Fcommercelayer",{"title":185,"type":186,"url":187,"isExternal":7},"Bluesky","bluesky-logo","https:\u002F\u002Fbsky.app\u002Fprofile\u002Fcommercelayer.io",{"title":189,"type":190,"url":191,"isExternal":7},"X","x-logo","https:\u002F\u002Fx.com\u002Fcommercelayer",{"title":193,"type":194,"url":195,"isExternal":7},"LinkedIn","linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fcommerce-layer",{"pagesWithoutCtaPanel":197,"ctaPanel":198,"notification":208,"showApiResponseTime":11,"newsletterSignup":216},[],{"title":199,"subtitle":200,"ctas":201},"Ready to get started?","Discover how we can help your business.",[202,205],{"_modelApiKey":203,"text":204,"isExternal":7,"url":75,"openForm":11},"link","Get started for free",{"_modelApiKey":203,"text":206,"isExternal":11,"openForm":7,"form":207},"Contact us",{"_modelApiKey":55,"slug":138,"name":139},{"showNotification":7,"link":209,"linkText":210,"text":215},{"_modelApiKey":203,"text":210,"isExternal":11,"link":211,"openForm":11},"Read article",{"_modelApiKey":212,"slug":213,"title":214},"blog_post","the-new-payments-api-built-for-how-people-pay-now","The new Payments API. Built for how people pay now.","\u003Cp>The new Payments API. Built for how people pay now.\u003C\u002Fp>\n",{"ctaText":217},"Subscribe",[219,225,229,236,240,246,249,253,261,262,267,272,274],{"_modelApiKey":55,"slug":220,"pardotFormEmbedCode":221,"formEmbedMinHeight":222,"successTitle":223,"successCopy":224},"agentic-commerce","https:\u002F\u002Fcommercelayer.fillout.com\u002Ft\u002F5w4Khwgxr6us?whitepaper=agentic",520,"Thank you for your request!","We’ll be in touch soon to arrange a date and time for your personalized demo.",{"_modelApiKey":55,"slug":226,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"successTitle":223,"successCopy":224},"shopify","https:\u002F\u002Fform.fillout.com\u002Ft\u002FgVnLSmKZwRus",850,{"_modelApiKey":55,"slug":130,"pardotFormEmbedCode":230,"formEmbedMinHeight":231,"displayTitle":232,"copy":233,"successTitle":234,"successCopy":235},"https:\u002F\u002Fform.fillout.com\u002Ft\u002FmVDF8aAiofus",500,"We give back.","\u003Cp>Part of Commerce Layer&#39;s company ethos is to empathize and support one another: co-workers, customers, and community. We want to do our part to help nonprofit organizations and small businesses who may feel adversely affected by the current global economic climate, and as a result, need in-kind support to reach their customers.\u003C\u002Fp>\n\n\u003Cp>If this general description matches your organization’s needs, please tell us your story by completing the following form so we can consider helping your cause.\u003C\u002Fp>\n","Thank you!","We will contact you soon to learn more about your story.",{"_modelApiKey":55,"slug":237,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":238,"copy":239},"book-an-onboarding-session","Book an onboarding session","\u003Cp>Schedule some time and our devs will help you set up a Commerce Layer + Sanity store.\u003C\u002Fp>\n",{"_modelApiKey":55,"slug":241,"pardotFormEmbedCode":242,"formEmbedMinHeight":243,"displayTitle":244,"successTitle":234,"successCopy":245},"download-our-whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=ComposableCommerce",450,"Download our whitepaper.","Check your inbox. You should receive a download link soon.",{"_modelApiKey":55,"slug":247,"pardotFormEmbedCode":248,"formEmbedMinHeight":243,"displayTitle":244,"successTitle":234,"successCopy":245},"download-the-content-and-commerce-whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=ContentAndCommerce",{"_modelApiKey":55,"slug":250,"pardotFormEmbedCode":251,"formEmbedMinHeight":243,"displayTitle":244,"copy":252,"successTitle":234,"successCopy":245},"whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=Jamstack","\u003Cp>Commerce Layer is the perfect platform for building commerce on the Jamstack. Convert any design into pixel perfect HTML pages. Connect a best-of-breed headless CMS. Go serverless and forget the hassle of managing complex infrastructures.\u003C\u002Fp>\n",{"_modelApiKey":55,"slug":254,"pardotFormEmbedCode":255,"formEmbedMinHeight":256,"displayTitle":257,"copy":258,"successTitle":259,"successCopy":260},"contact-links","https:\u002F\u002Fform.fillout.com\u002Ft\u002FeWPyhHGHT7us",750,"Contact us.","\u003Cp>Tell us about your project. Our team will reach out to you for a personalized offer.\u003C\u002Fp>\n","Thanks for your message!","We’ll be in touch soon to discuss your project details.",{"_modelApiKey":55,"slug":138,"pardotFormEmbedCode":255,"formEmbedMinHeight":256,"displayTitle":257,"copy":258,"successTitle":259,"successCopy":260},{"_modelApiKey":55,"slug":263,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":264,"successTitle":265,"successCopy":266},"demo-embed","Want to see it in action? Request a demo.","Thanks for your request!","We’ll be in touch soon to arrange a date and time for your free live demo.",{"_modelApiKey":55,"slug":53,"pardotFormEmbedCode":268,"formEmbedMinHeight":228,"displayTitle":269,"copy":270,"successTitle":234,"successCopy":271},"https:\u002F\u002Fform.fillout.com\u002Ft\u002F9MTkDWMUSqus","Become our partner.","\u003Cp>Join us and get our guidance in helping your clients succeed. Our partners are our ambassadors. We are committed to your success as much as you are.\u003C\u002Fp>\n","We will contact you soon to discuss the details of our partnership.",{"_modelApiKey":55,"slug":273,"pardotFormEmbedCode":268,"formEmbedMinHeight":228,"displayTitle":269,"copy":270,"successTitle":234,"successCopy":271},"partners-embed",{"_modelApiKey":55,"slug":143,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":275,"copy":276,"successTitle":265,"successCopy":266},"Get a free live demo.","\u003Cp>Get a personalized demo and learn more about what Commerce Layer can do for your company.\u003C\u002Fp>\n",{"_modelApiKey":212,"link":278,"subtitle":283,"date":283,"author":284,"additionalAuthors":294,"icon":295,"abstract":296,"tag":297,"displayTitle":279,"cardRatio":300,"featured":11,"seoMetaTags":301,"layout":365,"blogPostCarousel":303,"_seoMetaTags":545,"authors":579},{"text":279,"isExternal":11,"url":280,"link":281},"Build your first custom dashboard app in minutes.","",{"_modelApiKey":212,"slug":282},"build-your-first-custom-dashboard-app-in-minutes","October 3, 2025",{"name":285,"picture":286},"Giuseppe Ciotola",{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":291,"customData":293},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1758637812-giuseppe.jpg","Giuseppe Ciotola avatar",1280,"jpg",{"x":292,"y":292},0.5,{},[],"circles-three-plus","A quick guide to creating a custom Commerce Layer dashboard app — list resources, view details, and extend your workflows using our starter templates.",{"color":298,"name":299},"gray","Tutorials","portrait",[302,306,310,313,317,320,323,327,331,335,338,342,346,350,354,357,361],{"attributes":303,"tag":304,"content":305},null,"title","Build your first custom dashboard app in minutes",{"attributes":307,"tag":309,"content":303},{"property":308,"content":305},"og:title","meta",{"attributes":311,"tag":309,"content":303},{"name":312,"content":305},"twitter:title",{"attributes":314,"tag":309,"content":303},{"name":315,"content":316},"description","Learn how to create a custom Commerce Layer dashboard app. From setup to deployment, this guide shows you how to extend workflows with ease.",{"attributes":318,"tag":309,"content":303},{"property":319,"content":316},"og:description",{"attributes":321,"tag":309,"content":303},{"name":322,"content":316},"twitter:description",{"attributes":324,"tag":309,"content":303},{"property":325,"content":326},"og:image","https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1758704549-custom-apps-pt2.jpg?auto=format&fit=max&w=1200",{"attributes":328,"tag":309,"content":303},{"property":329,"content":330},"og:image:width","1200",{"attributes":332,"tag":309,"content":303},{"property":333,"content":334},"og:image:height","630",{"attributes":336,"tag":309,"content":303},{"name":337,"content":326},"twitter:image",{"attributes":339,"tag":309,"content":303},{"property":340,"content":341},"og:locale","en",{"attributes":343,"tag":309,"content":303},{"property":344,"content":345},"og:type","article",{"attributes":347,"tag":309,"content":303},{"property":348,"content":349},"og:site_name","Commerce Layer",{"attributes":351,"tag":309,"content":303},{"property":352,"content":353},"article:modified_time","2025-10-08T16:35:02Z",{"attributes":355,"tag":309,"content":303},{"property":356,"content":280},"article:publisher",{"attributes":358,"tag":309,"content":303},{"name":359,"content":360},"twitter:card","summary_large_image",{"attributes":362,"tag":309,"content":303},{"name":363,"content":364},"twitter:site","@commercelayer",[366,371,376,384,391,394,399,402,409,418,421,424,431,439,446,453,456,460,463,465,468,470,473,480,482,485,492,495,498,501,503,510,517,525,532,539,542],{"_modelApiKey":367,"icon":368,"copy":369,"color":370},"banner","bookmark","\u003Cp>This is the second of a two-part article about Commerce Layer&#39;s dashboard custom apps. In the \u003Ca href=\"\u002Fblog\u002Fadding-a-custom-app-to-your-commerce-layer-dashboard\">first part\u003C\u002Fa>, we explored what custom apps are, why they matter, and how to add them to your Commerce Layer dashboard. We suggest you read it first, before starting the following step-by-step tutorial.\u003C\u002Fp>\n","red",{"_modelApiKey":372,"copy":373,"listStyle":374,"darkTheme":11,"buttons":375,"color":370,"indent":7},"wysiwyg","\u003Cp>In this example, we will create a generic app that lists some resources: customers, markets, shipments, and SKUs. From the list, you will be able to click through to the detail page of each item. It is a simple but powerful way to understand the structure of a custom app and prepare for more advanced scenarios.\u003C\u002Fp>\n\n\u003Ch4>What you need before starting\u003C\u002Fh4>\n\n\u003Cp>To follow along, make sure you have:\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>A Commerce Layer organization with some existing data (markets, customers, or SKUs).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Node.js\u003C\u002Fstrong> with pnpm installed.\u003C\u002Fli>\n\u003Cli>Basic knowledge of \u003Cstrong>React\u003C\u002Fstrong> and \u003Cstrong>TypeScript\u003C\u002Fstrong>.\u003C\u002Fli>\n\u003C\u002Ful>\n","directional",[],{"id":377,"copy":378,"_modelApiKey":379,"listStyle":374,"subtitle":380,"title":381,"darkTheme":11,"buttons":382,"sectionId":383,"color":370},"Eb6YSyCETwezEv7Fmxl8RQ","\u003Cp>We maintain \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fdashboard-apps\">a repository\u003C\u002Fa> called \u003Ccode>dashboard-apps\u003C\u002Fcode>, which hosts the current dashboard apps released as open source project. Use it as a template to create your own repository by leveraging GitHub’s \u003Cem>Create from template\u003C\u002Fem> tool:\u003C\u002Fp>\n","wysiwyg_section","Step 1","Create your repository from the template",[],"step-1",{"_modelApiKey":385,"title":386,"preTitle":387,"linkText":388,"darkTheme":11,"link":389},"link_block","Dashboard Apps","Open-source","Use as template",{"_modelApiKey":203,"text":388,"label":280,"subtitle":280,"icon":280,"isExternal":7,"url":390,"link":303,"anchor":280,"openForm":11,"form":303,"htmlClassNames":280},"https:\u002F\u002Fgithub.com\u002Fnew?template_owner=commercelayer&template_name=dashboard-apps",{"_modelApiKey":372,"copy":392,"listStyle":374,"darkTheme":11,"buttons":393,"color":370,"indent":11},"\u003Cp>Once your repository is created, install the dependencies with the command below:\u003C\u002Fp>\n",[],{"_modelApiKey":395,"code":396,"showLineNumbers":11,"highlightLines":280,"language":397,"size":398},"code_block","pnpm install","shell","Standard",{"_modelApiKey":372,"copy":400,"listStyle":374,"darkTheme":11,"buttons":401,"color":370,"indent":11},"\u003Cp>Inside the \u003Ccode>\u002Fapps\u003C\u002Fcode> folder, you will find a starter template called \u003Ccode>my_sample_app\u003C\u002Fcode>, which will serve as the foundation for our project alongside the other available apps.\u003C\u002Fp>\n",[],{"id":403,"copy":404,"_modelApiKey":379,"listStyle":374,"subtitle":405,"title":406,"darkTheme":11,"buttons":407,"sectionId":408,"color":370},"BHBmpHfYRieO4qUq4_m1mQ","\u003Cp>The starter already includes the essentials as you can see checking the minimal structure provided withing the \u003Ccode>my_sample_app\u003C\u002Fcode> folder. Its entry point (\u003Ccode>main.tsx\u003C\u002Fcode>) mounts the \u003Ccode>TokenProvider\u003C\u002Fcode> component, which manages authentication and reads the access token. The provider is already configured with \u003Ccode>kind=&quot;generic&quot;\u003C\u002Fcode>. This allows the app to \u003Ca href=\"https:\u002F\u002Fdocs.commercelayer.io\u002Fprovisioning\u002Fgetting-started\u002Fapplications#generic\">fetch multiple types of resources\u003C\u002Fa> instead of being restricted to just one set of permissions (as explain in the previous article):\u003C\u002Fp>\n","Step 2","Set up your app",[],"step-2",{"_modelApiKey":410,"border":11,"caption":280,"image":411,"size":417,"darkTheme":11,"rounded":7,"color":370},"image_block",{"url":412,"alt":413,"title":413,"width":289,"height":414,"format":290,"focalPoint":415,"customData":416},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1758706539-token-provider-kind-generic-vs-code.jpg","Token provider kind generic VS code",797,{"x":292,"y":292},{},"standard",{"_modelApiKey":372,"copy":419,"listStyle":374,"darkTheme":11,"buttons":420,"color":370,"indent":11},"\u003Cp>You are free to rename the app folder and change the \u003Ccode>appSlug\u003C\u002Fcode> value. This slug defines the URL path where your app will be accessible, in this example:\u003C\u002Fp>\n",[],{"_modelApiKey":395,"code":422,"showLineNumbers":11,"highlightLines":280,"language":423,"size":398},"https:\u002F\u002Fyour-domain\u002Fmy_sample_app","http",{"id":425,"copy":426,"_modelApiKey":379,"listStyle":374,"subtitle":427,"title":428,"darkTheme":11,"buttons":429,"sectionId":430,"color":370},"el8FPVVoTC6pRgfxlIqT4A","\u003Cp>Next we will configure navigation. Open \u003Ccode>\u002Fsrc\u002Fdata\u002Froutes.ts\u003C\u002Fcode> and add the routes for your application. Besides the default home page, you will want to include a list view and a details view.\u003C\u002Fp>\n\n\u003Cp>The starter app, along with all other apps, uses \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmolefrog\u002Fwouter\">wouter\u003C\u002Fa> for routing, but you are free to replace it with any library of your choice.\u003C\u002Fp>\n","Step 3","Add routes and pages",[],"step-3",{"_modelApiKey":410,"border":11,"caption":280,"image":432,"size":417,"darkTheme":11,"rounded":7,"color":370},{"url":433,"alt":434,"title":434,"width":435,"height":436,"format":290,"focalPoint":437,"customData":438},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1758708500-routes-vs-code.jpg","Routes VS code",2538,1530,{"x":292,"y":292},{},{"id":440,"copy":441,"_modelApiKey":379,"listStyle":374,"subtitle":442,"title":443,"darkTheme":11,"buttons":444,"sectionId":445,"color":370},"W9O-8pPDSy2jb1RoRwWhQg","\u003Cp>In the \u003Ccode>\u002Fsrc\u002Fpages\u003C\u002Fcode> folder, create a new file for each of the routes you just defined and import them into \u003Ccode>App.tsx\u003C\u002Fcode>:\u003C\u002Fp>\n","Step 4","Create the pages",[],"step-4",{"_modelApiKey":410,"border":11,"caption":280,"image":447,"size":417,"darkTheme":11,"rounded":7,"color":370},{"url":448,"alt":449,"title":449,"width":289,"height":450,"format":290,"focalPoint":451,"customData":452},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1758880317-create-pages-vs-code.jpg","Create pages VS code",1070,{"x":292,"y":292},{},{"_modelApiKey":372,"copy":454,"listStyle":374,"darkTheme":11,"buttons":455,"color":370,"indent":11},"\u003Cp>For the home page we will keep things simple. Using components like \u003Ccode>HomePageLayout\u003C\u002Fcode>, \u003Ccode>Spacer\u003C\u002Fcode>, \u003Ccode>List\u003C\u002Fcode>, and \u003Ccode>ListItem\u003C\u002Fcode> you can display a grid of resource types such as customers, markets, shipments, and SKUs.\u003C\u002Fp>\n\n\u003Cp>If you want to dive deeper into the components we used, check out the App Elements library \u003Ca href=\"https:\u002F\u002Fcommercelayer.github.io\u002Fapp-elements\">documentation\u003C\u002Fa>. It is packed with examples and explanations.\u003C\u002Fp>\n",[],{"_modelApiKey":395,"code":457,"showLineNumbers":11,"highlightLines":280,"language":458,"size":459},"import { appRoutes } from '#data\u002Froutes'\nimport {\n  formatResourceName,\n  HomePageLayout,\n  Icon,\n  List,\n  ListItem,\n  Spacer\n} from '@commercelayer\u002Fapp-elements'\nimport type { ListableResourceType } from '@commercelayer\u002Fsdk'\nimport type { FC } from 'react'\nimport { useLocation } from 'wouter'\n\nconst resources: ListableResourceType[] = [\n  'customers',\n  'markets',\n  'shipments',\n  'skus'\n]\n\nconst Page: FC = () => {\n  const [, setLocation] = useLocation()\n  return (\n    \u003CHomePageLayout title='My Sample App'>\n      \u003CSpacer top='14'>\n        \u003CList title='Resources'>\n          {resources.map((resourceType) => (\n            \u003CListItem\n              key={resourceType}\n              onClick={() => {\n                setLocation(appRoutes.list.makePath({ resourceType }))\n              }}\n            >\n              {formatResourceName({\n                resource: resourceType,\n                format: 'title',\n                count: 'plural'\n              })}\n              \u003CIcon name='caretRight' \u002F>\n            \u003C\u002FListItem>\n          ))}\n        \u003C\u002FList>\n      \u003C\u002FSpacer>\n    \u003C\u002FHomePageLayout>\n  )\n}\n\nexport default Page","typescript","Wide",{"_modelApiKey":372,"copy":461,"listStyle":374,"darkTheme":11,"buttons":462,"color":370,"indent":11},"\u003Cp>The list page goes one step further. For this page, we will combine simple UI elements like \u003Ccode>PageLayout\u003C\u002Fcode> and \u003Ccode>ListItem\u003C\u002Fcode> with the \u003Ccode>useResourceList\u003C\u002Fcode> hook, which takes care of fetching data and even supports infinite scrolling out of the box.\u003C\u002Fp>\n",[],{"_modelApiKey":395,"code":464,"showLineNumbers":11,"highlightLines":280,"language":458,"size":459},"import { appRoutes } from '#data\u002Froutes'\nimport {\n  Icon,\n  ListItem,\n  PageLayout,\n  type PageProps,\n  Text,\n  formatResourceName,\n  useResourceList\n} from '@commercelayer\u002Fapp-elements'\nimport type { FC } from 'react'\nimport { useLocation } from 'wouter'\n\nconst Page: FC\u003CPageProps\u003Ctypeof appRoutes.list>> = ({ params }) => {\n  const { resourceType } = params\n  const [, setLocation] = useLocation()\n  const { ResourceList, isLoading } = useResourceList({\n    type: resourceType,\n    query: {\n      pageSize: 25\n    }\n  })\n\n  if (isLoading) {\n    return null\n  }\n\n  return (\n    \u003CPageLayout\n      title={formatResourceName({\n        resource: resourceType,\n        format: 'title',\n        count: 'plural'\n      })}\n      navigationButton={{\n        label: 'Select type',\n        onClick: () => {\n          setLocation(appRoutes.home.makePath({}))\n        }\n      }}\n    >\n      \u003CResourceList\n        title='All'\n        ItemTemplate={({ resource }) => {\n          if (resource == null) {\n            return null\n          }\n          return (\n            \u003CListItem\n              onClick={() => {\n                setLocation(\n                  appRoutes.details.makePath({\n                    resourceType,\n                    resourceId: resource.id\n                  })\n                )\n              }}\n            >\n              \u003Cdiv>\n                \u003CText tag='div' weight='semibold'>\n                  #{resource.id}\n                \u003C\u002FText>\n                \u003CText tag='div' variant='info'>\n                  {\u002F* for customers *\u002F}\n                  {'email' in resource && resource.email}\n                  {\u002F* for other markets and skus *\u002F}\n                  {'name' in resource && resource.name}\n                  {\u002F* for other shipments *\u002F}\n                  {'number' in resource && resource.number}\n                \u003C\u002FText>\n              \u003C\u002Fdiv>\n              \u003CIcon name='caretRight' \u002F>\n            \u003C\u002FListItem>\n          )\n        }}\n      \u002F>\n    \u003C\u002FPageLayout>\n  )\n}\n\nexport default Page\n",{"_modelApiKey":372,"copy":466,"listStyle":374,"darkTheme":11,"buttons":467,"color":370,"indent":11},"\u003Cp>Finally, the details page will use the \u003Ccode>useCoreApi\u003C\u002Fcode> hook to fetch a single resource. To display the information we’ll use components such as \u003Ccode>ResourceDetails\u003C\u002Fcode> and \u003Ccode>ResourceMetadata\u003C\u002Fcode> from App Elements.\u003C\u002Fp>\n",[],{"_modelApiKey":395,"code":469,"showLineNumbers":11,"highlightLines":280,"language":458,"size":459},"import { appRoutes } from '#data\u002Froutes'\nimport {\n  EmptyState,\n  PageLayout,\n  type PageProps,\n  ResourceDetails,\n  ResourceMetadata,\n  Spacer,\n  formatResourceName,\n  useCoreApi\n} from '@commercelayer\u002Fapp-elements'\nimport type { FC } from 'react'\nimport { useLocation } from 'wouter'\n\nconst Page: FC\u003CPageProps\u003Ctypeof appRoutes.details>> = ({ params }) => {\n  const { resourceType, resourceId } = params\n  const [, setLocation] = useLocation()\n  const { data, isLoading, mutate } = useCoreApi(resourceType, 'retrieve', [\n    resourceId\n  ])\n\n  return (\n    \u003CPageLayout\n      title={formatResourceName({\n        resource: resourceType,\n        format: 'title',\n        count: 'singular'\n      })}\n      navigationButton={{\n        label: 'Back',\n        onClick: () => {\n          setLocation(appRoutes.list.makePath({ resourceType }))\n        }\n      }}\n    >\n      {isLoading ? (\n        \u003Cdiv>Loading...\u003C\u002Fdiv>\n      ) : data == null ? (\n        \u003CEmptyState title='Resource not found' \u002F>\n      ) : (\n        \u003C>\n          \u003CSpacer bottom='14'>\n            \u003CResourceDetails\n              resource={data}\n              onUpdated={async () => {\n                void mutate()\n              }}\n            \u002F>\n          \u003C\u002FSpacer>\n          \u003CResourceMetadata\n            resourceType={resourceType}\n            resourceId={resourceId}\n          \u002F>\n        \u003C\u002F>\n      )}\n    \u003C\u002FPageLayout>\n  )\n}\n\nexport default Page\n",{"_modelApiKey":367,"icon":471,"copy":472,"color":370},"magic-wand","\u003Cp>Remember, all of these components, hooks, and helpers are provided by our \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fapp-elements\">App Elements\u003C\u002Fa> open-source library.\u003C\u002Fp>\n",{"id":474,"copy":475,"_modelApiKey":379,"listStyle":374,"subtitle":476,"title":477,"darkTheme":11,"buttons":478,"sectionId":479,"color":370},"CX3Sbm5WR8OOUoNXt3fdAg","\u003Cp>To test your work, start the dev server:\u003C\u002Fp>\n","Step 5","Run it locally",[],"step-5",{"_modelApiKey":395,"code":481,"showLineNumbers":11,"highlightLines":280,"language":397,"size":398},"pnpm dev",{"_modelApiKey":372,"copy":483,"listStyle":374,"darkTheme":11,"buttons":484,"color":370,"indent":11},"\u003Cp>Go to \u003Ca href=\"http:\u002F\u002Flocalhost:5173\">http:\u002F\u002Flocalhost:5173\u003C\u002Fa> and you will see a list of all the apps included in this project. You are free to remove any apps from the \u003Ccode>\u002Fapps\u003C\u002Fcode> directory that you do not need, or keep them if you want to modify them later:\u003C\u002Fp>\n",[],{"_modelApiKey":410,"border":7,"caption":280,"image":486,"size":417,"darkTheme":11,"rounded":7,"color":370},{"url":487,"alt":488,"title":488,"width":289,"height":489,"format":290,"focalPoint":490,"customData":491},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1759421620-app-router-my-sample-app-list.jpg","My sample app within app list",1097,{"x":292,"y":292},{},{"_modelApiKey":372,"copy":493,"listStyle":374,"darkTheme":11,"buttons":494,"color":370,"indent":11},"\u003Cp>Before navigating the apps, you will need an access token in query string, as described in the second step when we introduced the \u003Ccode>TokenProvider\u003C\u002Fcode> in \u003Ccode>main.tsx\u003C\u002Fcode>.\u003C\u002Fp>\n\n\u003Cp>Keep in mind that each app requires its own specific access token. Luckily, during development, you can use a single integration token. This token can be generated either via the API or by using the \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fcommercelayer-cli\">Commerce Layer CLI\u003C\u002Fa>.\u003C\u002Fp>\n",[],{"_modelApiKey":367,"icon":496,"copy":497,"color":370},"info","\u003Cp>Note that when running the custom app inside the dashboard, the dashboard itself will generate the correct access token automatically.\u003C\u002Fp>\n",{"_modelApiKey":372,"copy":499,"listStyle":374,"darkTheme":11,"buttons":500,"color":370,"indent":11},"\u003Cp>Once you have your development token, simply append it to the URL to open your app: \u003C\u002Fp>\n",[],{"_modelApiKey":395,"code":502,"showLineNumbers":11,"highlightLines":280,"language":423,"size":398},"http:\u002F\u002Flocalhost:5173\u002F?accessToken=your_token_here",{"_modelApiKey":410,"border":7,"caption":504,"image":505,"size":417,"darkTheme":11,"rounded":7,"color":370},"The custom app homepage",{"url":506,"alt":507,"title":507,"width":289,"height":414,"format":290,"focalPoint":508,"customData":509},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1759422764-my-sample-app-resources.jpg","My sample app home",{"x":292,"y":292},{},{"_modelApiKey":410,"border":7,"caption":511,"image":512,"size":417,"darkTheme":11,"rounded":7,"color":370},"The SKUs listing page",{"url":513,"alt":514,"title":514,"width":289,"height":489,"format":290,"focalPoint":515,"customData":516},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1759422816-my-sample-app-skus.jpg","My Sample App SKUs",{"x":292,"y":292},{},{"_modelApiKey":410,"border":7,"caption":518,"image":519,"size":417,"darkTheme":11,"rounded":7,"color":370},"The single SKU details page",{"url":520,"alt":521,"title":521,"width":289,"height":522,"format":290,"focalPoint":523,"customData":524},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1759422871-my-sample-app-sku-id.jpg","My Sample App SKU details",944,{"x":292,"y":292},{},{"id":526,"copy":527,"_modelApiKey":379,"listStyle":374,"subtitle":528,"title":529,"darkTheme":11,"buttons":530,"sectionId":531,"color":370},"JB9O_bTyTq-3yHfFnvI12g","\u003Cp>When you are ready, deploy your app to the hosting service of your choice. Then simply add its URL to your Commerce Layer dashboard. From that moment, your entire team can use it as if it was part of the default environment:\u003C\u002Fp>\n","Step 6","Deploy and connect to your dashboard",[],"step-6",{"_modelApiKey":410,"border":7,"caption":280,"image":533,"size":417,"darkTheme":11,"rounded":7,"color":370},{"url":534,"alt":535,"title":535,"width":289,"height":536,"format":290,"focalPoint":537,"customData":538},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1759423208-new-generic-app.jpg","New generic app",724,{"x":292,"y":292},{},{"_modelApiKey":367,"icon":540,"copy":541,"color":370},"code","\u003Cp>The full code is available \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fdashboard-apps\u002Fpull\u002F330\">here\u003C\u002Fa>. This branch includes all the steps described above, assembled into a fully working final version.\u003C\u002Fp>\n",{"_modelApiKey":372,"copy":543,"listStyle":374,"darkTheme":11,"buttons":544,"color":370,"indent":11},"\u003Ch3>Unlocking new possibilities\u003C\u002Fh3>\n\n\u003Cp>The custom app we just built is intentionally simple. Yet it shows how easy it is to extend the dashboard and adapt it to your needs.\u003C\u002Fp>\n\n\u003Cp>With App Elements and our starter repo, you have everything you need to create powerful tools that improve your workflows.\u003C\u002Fp>\n\n\u003Cp>Custom apps unlock a new level of flexibility. Whether you are cloning and tweaking an existing app or starting from scratch, you now have the freedom to shape the dashboard around your business.\u003C\u002Fp>\n",[],[546,547,549,551,553,555,557,559,561,563,565,567,569,571,573,575,577],{"attributes":303,"tag":304,"content":305},{"attributes":548,"tag":309,"content":303},{"property":308,"content":305},{"attributes":550,"tag":309,"content":303},{"name":312,"content":305},{"attributes":552,"tag":309,"content":303},{"name":315,"content":316},{"attributes":554,"tag":309,"content":303},{"property":319,"content":316},{"attributes":556,"tag":309,"content":303},{"name":322,"content":316},{"attributes":558,"tag":309,"content":303},{"property":325,"content":326},{"attributes":560,"tag":309,"content":303},{"property":329,"content":330},{"attributes":562,"tag":309,"content":303},{"property":333,"content":334},{"attributes":564,"tag":309,"content":303},{"name":337,"content":326},{"attributes":566,"tag":309,"content":303},{"property":340,"content":341},{"attributes":568,"tag":309,"content":303},{"property":344,"content":345},{"attributes":570,"tag":309,"content":303},{"property":348,"content":349},{"attributes":572,"tag":309,"content":303},{"property":352,"content":353},{"attributes":574,"tag":309,"content":303},{"property":356,"content":280},{"attributes":576,"tag":309,"content":303},{"name":359,"content":360},{"attributes":578,"tag":309,"content":303},{"name":363,"content":364},[580],{"name":285,"picture":581},{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":582,"customData":583},{"x":292,"y":292},{},1791534736612]