[{"data":1,"prerenderedAt":439},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-pim":121,"100-apps-100-hours-pim-next":171,"sales-reps":187},{"items":4},[5,29,49,69],{"id":6,"title":7,"url":8,"page":8,"children":9},"522e608a-77b0-4333-820d-d4f44be2ade1","Solutions",null,[10,15,20,25],{"id":11,"title":12,"url":8,"page":13},"fcafe85a-a798-4710-9e7a-776fe413aae5","Headless CMS",{"permalink":14},"/solutions/headless-cms",{"id":16,"title":17,"url":8,"page":18},"79972923-93cf-4777-9e32-5c9b0315fc10","Backend-as-a-Service",{"permalink":19},"/solutions/backend-as-a-service",{"id":21,"title":22,"url":8,"page":23},"0fa8d0c1-7b64-4f6f-939d-d7fdb99fc407","Product Information",{"permalink":24},"/solutions/product-information-management",{"id":26,"title":27,"url":28,"page":8},"63946d54-6052-4780-8ff4-91f5a9931dcc","100+ Things to Build","https://directus.io/blog/100-tools-apps-and-platforms-you-can-build-with-directus",{"id":30,"title":31,"url":8,"page":8,"children":32},"8ab4f9b1-f3e2-44d6-919b-011d91fe072f","Resources",[33,37,41,45],{"id":34,"title":35,"url":36,"page":8},"f951fb84-8777-4b84-9e91-996fe9d25483","Documentation","https://docs.directus.io",{"id":38,"title":39,"url":40,"page":8},"366febc7-a538-4c08-a326-e6204957f1e3","Guides","https://docs.directus.io/guides/",{"id":42,"title":43,"url":44,"page":8},"aeb9128e-1c5f-417f-863c-2449416433cd","Community","https://directus.chat",{"id":46,"title":47,"url":48,"page":8},"da1c2ed8-0a77-49b0-a903-49c56cb07de5","Release Notes","https://github.com/directus/directus/releases",{"id":50,"title":51,"url":8,"page":8,"children":52},"d61fae8c-7502-494a-822f-19ecff3d0256","Support",[53,57,61,65],{"id":54,"title":55,"url":56,"page":8},"8c43c781-7ebd-475f-a931-747e293c0a88","Issue Tracker","https://github.com/directus/directus/issues",{"id":58,"title":59,"url":60,"page":8},"d77bb78e-cf7b-4e01-932a-514414ba49d3","Feature Requests","https://github.com/directus/directus/discussions?discussions_q=is:open+sort:top",{"id":62,"title":63,"url":64,"page":8},"4346be2b-2c53-476e-b53b-becacec626a6","Community Chat","https://discord.com/channels/725371605378924594/741317677397704757",{"id":66,"title":67,"url":68,"page":8},"26c115d2-49f7-4edc-935e-d37d427fb89d","Cloud Dashboard","https://directus.cloud",{"id":70,"title":71,"url":8,"page":8,"children":72},"49141403-4f20-44ac-8453-25ace1265812","Organization",[73,78,84,88],{"id":74,"title":75,"url":76,"page":77},"1f36ea92-8a5e-47c8-914c-9822a8b9538a","About","/about",{"permalink":76},{"id":79,"title":80,"url":81,"page":82},"b84bf525-5471-4b14-a93c-225f6c386005","Careers","#",{"permalink":83},"/careers",{"id":85,"title":86,"url":87,"page":8},"86aabc3a-433d-434b-9efa-ad1d34be0a34","Brand Assets","https://drive.google.com/drive/folders/1lBOTba4RaA5ikqOn8Ewo4RYzD0XcymG9?usp=sharing",{"id":89,"title":90,"url":8,"page":91},"8d2fa1e3-198e-4405-81e1-2ceb858bc237","Contact",{"permalink":92},"/contact",{"items":94},[95,101,107,113],{"id":96,"title":97,"url":8,"page":98,"children":100},"8a1b7bfa-429d-4ffc-a650-2a5fdcf356da","Cloud Policies",{"permalink":99},"/cloud-policies",[],{"id":102,"title":103,"url":81,"page":104,"children":106},"bea848ef-828f-4306-8017-6b00ec5d4a0c","License",{"permalink":105},"/bsl",[],{"id":108,"title":109,"url":81,"page":110,"children":112},"4e914f47-4bee-42b7-b445-3119ee4196ef","Terms",{"permalink":111},"/terms",[],{"id":114,"title":115,"url":81,"page":116,"children":118},"ea69eda6-d317-4981-8421-fcabb1826bfd","Privacy",{"permalink":117},"/privacy",[],{"description":120},"\u003Cp>A composable backend to build your Headless CMS, BaaS, and more.&nbsp;\u003C/p>",{"id":122,"slug":123,"vimeo_id":124,"description":125,"tile":126,"length":127,"resources":8,"people":128,"episode_number":132,"published":133,"title":134,"video_transcript_html":135,"video_transcript_text":136,"content":8,"status":137,"episode_people":138,"recommendations":152,"season":153,"seo":8},"dcb952e0-7d13-43ea-9b1c-f2ca63efd07d","pim","895881145","Join Bryant as he tackles creation of a product information management system to manage and distribute product data to e-commerce channels. He has one hour to build the data model, add some data, and create an integration to automatically push products to Shopify.","b2764b86-d5df-4dc1-90d2-2c7bcdc1462c",65,[129],{"name":130,"url":131},"Bryant Gillespie","https://directus.io/team/bryant-gillespie",5,"2024-01-08","Mission: Product Information Management","\u003Cp>Speaker 0: Hi. Welcome back to the next episode of 100 Apps 100 Hours where we build some of your favorite apps or die trying in 1 hour or less. So, I am your host, Brian Gillespie, a developer advocate at Directus. And, today we have PIM, which I've only started learning about in the last few months in detail. So PIM stands for product information management, sort of like a CMS for your products.\u003C/p>\u003Cp>But the main problem that you solve with PIM is, having a single source of truth for all of your product data. Imagine that you've got different ecommerce channels like a Shopify store or you're selling on the Amazon Marketplace or walmart.com. Being able to manage all those different products and all the variations and all the colors and images and everything that goes along with selling those products in one place instead of managing it in 3 different places or 5 different places is tremendously valuable. So there's a couple of other tools out there in the space. Pimcore is the name of one of them, SalesLair and Plytix are a few of the ones that I took a look at.\u003C/p>\u003Cp>And when it comes to PIN, before we dive in to actually building this, let's just take a look at at one of these solutions. So we've got a single source of truth where we find some screenshots. Can we find some screenshots? Kind of combines, all of my products, all of my assets, and you can generate things like catalogs or product sheets and sync this product data to those individual channels, which is the the biggest part or the, to me, where the real value is. I manage all of this in one place and sync it elsewhere.\u003C/p>\u003Cp>So we are going to build APEM in 1 hour or less. Right? If you've caught some of the episodes other episodes you already know the rules of the game in that there are 60 minutes to plan and build no more no less, and the other rules are there are no rules use whatever you have at your disposal which I will take plenty of advantage of in this specific episode. Alright, so with that let's open up our timer over here on the left and we'll get started. So the first thing that I like to do anytime I'm building an app is just a little bit of planning.\u003C/p>\u003Cp>So what's the functionality? What does our data model potentially look like on the back end? I like to work back end first just because it's so much nicer building a front end if I need 1 in, with actual live data. So what are the features or the functionality that we need out of a PIM? We need to be able to store all of our, store and manage our product data.\u003C/p>\u003Cp>Store and manage, all product assets, images, video, spec sheets, etcetera. Let's make this a nice list. I'm a recovering designer. I can't ever get that out of me. And then we need to be able to store and manage, we need to control product variations.\u003C/p>\u003Cp>Product variations like color and size, you know, other things like that. Then we want to be able to sync that data with other, with ecommerce. So let's call that Shopify in this case. And, you know, maybe even have a simple product catalog, where we could show off those products. Good enough.\u003C/p>\u003Cp>That looks like a pretty good set of base functionality for a PIM. We've got the single source of truth for that. Now let's dive into what our specific data model might look like. So we've got our products, and I'll do the lowercase version here. No fill.\u003C/p>\u003Cp>We could keep this purple color though. I like that. Alright, so we've got our products, we've got variants of those products, so maybe product variants. I can imagine this is like the specific color, size, g10, things like that. We got a name, assets, other data.\u003C/p>\u003Cp>Very lovely. What else are we going to have? Probably some categories for our products. Categories or collections. Maybe those are even recursive.\u003C/p>\u003Cp>What else do we have? We have assets, which our actual back end that we're using today, Directus, will take care of for us. What else is gonna be inside our data model? This looks like a pretty good start. Right?\u003C/p>\u003Cp>So let's actually dive in. We're about 3 minutes into it. Hate to spend too much time planning. Alright. So we will log into our back end, Directus, today.\u003C/p>\u003Cp>I've got a blank instance set up. This is using the cloud service. So this is actually running on Directus cloud and not locally because we do wanna sync this data, and, I don't wanna really struggle with cores issues or anything else, in local development trying to communicate with Shopify or other systems. So you can see I've got a blank instance, and it might be helpful just to leave this up side by side. Keep an eye on the clock.\u003C/p>\u003Cp>So I've got my blank instance of Directus. What I love about it is how easy it is to go in and build out our data model. So let's just start by creating a new collection. We've got Products and we're gonna give this a generated UUID for the primary key field. We'll add in a status, a date created, user updated, just some of the system fields, that are, like, built in utilities, basically.\u003C/p>\u003Cp>You know, we wanna keep track of what user updated, who, and when, just so we've got that information. Alright. So as far as products, we've, what, got a name for the product. We probably have a SKU number, SKU number, SKU part number. You know, you could have a couple of different items here.\u003C/p>\u003Cp>We've certainly got a description of the product, which we'll use our WYSIWYG editor inside Directus for. That way we can embed rich content. Sounds great. What else are we gonna have on the individual product? You know, a like, the price information may live on the the product variance there, so I'll keep it there.\u003C/p>\u003Cp>We're gonna have assets for this specific product as well. So I can go in and we'll create a new field and we'll use the relational files interface here which will actually create a junction table in our SQL database for us. So we'll do the product let's call this product Assets. Sounds great. And I could go into the advanced editor just to see what is happening behind the scenes.\u003C/p>\u003Cp>So we've got one collection called Products. We've got a related collection called Directus Files that is a built in system collection within Directus and then it automatically creates this junction table for us. Now if I wanted to adjust the name of the fields or control what this actual collection is named, I could turn off autofill and make that available. You know, and maybe I do want to add the concerning field or the reverse field to our products and we'll do a sort field just so I could control the primary images. Everything else looks great.\u003C/p>\u003Cp>We'll just save this, and now we've got some product data, right, or a product model where we can start uploading our data. Hey, this is a shirt and the SKU number is 1234. This is the best shirt ever. And we could even go in and upload an asset. We're probably gonna do a little bit of copyright infringement here.\u003C/p>\u003Cp>Just copy the image URL. We can upload this and I can do that by URL. Cannot fetch file from URL. Okay. Service unavailable.\u003C/p>\u003Cp>So I guess I'm going to have to download this image and then we'll just upload. So we get a pretty good idea of our basic functionality here. So now we've got our assets, and let's say I did want to browse this like a potential catalog. I can go into Directus, I could change this to the card view from our information sidebar and then I would just adjust my image source here. It looks like we don't support many to many fields there.\u003C/p>\u003Cp>So maybe we go back in and add a, like, a featured image. Featured image. Great. Alright. So this is just a single image that we want.\u003C/p>\u003Cp>So we'll go into that specific product, and I could pick this same image from the database. Alright. Great. So now we've got the featured image, we've got a name for the product, we've got our SKU number, and I can even go in and control how the image is cropped or not. So great.\u003C/p>\u003Cp>Now we've kind of got this, catalog looking module happening. Let's go in and finish fleshing out our data model. So I'll just clean this up a little bit. We've got our product variance and we'll use the generated UUID. Same thing, I will go in and add all these separate fields.\u003C/p>\u003Cp>Maybe we do have a sort field on the variant in case one variant takes priority over the other. Yeah. Kinda hit or miss. Alright. So we've got, our product variants.\u003C/p>\u003Cp>What we are going to do is create a many to one relationship back to our parent product. So this will be the product, and we're gonna choose the products collection. And this is a mini to one relationship. So I'm gonna go into the advanced field settings here. We'll just create a new, a one to many on the other side of the equation back on our products table for product variance.\u003C/p>\u003Cp>So now what I've done, in effect, is link these two collections or these two tables in our database together so that we can query and keep track of them. So now let's do what do we want to add for our other variant data, you know, so we've got color, that's just a string, we've got, size, etcetera. Great. We want to control a price here. You know, maybe we got that g ten, you know, unique identifier.\u003C/p>\u003Cp>What is that? The global trade identification number? Something like that. Let's go in and add a decimal. Actually, that'd be that'd be a float.\u003C/p>\u003Cp>And then we'll do maybe just 2 decimal places. Let's call this price. Great. Alright. So now we've got some items on our variants.\u003C/p>\u003Cp>Let's go back and actually look at how this works. So I go back into our product and we can see we've got our variants here and if I wanted to create a new variant, I can. Let's call this red, we'll call it small. The GTIN is 666 and the price for this is $25. We could go in and create another variant.\u003C/p>\u003Cp>This is red, this is medium, you know, 777, and the price is $16. Whatever. I can go in and actually set up our like what fields are displayed here. This doesn't look very great just to see the UUIDs. So let's sort that out, right.\u003C/p>\u003Cp>And we'll make this just a little bit bigger as well. Alright, so we go back into our data model, we go to our product variants, we look for our interface and we have this, control over do we want a list, do we want a table. Let's use the table in this case and we will add our color, size, maybe we'll do GTIN and price combinations. Now you also may have, like a variant that has a different image as well. So we could potentially add that as well.\u003C/p>\u003Cp>We'll just keep it simple for now and for the related fields, let's show the color. We'll add just like a little label for it so we know what it is, we'll add the size and we'll add size here. So let's see how that looks inside our PIM and great. Now we can see here's the different variants, there's the pricing, which, you know, maybe we wanna format that a little more, but great. And we can click on those to edit that information all that we need.\u003C/p>\u003Cp>Alright, now let's build out our categories, right? This is the only thing remaining on our data model. We want to build out categories for our different products. So we have a category, we'll use the generated UUID again, and not sure we need like a status on these, but maybe we do wanna sort and cool. Alright, so we've got a category name.\u003C/p>\u003Cp>Probably a short description for category. You know, we keep kinda splitting hairs which you on what detail you you need for something like this. If we look at, let's let's look at something like Pimcore. What what do they have available on their website? This looks really kinda messy to me.\u003C/p>\u003Cp>So we've got, different objects. We've got media. You know, I don't really know for certain how others set up their data model for this, but to me this is how I would do it. Now I could go in and have something like another classification for this. We could use, like, tags as a different type of taxonomy, a different way to organize the data.\u003C/p>\u003Cp>But I also have, you know, probably subcategories and parent categories. So Directus makes doing a recursive relationship like that very, very easy, where I can go in and do a many to 1 or a one to many relationship. So I'll just do one to many. We'll call this the, these are gonna be subcategories. Let's just do let's call let's do it the reverse.\u003C/p>\u003Cp>So this will be the parent category. We'll use the categories collection. So we're creating a relationship, a recursive relationship, and we're gonna do the subcategories. So we've got parent category which will be a single parent, then we have subcategories. Great.\u003C/p>\u003Cp>So what effect does that give us? Right? If I go into categories so let's create a new category we'll call it apparel. This is, let's call it actually, well, like outerwear or something like that. Great.\u003C/p>\u003Cp>Hoodies, jackets, and such. And we don't have a parent category or a subcategory yet, so let's just create a subcategory like hoodies. Hoodies. Great. We'll save that and now we can see we've got subcategories and, you know, potentially nest these.\u003C/p>\u003Cp>If I go back to our product, now we want to create a relationship with that category. So in this case, typically, a product may only have a single category. I I've seen some of these systems that will have, like, smart collections, which I think is a a really neat way to model it. And I think even Shopify, so I've got a a Shopify store set up here. Let's let's just take a look at how they handle it.\u003C/p>\u003Cp>If this can be in a multiple categories or no? So it looks just like a single category. You can set up different collections for it. So we'll keep this to be a mini to 1. So we've got a category and our related collection will be categories.\u003C/p>\u003Cp>And for our display template, maybe we just want to show the name of that category. Now, one of the other things I can do is control the interface to how the related values are displayed, just to make sure this looks how we want. So now if I go into our product, we can select the category hoodies or outerwear specifically. Great. Cool.\u003C/p>\u003Cp>Now, let's say what functionality, let's go back here. I always get ahead of myself. So now we've got a way to store and manage all of our product data. We've got a way to store all of our product assets, right? So I could go in and upload.\u003C/p>\u003Cp>Let's say I've got a PDF here. I could go in and upload this PDF. If I've got a spreadsheet, I could certainly do that as well. You know, I've got a CSV or whatever data that I've got, I can upload that and store it within this product. But, you know, what fun is it having these products in isolation?\u003C/p>\u003Cp>We wanna be able to use these specific products. So out of the box, because we've set up this data model, we know the structure and everything, Directus gives us the ability to well, it just gives us REST and GraphQL APIs. So I could go in and do something like this where we've got items. Products. I'm getting an error message because I haven't set up permissions, but let's imagine we wanted to allow anybody to access this product information.\u003C/p>\u003Cp>I can go to our setup, We'll go to access control and we'll go to our public role, which just controls what information is publicly available. And because none of this is secret at this point, let's go in and try it now. So if I hit refresh, we go to our products, we can see our product data. So there's the shirt, there's the SKU number, there's the description, we've got a featured image, UUID, And when it comes to our variants, we can't I can't see that information. That sucks.\u003C/p>\u003Cp>How do we fix this? I can actually do something like this, which is one of my favorite features inside Directus that I can query the related collections or the related fields from a single API call. So it's very GraphQL like even though this is REST based, and I, you know, there's a GraphQL API. I'm not a super huge fan. You may be.\u003C/p>\u003Cp>No sweat. So I could do something. We'll give, like, we'll use a wildcard here for all the root level fields. And then let's go in and do something like this where we have product variance and then we'll get all of the brute level fields for that. So as soon as I do that, now you can see I start to see that data that we want.\u003C/p>\u003Cp>And maybe I don't need all of this. Maybe we just need the ID product_variance dot color. I could get a little more sophisticated and copy paste dot size dot price dot g10. Alright. And now that will only return the data that I'm interested in which is nice prevents over fetching keeps things really fast when we are sending a lot of data over the network.\u003C/p>\u003Cp>Alright. So we've got the ability to store and manage all of our product assets. Let's control our product variations. How do we sync this data with an ecommerce system? Right?\u003C/p>\u003Cp>We're managing all of this in a single location. Maybe I've got Shopify, which is what we're gonna use. Maybe I've got an Amazon storefront setup. Maybe we've got 3 or 4 other things going on. Right?\u003C/p>\u003Cp>So how do we sync this information this data over? So I'm gonna go to our store and this is just the the Shopify demo store that I've set up. None of these products or anything that I created. Right? It looks like this is set up for snowboarding, which I tried once or twice, wasn't very good at it.\u003C/p>\u003Cp>But I'm gonna go into the settings of this and we're gonna try to find how to, get access to the API here. Let's look and see domains. Maybe it's here inside the apps. Okay. So we'll look for develop apps.\u003C/p>\u003Cp>We're gonna create an app that can connect with this storefront. Let's just take a look at it and see what it actually looks like on the front end. We got a password protection right now. We're gonna call this the Directus Shopify sync, maybe? We'll create this app inside the Shopify store.\u003C/p>\u003Cp>Alright. So it looks like the next thing that we'll need to do, we're probably gonna be using the admin API because we're not building a shopping experience. We're trying to send product data to Shopify, so that we can have that experience inside Shopify. So let's configure our access scopes. It's probably gonna be around products.\u003C/p>\u003Cp>And I'm not sure exactly which one of these I need. Product listings, product feeds, viewer manage products, let's go with that. We see the webhook subscriptions. We'll come back to webhooks. Let's just save that.\u003C/p>\u003Cp>What else do we need? We need some actual credentials. So we need an access token, a way to get that data. Use your client secret to verify incoming webhooks. Let's go ahead and install this app.\u003C/p>\u003Cp>This will give this app access to our storefront data. And we're only gonna reveal this token once and boom, we don't need it any longer. Not sure exactly how to set up the webhooks. Is that part of this? Start using the admin API.\u003C/p>\u003Cp>Do you have to create webhooks through the through the API? I don't know. If your app creates a webhook subscription oh, okay. So it looks like you do have to create webhooks using the API. Yeah.\u003C/p>\u003Cp>Alright. We'll come back to that. No worries. Alright. So now I've got this product information inside our account, I want to send it over to Shopify.\u003C/p>\u003Cp>How can I do that? Well, I could probably write some kind of custom app or I could just go in and use the flows, the the automation builder inside Directus. We're looking pretty good on time. We're at 36 minutes. So I'm just gonna make this full screen so we can dive in a little deeper.\u003C/p>\u003Cp>Let's call this send products to Shopify. Now how are we gonna trigger this? There's multiple ways to set up triggers. So I could manually trigger this. I could set it up on a cron job, so a regular interval.\u003C/p>\u003Cp>For now, I'm just gonna manually trigger this and we'll go we'll trigger this on our products section and maybe we'll require confirmation. Are you sure? Yes, we're sure. So the location here allows me to control where I can actually trigger this. So I'm just gonna trigger this on the collection page only.\u003C/p>\u003Cp>No. I could do both, you know, I could potentially send one product or maybe I want to send all of the products. We'll just keep that set up. Alright. So now let's go in.\u003C/p>\u003Cp>We'll save this flow and if we go back to our products page on the right hand side, I could see the option to send this to Shopify. So check an item. Are you sure? Yes, we'll run a flow. And if I go back to our flow, I refresh and I don't see my logs.\u003C/p>\u003Cp>Where are the logs? I ran this flow. Where is it? Let's make it not require a selection. Not sure if something happened here.\u003C/p>\u003Cp>What's what's going on? So we'll go to our flow. We'll hit send products to Shopify. Go to flows. Okay.\u003C/p>\u003Cp>Now they're showing up and we can see kind of the data that is being triggered here. So just collections, products. Great. Cool. So now, how do we get this actual data, right?\u003C/p>\u003Cp>We will go in and read the data. So let's just call it read products. That's gonna be the name of this operation. We're gonna select all the products from the collection products and then I could, like, filter this down if I wanted to. You know, maybe I I just leave it all for now.\u003C/p>\u003Cp>We're gonna select all the IDs and save it. And then once we read that data, we want to send to Shopify. Great. We'll, actually use the webhook HTTP request. How are we going to send the data?\u003C/p>\u003Cp>It's probably gonna be a post method. We're gonna go let's go back to Shopify really quickly. So we've got our storefront API documentation. Zoom in a little bit. Do we want the storefront?\u003C/p>\u003Cp>I don't think we do. So many tabs. So we go to admin. Oh, developer tools, API libraries. Oh, boy.\u003C/p>\u003Cp>Getting into the thick of it now. Let's go back to where's our actual store? Right. Explore, start using the admin API. Okay.\u003C/p>\u003Cp>Very confusing documentation here. Alright. So we'll go to our REST API reference. We'll look for products is probably what we want. We want to create a product.\u003C/p>\u003Cp>So there's what the product resource looks like. Here's our create a product, and there's our URL. So what does the base URL look like? Authentication. Okay.\u003C/p>\u003Cp>So it looks like the URL structure here is the Shopify unique shop ID or the shop slug, I'm assuming. My shopify.com/admin/api/ apiversion/resource. So our base URL setup so if we go back, will look something like this. Let's just pick up this actual URL. 100 apps, 100 hours, nice slugified version.\u003C/p>\u003Cp>Myshopifiedot com/ what was it? Real genius here. Okay. Let's go half screen. Myshopify.com/admin/api/ what?\u003C/p>\u003Cp>2023 dash 10 dash products dot JSON. Okay. So there's our URL. Great. Now I've got that access token.\u003C/p>\u003Cp>Authentication. Looks like we have to pass that as a header as well. So we'll go into direct us. We'll add some headers. Got content dash type.\u003C/p>\u003Cp>That's gonna be application slash JSON. And then we've got x Shopify access token. And then we've got this special token that you're not gonna look at, you're not gonna read, you're not gonna try to use. Alright. So now we can send a request.\u003C/p>\u003Cp>So we go down. We're looking for products again. We're gonna create a product inside Shopify. Alright. So what are the details we need for our products?\u003C/p>\u003Cp>We got a product dot title, body HTML, that's our description. We've got a product vendor, that's one thing we forgot inside our pin that we could add. We've got the product status. So, let's go in and just do something really simple. We'll do products.\u003C/p>\u003Cp>Okay. So there's our product. Then we're gonna have a title for the product, what's that gonna be? Let's take a step back here. I'm just gonna save this.\u003C/p>\u003Cp>I need to see what actual data is coming from this call here before we get carried away. Create a new product. Can you create is there an endpoint for creating multiple products? That's what I'm interested in. Create a new product.\u003C/p>\u003Cp>Retrieve a list of products. Looks like we're just going to be able to create a single product at a time. So we may have to break this apart and do 2 separate flows. Imagine that I had another product. We'll call it test product.\u003C/p>\u003Cp>No. Let's not do that. Let's call it a hoodie. So it is hoodie 123. Let's go to unsplash maybe.\u003C/p>\u003Cp>Do a hoodie. And this looks like a hoodie with a statement so we'll just copy paste that. Okay. We'll import the file from the URL. Boom.\u003C/p>\u003Cp>We got a featured image. Put a description. Great. Alright. So now we get 2 products.\u003C/p>\u003Cp>We're just gonna select these products. We're gonna send the well, what happens if we don't select? We could still run this flow. We'll We'll take a look. We'll send these products down to our flow.\u003C/p>\u003Cp>And let's just look at the log. Right? So now I'm getting an array of products. Okay. And maybe we wanna do we're gonna break this apart because we're gonna have to send a single product to Shopify.\u003C/p>\u003Cp>But before we do that, I want to format products. Right? So we'll take our product. We'll run the format function. So Directus allows you to run arbitrary JavaScript inside the flows, which is really nice for stuff like this.\u003C/p>\u003Cp>We've got the data, so our products are gonna be constant products equals data dot read products. I think that's gonna be it. And then we have some formatting logic here. Right. Just thinking out loud.\u003C/p>\u003Cp>So now I'm just gonna save this really quick. And one of the other things that I like to do when I'm working with these flows is just copy the data I'm getting back right here, and I'm gonna put it inside my code editor. I've got a starter project here just in case we needed it. So now I've got the data structure. And if I look at Shopify gosh.\u003C/p>\u003Cp>We've got, like, 35 different tabs going on. That's the fun of doing these live is actually trying to figure out how to all how to keep it all sane on one screen. Admin, API. Alright. So we go to the admin API, Rest admin API.\u003C/p>\u003Cp>So we go to our products. What things do we need to pass to our products at a most at a minimum just to actually create these products. So here we go. Here's a new draft product type of setup. Creates a new draft product.\u003C/p>\u003Cp>And can we do let's just make this even more complicated. Right? We'll do like a split screen? Somebody still needs to teach me to properly arc. Alright.\u003C/p>\u003Cp>So I've got Directus, got our product. Okay. We got like 30 things going on here. A lot of fun. Alright.\u003C/p>\u003Cp>So, let's run a format script. We've got our data over here that is coming from our Directus database. Let's go in and add our logic here. We got products. Let's do Shopify products dot map.\u003C/p>\u003Cp>Oh, no. Shopify products equals products dot map product. Yeah. Products. And then we'll return something.\u003C/p>\u003Cp>We got our function there. And then at the end of this, we're gonna return Shopify products. So we're just gonna return the array of products we want from Shopify. Alright. So we got the products and then we're gonna return a title for the product which is gonna be the product dot what name.\u003C/p>\u003Cp>We need a comma there. Then we've got the body_html, that's gonna be product dot description. We've got product dot vendor. We don't have that product dot type. We could fetch that, and then for now, let's just try this and see how far that actually gets us.\u003C/p>\u003Cp>Product dot name. We're gonna return our Shopify products. Let's go back in and I'll just make this full screen. Do products. I will look for our sidebar.\u003C/p>\u003Cp>Where are you sidebar? There you are, mister sidebar. We'll send the products to Shopify. Let's just test our flow and see what came out the other side. Right.\u003C/p>\u003Cp>So this looks good. This is our format that we're gonna send to Shopify. I'm gonna take just my my headers that I've got here. Let's just stuff those in this, take my URL that we so carefully constructed. And, I'm just gonna leave that.\u003C/p>\u003Cp>We're gonna create a new flow. So we're gonna go in and create Shopify product and this will be triggered from that other flow, so triggered by another flow. We'll leave the response body blank and here we're just gonna do the HTTP request that we just did. So I'm gonna paste in that. I've got my headers.\u003C/p>\u003Cp>Still stuck on the clipboard somewhere. I'll edit raw value and now we've got that. And here, I think it is just going to be something like this where we do actually, hit save. We'll just disconnect this for now. Hit save.\u003C/p>\u003Cp>Go back to our other flow and trigger that flow, and we'll just see what trigger Shopify, create. And Directus will create a key for you, you can also rename this key, but this key, all the data that gets generated in this operation gets appended to the flow underneath this specific key. Let's just take a quick peek at time. How are we doing? We've got 20 minutes left.\u003C/p>\u003Cp>Great. And then we're just gonna trigger this flow. So we'll select the Shopify flow. It's okay to create these in parallel and then for our payload, we're gonna do simply this. We've got the double curly brackets.\u003C/p>\u003Cp>We've got the data. No. What's the name of that operation? Format product. Is that what the key is?\u003C/p>\u003Cp>We're just gonna use this key. So we're gonna trigger the flow and the data from that, format product, we're gonna pass that to that other flow. Alright. So fingers crossed, stay with me here, we are building cool stuff. So now we're gonna send these products to Shopify, running that flow.\u003C/p>\u003Cp>And we can see here we triggered this. We got back null null, this is what we sent to that. And if we go to our other flow, where we're actually creating, we can see that this ran twice, which is the what we actually want. We wanna see the the payload that we got was the product. So we're just gonna pick up the trigger.\u003C/p>\u003Cp>Payload and that's what we're gonna send. Great. Amazing. So, in our request body here, we're gonna do something like this where we've got, quotations, double curly bracket, trigger dot payload and trigger is, prefixed with a dollar sign just because it's a unique, special key to get the trigger data. And fingers crossed.\u003C/p>\u003Cp>Right? Can we send this data to Shopify? Can we build a working PIM in an hour? Let's test it out. We're gonna run I I don't think it matters if I select these or not.\u003C/p>\u003Cp>Let's go in. We'll see. We have our send products to Shopify. That ran. Hopefully triggered this other flow that we have.\u003C/p>\u003Cp>We got a Shopify product. Bad request. By accessing the Shopify, you agree to parameters are missing or invalid. Okay. So we're missing some parameters.\u003C/p>\u003Cp>Let's take a look at the actual data that we sent. Payload, got the product resource, body HTML, supports HTML formatting, product status dot draft, Shopify access token. It looks like the body is not defined. So, I think I goofed up and that and maybe we're just sending the trigger. Let's take a look.\u003C/p>\u003Cp>Did I get it wrong? Do we just need to send the trigger? So, let's make this more fun. Right. Let's go to our Shopify store.\u003C/p>\u003Cp>Alright. We're gonna close this guy. Alright. I'm on the products. I'm gonna hit refresh.\u003C/p>\u003Cp>Dun dun duh. I don't I don't see any products in here. Alright. So, let's go back. Obviously, I goofed somewhere again.\u003C/p>\u003Cp>And we're definitely in danger of running out of time here. We got a bad request, missing parameters. Okay. So there's the body, product title, product dot HTML. What are we missing?\u003C/p>\u003Cp>100 apps, 100 hours. We've got our access token. We've got our content type. Required parameter. Did I spell something wrong?\u003C/p>\u003Cp>This is always the danger of working against the clock like this. What is this issue going to be? Request body is product. Alright. So product title body HTML.\u003C/p>\u003Cp>I don't see where this is going wrong. Shopify access token. Bad request. Let's take a look at our app. Do we have the wrong scopes or something?\u003C/p>\u003Cp>Let's just try one quick tweak. Go back to our send products for our format product. Let's just leave out the title. Right. So we'll just comment this out.\u003C/p>\u003Cp>Return Shopify products. Send products to Shopify, flows, bad request, product underscore. Is it the way that it is actually sending this data? Sometimes if we do something like this, where we got trigger. Boom boom, Ron.\u003C/p>\u003Cp>Created. Boom. Alright. So dramatic reveal time. Dun dun dun.\u003C/p>\u003Cp>We have sent this data to Shopify. Did we? Where do we go? Okay. So we've got a hoodie, we've got a shirt.\u003C/p>\u003Cp>There they are. Boom. Now they are in our system. That is how it's done, ladies and gentlemen. Where where we at on time?\u003C/p>\u003Cp>We got 13 minutes left. We have managed to sync product data. Let's let's just take this one step further if we can, and let's create a product with let's at least get the images over there. Right? So on our product resource, we've got a list of product images, create a new product image, create a product image using a source URL that will be downloaded to Shopify.\u003C/p>\u003Cp>Okay. So it looks like you can you may be able to pass these, and I'll just go in and very briefly let's just delete these products. Delete these products. Can't be undone. Those are deleted.\u003C/p>\u003Cp>Those are no longer in the system. Let's adjust our payload here. So we're gonna go back to the formatting option. We'll comment that back in. We can have that now and then on the Shopify side, we've got okay.\u003C/p>\u003Cp>So we got a product dot images. This will be images and then we've got an array And then what? Array of objects, and then we just got a source attribute. So the source if we look at our code again, Right. We've got our featured image.\u003C/p>\u003Cp>Let's just pluck that for now And, we're gonna do something like this where we've got the source is gonna be, HTTPS direct us app slash assets slash what was the name of that? Featured underscore image. Featured. Oop. Gotta do our little squirrellys featured image.\u003C/p>\u003Cp>Close that off. Should be good. The last piece of the puzzle for this is going to be enabling permissions for the actual files inside Directus as well, Because Shopify needs to access that. So 2 ways I could do it. I can add an access token, when I'm sending that call to Shopify Or in this case, I'm just gonna go in and we'll keep this really simple.\u003C/p>\u003Cp>Under direct as files, we'll do all access. And we're just going to send and cross our fingers hoping that this flow actually works. We could do the dramatic reveal inside Shopify. Right? I broke something trying to send the images over.\u003C/p>\u003Cp>Where did you go? Where did you go? Alright. What broke? Right?\u003C/p>\u003Cp>Clearly, we broke something. Great Shopify product. What do we send? Product title hoodie. Do we actually break something inside the I don't see my logs.\u003C/p>\u003Cp>4 to 5 minutes ago, that's not correct. Run Shopify flows. Go into our flow. Less than a minute ago, featured image is not defined. Okay.\u003C/p>\u003Cp>So we did not have one with a featured image, and that broke. What did I do wrong? Again, always fun on the clock. I guess you have to actually use product dot featured image. Right?\u003C/p>\u003Cp>Featured image. That would make a ton of sense. Too much sense actually. Alright. Last time.\u003C/p>\u003Cp>Send products to Shopify. Did it work? Let's test our flow. Create Shopify product. Created.\u003C/p>\u003Cp>Good looking. Alright. So if we open up Shopify, we go to our products, we can see our hoodie. Here is our actual data, so there's the image that we've got, there's the shirt that we loaded up, and this is all coming from our pin, our back end, our single source of truth. So that is how it's done, ladies and gentlemen.\u003C/p>\u003Cp>We are syncing with Shopify. Last on our list, can we get this in 8 minutes? I have no idea. We've got a simple product catalog. Now, I don't think we could do this in 8 minutes, but let's give it a shot, right?\u003C/p>\u003Cp>We want to display all of our products on the front end of our website, so people can browse those. So because there are no rules, I am a huge fan of Tailwind UI and Tailwind CSS in general. They've got some great looking ecommerce components that we may just repurpose for this, like, here's a nice product list. And I've got a Nuxt starter application sitting here just ready to be used. So let's see if we can actually get this product information displayed and rendering on a product catalog in just a couple minutes.\u003C/p>\u003Cp>Alright, so we're gonna just forget Shopify for now. Let's simplify. Alright. This looks pretty good. This is a with border grid.\u003C/p>\u003Cp>We've got a view component here. We've got our products. Let's just copy this template. Alright, so we're gonna go to our index page. We probably got some script action that we'll do there.\u003C/p>\u003Cp>For now, I'm gonna if we just copy their products. Right. Constant products. Not gonna look great, and they've got an icon. So we'll do the star.\u003C/p>\u003Cp>Let's just omit the products ratings for now. Right? Just comment those up. Run this. If I look at local host, say now we've got got something cooking here.\u003C/p>\u003Cp>K. And now, let's actually fetch these products from Directus. We wanna do this live. Right? Going to do what, we got our products.\u003C/p>\u003Cp>Is it equal to await use directus? I've got a just a composable here to fetch this data using the rectus SDK. We're going to read items and that's gonna be products. And is this actually gonna be all we really need? Usually, when I am developing, I often tend to do stuff like this where I will just render out the data inside the browser just to make sure we're seeing we're getting the stuff that we need.\u003C/p>\u003Cp>Item/products failed. Oh, wait. Use direct us, read items, products. Why is this not working correctly? Well, that would be because I didn't actually set up the directus URL properly.\u003C/p>\u003Cp>We are at 5 minutes, so I'm gonna load my PIM URL here. That is my Directus base URL. The server token, probably not even needed in this setup, but, we'll just reload. Let's fire back up the dev server. Let's see if we can get this data rendering from Directus.\u003C/p>\u003Cp>Boom. So there's our actual data. Right? We no longer need this. Let's uncomment.\u003C/p>\u003Cp>And just looking at this data, we've got the v4productandproducts. We wanna render the product dot id, we'll have that information. The image source, because we can use Directus as an image provider it will transform assets for you. I'm just going to use the NUTS image tag which is already set up in this. We will use product dot featured image.\u003C/p>\u003Cp>We'll just leave alt text blank for now because there is there's none. We haven't got that set up. We got product dothref. We don't have one of those currently, but we could do slash products slash what? Product dot ID.\u003C/p>\u003Cp>That would be the probably the URL. We got a product dot name, we got a product dot price. What else? I wanna fix the template structure here and then maybe we want to render out, div. We'll do like a tailwindpros class and give this v html.\u003C/p>\u003Cp>We'll render the product description. Input must be a string received undefined. So if there let's make sure oh, forgot the item there. Boom. So there we have it.\u003C/p>\u003Cp>We have got our items rendered out. You know, we could continue fleshing this out with the help of Tailwind into a catalog. But now we have done several things, Right? Let's recap with our final two and a half minutes. We have created a PIM system that can store and manage all of our product data, store all of our product assets, control all of our product variations, we can sync that data with Shopify, and we can have a simple catalog.\u003C/p>\u003Cp>But, Bryant, you forgot one thing, our translations for this data. What if I want to have it in French or German or, Canadian? Bad bad joke for all all of our Canadian followers. Apologies there. So with a minute 30, how can we set up, translations for this?\u003C/p>\u003Cp>Right? How can I control other languages? So I'm gonna go into our products. The first thing I'm gonna do is just create a languages collection. We've got a language code.\u003C/p>\u003Cp>Alright. We've got a title or name of the language. Alright. And let's create 2 languages. We have EN, US, English.\u003C/p>\u003Cp>Are we gonna do it 55 seconds? Let's just say fr for fridge. I think that's the code. No idea. We'll go back to our product.\u003C/p>\u003Cp>Let's go in and there's a special translations interface within Directus. Clock is ticking, Brian. So we got the code, we got the direction field, that would probably be right to left. Use current language, we'll save this And then inside our product translations, we would go in and do, name, We would do a description. And where we at on time?\u003C/p>\u003Cp>10 seconds. Brian, can you do it? 10, 9, 8, 7. How do we add translations for this? We can do English name.\u003C/p>\u003Cp>So close. We hit the 60 seconds. I'm just going to finish this or the 60 minutes. We can do the French translation here and save it. So, with Directus we can manage all of these different things.\u003C/p>\u003Cp>We hit time there, that felt really good though. We can manage translations or multilingual content. Alright. So that was a fun one. Multilingual content.\u003C/p>\u003Cp>Really enjoyed this one. Building a PIM in 60 minutes or less. There's obviously more to this, but my next steps would be to flesh out that sync engine a little bit, so that if we made updates inside Directus, I could send those over to Shopify. I could even use the Shopify webhooks in this case, if I made a change to that information in Shopify to sync it back into Directus. I would also probably go in and flesh out the translations interface just a little bit more.\u003C/p>\u003Cp>So that I have all the necessary fields. You know, and I may even have specific fields that we want to add to Shopify versus something like Amazon. So we could go in and do, you know, some more different some different channels that we wanna send this data to. But for 60 minutes, amazing to see what you can actually build with Directus. Stay tuned for the next episode.\u003C/p>\u003Cp>I'm sure we'll have a good one and you'll get to see me hack and slash and burn my way through another product.\u003C/p>","Hi. Welcome back to the next episode of 100 Apps 100 Hours where we build some of your favorite apps or die trying in 1 hour or less. So, I am your host, Brian Gillespie, a developer advocate at Directus. And, today we have PIM, which I've only started learning about in the last few months in detail. So PIM stands for product information management, sort of like a CMS for your products. But the main problem that you solve with PIM is, having a single source of truth for all of your product data. Imagine that you've got different ecommerce channels like a Shopify store or you're selling on the Amazon Marketplace or walmart.com. Being able to manage all those different products and all the variations and all the colors and images and everything that goes along with selling those products in one place instead of managing it in 3 different places or 5 different places is tremendously valuable. So there's a couple of other tools out there in the space. Pimcore is the name of one of them, SalesLair and Plytix are a few of the ones that I took a look at. And when it comes to PIN, before we dive in to actually building this, let's just take a look at at one of these solutions. So we've got a single source of truth where we find some screenshots. Can we find some screenshots? Kind of combines, all of my products, all of my assets, and you can generate things like catalogs or product sheets and sync this product data to those individual channels, which is the the biggest part or the, to me, where the real value is. I manage all of this in one place and sync it elsewhere. So we are going to build APEM in 1 hour or less. Right? If you've caught some of the episodes other episodes you already know the rules of the game in that there are 60 minutes to plan and build no more no less, and the other rules are there are no rules use whatever you have at your disposal which I will take plenty of advantage of in this specific episode. Alright, so with that let's open up our timer over here on the left and we'll get started. So the first thing that I like to do anytime I'm building an app is just a little bit of planning. So what's the functionality? What does our data model potentially look like on the back end? I like to work back end first just because it's so much nicer building a front end if I need 1 in, with actual live data. So what are the features or the functionality that we need out of a PIM? We need to be able to store all of our, store and manage our product data. Store and manage, all product assets, images, video, spec sheets, etcetera. Let's make this a nice list. I'm a recovering designer. I can't ever get that out of me. And then we need to be able to store and manage, we need to control product variations. Product variations like color and size, you know, other things like that. Then we want to be able to sync that data with other, with ecommerce. So let's call that Shopify in this case. And, you know, maybe even have a simple product catalog, where we could show off those products. Good enough. That looks like a pretty good set of base functionality for a PIM. We've got the single source of truth for that. Now let's dive into what our specific data model might look like. So we've got our products, and I'll do the lowercase version here. No fill. We could keep this purple color though. I like that. Alright, so we've got our products, we've got variants of those products, so maybe product variants. I can imagine this is like the specific color, size, g10, things like that. We got a name, assets, other data. Very lovely. What else are we going to have? Probably some categories for our products. Categories or collections. Maybe those are even recursive. What else do we have? We have assets, which our actual back end that we're using today, Directus, will take care of for us. What else is gonna be inside our data model? This looks like a pretty good start. Right? So let's actually dive in. We're about 3 minutes into it. Hate to spend too much time planning. Alright. So we will log into our back end, Directus, today. I've got a blank instance set up. This is using the cloud service. So this is actually running on Directus cloud and not locally because we do wanna sync this data, and, I don't wanna really struggle with cores issues or anything else, in local development trying to communicate with Shopify or other systems. So you can see I've got a blank instance, and it might be helpful just to leave this up side by side. Keep an eye on the clock. So I've got my blank instance of Directus. What I love about it is how easy it is to go in and build out our data model. So let's just start by creating a new collection. We've got Products and we're gonna give this a generated UUID for the primary key field. We'll add in a status, a date created, user updated, just some of the system fields, that are, like, built in utilities, basically. You know, we wanna keep track of what user updated, who, and when, just so we've got that information. Alright. So as far as products, we've, what, got a name for the product. We probably have a SKU number, SKU number, SKU part number. You know, you could have a couple of different items here. We've certainly got a description of the product, which we'll use our WYSIWYG editor inside Directus for. That way we can embed rich content. Sounds great. What else are we gonna have on the individual product? You know, a like, the price information may live on the the product variance there, so I'll keep it there. We're gonna have assets for this specific product as well. So I can go in and we'll create a new field and we'll use the relational files interface here which will actually create a junction table in our SQL database for us. So we'll do the product let's call this product Assets. Sounds great. And I could go into the advanced editor just to see what is happening behind the scenes. So we've got one collection called Products. We've got a related collection called Directus Files that is a built in system collection within Directus and then it automatically creates this junction table for us. Now if I wanted to adjust the name of the fields or control what this actual collection is named, I could turn off autofill and make that available. You know, and maybe I do want to add the concerning field or the reverse field to our products and we'll do a sort field just so I could control the primary images. Everything else looks great. We'll just save this, and now we've got some product data, right, or a product model where we can start uploading our data. Hey, this is a shirt and the SKU number is 1234. This is the best shirt ever. And we could even go in and upload an asset. We're probably gonna do a little bit of copyright infringement here. Just copy the image URL. We can upload this and I can do that by URL. Cannot fetch file from URL. Okay. Service unavailable. So I guess I'm going to have to download this image and then we'll just upload. So we get a pretty good idea of our basic functionality here. So now we've got our assets, and let's say I did want to browse this like a potential catalog. I can go into Directus, I could change this to the card view from our information sidebar and then I would just adjust my image source here. It looks like we don't support many to many fields there. So maybe we go back in and add a, like, a featured image. Featured image. Great. Alright. So this is just a single image that we want. So we'll go into that specific product, and I could pick this same image from the database. Alright. Great. So now we've got the featured image, we've got a name for the product, we've got our SKU number, and I can even go in and control how the image is cropped or not. So great. Now we've kind of got this, catalog looking module happening. Let's go in and finish fleshing out our data model. So I'll just clean this up a little bit. We've got our product variance and we'll use the generated UUID. Same thing, I will go in and add all these separate fields. Maybe we do have a sort field on the variant in case one variant takes priority over the other. Yeah. Kinda hit or miss. Alright. So we've got, our product variants. What we are going to do is create a many to one relationship back to our parent product. So this will be the product, and we're gonna choose the products collection. And this is a mini to one relationship. So I'm gonna go into the advanced field settings here. We'll just create a new, a one to many on the other side of the equation back on our products table for product variance. So now what I've done, in effect, is link these two collections or these two tables in our database together so that we can query and keep track of them. So now let's do what do we want to add for our other variant data, you know, so we've got color, that's just a string, we've got, size, etcetera. Great. We want to control a price here. You know, maybe we got that g ten, you know, unique identifier. What is that? The global trade identification number? Something like that. Let's go in and add a decimal. Actually, that'd be that'd be a float. And then we'll do maybe just 2 decimal places. Let's call this price. Great. Alright. So now we've got some items on our variants. Let's go back and actually look at how this works. So I go back into our product and we can see we've got our variants here and if I wanted to create a new variant, I can. Let's call this red, we'll call it small. The GTIN is 666 and the price for this is $25. We could go in and create another variant. This is red, this is medium, you know, 777, and the price is $16. Whatever. I can go in and actually set up our like what fields are displayed here. This doesn't look very great just to see the UUIDs. So let's sort that out, right. And we'll make this just a little bit bigger as well. Alright, so we go back into our data model, we go to our product variants, we look for our interface and we have this, control over do we want a list, do we want a table. Let's use the table in this case and we will add our color, size, maybe we'll do GTIN and price combinations. Now you also may have, like a variant that has a different image as well. So we could potentially add that as well. We'll just keep it simple for now and for the related fields, let's show the color. We'll add just like a little label for it so we know what it is, we'll add the size and we'll add size here. So let's see how that looks inside our PIM and great. Now we can see here's the different variants, there's the pricing, which, you know, maybe we wanna format that a little more, but great. And we can click on those to edit that information all that we need. Alright, now let's build out our categories, right? This is the only thing remaining on our data model. We want to build out categories for our different products. So we have a category, we'll use the generated UUID again, and not sure we need like a status on these, but maybe we do wanna sort and cool. Alright, so we've got a category name. Probably a short description for category. You know, we keep kinda splitting hairs which you on what detail you you need for something like this. If we look at, let's let's look at something like Pimcore. What what do they have available on their website? This looks really kinda messy to me. So we've got, different objects. We've got media. You know, I don't really know for certain how others set up their data model for this, but to me this is how I would do it. Now I could go in and have something like another classification for this. We could use, like, tags as a different type of taxonomy, a different way to organize the data. But I also have, you know, probably subcategories and parent categories. So Directus makes doing a recursive relationship like that very, very easy, where I can go in and do a many to 1 or a one to many relationship. So I'll just do one to many. We'll call this the, these are gonna be subcategories. Let's just do let's call let's do it the reverse. So this will be the parent category. We'll use the categories collection. So we're creating a relationship, a recursive relationship, and we're gonna do the subcategories. So we've got parent category which will be a single parent, then we have subcategories. Great. So what effect does that give us? Right? If I go into categories so let's create a new category we'll call it apparel. This is, let's call it actually, well, like outerwear or something like that. Great. Hoodies, jackets, and such. And we don't have a parent category or a subcategory yet, so let's just create a subcategory like hoodies. Hoodies. Great. We'll save that and now we can see we've got subcategories and, you know, potentially nest these. If I go back to our product, now we want to create a relationship with that category. So in this case, typically, a product may only have a single category. I I've seen some of these systems that will have, like, smart collections, which I think is a a really neat way to model it. And I think even Shopify, so I've got a a Shopify store set up here. Let's let's just take a look at how they handle it. If this can be in a multiple categories or no? So it looks just like a single category. You can set up different collections for it. So we'll keep this to be a mini to 1. So we've got a category and our related collection will be categories. And for our display template, maybe we just want to show the name of that category. Now, one of the other things I can do is control the interface to how the related values are displayed, just to make sure this looks how we want. So now if I go into our product, we can select the category hoodies or outerwear specifically. Great. Cool. Now, let's say what functionality, let's go back here. I always get ahead of myself. So now we've got a way to store and manage all of our product data. We've got a way to store all of our product assets, right? So I could go in and upload. Let's say I've got a PDF here. I could go in and upload this PDF. If I've got a spreadsheet, I could certainly do that as well. You know, I've got a CSV or whatever data that I've got, I can upload that and store it within this product. But, you know, what fun is it having these products in isolation? We wanna be able to use these specific products. So out of the box, because we've set up this data model, we know the structure and everything, Directus gives us the ability to well, it just gives us REST and GraphQL APIs. So I could go in and do something like this where we've got items. Products. I'm getting an error message because I haven't set up permissions, but let's imagine we wanted to allow anybody to access this product information. I can go to our setup, We'll go to access control and we'll go to our public role, which just controls what information is publicly available. And because none of this is secret at this point, let's go in and try it now. So if I hit refresh, we go to our products, we can see our product data. So there's the shirt, there's the SKU number, there's the description, we've got a featured image, UUID, And when it comes to our variants, we can't I can't see that information. That sucks. How do we fix this? I can actually do something like this, which is one of my favorite features inside Directus that I can query the related collections or the related fields from a single API call. So it's very GraphQL like even though this is REST based, and I, you know, there's a GraphQL API. I'm not a super huge fan. You may be. No sweat. So I could do something. We'll give, like, we'll use a wildcard here for all the root level fields. And then let's go in and do something like this where we have product variance and then we'll get all of the brute level fields for that. So as soon as I do that, now you can see I start to see that data that we want. And maybe I don't need all of this. Maybe we just need the ID product_variance dot color. I could get a little more sophisticated and copy paste dot size dot price dot g10. Alright. And now that will only return the data that I'm interested in which is nice prevents over fetching keeps things really fast when we are sending a lot of data over the network. Alright. So we've got the ability to store and manage all of our product assets. Let's control our product variations. How do we sync this data with an ecommerce system? Right? We're managing all of this in a single location. Maybe I've got Shopify, which is what we're gonna use. Maybe I've got an Amazon storefront setup. Maybe we've got 3 or 4 other things going on. Right? So how do we sync this information this data over? So I'm gonna go to our store and this is just the the Shopify demo store that I've set up. None of these products or anything that I created. Right? It looks like this is set up for snowboarding, which I tried once or twice, wasn't very good at it. But I'm gonna go into the settings of this and we're gonna try to find how to, get access to the API here. Let's look and see domains. Maybe it's here inside the apps. Okay. So we'll look for develop apps. We're gonna create an app that can connect with this storefront. Let's just take a look at it and see what it actually looks like on the front end. We got a password protection right now. We're gonna call this the Directus Shopify sync, maybe? We'll create this app inside the Shopify store. Alright. So it looks like the next thing that we'll need to do, we're probably gonna be using the admin API because we're not building a shopping experience. We're trying to send product data to Shopify, so that we can have that experience inside Shopify. So let's configure our access scopes. It's probably gonna be around products. And I'm not sure exactly which one of these I need. Product listings, product feeds, viewer manage products, let's go with that. We see the webhook subscriptions. We'll come back to webhooks. Let's just save that. What else do we need? We need some actual credentials. So we need an access token, a way to get that data. Use your client secret to verify incoming webhooks. Let's go ahead and install this app. This will give this app access to our storefront data. And we're only gonna reveal this token once and boom, we don't need it any longer. Not sure exactly how to set up the webhooks. Is that part of this? Start using the admin API. Do you have to create webhooks through the through the API? I don't know. If your app creates a webhook subscription oh, okay. So it looks like you do have to create webhooks using the API. Yeah. Alright. We'll come back to that. No worries. Alright. So now I've got this product information inside our account, I want to send it over to Shopify. How can I do that? Well, I could probably write some kind of custom app or I could just go in and use the flows, the the automation builder inside Directus. We're looking pretty good on time. We're at 36 minutes. So I'm just gonna make this full screen so we can dive in a little deeper. Let's call this send products to Shopify. Now how are we gonna trigger this? There's multiple ways to set up triggers. So I could manually trigger this. I could set it up on a cron job, so a regular interval. For now, I'm just gonna manually trigger this and we'll go we'll trigger this on our products section and maybe we'll require confirmation. Are you sure? Yes, we're sure. So the location here allows me to control where I can actually trigger this. So I'm just gonna trigger this on the collection page only. No. I could do both, you know, I could potentially send one product or maybe I want to send all of the products. We'll just keep that set up. Alright. So now let's go in. We'll save this flow and if we go back to our products page on the right hand side, I could see the option to send this to Shopify. So check an item. Are you sure? Yes, we'll run a flow. And if I go back to our flow, I refresh and I don't see my logs. Where are the logs? I ran this flow. Where is it? Let's make it not require a selection. Not sure if something happened here. What's what's going on? So we'll go to our flow. We'll hit send products to Shopify. Go to flows. Okay. Now they're showing up and we can see kind of the data that is being triggered here. So just collections, products. Great. Cool. So now, how do we get this actual data, right? We will go in and read the data. So let's just call it read products. That's gonna be the name of this operation. We're gonna select all the products from the collection products and then I could, like, filter this down if I wanted to. You know, maybe I I just leave it all for now. We're gonna select all the IDs and save it. And then once we read that data, we want to send to Shopify. Great. We'll, actually use the webhook HTTP request. How are we going to send the data? It's probably gonna be a post method. We're gonna go let's go back to Shopify really quickly. So we've got our storefront API documentation. Zoom in a little bit. Do we want the storefront? I don't think we do. So many tabs. So we go to admin. Oh, developer tools, API libraries. Oh, boy. Getting into the thick of it now. Let's go back to where's our actual store? Right. Explore, start using the admin API. Okay. Very confusing documentation here. Alright. So we'll go to our REST API reference. We'll look for products is probably what we want. We want to create a product. So there's what the product resource looks like. Here's our create a product, and there's our URL. So what does the base URL look like? Authentication. Okay. So it looks like the URL structure here is the Shopify unique shop ID or the shop slug, I'm assuming. My shopify.com/admin/api/ apiversion/resource. So our base URL setup so if we go back, will look something like this. Let's just pick up this actual URL. 100 apps, 100 hours, nice slugified version. Myshopifiedot com/ what was it? Real genius here. Okay. Let's go half screen. Myshopify.com/admin/api/ what? 2023 dash 10 dash products dot JSON. Okay. So there's our URL. Great. Now I've got that access token. Authentication. Looks like we have to pass that as a header as well. So we'll go into direct us. We'll add some headers. Got content dash type. That's gonna be application slash JSON. And then we've got x Shopify access token. And then we've got this special token that you're not gonna look at, you're not gonna read, you're not gonna try to use. Alright. So now we can send a request. So we go down. We're looking for products again. We're gonna create a product inside Shopify. Alright. So what are the details we need for our products? We got a product dot title, body HTML, that's our description. We've got a product vendor, that's one thing we forgot inside our pin that we could add. We've got the product status. So, let's go in and just do something really simple. We'll do products. Okay. So there's our product. Then we're gonna have a title for the product, what's that gonna be? Let's take a step back here. I'm just gonna save this. I need to see what actual data is coming from this call here before we get carried away. Create a new product. Can you create is there an endpoint for creating multiple products? That's what I'm interested in. Create a new product. Retrieve a list of products. Looks like we're just going to be able to create a single product at a time. So we may have to break this apart and do 2 separate flows. Imagine that I had another product. We'll call it test product. No. Let's not do that. Let's call it a hoodie. So it is hoodie 123. Let's go to unsplash maybe. Do a hoodie. And this looks like a hoodie with a statement so we'll just copy paste that. Okay. We'll import the file from the URL. Boom. We got a featured image. Put a description. Great. Alright. So now we get 2 products. We're just gonna select these products. We're gonna send the well, what happens if we don't select? We could still run this flow. We'll We'll take a look. We'll send these products down to our flow. And let's just look at the log. Right? So now I'm getting an array of products. Okay. And maybe we wanna do we're gonna break this apart because we're gonna have to send a single product to Shopify. But before we do that, I want to format products. Right? So we'll take our product. We'll run the format function. So Directus allows you to run arbitrary JavaScript inside the flows, which is really nice for stuff like this. We've got the data, so our products are gonna be constant products equals data dot read products. I think that's gonna be it. And then we have some formatting logic here. Right. Just thinking out loud. So now I'm just gonna save this really quick. And one of the other things that I like to do when I'm working with these flows is just copy the data I'm getting back right here, and I'm gonna put it inside my code editor. I've got a starter project here just in case we needed it. So now I've got the data structure. And if I look at Shopify gosh. We've got, like, 35 different tabs going on. That's the fun of doing these live is actually trying to figure out how to all how to keep it all sane on one screen. Admin, API. Alright. So we go to the admin API, Rest admin API. So we go to our products. What things do we need to pass to our products at a most at a minimum just to actually create these products. So here we go. Here's a new draft product type of setup. Creates a new draft product. And can we do let's just make this even more complicated. Right? We'll do like a split screen? Somebody still needs to teach me to properly arc. Alright. So I've got Directus, got our product. Okay. We got like 30 things going on here. A lot of fun. Alright. So, let's run a format script. We've got our data over here that is coming from our Directus database. Let's go in and add our logic here. We got products. Let's do Shopify products dot map. Oh, no. Shopify products equals products dot map product. Yeah. Products. And then we'll return something. We got our function there. And then at the end of this, we're gonna return Shopify products. So we're just gonna return the array of products we want from Shopify. Alright. So we got the products and then we're gonna return a title for the product which is gonna be the product dot what name. We need a comma there. Then we've got the body_html, that's gonna be product dot description. We've got product dot vendor. We don't have that product dot type. We could fetch that, and then for now, let's just try this and see how far that actually gets us. Product dot name. We're gonna return our Shopify products. Let's go back in and I'll just make this full screen. Do products. I will look for our sidebar. Where are you sidebar? There you are, mister sidebar. We'll send the products to Shopify. Let's just test our flow and see what came out the other side. Right. So this looks good. This is our format that we're gonna send to Shopify. I'm gonna take just my my headers that I've got here. Let's just stuff those in this, take my URL that we so carefully constructed. And, I'm just gonna leave that. We're gonna create a new flow. So we're gonna go in and create Shopify product and this will be triggered from that other flow, so triggered by another flow. We'll leave the response body blank and here we're just gonna do the HTTP request that we just did. So I'm gonna paste in that. I've got my headers. Still stuck on the clipboard somewhere. I'll edit raw value and now we've got that. And here, I think it is just going to be something like this where we do actually, hit save. We'll just disconnect this for now. Hit save. Go back to our other flow and trigger that flow, and we'll just see what trigger Shopify, create. And Directus will create a key for you, you can also rename this key, but this key, all the data that gets generated in this operation gets appended to the flow underneath this specific key. Let's just take a quick peek at time. How are we doing? We've got 20 minutes left. Great. And then we're just gonna trigger this flow. So we'll select the Shopify flow. It's okay to create these in parallel and then for our payload, we're gonna do simply this. We've got the double curly brackets. We've got the data. No. What's the name of that operation? Format product. Is that what the key is? We're just gonna use this key. So we're gonna trigger the flow and the data from that, format product, we're gonna pass that to that other flow. Alright. So fingers crossed, stay with me here, we are building cool stuff. So now we're gonna send these products to Shopify, running that flow. And we can see here we triggered this. We got back null null, this is what we sent to that. And if we go to our other flow, where we're actually creating, we can see that this ran twice, which is the what we actually want. We wanna see the the payload that we got was the product. So we're just gonna pick up the trigger. Payload and that's what we're gonna send. Great. Amazing. So, in our request body here, we're gonna do something like this where we've got, quotations, double curly bracket, trigger dot payload and trigger is, prefixed with a dollar sign just because it's a unique, special key to get the trigger data. And fingers crossed. Right? Can we send this data to Shopify? Can we build a working PIM in an hour? Let's test it out. We're gonna run I I don't think it matters if I select these or not. Let's go in. We'll see. We have our send products to Shopify. That ran. Hopefully triggered this other flow that we have. We got a Shopify product. Bad request. By accessing the Shopify, you agree to parameters are missing or invalid. Okay. So we're missing some parameters. Let's take a look at the actual data that we sent. Payload, got the product resource, body HTML, supports HTML formatting, product status dot draft, Shopify access token. It looks like the body is not defined. So, I think I goofed up and that and maybe we're just sending the trigger. Let's take a look. Did I get it wrong? Do we just need to send the trigger? So, let's make this more fun. Right. Let's go to our Shopify store. Alright. We're gonna close this guy. Alright. I'm on the products. I'm gonna hit refresh. Dun dun duh. I don't I don't see any products in here. Alright. So, let's go back. Obviously, I goofed somewhere again. And we're definitely in danger of running out of time here. We got a bad request, missing parameters. Okay. So there's the body, product title, product dot HTML. What are we missing? 100 apps, 100 hours. We've got our access token. We've got our content type. Required parameter. Did I spell something wrong? This is always the danger of working against the clock like this. What is this issue going to be? Request body is product. Alright. So product title body HTML. I don't see where this is going wrong. Shopify access token. Bad request. Let's take a look at our app. Do we have the wrong scopes or something? Let's just try one quick tweak. Go back to our send products for our format product. Let's just leave out the title. Right. So we'll just comment this out. Return Shopify products. Send products to Shopify, flows, bad request, product underscore. Is it the way that it is actually sending this data? Sometimes if we do something like this, where we got trigger. Boom boom, Ron. Created. Boom. Alright. So dramatic reveal time. Dun dun dun. We have sent this data to Shopify. Did we? Where do we go? Okay. So we've got a hoodie, we've got a shirt. There they are. Boom. Now they are in our system. That is how it's done, ladies and gentlemen. Where where we at on time? We got 13 minutes left. We have managed to sync product data. Let's let's just take this one step further if we can, and let's create a product with let's at least get the images over there. Right? So on our product resource, we've got a list of product images, create a new product image, create a product image using a source URL that will be downloaded to Shopify. Okay. So it looks like you can you may be able to pass these, and I'll just go in and very briefly let's just delete these products. Delete these products. Can't be undone. Those are deleted. Those are no longer in the system. Let's adjust our payload here. So we're gonna go back to the formatting option. We'll comment that back in. We can have that now and then on the Shopify side, we've got okay. So we got a product dot images. This will be images and then we've got an array And then what? Array of objects, and then we just got a source attribute. So the source if we look at our code again, Right. We've got our featured image. Let's just pluck that for now And, we're gonna do something like this where we've got the source is gonna be, HTTPS direct us app slash assets slash what was the name of that? Featured underscore image. Featured. Oop. Gotta do our little squirrellys featured image. Close that off. Should be good. The last piece of the puzzle for this is going to be enabling permissions for the actual files inside Directus as well, Because Shopify needs to access that. So 2 ways I could do it. I can add an access token, when I'm sending that call to Shopify Or in this case, I'm just gonna go in and we'll keep this really simple. Under direct as files, we'll do all access. And we're just going to send and cross our fingers hoping that this flow actually works. We could do the dramatic reveal inside Shopify. Right? I broke something trying to send the images over. Where did you go? Where did you go? Alright. What broke? Right? Clearly, we broke something. Great Shopify product. What do we send? Product title hoodie. Do we actually break something inside the I don't see my logs. 4 to 5 minutes ago, that's not correct. Run Shopify flows. Go into our flow. Less than a minute ago, featured image is not defined. Okay. So we did not have one with a featured image, and that broke. What did I do wrong? Again, always fun on the clock. I guess you have to actually use product dot featured image. Right? Featured image. That would make a ton of sense. Too much sense actually. Alright. Last time. Send products to Shopify. Did it work? Let's test our flow. Create Shopify product. Created. Good looking. Alright. So if we open up Shopify, we go to our products, we can see our hoodie. Here is our actual data, so there's the image that we've got, there's the shirt that we loaded up, and this is all coming from our pin, our back end, our single source of truth. So that is how it's done, ladies and gentlemen. We are syncing with Shopify. Last on our list, can we get this in 8 minutes? I have no idea. We've got a simple product catalog. Now, I don't think we could do this in 8 minutes, but let's give it a shot, right? We want to display all of our products on the front end of our website, so people can browse those. So because there are no rules, I am a huge fan of Tailwind UI and Tailwind CSS in general. They've got some great looking ecommerce components that we may just repurpose for this, like, here's a nice product list. And I've got a Nuxt starter application sitting here just ready to be used. So let's see if we can actually get this product information displayed and rendering on a product catalog in just a couple minutes. Alright, so we're gonna just forget Shopify for now. Let's simplify. Alright. This looks pretty good. This is a with border grid. We've got a view component here. We've got our products. Let's just copy this template. Alright, so we're gonna go to our index page. We probably got some script action that we'll do there. For now, I'm gonna if we just copy their products. Right. Constant products. Not gonna look great, and they've got an icon. So we'll do the star. Let's just omit the products ratings for now. Right? Just comment those up. Run this. If I look at local host, say now we've got got something cooking here. K. And now, let's actually fetch these products from Directus. We wanna do this live. Right? Going to do what, we got our products. Is it equal to await use directus? I've got a just a composable here to fetch this data using the rectus SDK. We're going to read items and that's gonna be products. And is this actually gonna be all we really need? Usually, when I am developing, I often tend to do stuff like this where I will just render out the data inside the browser just to make sure we're seeing we're getting the stuff that we need. Item/products failed. Oh, wait. Use direct us, read items, products. Why is this not working correctly? Well, that would be because I didn't actually set up the directus URL properly. We are at 5 minutes, so I'm gonna load my PIM URL here. That is my Directus base URL. The server token, probably not even needed in this setup, but, we'll just reload. Let's fire back up the dev server. Let's see if we can get this data rendering from Directus. Boom. So there's our actual data. Right? We no longer need this. Let's uncomment. And just looking at this data, we've got the v4productandproducts. We wanna render the product dot id, we'll have that information. The image source, because we can use Directus as an image provider it will transform assets for you. I'm just going to use the NUTS image tag which is already set up in this. We will use product dot featured image. We'll just leave alt text blank for now because there is there's none. We haven't got that set up. We got product dothref. We don't have one of those currently, but we could do slash products slash what? Product dot ID. That would be the probably the URL. We got a product dot name, we got a product dot price. What else? I wanna fix the template structure here and then maybe we want to render out, div. We'll do like a tailwindpros class and give this v html. We'll render the product description. Input must be a string received undefined. So if there let's make sure oh, forgot the item there. Boom. So there we have it. We have got our items rendered out. You know, we could continue fleshing this out with the help of Tailwind into a catalog. But now we have done several things, Right? Let's recap with our final two and a half minutes. We have created a PIM system that can store and manage all of our product data, store all of our product assets, control all of our product variations, we can sync that data with Shopify, and we can have a simple catalog. But, Bryant, you forgot one thing, our translations for this data. What if I want to have it in French or German or, Canadian? Bad bad joke for all all of our Canadian followers. Apologies there. So with a minute 30, how can we set up, translations for this? Right? How can I control other languages? So I'm gonna go into our products. The first thing I'm gonna do is just create a languages collection. We've got a language code. Alright. We've got a title or name of the language. Alright. And let's create 2 languages. We have EN, US, English. Are we gonna do it 55 seconds? Let's just say fr for fridge. I think that's the code. No idea. We'll go back to our product. Let's go in and there's a special translations interface within Directus. Clock is ticking, Brian. So we got the code, we got the direction field, that would probably be right to left. Use current language, we'll save this And then inside our product translations, we would go in and do, name, We would do a description. And where we at on time? 10 seconds. Brian, can you do it? 10, 9, 8, 7. How do we add translations for this? We can do English name. So close. We hit the 60 seconds. I'm just going to finish this or the 60 minutes. We can do the French translation here and save it. So, with Directus we can manage all of these different things. We hit time there, that felt really good though. We can manage translations or multilingual content. Alright. So that was a fun one. Multilingual content. Really enjoyed this one. Building a PIM in 60 minutes or less. There's obviously more to this, but my next steps would be to flesh out that sync engine a little bit, so that if we made updates inside Directus, I could send those over to Shopify. I could even use the Shopify webhooks in this case, if I made a change to that information in Shopify to sync it back into Directus. I would also probably go in and flesh out the translations interface just a little bit more. So that I have all the necessary fields. You know, and I may even have specific fields that we want to add to Shopify versus something like Amazon. So we could go in and do, you know, some more different some different channels that we wanna send this data to. But for 60 minutes, amazing to see what you can actually build with Directus. Stay tuned for the next episode. I'm sure we'll have a good one and you'll get to see me hack and slash and burn my way through another product.","published",[139],{"people_id":140},{"id":141,"first_name":142,"last_name":143,"avatar":144,"bio":145,"links":146},"791e1503-1d88-463d-9347-0b9192933576","Bryant","Gillespie","9013afc8-e8d7-4182-9b18-44db08117bb9","Developer Advocate at Directus",[147,149],{"url":131,"service":148},"website",{"service":150,"url":151},"github","https://github.com/bryantgillespie",[],{"id":154,"number":155,"year":156,"episodes":157,"show":168},"56dda5ff-2c3a-41ce-ae3a-580d6101026b",1,"2023",[158,159,160,161,122,162,163,164,165,166,167],"cb4e067f-9507-4e18-ab9a-435565f9e653","8434838a-8e4f-489a-8da2-fbf10de5de6a","c997b25e-400c-4350-bba4-f63853d844f7","1109be0d-8ab5-479b-a052-8ad30d9ffb1c","0aae287d-3916-4d91-9310-25828998e562","8fed0eee-43f6-4767-8b77-79da7a059821","a311b57d-34e7-4073-9cf3-6a8c6c0f8b85","9271a4fc-addf-4400-9591-f2f2ec07bd79","30c48566-52cd-4728-a633-ca9675acc959","c067c012-5fe1-4d70-8946-8bfc8e1b22c0",{"title":169,"tile":170},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"id":172,"slug":173,"season":174,"vimeo_id":175,"description":176,"tile":177,"length":178,"resources":8,"people":8,"episode_number":155,"published":179,"title":180,"video_transcript_html":181,"video_transcript_text":182,"content":8,"seo":183,"status":137,"episode_people":184,"recommendations":186},"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","crm","14fda5f2-95de-4dbe-a4e2-3fd956c21c19","936325383","In this intense one-hour challenge, watch as Bryant incredibly builds a full-featured custom CRM from the ground up using Directus. He builds contacts, organizations, deal pipelines, activities, and more.","f6b880a0-5cd2-45ad-beff-3117f0a78581",56,"2024-04-19","Mission: Customer Relationship Manager","\u003Cp>Speaker 0: Hi. Welcome back to another episode of 100 apps. 100 hours where we build some of your favorite apps or try to rebuild some of your favorite apps in 1 hour or less, or get publicly humiliated trying. Alright. Super excited to be back for another season.\u003C/p>\u003Cp>If you are new to 100 apps 100 hours, there are 2 basic rules. Number 1, you have 60 minutes to build and plan and build, no more, no less. So when that clock strikes 0, that's it. And then the second rule is there are no other rules. Use whatever you have at your disposal to complete the functionality.\u003C/p>\u003Cp>That's it. Let's dive into this episode. So today, I've got a custom CRM. Tools like Salesforce, Pipedrive, HubSpot, they really need no introduction. Everybody needs a CRM.\u003C/p>\u003Cp>A lot of them, depending on your purposes, may be way too much for what you need or they may not be specific enough for your industry. So we are going to build our own custom CRM in 1 hour or less. Let's do it baby. Alright. So, let's start the clock and away we go.\u003C/p>\u003Cp>So, when we plan our CRM, what do we need out of a CRM? Right? What kind of functionality do we want to see inside our CRM? So basically, we want to manage all of our contacts. We want to manage all the different organizations those contacts belong to.\u003C/p>\u003Cp>What else? We wanna manage our sales pipeline. Manage sales pipeline. That's gonna be deals or activities. We want to be able to track activities and follow-up.\u003C/p>\u003Cp>So this seems like a a pretty good set of functionality for a basic CRM. I'm sure we might embellish this a little bit depending on how far we get, but let's dive into actually fleshing out what the data model just might look like for something like this. So we'll drag a nice square up here. We're gonna have contacts. We could just call those people.\u003C/p>\u003Cp>I'm a big fan of that. We're gonna have organizations. There's definitely gonna be a relationship between those 2, but I I feel like this could be a many to many relationship because one contact could belong to multiple organizations, and and some CRMs make that a little more difficult to do than others. What else do we have? We're gonna have deals or opportunities.\u003C/p>\u003Cp>Deals is kind of a a standard nomenclature. We're gonna have activities that are attached to what, those are attached to organizations, they're attached to contacts, they're probably also gonna be attached to deals. What else are we going to have? We're probably going to have some sales reps, those are going to be our users inside our accounts. This looks pretty good for a a base set of functionality.\u003C/p>\u003Cp>Let's dive in and actually start building something. So, I'm just gonna pull up my Directus instance, that's what we're using on the back end. Totally blank instance, we'll zoom in on this, and let's start building. Right? Let's create our first collection.\u003C/p>\u003Cp>Let's start with contacts. So we're gonna give this a contacts as the name. For the primary key, let's use generated UUID. What kind of fields do we wanna add for our contacts? Right?\u003C/p>\u003Cp>When we think of contacts, we're gonna need name, email, but we may also wanna track when this was updated, when it was created. Directus makes that super easy. And one tip that you may not have realized if you've used Directus before, is I can go in and change these to be whatever I want. So if I wanted this to be created at and created by, I could go in and update those, change them to be whatever I want. Updated at, updated by.\u003C/p>\u003Cp>That's the the nomenclature that you're used to. Do we actually need a sort for our contacts? I don't think. Let's just go ahead and save this. Alright.\u003C/p>\u003Cp>So for our contacts, we're gonna add a first name. Great. We'll make that a string. Let's do half width for that. I can go in and click the three little dots here and just quickly duplicate this field to save myself some time.\u003C/p>\u003Cp>And, of course, as I am plotting away on this, Directus underneath is mirroring all these changes to my database schema. So we've got our first name, we've got our last name, we're gonna need a email or an email. Great. Let's require this value. And, you know, I could even go as far as, like, making this unique if I I wanted to have some type of unique identifier to match these up with.\u003C/p>\u003Cp>Alright. As far as an interface, you know, I could get fancy with this and we could add a little email icon like an at symbol. That's great. I think everything else is good. You know, one of the other nice things that's built into Directus is I can go in and add my own custom reg x validation.\u003C/p>\u003Cp>So, you know, I can put in some type of reg x that matches email addresses. I don't have one of those handy here, but we'll go ahead and keep marching along here. So we got first name, we got last name, we got email. We'll probably have a phone number at some point, but let's let's go for, like, a job title, probably. I'm trying to think of all the standard fields.\u003C/p>\u003Cp>Maybe we have some notes. We could set that to be a text area field. I don't really need formatting for those. Great. Job title.\u003C/p>\u003Cp>And maybe honestly, let let's strip this out because I'm I'm thinking about that many to many relationship with organizations, and we may have, different titles at different organizations. So I'll show you how we can handle that coming up. Alright. So we got first name, we got email. We could go ahead and add phone number as a string.\u003C/p>\u003Cp>Phone or phone number. Let's just keep it short. We'll say phone. Let's give it a phone icon. Great.\u003C/p>\u003Cp>K. Phone, email. We'll put email above phone. Cool. Looks nice.\u003C/p>\u003Cp>Alright. Let's just take a look. Right? We've got first name. Go ahead.\u003C/p>\u003Cp>Oh, I was gonna add my wife here, but she's gonna get mad at me if I misspell it. Right? Ashley atexample.com. I had a phone number, 555-5555. Here's some nice notes for our contact.\u003C/p>\u003Cp>These are looking great. Right? We've got our contact in here. I could potentially sort and filter this a a hundred different ways, maybe change the layout. But let's dive into the next collection that we wanna set up, our organizations.\u003C/p>\u003Cp>So we'll have organizations, again, for the primary key, I'm gonna use ID and just use generated UUID as the type. And, again, I could change created at I could change this structure for these system fields to be whatever I want. Created by, date updated, that's gonna be updated at, if I can actually spell. And we use this one as well and call it updated by. Great.\u003C/p>\u003Cp>Okay. So now we've got an organization. For our organization, what are we gonna have? We're gonna have a name of the organization. Right?\u003C/p>\u003Cp>Probably, some addresses for that organization. Right? We could have multiple. So that's where we might reach into an another bag of just a different table. What else are we gonna have for an organization?\u003C/p>\u003Cp>You know, let's just do something like country in case we want to, even, like, automatically route new organizations to a specific sales rep. Potentially, we've got a name. What are we gonna have? We're gonna have a website. You know, we may have a a logo if we wanna track that for a company.\u003C/p>\u003Cp>So let's do an image file. Let's call it logo. You know, you might even add things like brand color, things like that if if you're really in tracking that. And then we'll just add another section for notes. This is gonna be a text area field with the type text, and we'll hit save.\u003C/p>\u003Cp>So we've got our organizations. We've got our contacts. Let's make a link between the 2. Right? So how do we go about that?\u003C/p>\u003Cp>Depends on how you want the data to actually be structured. Right? In an application, depending on the setup, maybe a user can only belong to one specific organization. But often inside of CRM and maybe I am working with the local little league. I am a member of the board there, and then I'm also a developer advocate here at Directus or, you know, a a founder at Better Side Shop.\u003C/p>\u003Cp>So a lot of different relationships that is gonna be modeled with the mini to mini relationship inside Directus. Here's how we set that up. It's gonna be pretty easy. We'll just go to create field. We'll look for a many to many relationship.\u003C/p>\u003Cp>And because we're on organizations, our key here is gonna be contact for the or contacts, I should say. Our related collection is gonna be contacts and then we'll just go through and paint by numbers here. Do we want to show these in a list or a table? I'm good either way. We definitely want to show a link to that item so we can get to that specific contact, but I'm gonna pop open this advanced field creation mode just to show you that you do have control over the naming of the junction collection and the individual fields within that.\u003C/p>\u003Cp>So, the default setup here is just going to take this table name and this table name or this collection name and this collection name and marry the 2 together and we end up with organizations underscore contacts. Works for me. We'll leave that autofill set up. And then on the reverse, I'm gonna add that many to many field to the contacts as well. We're gonna keep that as the organizations.\u003C/p>\u003Cp>Alright? In this case, we may have a sort field. So we wanna control the the sorting for contacts, like who's 1st, 2nd, 3rd, that could be helpful in, like, a primary contact situation. And then we have some of our relational triggers. Right?\u003C/p>\u003Cp>On deselect of organizations' contacts, what do we want to do here? Yeah. Maybe I want to delete that association. So I'll set these to cascade. Great.\u003C/p>\u003Cp>So now that back out, we can see we've got our contacts field here. If we go into, yep, looks like we've got, an extra contact table created. I might have typed something wrong. But, so we can see contacts there in the organizations. And then on our contact, I should see organizations here.\u003C/p>\u003Cp>Let's just add an organization. Right? So let's say my wife worked at Tesla. That's in the United States. And, you know, we could add the website, logo, notes.\u003C/p>\u003Cp>I could choose existing contacts. But I'm just going to go ahead and say 1. Right? So now, I could see I've got that organization here, but it's showing an ID, which is not super helpful. Right?\u003C/p>\u003Cp>So I can go into the organization itself and control the display template where I have a name. I could even potentially add the logo to that if I wanted. So now if we check it again, we can see, okay, here's the organizations that they're a part of. Right? Now, you also have the ability to manage the data inside that junction collection.\u003C/p>\u003Cp>Right? So if I go in and and like I said, I may have a different job title at all these different organizations. So I could go in and add a new field here inside this junction collection for job title. Great. Alright.\u003C/p>\u003Cp>So, now if we go into Tesla, we can see the job title is down here at the bottom, but Directus also gives me an easy way to control where that displays. So if I go to our many to many relationship inside our contacts, I can go to maybe we wanna add a sort field for this just to make sure. And then if I go to the interface, I can control where my junction fields are located. So I can put this at the top, which should be great. Okay.\u003C/p>\u003Cp>So now if we just check that out one more time. Right. Now I could see director of DirectUs. So now I could give a specific job title to this specific person within that organization. I could go in and add a new organization as well, like, hey, the little league.\u003C/p>\u003Cp>We can say a board member. Right? Little League Baseball is the name of the organization. Great. Okay.\u003C/p>\u003Cp>So now there my wife is a part of 2 organizations, different job titles for each. Right? Alright. How are we looking on time? We're looking great.\u003C/p>\u003Cp>Let's move on to our next collection that we want to set up. That's gonna be what? Our deals, our activities, what do we want to set up next? Let's go for deals or opportunities. Deals is probably the standard naming for this.\u003C/p>\u003Cp>So that's what we'll stick with. Could be opportunities. Could be something else. That's fine. We'll do created at.\u003C/p>\u003Cp>Just to keep the same structure, we'll do created by. And by adding these, whenever this record is created, it's going to populate a timestamp and a user. The same thing for Updated, whenever it gets updated or who updates it, it's gonna populate that info for us. Updated by. Great.\u003C/p>\u003Cp>Okay. So our deals what we're gonna have for our deal? We'll probably have a name or a title of the deal. Deal name sounds great. It could be good to prefix some of these sometimes if you're dealing with a lot of nested relational data.\u003C/p>\u003Cp>So you get used to seeing name in there a 100 times. Could be confusing whether that's the actual deal name or the organization name or the contact name. So we'll just save the deal name. What else are we gonna have on this deal? Right?\u003C/p>\u003Cp>We'll probably have a average dollar amount or a potential dollar amount. Let's set that to be, decimal. Deal value. We can add a nice little dollar sign to the icon to make this look nice and pretty. And then we are going to have some related fields.\u003C/p>\u003Cp>But before we do that, let's just add, like, some deal notes or something like that. Sounds great. Okay. So we got a deal name, we got a deal value, you know, maybe these are side by side, not a big deal. Let's go in, and now we want to add a relationship to the organization and probably to our primary contact for this deal.\u003C/p>\u003Cp>So those are both gonna be many to one relationships. And we'll set a key of organization here. So the related collection, we'll set that to be organizations. Great. And I could control the display template here.\u003C/p>\u003Cp>We'll just use the name and we hit save. So now we've got an organization that we're gonna tie to the deal. I'll make that half width. And then we also want to add a primary contact. So we'll just say primary contact.\u003C/p>\u003Cp>The related collection here is going to be the Contacts collection. And for the Display template, let's use first name, last name, and let's use this format, which I think is typically how it displays inside an email client where you have the email address inside these less than or greater than SQL or symbols. So let's save that. We've got our primary contact. We've got our notes.\u003C/p>\u003Cp>We've got our deal. What do we need next? Right? We need to track where that deal is at. So we could use, like, a a status field as, like, a drop down for that potentially.\u003C/p>\u003Cp>Right? What's the status of this particular deal? Blah blah blah. Or we could do something else where we have a relationship to a deal stage or our individual pipeline. Right?\u003C/p>\u003Cp>Because then we may want to, potentially have separate pipelines or we we wanna give more control back to our users, so the the sales reps or the sales manager to control that actual pipeline without getting into the admin section. So how can we do that? Right? Looks like I've got a little extra couple of fields that I've been creating here inadvertently. Alright, so now let's add a new collection.\u003C/p>\u003Cp>Let's just call it Deal Stages. We want to manage what are the stages of a particular deal. As far as the optional fields, maybe I don't really not super concerned with these, deal stages. We're gonna call this the name of this particular stage. Maybe we wanna give it a color so we can add some color to it.\u003C/p>\u003Cp>And, you know, I guess we could even give it an icon if we wanted to. We could play around with that and see what that looks like. Deal stages. Let's make both of those half width. And, now, let's just go through and map some of these particular stages.\u003C/p>\u003Cp>Right? And I and I, you know, I've got this s on here. One of the other things that you could do inside Directus, you can't control the translations for all of these. So even if you're working in English, on the the back end, you may want to use prefix tables to keep things nice and organized as a developer. This is a great way to control what displays via the interface to your actual users within the application.\u003C/p>\u003Cp>So I could just call this deal stages or pipeline stages, whatever makes a lot of sense here, and hit save. Alright. So it still shows me deals_stages here, but when I start looking inside the actual app, there we go, we can see our pipeline stages. So, let's create our first one. Right?\u003C/p>\u003Cp>Let's just say, New, this is inbound. Do we have a symbol? We do have a symbol for this. Right? So let's say red.\u003C/p>\u003Cp>This is we need to take action on this. What's next? Right? Qualified, maybe? Contacted?\u003C/p>\u003Cp>Or, let's say assigned. Right? We forgot to assign those to a sales rep. Assign assignment. That's a nice looking icon for that.\u003C/p>\u003Cp>Then we'll say qualified. You know, if we're doing software sales, like, a check mark. Okay. What else? Demo.\u003C/p>\u003Cp>Let's save green. Oh, let's make this orange. Do we have like a demo? Maybe a TV? What have we got?\u003C/p>\u003Cp>Yeah, there we go. That works for a demo as far as the icons. And then we are going to set, like, a last stage, like, proposal. Great. Let's just go gray.\u003C/p>\u003Cp>Cool. Document. Awesome. Alright. So I can control the way that these are displayed through my actual settings, here.\u003C/p>\u003Cp>So I can go into each specific one. And on the display tab, let's display a colored dot for the color. And then for the icon, we're just gonna display the actual icon. Alright. Great.\u003C/p>\u003Cp>If we take a look now, I can see what these actually look like. Cool. And then for my display template for the deal stages, I may even go in and just mirror that same structure where I have a color, then I have an icon with a name. Okay. So, now, what we've done, we've effectively given control over these, and we probably actually need a sort on these as well, so so we can control that value.\u003C/p>\u003Cp>So, let's go in and quickly add a sort. That's going to be an integer value. And because this is coming after the fact, after we've created this actual table, I'm just gonna scroll down a little bit, and I will have a sort field here. So we'll choose sort just to make sure we manage that. And now I should be able to drag and drop these, in whatever order that I like.\u003C/p>\u003Cp>Again, we're giving control back to the users of our custom CRM. We're definitely gonna prevent them from getting inside the admin so they don't mess with the data model. But here, we give them the ability to control what those pipeline stages are gonna be. So now we need to add those to our actual deals. Right?\u003C/p>\u003Cp>So we're gonna go into our deals. We're going to create a many to one relationship. And, you know, this is going to be the deal stage or just stage. And we're gonna use deals underscore stages as the related collection. We can open this up and and just see what's going on.\u003C/p>\u003Cp>I I don't really need to create that inverse relationship. I could. Doesn't make a ton of sense, though. I'm not sure. Alright.\u003C/p>\u003Cp>Display related values, validation. Let's keep it very simple, and then we're gonna use our deal stage. Alright. So now if I go into our deals Why is this oh, it goes in the machine. I don't know what's going on on this particular example, but it keeps creating additional fields that we don't need.\u003C/p>\u003Cp>Alright. So let's create a new deal. Alright. This is gonna be a new deal. We can see, hey, there's our stage.\u003C/p>\u003Cp>That looks great. The deal name is 100 apps, 100 hours contract. It's worth $1,000,000. So we'll add that. How many zeros do we have there?\u003C/p>\u003Cp>1, 2, 3, 4, 5, 6. Alright. Let's make this for the Little League team. Great. My wife is the primary contact.\u003C/p>\u003Cp>Here's some notes on this deal. Great. Did we save it? Numeric value and deals is out of range. Uh-oh.\u003C/p>\u003Cp>Wouldn't be a 100 apps, a 100 hours without, some type of issue. Right? So let's take a look at this. The deal value, what did I set? Precision and scale.\u003C/p>\u003Cp>Maybe we bumped this up way up. Probably could have gone with integers for this as well. Do I have a minimum and a maximum? I I don't have that set. So not entirely sure what's going on.\u003C/p>\u003Cp>Let's just try this again. 100 apps, 100 hours contract, 1 100,000, 1,000,000. Try it again. Save. Okay.\u003C/p>\u003Cp>Alright. So now, we've got this particular deal set up. This is kind of an underwhelming view. Right? And, again, I might control how these things display.\u003C/p>\u003Cp>So if I just go into our deals, we go to the deal stage. If I wanna control how it displays, I can go in and set this. So let's do the color, do the icon, do the name, just because, I'm a I'm a very visual person, and I suspect, a lot of your end users may be as well. We still don't see the organization there. So, again, we'll go back.\u003C/p>\u003Cp>We'll adjust our organization. We wanna display related values. We want to show the name of that specific organization. And let's get even fancier and maybe we wanna show that logo. I'm gonna click on this and then you'll see this one that has a little magic beside it that's a thumbnail.\u003C/p>\u003Cp>That's just a shortcut for, like, a nice thumbnail sized image instead of loading the actual image size. So I donit have a logo for this company, but I could quickly find 1. Alright. So, we'll just copy this image address, go back to our instance, let's load up that specific company, they who shall not be named, Hit import. Oh, that's a data URL.\u003C/p>\u003Cp>Let's find an actual URL that we can just copy. Great. Let's try this again. We can import via URL. Cool.\u003C/p>\u003Cp>And now when I'm looking at my deals, I can see the logo of that organization. That makes it really handy, when I'm working on a deal just to have that extra extra visual reinforcement. Now, when I'm looking at deals, I may want to set up like a traditional pipeline type of view. So I could do that really easily just by switching the layout here inside Directus. So we'll change this to a Kanban layout.\u003C/p>\u003Cp>Let's group by the deal stage and the group title is gonna be the name. So I can see new assigned qualified demo. We have that Kanban view that we're used to. As far as the text, what do we wanna look at? We want to control, let's say, when this was created.\u003C/p>\u003Cp>I'm not really concerned about that. The tags, what do we want to set that to be? Do we want a card image? Honestly, this looks okay. Do we need an actual user on here?\u003C/p>\u003Cp>Probably not. Cool. Alright. We'll just keep it as is and let's let's start fleshing this out a little more as well. Right?\u003C/p>\u003Cp>So within a deal, when I'm working this deal, we're going to have activities assigned to this as it moves across the pipeline. And, for that, let's just go in and add a new table, a new collection, we're going to call it activities. Activities, I think that's the correct spelling. We use generated UID, we'll do created at, createdby, create up, updatedat, updated by. Great.\u003C/p>\u003Cp>And, in this case, maybe we do give a status for this particular activity. Right. Status, great. Let's give a name of the activity, probably a type of activity. Makes sense.\u003C/p>\u003Cp>We use a drop down for that. It seems pretty straightforward or, you know, maybe they're not gonna need to change the activity types on a a very frequent basis. So we'll call it the activity type, and let's add a couple choices to this. So this is a relatively new addition to Directus within the drop down, the ability to have an icon and a color. So let's say text is a phone call, value is a phone.\u003C/p>\u003Cp>One of the other cool things if you want to translate this value, you could use this phone t, or this dollar sign t for a translatable string. And then anytime you have users who are using the app within a different language, inside the settings, you can control all those custom translations. We'll take a look at that in a moment. Let's call this what? Phone call.\u003C/p>\u003Cp>The value could just be phone call, depending on how I wanna store this. Right? It may have an underscore within it as well. It could just be the same thing. Either way.\u003C/p>\u003Cp>Look for that phone. Great. And I could even add a color for that if needed. Maybe I just wanna keep these all the same. Phone call, we'll call it a meeting.\u003C/p>\u003Cp>And for this, do we have a meeting icon? Right? We calendar looks nice. Maybe we want to track demos separate from meetings. Alright.\u003C/p>\u003Cp>So we drag a demo. That was the TV icon that we had. Great. What else are we gonna need? Like, an email?\u003C/p>\u003Cp>Not sure I would Yeah. Maybe, like, a follow-up email. We wanna reschedule that. And then we'll do an email. Great.\u003C/p>\u003Cp>Demo, phone call. I think I'm gonna set this to be underscore value. Cool. Alright. Looking good.\u003C/p>\u003Cp>Activity type. Okay. What else are we going to need for this? We need a due date for this activity and the status, we can use as as is. It's published draft, archived.\u003C/p>\u003Cp>What do I really care about this activity? It probably isn't completed or not. Right? So maybe we scrap status, and maybe we just go for a toggle instead. Right?\u003C/p>\u003Cp>Hey. Is this completed? The default value, great. For our label, maybe we change it to completed and give it a color of color on. Color off is red, just to show that it has not been completed.\u003C/p>\u003Cp>Great. And let's just clean up this form a little bit, making sure everything looks nice. Okay. We are going to add a date for this, and let's set a specific date and time that this thing occurred. Call it due date, end date.\u003C/p>\u003Cp>Due date seems reasonable, but when do we actually need to complete this specific task? Okay. So now we've got an activity. We want to, link this to our actual deal so we can track those. Right?\u003C/p>\u003Cp>So, what I'm gonna do in this case is create a mini to one relationship because this activity could only belong to a single deal. Right? We're gonna use the key of deal. The related collection, we'll set that to deals. We'll show the name of that deal.\u003C/p>\u003Cp>Maybe we show the actual organization as well so I can actually dig into the related collections and and show values from that? Excuse me one second. Sorry about that. Cool. Alright.\u003C/p>\u003Cp>So now, what I forgot to do is create that inverse relationship. You can actually set that up via direct us when you're creating that relationship. But now I can also just go into our deals, not deal. We'll go into our deals, and now we're gonna create a one to many relationship back to those activities. So we'll call this key of activities.\u003C/p>\u003Cp>We've got activities. The foreign key will be deal. That already exists. Maybe we wanna show these in a table. We'll choose the columns, due date, Name, Due Date, Activity Type.\u003C/p>\u003Cp>Seems pretty savvy. And I can even filter these, right? So if I wanted to see just activities where they were not completed. We can enable search and filtering and show a link to these. We'll take a look at what all these look like in just a moment.\u003C/p>\u003Cp>But we've forgotten one important step through this whole process is, hey. We need somebody to assign these deals to. Right? So, let's add a many to one relationship. We'll call that the deal owner or, you know, you potentially say who this is assigned to.\u003C/p>\u003Cp>Like, the deal owners, again, kind of standard naming in these scenarios. And for the related collection here, we're gonna use directus underscore users. So these are gonna be actual users of the application that we're assigning this to. Invalid payload collections can't start with direct us users. Oh, deal owner.\u003C/p>\u003Cp>Let's go to our related collection, and let's get direct us underscore users. And, in this case, we're gonna show the first name, last name. We may back up and do an avatar as well. So just the the thumbnail, the avatar, I could move these around just by using edit raw value. You'll see these are just, the standard mustache syntax that you see throughout Directus as well.\u003C/p>\u003Cp>Excuse me. Let me get this a drink of water. I'm actually dying. That's a turn of fate. Okay.\u003C/p>\u003Cp>Alright. So we're gonna save this. Deal owner already exists in deals. Okay. Alright.\u003C/p>\u003Cp>Great. Let's move this around. Maybe we make deal stage half width. We slide deal owner up there. Let's actually take a look at this now.\u003C/p>\u003Cp>I should be able to assign folks. So let's create a new user. We'll just call it sales rep. Salesrep@example.com. And maybe we give them a nice avatar.\u003C/p>\u003Cp>Right? Sales rep, avatar. Let's just see what Google comes back with. John's inside sales rep. Yeah.\u003C/p>\u003Cp>This looks this is perfect. This is my guy right here. Alright. There's his avatar. We'll just save that.\u003C/p>\u003Cp>And now we can see who we've assigned this particular deal to. And now maybe within the deal card user here, I wanna show who that deal owner is. Great. Mister sales rep. Looking good.\u003C/p>\u003Cp>Alright. One of the other things that we need to do on our activities, we probably got a an owner of that activity or assigned to. It's been assigned to somebody on the team. Again, that's going to be assigned to a direct us user. We'll save that.\u003C/p>\u003Cp>And lots going on here. Just some type of weird glitch. I could see a couple of extra collections. I'll just remove these. Alright.\u003C/p>\u003Cp>Cool. So now weive got a deal. Weive got a table full of activities. I can go ahead and add these, like, follow-up on proposal. This is assigned to Mr.\u003C/p>\u003Cp>Sales Rep. We've got the activity type. We can see that's going to be, just a quick phone call. This is completed. We can see that conditional, conditional formatting for that.\u003C/p>\u003Cp>And we can add a due date of, let's say, the next Friday. Great. Save that. Keep editing. Cool.\u003C/p>\u003Cp>Alright. So, now, we've got the basic inner workings of a CRM. Right? We've got our deals, we've got contacts, we've got organizations, we've got our different pipeline stages. Right?\u003C/p>\u003Cp>If I wanted to organize these things a bit, we could go in and add different icons for each of these. So, you know, maybe we set some people icons for our contacts. We've got our organizations. Do we have an organization option? Let's look and see.\u003C/p>\u003Cp>Business. Is there a business? There you go. That looks somewhat like a business. We've got activities.\u003C/p>\u003Cp>Maybe this will be like a checklist. Cool. We've got our deals. Let's make those the money. Dollar signs, that's great.\u003C/p>\u003Cp>And then, deal stages, to me this is like a settings. Right? So I could create a new folder, let's just call it settings folder. You don't necessarily have to add this, but maybe we just do to keep it clean. And we look for, like, a settings icon just to use here.\u003C/p>\u003Cp>That's great. This one looks magical. Settings suggest. Right? And, again, I can change the name of this to just say settings.\u003C/p>\u003Cp>So, it still creates this collection, but, we can call it whatever we want. So we'll drag this up within settings folder. We'll drag deal stages and, this will be, what, like the Kanban view. There we go. Awesome.\u003C/p>\u003Cp>Okay. So now we get a little more organization to this. One of the other things that you might do and, you know, that you use all the time within ACRM are saved views. Right? So Directus gives you that ability with bookmarks.\u003C/p>\u003Cp>We'll just go in to the top here and maybe I want to sort by a specific sales rep. Right? Like, the deal owner is, a specific person and specific name is sales rep. That's the only one at this point. But I could go in and create a bookmark for this, and we could call it, deals sales rep Man.\u003C/p>\u003Cp>And we can change this up, give it a color. Now, within that collection, now we can see we've got our deals for Sales Rep Van, and I could save that bookmark. So even if I go into the main deals view, and maybe we change this back to a table view, which could be easier for, you know, maybe a sales manager or something who's controlling this. So I had deal owner back to this as well. Now if I go back, deal sales rep man view, boom, there it is.\u003C/p>\u003Cp>It's saved. I could go in and update this if I wanted to as well. So now we've got our pipeline. When we go into each one of the deal, we have our name, we've got our organization, we've got the notes, we've got our activities. You know, we can mark these activities off as completed.\u003C/p>\u003Cp>That seems like a great CRM baseline. Let's let's take a look at where we're at. Right? We got, like, 16 minutes on the clock. This feels like a win.\u003C/p>\u003Cp>I don't I don't know if we wanna run that one, let's discuss where we could go from here, right, maybe we want to automatically send some emails when it hits a certain stage in the pipeline. So let's just call these things done. Right? We can manage all of our contacts. We can manage all of our organizations.\u003C/p>\u003Cp>We can manage our sales pipelines. We can track our activities and follow-up. So let's say, you know, we get a new deal inbound. Maybe we want to automatically assign that to a a particular person, or we want to send a notification to our sales rep when that assignment happens. Let's figure out how to do that.\u003C/p>\u003Cp>Right? So I'm gonna go in. Let's just create a new deal. We'll say actually, let's wait a moment. Let's go into our flows.\u003C/p>\u003Cp>This is a good example. Right? Whenever a a new deal comes inbound, we want to send an email notification to our sales rep to to let them know. Alright. So we're gonna create a new flow.\u003C/p>\u003Cp>We'll just call it new inbound deal. Pretty straightforward. We could change this to the new symbol if we want to and just do a trigger setup. So what are we going to choose here? Directus gives you a ton of different options, as far as what to use when you're creating a flow.\u003C/p>\u003Cp>In this case, we're going to use the event hook. So when a certain event happens inside the platform, we wanna trigger an automation. The type that we're gonna choose here is action non blocking because we don't, the the filter allows you to basically either adjust the payload when a new deal gets created or a new event happens. Action non blocking, again, that runs after a create or update action. So in this case, let's do the items dot create.\u003C/p>\u003Cp>Whenever we're gonna trigger this based on the deals collection. And cool. Alright, so now I'm just going to save this, right? I'm going to go in and let's create a new deal inside the system. It's in the new stage.\u003C/p>\u003Cp>We're gonna assign this to mister sales rep. New deal automation. And we add, let's set this one to be for the little league. Again, we choose a specific contact, add some notes, and, maybe, the deal value, we'll just ballpark it at $5,000. Great.\u003C/p>\u003Cp>If I go back, now I can see that in my logs, I've got this flow. Here's the payload of this particular flow, and we could see who the deal owner is on this particular deal. How do we send an email notification to that specific owner? Right? I'm just gonna take this, copy it, and I open up just my Versus code editor where I've got my 100 apps, just, Docker Compose file here set up to run this locally.\u003C/p>\u003Cp>I'm just going to save that in case I need it. And, next, let's flesh this out a little bit. Right? So you can see the data we're getting back here. We're probably gonna need to look up that specific user.\u003C/p>\u003Cp>We can find them there. Those are the deal owner. We could send them a notification inside the app or we could send them a notification via email. Right? There's 2 options there.\u003C/p>\u003Cp>We've got notification. In this case, we've got the UUID of the user that we're going to send that to. So, you know, potentially, you want to send that in app. App. In this case, if there's a new deal, they're probably gonna be in their inbox.\u003C/p>\u003Cp>We're going to send that new deal to them. But let's actually find that email address first, though. Alright. So we're gonna read data from a specific collection. Let's call it Find User is the actual step we're gonna do here.\u003C/p>\u003Cp>For the permissions, let's just give full access. And, for the collection, you can see I don't have the Directus Users collection here, but I can go in and edit my raw value and just use Directus underscore users. And for our IDs, right, what are we gonna put here? So if I open this back up, we're gonna use the trigger. Payload.\u003C/p>\u003Cp>Dealowner. So we'll do this, we'll do trigger dot payload dot deal underscore owner. And I'm gonna wrap this in mustache syntax. And let's try this again. So we'll read the user.\u003C/p>\u003Cp>Let's go ahead and maybe just add this send email as well. So we'll send the email, and this is gonna be the read underscore user. We're using the key that we set of the previous operation within that flow. Readuser. Email should be.\u003C/p>\u003Cp>We'll input that. New deal assigned to you. And hey. Read_user.firstname. We've assigned a new deal to you.\u003C/p>\u003Cp>And then I can even go through and add those different variables if I wanted to for things like the deal name or the deal ID and and add a link back to that. So let's just try trigger. Payload.deal_name. Great. We'll save it.\u003C/p>\u003Cp>And now let's just test this out. I'm not actually sure if I've got emails set up here locally on this particular instance though, so that could be fun. But we should be able to actually see if this runs. In light of that bit of news, because it just looking at my configuration here, I do not have email configured here locally. So let's let's detach that one, and let's just test the notifications.\u003C/p>\u003Cp>Send notification. Cool. Send notification. The find_user.id. Cool.\u003C/p>\u003Cp>Full access. And it was a new deal assigned. We'll just add the key here. So that'll be trigger dot payload. Or no.\u003C/p>\u003Cp>Actually, it may be something like this where we have key. Alright. Let's just take a look just to make sure. Alright. Within our payload, we can see the key there.\u003C/p>\u003Cp>That's good. In that case, we probably didn't need to get the actual user there, but that's okay. I'm just gonna copy this message that we set here. Just paste that. And let's take a look at where this gets us.\u003C/p>\u003Cp>Alright. So we've got a new inbound deal. We're going to read the data of the user that we've assigned that to and we're going to send a notification to that specific person. Alright. Now we've got a new deal, deal stage.\u003C/p>\u003Cp>Let's just set it to new. Deal owner. I'm gonna choose myself here so we could test this notification. Say deal name. Test deal.\u003C/p>\u003Cp>It's worth $6,000,000. Very nice deal. We got a primary contact. Here's some notes. We'll just save that and let's see what happens.\u003C/p>\u003Cp>Right? We go to our Flow, we get a new inbound deal, we can see our logs, send notification, recipient is so and so. Hey, undefined. We've sent you a new test deal. Underscore first name.\u003C/p>\u003Cp>Why did that not come back? Read underscore oh, that's why I used the wrong key. Sometimes that happens. Find user dot first name. Great.\u003C/p>\u003Cp>So now, if I look and I check my activity log, I can see that I've got this new deal signed. Here's the notification for that. And if I click on it where it says view content, it should take me to that specific test deal. Great. Awesome.\u003C/p>\u003Cp>Let's call that a win. Right? We've got our custom CRM built out. We've set up some automation for this. We could go further and flesh this out a a ton of different ways.\u003C/p>\u003Cp>Right? If we talk about it, like, we could go through and send emails to our actual primary contact when it reaches a certain stage, that would be fairly easy to do using direct dis flows. You know, we could maybe even go as far as, like, a future future iteration of this could be setting up an inbox to parse incoming emails like a BCC functionality and add these to those specific deals as well. But this this feels really good. I'm I'm calling this a win.\u003C/p>\u003Cp>That's our custom CRM. Thanks for joining me on this episode of 100 apps, 100 hours. We'll catch you on the next one.\u003C/p>","Hi. Welcome back to another episode of 100 apps. 100 hours where we build some of your favorite apps or try to rebuild some of your favorite apps in 1 hour or less, or get publicly humiliated trying. Alright. Super excited to be back for another season. If you are new to 100 apps 100 hours, there are 2 basic rules. Number 1, you have 60 minutes to build and plan and build, no more, no less. So when that clock strikes 0, that's it. And then the second rule is there are no other rules. Use whatever you have at your disposal to complete the functionality. That's it. Let's dive into this episode. So today, I've got a custom CRM. Tools like Salesforce, Pipedrive, HubSpot, they really need no introduction. Everybody needs a CRM. A lot of them, depending on your purposes, may be way too much for what you need or they may not be specific enough for your industry. So we are going to build our own custom CRM in 1 hour or less. Let's do it baby. Alright. So, let's start the clock and away we go. So, when we plan our CRM, what do we need out of a CRM? Right? What kind of functionality do we want to see inside our CRM? So basically, we want to manage all of our contacts. We want to manage all the different organizations those contacts belong to. What else? We wanna manage our sales pipeline. Manage sales pipeline. That's gonna be deals or activities. We want to be able to track activities and follow-up. So this seems like a a pretty good set of functionality for a basic CRM. I'm sure we might embellish this a little bit depending on how far we get, but let's dive into actually fleshing out what the data model just might look like for something like this. So we'll drag a nice square up here. We're gonna have contacts. We could just call those people. I'm a big fan of that. We're gonna have organizations. There's definitely gonna be a relationship between those 2, but I I feel like this could be a many to many relationship because one contact could belong to multiple organizations, and and some CRMs make that a little more difficult to do than others. What else do we have? We're gonna have deals or opportunities. Deals is kind of a a standard nomenclature. We're gonna have activities that are attached to what, those are attached to organizations, they're attached to contacts, they're probably also gonna be attached to deals. What else are we going to have? We're probably going to have some sales reps, those are going to be our users inside our accounts. This looks pretty good for a a base set of functionality. Let's dive in and actually start building something. So, I'm just gonna pull up my Directus instance, that's what we're using on the back end. Totally blank instance, we'll zoom in on this, and let's start building. Right? Let's create our first collection. Let's start with contacts. So we're gonna give this a contacts as the name. For the primary key, let's use generated UUID. What kind of fields do we wanna add for our contacts? Right? When we think of contacts, we're gonna need name, email, but we may also wanna track when this was updated, when it was created. Directus makes that super easy. And one tip that you may not have realized if you've used Directus before, is I can go in and change these to be whatever I want. So if I wanted this to be created at and created by, I could go in and update those, change them to be whatever I want. Updated at, updated by. That's the the nomenclature that you're used to. Do we actually need a sort for our contacts? I don't think. Let's just go ahead and save this. Alright. So for our contacts, we're gonna add a first name. Great. We'll make that a string. Let's do half width for that. I can go in and click the three little dots here and just quickly duplicate this field to save myself some time. And, of course, as I am plotting away on this, Directus underneath is mirroring all these changes to my database schema. So we've got our first name, we've got our last name, we're gonna need a email or an email. Great. Let's require this value. And, you know, I could even go as far as, like, making this unique if I I wanted to have some type of unique identifier to match these up with. Alright. As far as an interface, you know, I could get fancy with this and we could add a little email icon like an at symbol. That's great. I think everything else is good. You know, one of the other nice things that's built into Directus is I can go in and add my own custom reg x validation. So, you know, I can put in some type of reg x that matches email addresses. I don't have one of those handy here, but we'll go ahead and keep marching along here. So we got first name, we got last name, we got email. We'll probably have a phone number at some point, but let's let's go for, like, a job title, probably. I'm trying to think of all the standard fields. Maybe we have some notes. We could set that to be a text area field. I don't really need formatting for those. Great. Job title. And maybe honestly, let let's strip this out because I'm I'm thinking about that many to many relationship with organizations, and we may have, different titles at different organizations. So I'll show you how we can handle that coming up. Alright. So we got first name, we got email. We could go ahead and add phone number as a string. Phone or phone number. Let's just keep it short. We'll say phone. Let's give it a phone icon. Great. K. Phone, email. We'll put email above phone. Cool. Looks nice. Alright. Let's just take a look. Right? We've got first name. Go ahead. Oh, I was gonna add my wife here, but she's gonna get mad at me if I misspell it. Right? Ashley atexample.com. I had a phone number, 555-5555. Here's some nice notes for our contact. These are looking great. Right? We've got our contact in here. I could potentially sort and filter this a a hundred different ways, maybe change the layout. But let's dive into the next collection that we wanna set up, our organizations. So we'll have organizations, again, for the primary key, I'm gonna use ID and just use generated UUID as the type. And, again, I could change created at I could change this structure for these system fields to be whatever I want. Created by, date updated, that's gonna be updated at, if I can actually spell. And we use this one as well and call it updated by. Great. Okay. So now we've got an organization. For our organization, what are we gonna have? We're gonna have a name of the organization. Right? Probably, some addresses for that organization. Right? We could have multiple. So that's where we might reach into an another bag of just a different table. What else are we gonna have for an organization? You know, let's just do something like country in case we want to, even, like, automatically route new organizations to a specific sales rep. Potentially, we've got a name. What are we gonna have? We're gonna have a website. You know, we may have a a logo if we wanna track that for a company. So let's do an image file. Let's call it logo. You know, you might even add things like brand color, things like that if if you're really in tracking that. And then we'll just add another section for notes. This is gonna be a text area field with the type text, and we'll hit save. So we've got our organizations. We've got our contacts. Let's make a link between the 2. Right? So how do we go about that? Depends on how you want the data to actually be structured. Right? In an application, depending on the setup, maybe a user can only belong to one specific organization. But often inside of CRM and maybe I am working with the local little league. I am a member of the board there, and then I'm also a developer advocate here at Directus or, you know, a a founder at Better Side Shop. So a lot of different relationships that is gonna be modeled with the mini to mini relationship inside Directus. Here's how we set that up. It's gonna be pretty easy. We'll just go to create field. We'll look for a many to many relationship. And because we're on organizations, our key here is gonna be contact for the or contacts, I should say. Our related collection is gonna be contacts and then we'll just go through and paint by numbers here. Do we want to show these in a list or a table? I'm good either way. We definitely want to show a link to that item so we can get to that specific contact, but I'm gonna pop open this advanced field creation mode just to show you that you do have control over the naming of the junction collection and the individual fields within that. So, the default setup here is just going to take this table name and this table name or this collection name and this collection name and marry the 2 together and we end up with organizations underscore contacts. Works for me. We'll leave that autofill set up. And then on the reverse, I'm gonna add that many to many field to the contacts as well. We're gonna keep that as the organizations. Alright? In this case, we may have a sort field. So we wanna control the the sorting for contacts, like who's 1st, 2nd, 3rd, that could be helpful in, like, a primary contact situation. And then we have some of our relational triggers. Right? On deselect of organizations' contacts, what do we want to do here? Yeah. Maybe I want to delete that association. So I'll set these to cascade. Great. So now that back out, we can see we've got our contacts field here. If we go into, yep, looks like we've got, an extra contact table created. I might have typed something wrong. But, so we can see contacts there in the organizations. And then on our contact, I should see organizations here. Let's just add an organization. Right? So let's say my wife worked at Tesla. That's in the United States. And, you know, we could add the website, logo, notes. I could choose existing contacts. But I'm just going to go ahead and say 1. Right? So now, I could see I've got that organization here, but it's showing an ID, which is not super helpful. Right? So I can go into the organization itself and control the display template where I have a name. I could even potentially add the logo to that if I wanted. So now if we check it again, we can see, okay, here's the organizations that they're a part of. Right? Now, you also have the ability to manage the data inside that junction collection. Right? So if I go in and and like I said, I may have a different job title at all these different organizations. So I could go in and add a new field here inside this junction collection for job title. Great. Alright. So, now if we go into Tesla, we can see the job title is down here at the bottom, but Directus also gives me an easy way to control where that displays. So if I go to our many to many relationship inside our contacts, I can go to maybe we wanna add a sort field for this just to make sure. And then if I go to the interface, I can control where my junction fields are located. So I can put this at the top, which should be great. Okay. So now if we just check that out one more time. Right. Now I could see director of DirectUs. So now I could give a specific job title to this specific person within that organization. I could go in and add a new organization as well, like, hey, the little league. We can say a board member. Right? Little League Baseball is the name of the organization. Great. Okay. So now there my wife is a part of 2 organizations, different job titles for each. Right? Alright. How are we looking on time? We're looking great. Let's move on to our next collection that we want to set up. That's gonna be what? Our deals, our activities, what do we want to set up next? Let's go for deals or opportunities. Deals is probably the standard naming for this. So that's what we'll stick with. Could be opportunities. Could be something else. That's fine. We'll do created at. Just to keep the same structure, we'll do created by. And by adding these, whenever this record is created, it's going to populate a timestamp and a user. The same thing for Updated, whenever it gets updated or who updates it, it's gonna populate that info for us. Updated by. Great. Okay. So our deals what we're gonna have for our deal? We'll probably have a name or a title of the deal. Deal name sounds great. It could be good to prefix some of these sometimes if you're dealing with a lot of nested relational data. So you get used to seeing name in there a 100 times. Could be confusing whether that's the actual deal name or the organization name or the contact name. So we'll just save the deal name. What else are we gonna have on this deal? Right? We'll probably have a average dollar amount or a potential dollar amount. Let's set that to be, decimal. Deal value. We can add a nice little dollar sign to the icon to make this look nice and pretty. And then we are going to have some related fields. But before we do that, let's just add, like, some deal notes or something like that. Sounds great. Okay. So we got a deal name, we got a deal value, you know, maybe these are side by side, not a big deal. Let's go in, and now we want to add a relationship to the organization and probably to our primary contact for this deal. So those are both gonna be many to one relationships. And we'll set a key of organization here. So the related collection, we'll set that to be organizations. Great. And I could control the display template here. We'll just use the name and we hit save. So now we've got an organization that we're gonna tie to the deal. I'll make that half width. And then we also want to add a primary contact. So we'll just say primary contact. The related collection here is going to be the Contacts collection. And for the Display template, let's use first name, last name, and let's use this format, which I think is typically how it displays inside an email client where you have the email address inside these less than or greater than SQL or symbols. So let's save that. We've got our primary contact. We've got our notes. We've got our deal. What do we need next? Right? We need to track where that deal is at. So we could use, like, a a status field as, like, a drop down for that potentially. Right? What's the status of this particular deal? Blah blah blah. Or we could do something else where we have a relationship to a deal stage or our individual pipeline. Right? Because then we may want to, potentially have separate pipelines or we we wanna give more control back to our users, so the the sales reps or the sales manager to control that actual pipeline without getting into the admin section. So how can we do that? Right? Looks like I've got a little extra couple of fields that I've been creating here inadvertently. Alright, so now let's add a new collection. Let's just call it Deal Stages. We want to manage what are the stages of a particular deal. As far as the optional fields, maybe I don't really not super concerned with these, deal stages. We're gonna call this the name of this particular stage. Maybe we wanna give it a color so we can add some color to it. And, you know, I guess we could even give it an icon if we wanted to. We could play around with that and see what that looks like. Deal stages. Let's make both of those half width. And, now, let's just go through and map some of these particular stages. Right? And I and I, you know, I've got this s on here. One of the other things that you could do inside Directus, you can't control the translations for all of these. So even if you're working in English, on the the back end, you may want to use prefix tables to keep things nice and organized as a developer. This is a great way to control what displays via the interface to your actual users within the application. So I could just call this deal stages or pipeline stages, whatever makes a lot of sense here, and hit save. Alright. So it still shows me deals_stages here, but when I start looking inside the actual app, there we go, we can see our pipeline stages. So, let's create our first one. Right? Let's just say, New, this is inbound. Do we have a symbol? We do have a symbol for this. Right? So let's say red. This is we need to take action on this. What's next? Right? Qualified, maybe? Contacted? Or, let's say assigned. Right? We forgot to assign those to a sales rep. Assign assignment. That's a nice looking icon for that. Then we'll say qualified. You know, if we're doing software sales, like, a check mark. Okay. What else? Demo. Let's save green. Oh, let's make this orange. Do we have like a demo? Maybe a TV? What have we got? Yeah, there we go. That works for a demo as far as the icons. And then we are going to set, like, a last stage, like, proposal. Great. Let's just go gray. Cool. Document. Awesome. Alright. So I can control the way that these are displayed through my actual settings, here. So I can go into each specific one. And on the display tab, let's display a colored dot for the color. And then for the icon, we're just gonna display the actual icon. Alright. Great. If we take a look now, I can see what these actually look like. Cool. And then for my display template for the deal stages, I may even go in and just mirror that same structure where I have a color, then I have an icon with a name. Okay. So, now, what we've done, we've effectively given control over these, and we probably actually need a sort on these as well, so so we can control that value. So, let's go in and quickly add a sort. That's going to be an integer value. And because this is coming after the fact, after we've created this actual table, I'm just gonna scroll down a little bit, and I will have a sort field here. So we'll choose sort just to make sure we manage that. And now I should be able to drag and drop these, in whatever order that I like. Again, we're giving control back to the users of our custom CRM. We're definitely gonna prevent them from getting inside the admin so they don't mess with the data model. But here, we give them the ability to control what those pipeline stages are gonna be. So now we need to add those to our actual deals. Right? So we're gonna go into our deals. We're going to create a many to one relationship. And, you know, this is going to be the deal stage or just stage. And we're gonna use deals underscore stages as the related collection. We can open this up and and just see what's going on. I I don't really need to create that inverse relationship. I could. Doesn't make a ton of sense, though. I'm not sure. Alright. Display related values, validation. Let's keep it very simple, and then we're gonna use our deal stage. Alright. So now if I go into our deals Why is this oh, it goes in the machine. I don't know what's going on on this particular example, but it keeps creating additional fields that we don't need. Alright. So let's create a new deal. Alright. This is gonna be a new deal. We can see, hey, there's our stage. That looks great. The deal name is 100 apps, 100 hours contract. It's worth $1,000,000. So we'll add that. How many zeros do we have there? 1, 2, 3, 4, 5, 6. Alright. Let's make this for the Little League team. Great. My wife is the primary contact. Here's some notes on this deal. Great. Did we save it? Numeric value and deals is out of range. Uh-oh. Wouldn't be a 100 apps, a 100 hours without, some type of issue. Right? So let's take a look at this. The deal value, what did I set? Precision and scale. Maybe we bumped this up way up. Probably could have gone with integers for this as well. Do I have a minimum and a maximum? I I don't have that set. So not entirely sure what's going on. Let's just try this again. 100 apps, 100 hours contract, 1 100,000, 1,000,000. Try it again. Save. Okay. Alright. So now, we've got this particular deal set up. This is kind of an underwhelming view. Right? And, again, I might control how these things display. So if I just go into our deals, we go to the deal stage. If I wanna control how it displays, I can go in and set this. So let's do the color, do the icon, do the name, just because, I'm a I'm a very visual person, and I suspect, a lot of your end users may be as well. We still don't see the organization there. So, again, we'll go back. We'll adjust our organization. We wanna display related values. We want to show the name of that specific organization. And let's get even fancier and maybe we wanna show that logo. I'm gonna click on this and then you'll see this one that has a little magic beside it that's a thumbnail. That's just a shortcut for, like, a nice thumbnail sized image instead of loading the actual image size. So I donit have a logo for this company, but I could quickly find 1. Alright. So, we'll just copy this image address, go back to our instance, let's load up that specific company, they who shall not be named, Hit import. Oh, that's a data URL. Let's find an actual URL that we can just copy. Great. Let's try this again. We can import via URL. Cool. And now when I'm looking at my deals, I can see the logo of that organization. That makes it really handy, when I'm working on a deal just to have that extra extra visual reinforcement. Now, when I'm looking at deals, I may want to set up like a traditional pipeline type of view. So I could do that really easily just by switching the layout here inside Directus. So we'll change this to a Kanban layout. Let's group by the deal stage and the group title is gonna be the name. So I can see new assigned qualified demo. We have that Kanban view that we're used to. As far as the text, what do we wanna look at? We want to control, let's say, when this was created. I'm not really concerned about that. The tags, what do we want to set that to be? Do we want a card image? Honestly, this looks okay. Do we need an actual user on here? Probably not. Cool. Alright. We'll just keep it as is and let's let's start fleshing this out a little more as well. Right? So within a deal, when I'm working this deal, we're going to have activities assigned to this as it moves across the pipeline. And, for that, let's just go in and add a new table, a new collection, we're going to call it activities. Activities, I think that's the correct spelling. We use generated UID, we'll do created at, createdby, create up, updatedat, updated by. Great. And, in this case, maybe we do give a status for this particular activity. Right. Status, great. Let's give a name of the activity, probably a type of activity. Makes sense. We use a drop down for that. It seems pretty straightforward or, you know, maybe they're not gonna need to change the activity types on a a very frequent basis. So we'll call it the activity type, and let's add a couple choices to this. So this is a relatively new addition to Directus within the drop down, the ability to have an icon and a color. So let's say text is a phone call, value is a phone. One of the other cool things if you want to translate this value, you could use this phone t, or this dollar sign t for a translatable string. And then anytime you have users who are using the app within a different language, inside the settings, you can control all those custom translations. We'll take a look at that in a moment. Let's call this what? Phone call. The value could just be phone call, depending on how I wanna store this. Right? It may have an underscore within it as well. It could just be the same thing. Either way. Look for that phone. Great. And I could even add a color for that if needed. Maybe I just wanna keep these all the same. Phone call, we'll call it a meeting. And for this, do we have a meeting icon? Right? We calendar looks nice. Maybe we want to track demos separate from meetings. Alright. So we drag a demo. That was the TV icon that we had. Great. What else are we gonna need? Like, an email? Not sure I would Yeah. Maybe, like, a follow-up email. We wanna reschedule that. And then we'll do an email. Great. Demo, phone call. I think I'm gonna set this to be underscore value. Cool. Alright. Looking good. Activity type. Okay. What else are we going to need for this? We need a due date for this activity and the status, we can use as as is. It's published draft, archived. What do I really care about this activity? It probably isn't completed or not. Right? So maybe we scrap status, and maybe we just go for a toggle instead. Right? Hey. Is this completed? The default value, great. For our label, maybe we change it to completed and give it a color of color on. Color off is red, just to show that it has not been completed. Great. And let's just clean up this form a little bit, making sure everything looks nice. Okay. We are going to add a date for this, and let's set a specific date and time that this thing occurred. Call it due date, end date. Due date seems reasonable, but when do we actually need to complete this specific task? Okay. So now we've got an activity. We want to, link this to our actual deal so we can track those. Right? So, what I'm gonna do in this case is create a mini to one relationship because this activity could only belong to a single deal. Right? We're gonna use the key of deal. The related collection, we'll set that to deals. We'll show the name of that deal. Maybe we show the actual organization as well so I can actually dig into the related collections and and show values from that? Excuse me one second. Sorry about that. Cool. Alright. So now, what I forgot to do is create that inverse relationship. You can actually set that up via direct us when you're creating that relationship. But now I can also just go into our deals, not deal. We'll go into our deals, and now we're gonna create a one to many relationship back to those activities. So we'll call this key of activities. We've got activities. The foreign key will be deal. That already exists. Maybe we wanna show these in a table. We'll choose the columns, due date, Name, Due Date, Activity Type. Seems pretty savvy. And I can even filter these, right? So if I wanted to see just activities where they were not completed. We can enable search and filtering and show a link to these. We'll take a look at what all these look like in just a moment. But we've forgotten one important step through this whole process is, hey. We need somebody to assign these deals to. Right? So, let's add a many to one relationship. We'll call that the deal owner or, you know, you potentially say who this is assigned to. Like, the deal owners, again, kind of standard naming in these scenarios. And for the related collection here, we're gonna use directus underscore users. So these are gonna be actual users of the application that we're assigning this to. Invalid payload collections can't start with direct us users. Oh, deal owner. Let's go to our related collection, and let's get direct us underscore users. And, in this case, we're gonna show the first name, last name. We may back up and do an avatar as well. So just the the thumbnail, the avatar, I could move these around just by using edit raw value. You'll see these are just, the standard mustache syntax that you see throughout Directus as well. Excuse me. Let me get this a drink of water. I'm actually dying. That's a turn of fate. Okay. Alright. So we're gonna save this. Deal owner already exists in deals. Okay. Alright. Great. Let's move this around. Maybe we make deal stage half width. We slide deal owner up there. Let's actually take a look at this now. I should be able to assign folks. So let's create a new user. We'll just call it sales rep. Salesrep@example.com. And maybe we give them a nice avatar. Right? Sales rep, avatar. Let's just see what Google comes back with. John's inside sales rep. Yeah. This looks this is perfect. This is my guy right here. Alright. There's his avatar. We'll just save that. And now we can see who we've assigned this particular deal to. And now maybe within the deal card user here, I wanna show who that deal owner is. Great. Mister sales rep. Looking good. Alright. One of the other things that we need to do on our activities, we probably got a an owner of that activity or assigned to. It's been assigned to somebody on the team. Again, that's going to be assigned to a direct us user. We'll save that. And lots going on here. Just some type of weird glitch. I could see a couple of extra collections. I'll just remove these. Alright. Cool. So now weive got a deal. Weive got a table full of activities. I can go ahead and add these, like, follow-up on proposal. This is assigned to Mr. Sales Rep. We've got the activity type. We can see that's going to be, just a quick phone call. This is completed. We can see that conditional, conditional formatting for that. And we can add a due date of, let's say, the next Friday. Great. Save that. Keep editing. Cool. Alright. So, now, we've got the basic inner workings of a CRM. Right? We've got our deals, we've got contacts, we've got organizations, we've got our different pipeline stages. Right? If I wanted to organize these things a bit, we could go in and add different icons for each of these. So, you know, maybe we set some people icons for our contacts. We've got our organizations. Do we have an organization option? Let's look and see. Business. Is there a business? There you go. That looks somewhat like a business. We've got activities. Maybe this will be like a checklist. Cool. We've got our deals. Let's make those the money. Dollar signs, that's great. And then, deal stages, to me this is like a settings. Right? So I could create a new folder, let's just call it settings folder. You don't necessarily have to add this, but maybe we just do to keep it clean. And we look for, like, a settings icon just to use here. That's great. This one looks magical. Settings suggest. Right? And, again, I can change the name of this to just say settings. So, it still creates this collection, but, we can call it whatever we want. So we'll drag this up within settings folder. We'll drag deal stages and, this will be, what, like the Kanban view. There we go. Awesome. Okay. So now we get a little more organization to this. One of the other things that you might do and, you know, that you use all the time within ACRM are saved views. Right? So Directus gives you that ability with bookmarks. We'll just go in to the top here and maybe I want to sort by a specific sales rep. Right? Like, the deal owner is, a specific person and specific name is sales rep. That's the only one at this point. But I could go in and create a bookmark for this, and we could call it, deals sales rep Man. And we can change this up, give it a color. Now, within that collection, now we can see we've got our deals for Sales Rep Van, and I could save that bookmark. So even if I go into the main deals view, and maybe we change this back to a table view, which could be easier for, you know, maybe a sales manager or something who's controlling this. So I had deal owner back to this as well. Now if I go back, deal sales rep man view, boom, there it is. It's saved. I could go in and update this if I wanted to as well. So now we've got our pipeline. When we go into each one of the deal, we have our name, we've got our organization, we've got the notes, we've got our activities. You know, we can mark these activities off as completed. That seems like a great CRM baseline. Let's let's take a look at where we're at. Right? We got, like, 16 minutes on the clock. This feels like a win. I don't I don't know if we wanna run that one, let's discuss where we could go from here, right, maybe we want to automatically send some emails when it hits a certain stage in the pipeline. So let's just call these things done. Right? We can manage all of our contacts. We can manage all of our organizations. We can manage our sales pipelines. We can track our activities and follow-up. So let's say, you know, we get a new deal inbound. Maybe we want to automatically assign that to a a particular person, or we want to send a notification to our sales rep when that assignment happens. Let's figure out how to do that. Right? So I'm gonna go in. Let's just create a new deal. We'll say actually, let's wait a moment. Let's go into our flows. This is a good example. Right? Whenever a a new deal comes inbound, we want to send an email notification to our sales rep to to let them know. Alright. So we're gonna create a new flow. We'll just call it new inbound deal. Pretty straightforward. We could change this to the new symbol if we want to and just do a trigger setup. So what are we going to choose here? Directus gives you a ton of different options, as far as what to use when you're creating a flow. In this case, we're going to use the event hook. So when a certain event happens inside the platform, we wanna trigger an automation. The type that we're gonna choose here is action non blocking because we don't, the the filter allows you to basically either adjust the payload when a new deal gets created or a new event happens. Action non blocking, again, that runs after a create or update action. So in this case, let's do the items dot create. Whenever we're gonna trigger this based on the deals collection. And cool. Alright, so now I'm just going to save this, right? I'm going to go in and let's create a new deal inside the system. It's in the new stage. We're gonna assign this to mister sales rep. New deal automation. And we add, let's set this one to be for the little league. Again, we choose a specific contact, add some notes, and, maybe, the deal value, we'll just ballpark it at $5,000. Great. If I go back, now I can see that in my logs, I've got this flow. Here's the payload of this particular flow, and we could see who the deal owner is on this particular deal. How do we send an email notification to that specific owner? Right? I'm just gonna take this, copy it, and I open up just my Versus code editor where I've got my 100 apps, just, Docker Compose file here set up to run this locally. I'm just going to save that in case I need it. And, next, let's flesh this out a little bit. Right? So you can see the data we're getting back here. We're probably gonna need to look up that specific user. We can find them there. Those are the deal owner. We could send them a notification inside the app or we could send them a notification via email. Right? There's 2 options there. We've got notification. In this case, we've got the UUID of the user that we're going to send that to. So, you know, potentially, you want to send that in app. App. In this case, if there's a new deal, they're probably gonna be in their inbox. We're going to send that new deal to them. But let's actually find that email address first, though. Alright. So we're gonna read data from a specific collection. Let's call it Find User is the actual step we're gonna do here. For the permissions, let's just give full access. And, for the collection, you can see I don't have the Directus Users collection here, but I can go in and edit my raw value and just use Directus underscore users. And for our IDs, right, what are we gonna put here? So if I open this back up, we're gonna use the trigger. Payload. Dealowner. So we'll do this, we'll do trigger dot payload dot deal underscore owner. And I'm gonna wrap this in mustache syntax. And let's try this again. So we'll read the user. Let's go ahead and maybe just add this send email as well. So we'll send the email, and this is gonna be the read underscore user. We're using the key that we set of the previous operation within that flow. Readuser. Email should be. We'll input that. New deal assigned to you. And hey. Read_user.firstname. We've assigned a new deal to you. And then I can even go through and add those different variables if I wanted to for things like the deal name or the deal ID and and add a link back to that. So let's just try trigger. Payload.deal_name. Great. We'll save it. And now let's just test this out. I'm not actually sure if I've got emails set up here locally on this particular instance though, so that could be fun. But we should be able to actually see if this runs. In light of that bit of news, because it just looking at my configuration here, I do not have email configured here locally. So let's let's detach that one, and let's just test the notifications. Send notification. Cool. Send notification. The find_user.id. Cool. Full access. And it was a new deal assigned. We'll just add the key here. So that'll be trigger dot payload. Or no. Actually, it may be something like this where we have key. Alright. Let's just take a look just to make sure. Alright. Within our payload, we can see the key there. That's good. In that case, we probably didn't need to get the actual user there, but that's okay. I'm just gonna copy this message that we set here. Just paste that. And let's take a look at where this gets us. Alright. So we've got a new inbound deal. We're going to read the data of the user that we've assigned that to and we're going to send a notification to that specific person. Alright. Now we've got a new deal, deal stage. Let's just set it to new. Deal owner. I'm gonna choose myself here so we could test this notification. Say deal name. Test deal. It's worth $6,000,000. Very nice deal. We got a primary contact. Here's some notes. We'll just save that and let's see what happens. Right? We go to our Flow, we get a new inbound deal, we can see our logs, send notification, recipient is so and so. Hey, undefined. We've sent you a new test deal. Underscore first name. Why did that not come back? Read underscore oh, that's why I used the wrong key. Sometimes that happens. Find user dot first name. Great. So now, if I look and I check my activity log, I can see that I've got this new deal signed. Here's the notification for that. And if I click on it where it says view content, it should take me to that specific test deal. Great. Awesome. Let's call that a win. Right? We've got our custom CRM built out. We've set up some automation for this. We could go further and flesh this out a a ton of different ways. Right? If we talk about it, like, we could go through and send emails to our actual primary contact when it reaches a certain stage, that would be fairly easy to do using direct dis flows. You know, we could maybe even go as far as, like, a future future iteration of this could be setting up an inbox to parse incoming emails like a BCC functionality and add these to those specific deals as well. But this this feels really good. I'm I'm calling this a win. That's our custom CRM. Thanks for joining me on this episode of 100 apps, 100 hours. We'll catch you on the next one.","592c22ad-1cb2-4d65-9bd7-e0d7c98fe4f2",[185],"e9e66fa8-0650-4e37-ae8b-74755fdd5dca",[],{"reps":188},[189,245],{"name":190,"sdr":8,"link":191,"countries":192,"states":194},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[193],"United States",[195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244],"Michigan","Indiana","Ohio","West Virginia","Kentucky","Virginia","Tennessee","North Carolina","South Carolina","Georgia","Florida","Alabama","Mississippi","New York","MI","IN","OH","WV","KY","VA","TN","NC","SC","GA","FL","AL","MS","NY","Connecticut","CT","Delaware","DE","Maine","ME","Maryland","MD","Massachusetts","MA","New Hampshire","NH","New Jersey","NJ","Pennsylvania","PA","Rhode Island","RI","Vermont","VT","Washington DC","DC",{"name":246,"link":247,"countries":248},"Michelle Riber","https://meetings.hubspot.com/mriber",[249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,226,437,438],"Albania","ALB","Algeria","DZA","Andorra","AND","Angola","AGO","Austria","AUT","Belgium","BEL","Benin","BEN","Bosnia and Herzegovina","BIH","Botswana","BWA","Bulgaria","BGR","Burkina Faso","BFA","Burundi","BDI","Cameroon","CMR","Cape Verde","CPV","Central African Republic","CAF","Chad","TCD","Comoros","COM","Côte d'Ivoire","CIV","Croatia","HRV","Czech Republic","CZE","Democratic Republic of Congo","COD","Denmark","DNK","Djibouti","DJI","Egypt","EGY","Equatorial Guinea","GNQ","Eritrea","ERI","Estonia","EST","Eswatini","SWZ","Ethiopia","ETH","Finland","FIN","France","FRA","Gabon","GAB","Gambia","GMB","Ghana","GHA","Greece","GRC","Guinea","GIN","Guinea-Bissau","GNB","Hungary","HUN","Iceland","ISL","Ireland","IRL","Italy","ITA","Kenya","KEN","Latvia","LVA","Lesotho","LSO","Liberia","LBR","Libya","LBY","Liechtenstein","LIE","Lithuania","LTU","Luxembourg","LUX","Madagascar","MDG","Malawi","MWI","Mali","MLI","Malta","MLT","Mauritania","MRT","Mauritius","MUS","Moldova","MDA","Monaco","MCO","Montenegro","MNE","Morocco","MAR","Mozambique","MOZ","Namibia","NAM","Niger","NER","Nigeria","NGA","North Macedonia","MKD","Norway","NOR","Poland","POL","Portugal","PRT","Republic of Congo","COG","Romania","ROU","Rwanda","RWA","San Marino","SMR","São Tomé and Príncipe","STP","Senegal","SEN","Serbia","SRB","Seychelles","SYC","Sierra Leone","SLE","Slovakia","SVK","Slovenia","SVN","Somalia","SOM","South Africa","ZAF","South Sudan","SSD","Spain","ESP","Sudan","SDN","Sweden","SWE","Tanzania","TZA","Togo","TGO","Tunisia","TUN","Uganda","UGA","United Kingdom","GBR","Vatican City","VAT","Zambia","ZMB","Zimbabwe","ZWE","UK","Germany","Netherlands","Switzerland","CH","NL",1773850443203]