[{"data":1,"prerenderedAt":464},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"request-review-email-translations":121,"request-review-email-translations-next":192,"sales-reps":212},{"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":128,"people":8,"episode_number":132,"published":133,"title":134,"video_transcript_html":135,"video_transcript_text":136,"content":8,"status":137,"episode_people":138,"recommendations":170,"season":171,"seo":191},"5c9c888c-f527-4608-a2f7-56f156d00980","email-translations","944604881","In this recording of our live event on May 9 2024, Daniel, Jonathan, and Kevin discuss making email templates translatable","cf94e330-0c7e-4998-a002-a8df236def76",47,[129],{"name":130,"url":131},"Discussion","https://github.com/directus/directus/discussions/8239",7,"2024-05-16","Email Translations","\u003Cp>Speaker 0: Talk about what this feature is.\u003C/p>\u003Cp>Speaker 1: I'll I'll I'll in I'll intro because Daniel's gonna have to do all the hard hard work to do.\u003C/p>\u003Cp>Speaker 2: Oh, no.\u003C/p>\u003Cp>Speaker 1: So, we initially I think this was initially flagged as the make email templates translatable. So currently, especially for the system templates, not support translations on those. So things like your invite email, those are kind of hard coded as English only. You can do some things with the liquid templates, but even those are without some work are not really translatable. Mhmm.\u003C/p>\u003Cp>So the content, you have to do some variableization. Now with flows, I think with the advent of flows, you can do some things where you look up the data, pull your you know, based on content or other things, but be nice to make some modifications to the way that this platform is working to use either, the current translation strings or other capabilities, the translation solutions, interfaces on the platform itself, find a way to solve this problem so that emails can be multilingual more easily. So that's our goal. But I also saw that there I I found this that actually has an RFC spec. This was our our lovely CTO, mister Reich, creating this one liner, back in the day before we hardenforced RFCs.\u003C/p>\u003Cp>But then someone actually did a nice write up, and they referenced this particular ticket along with some other things. But they had some other capabilities on some event hooks and user invites and some things to actually extend the capabilities of the email solutions a little better. So we thought we'd use this as our our driving factor since it actually has RFC and it references the email translations. So that's the goal today, talk about how do we make the emails, the the platform emails, as well as other email things more easily translatable and easier to use and extend on the platform. Miss anything, Daniel?\u003C/p>\u003Cp>Anything here?\u003C/p>\u003Cp>Speaker 2: That's a nice, initial summary because, the pain of actually having a multilingual app, comes up quite quickly. As as soon as you got a couple different users from a couple different countries, you're you will run into this. So, this is largely only a problem for multilingual apps right now because, as of right now, you do have the ability to override our system email templates. So, technically, if you only have one language, you could just go into the, email template folder. And I even have looked up a link beforehand that I can share with the chat because I'm so organized, apparently, at least today.\u003C/p>\u003Cp>Speaker 1: Alright.\u003C/p>\u003Cp>Speaker 2: You may head on over to our docs under self hosted email templates, which will help you get started with how to do that. It's very, very simple. You have a template folder where you can put in the, the liquid template file, and you're basically done. Depending on what you want to do, of course, you know, you can make it more complicated. You can expand and expand on it, or add other functionality with your instance.\u003C/p>\u003Cp>But, technically, if you only have one language that you would like to serve, you can very easily do this. It's no problem. And even for the very, very, very brand new feature that was released this week, not sure how many of you guys have seen this because, I did a little thing with the help of the others, of course, because public registration also uses, an email template that you can override yourself. So that's the thing. You can expand on it, but as Jonathan said in the beginning, yeah, multi multilingual apps have a problem.\u003C/p>\u003Cp>Right? Like, how how do you do that as of right now? There's different, routes that you could go down on. Maybe just the the most most naive way would be to have a template per language that you just prefix with the ISO code of the language, for example. This would be the most naive way that I would not recommend, but, technically, we could do that just as a you know, to throw it in the room for discussion right now.\u003C/p>\u003Cp>So we could do that. I wouldn't do it that way, but, technically, we could.\u003C/p>\u003Cp>Speaker 0: One thing you mentioned, Jonathan, is you can kinda hack around with, if you build custom templates with, like, liquid tags. Do you have any more information about that? I'm not saying it would be an ideal approach, but how might that be approached now?\u003C/p>\u003Cp>Speaker 1: So liquid is just a, you know, a templating language, and you can you can grab templates in the flows as part of the email, service, that email notification service operation. And I believe if you supply variables, you can actually swap in the variables. So you could in in your Are you\u003C/p>\u003Cp>Speaker 0: using translation strings?\u003C/p>\u003Cp>Speaker 1: Look up the look up the multilingual contents, you know, store your your template data as a record using the current translations interface. So if we were to box here. I think in this particular demo, mail, settings, email templates. So if you were to set up, like, an email template like this. Right?\u003C/p>\u003Cp>So you can you could make this data driven, where instead of having this body here and the subject here and the name here, you could actually put those into a translation's interface, using the existing translations capabilities, then you manage your content. Now at this point, I can use a flow to grab this data. Now it doesn't work, I think, as Daniel's kind of referring to user invites. You'd have to do your own custom user invite and handle the, you know, on authentication or allow public registration kinds of things to do that. So to send a nonce you know, those are kind of system emails.\u003C/p>\u003Cp>Now we can override those with a liquid template. I'd have to play around. I think if we created a liquid template with variables in it, I think you could potentially use a flow to inject into that and swap in the multilingual from something like this, where I have the content lingually translated. But I don't have any testing.\u003C/p>\u003Cp>Speaker 0: I mean, you're talking about a naive way of doing this to be to have a template for each language, but why would we not simply store the contents of the email as a string in our translation files and then use that dynamic value? Surely, that's the the optimal way to do this. No?\u003C/p>\u003Cp>Speaker 1: That's That\u003C/p>\u003Cp>Speaker 2: would have been the better way that I wanted to to get into now.\u003C/p>\u003Cp>Speaker 0: Good.\u003C/p>\u003Cp>Speaker 2: So the like, in the beginning, I meant, like, the most naive way just to prefix every, email template with, like, enus.template. Liquid or whatever and then called it in every single language. Like, this is the most naive way that I could think of. But, as you might have guessed, by me emphasizing naive is it's very naive. It's not good because we have a very, very nice, crowd and integration for all of our translations for the app.\u003C/p>\u003Cp>So it would be very cool if we, instead of making every single template, store the languages directly, we would store like you suggested now, we would store every string inside that we want to translate very similar to our app translations. So we everybody from the community can what what's the what's the Crowded. Everybody can profit off of the, yeah, of the community's breadth and and width. Like, we have people coming from everywhere, every language. So it would be nice if we can do that.\u003C/p>\u003Cp>It's also for anyone that does not know this, head on over to no. I haven't. Oh, I have I have actually,\u003C/p>\u003Cp>Speaker 0: Locales. What what we're doing? Locales dot directors. Yeah.\u003C/p>\u003Cp>Speaker 2: Yes. If in case anybody did not know this, you can head over to locales.directors.io, and you'll see our app translations. You can contribute, can add your own. Take a look. Yeah.\u003C/p>\u003Cp>This would be a better solution to the email problem. So we can, automatically generate and pull in those, from user contributions. That would be very nice.\u003C/p>\u003Cp>Speaker 0: But there is something, you know, digging below the surface. That'd be fine for system emails. Like, for system emails, that's totally fine. Just use these. But scratching one level below the surface is the ability to act is to use translation strings maybe in custom templates.\u003C/p>\u003Cp>That's the one level on because I feel like the solution to make email tran make email templates translatable is fine for system emails. Like, that we have an approach. We have a you know, we have something. We just need to not hard code the values in English. But there's more than that, I suppose.\u003C/p>\u003Cp>Speaker 1: So I think that was one of the comments that was in the one of these tickets already. I think that was in here somewhere. Somebody mentioned that using the translation strings. I think it was the right somewhere along the way. But that is an option.\u003C/p>\u003Cp>Right? So to me, either something like this. Right? So you can do this yourself with flows for for anything non system email, whether we do the system emails in the translation strings here or whether it's in the actual crowd in files, because it's a system email, you could technically actually have that in all of the languages. I guess, technical question, how do I know what language to send the email in?\u003C/p>\u003Cp>So when I'm using a user invite, I guess, you set the language for the user, the default language for the user. Is that how you would manage that?\u003C/p>\u003Cp>Speaker 2: Yeah. For invites, I think it's a special case because you don't know the person. You could, under specific circumstances, not know what the person is speaking technically. But, like, for other system emails, we have the user. It's they are already registered, so you can just pick the language from the user or the default language of the instance.\u003C/p>\u003Cp>But for invites, I think we should fall back to the, default language of the instance Yeah. But also provide the opportunity to set a specific language inside of the, pop up. Yeah. Yeah. Exactly.\u003C/p>\u003Cp>Exactly. Yeah.\u003C/p>\u003Cp>Speaker 1: We'd add we'd add language here as well. Right? So not just the you know, you'd choose choose your role or whatever you're inviting this user to, but also have a language and set it to the default for the project as the initial. If If they wanna change it, they can choose from one of the languages. And, therefore, those things exist in the crowd inside, but that's part of the you don't have to do any translations inside the app for that.\u003C/p>\u003Cp>That's not a dynamic kind of thing necessarily. Although, if I'm overriding, then I still want. So that's something that we have to think about as well. Right? So where does this get managed, and how does this get managed?\u003C/p>\u003Cp>Crowdin is awesome for system, but the reality is I may want to customize the actual liquid tablet for this. And as soon as I do that, I'm now I now fall back outside of how do I manage the translations.\u003C/p>\u003Cp>Speaker 0: You know what would be really cool actually is if in settings, there was a a section called email templates, and our default ones were there and not deletable, and you could create new ones, and then use the built in director's translation interface to enable that. Like, that feels like a really nice flexible way to also potentially stop people needing to build their own liquid templates, like, as separate files and then load them into a directory. For many use cases, just being a just having a WYSIWYG with, well, a WYSIWYG would be plenty. Maybe with, like, a yield or, you know So\u003C/p>\u003Cp>Speaker 1: it it's this. This is fundamentally what you need is a subject and a body. That's all you really have to have for email. For the text, put this in as a, you know I I think again, the the trouble with making it a system side is I I also kind of want content users to be able to do this. So I'd I'd want my marketing teams or my whoever's to be able to manage the content in these.\u003C/p>\u003Cp>So in a sense, it would be a system user table, possibly, that would show up that you could make accessible to the content side. You could optionally make it as accessible maybe. We don't really we don't really do that with anything today. We don't we don't create anything here other than the languages table gets created if you haven't created it yet, for translations interface. I\u003C/p>\u003Cp>Speaker 0: I question that notion to a degree, though, because if you look at things like creating and editing flows, you can make exactly the same argument there. You wanna expose flows to more people in your organization, and we don't do that. I don't see why like, I think that that would be a bigger conversation about what is exposed versus what isn't and to what degree. So I I would be at peace with the idea that that is an admin setting, but I do get it. It feels a little more editorial perhaps.\u003C/p>\u003Cp>Speaker 1: Okay. That's a separate discussion then, really, I think. So one of because this actually came up this week. Somebody noted that if I make you know, I I'd like to be able to make some role editing capabilities accessible to a user, but not give them data model access or flow access or other things. So potentially being able to enable subsets of the settings for non admin users.\u003C/p>\u003Cp>So that's a separate discussion. But I do agree. I think email templates would be really awesome in here, so that I don't have to create liquid files or other things behind the scenes.\u003C/p>\u003Cp>Speaker 0: And you don't have access to the directory on professional cloud. That's the other big thing in my mind is, like, to really be a solution that works anywhere Directus is is hosted. It can't rely on dropping files in a persistent directory. It needs to be accessible via the data studio.\u003C/p>\u003Cp>Speaker 2: Some action in the chat. I envision being able to create custom email templates and sending it to a mailing service, specifically something something from directors. Could be separate thing, but would be good if it wasn't limited to settings page and flexible enough to support this.\u003C/p>\u003Cp>Speaker 1: The reality is, though, if I do want my own email templates, I could still create the user stuff for that, and I could mimic whatever was in the system. So, hopefully, over time, maybe there's additional system emails or things that we want for the platform. So we'd wanna be able to have those, and they could be manageable in the setting side if you wanted to replicate. Because Flows already supports, you know, grabbing things, you know, pull your variables, pull your data, pull your content. So, again, in this, like, agent c OS here, we have flows for, you know, sending the email.\u003C/p>\u003Cp>Right? So when you send the email, we're actually getting data from the trigger hooks and creates and posts and whatever, so we can pull in data from wherever. Right? So if there's data in a template or places that we wanna get it, we could do that. We can grab that ahead of the email.\u003C/p>\u003Cp>Speaker 0: Could you, show the drop down\u003C/p>\u003Cp>Speaker 2: link? Sorry.\u003C/p>\u003Cp>Speaker 0: Could you show the drop down again where it was marked down? What was the other option is attempt just in send email here. The other option is WYSIWYG or a template. Interesting. Because still in my mind, like, to be really flexible, you want templates to be able to have exactly data that is popped in there.\u003C/p>\u003Cp>So, yeah, I don't see why why you couldn't be authoring those indirectors.\u003C/p>\u003Cp>Speaker 2: You know?\u003C/p>\u003Cp>Speaker 1: I I think we should. I I think it would be awesome because having to go to liquid templates and have to have custom extension deployment capabilities or access to the server. Yeah. Not ideal. Right?\u003C/p>\u003Cp>So again, part of what I think this overall system flows discussion was about, in general. So moving away from the the simple thing that Raich put in here, this was a little bit more of handling this with some system flows and setups and management, being able to manage this from the front end as opposed to back end. So making it data driven. Right? The the things we enable for everyone else, let's enable this for ourselves and make this data driven.\u003C/p>\u003Cp>I love it.\u003C/p>\u003Cp>Speaker 2: Yeah. I I really want to go into that, issue as well, the system flows for, for the system actions. But, first, I wanna finish the other, message from the chat that was with the mailing service. Like, I'm not sure if I understand correctly because, like, this should be possible, right, with just the environment variables. You just define which SMTP server you want to use as an email service so you can actually connect to something else.\u003C/p>\u003Cp>And if you override the template, that is doing what you just said. Or maybe maybe am I missing something?\u003C/p>\u003Cp>Speaker 0: Maybe the ability to send them to custom places per run. Like, there's a few it depends what level of flexibility or abstraction you want to build in, but at its core, that's what happens with the email service In flows, at least.\u003C/p>\u003Cp>Speaker 1: In flows? I don't play with it much, but I don't think you get to choose your mail service here. Right?\u003C/p>\u003Cp>Speaker 0: No. No. But you configure that for the project. And then when emails are sent using this, that's how they get\u003C/p>\u003Cp>Speaker 1: sent. But they get sent with 1 service, not multiple services. Right? Single services.\u003C/p>\u003Cp>Speaker 0: I'm not I'm not sure I see I mean, maybe I'm missing it, but I'm not sure I see the value in being able to set set up multiple. I don't even think you can. I'm not sure you can. It'll be in config options.\u003C/p>\u003Cp>Speaker 2: I mean, technically, because, like, most email services have specific rest APIs that you can just call. So you can probably just set up a webhook event Use a web. Operation thingy. Yep. Send over the the specific payload, and then the email service should take over.\u003C/p>\u003Cp>So, yeah, technically, should be doable.\u003C/p>\u003Cp>Speaker 1: Nice. But I think we're single email service Yeah. On the scenes or\u003C/p>\u003Cp>Speaker 2: Yes.\u003C/p>\u003Cp>Speaker 1: And I I don't actually know.\u003C/p>\u003Cp>Speaker 0: Hello. The hello. Hello. Hello, Duff. We could should talk because I also built my wedding website with, with Directus and built a set of automations around that.\u003C/p>\u003Cp>So how funny is that? I wrote a whole blog post about this. It's one of the earliest blog posts on the developer blog. If you wanna go take a look in our docs. Yeah.\u003C/p>\u003Cp>That's fun. Gosh. Nerds really do like to overcomplicate weddings. Then don't they slash we.\u003C/p>\u003Cp>Speaker 1: Developers are as bad as some of our I\u003C/p>\u003Cp>Speaker 0: mean, I have to deal with\u003C/p>\u003Cp>Speaker 1: complicating things. Right?\u003C/p>\u003Cp>Speaker 0: That's amazing. Yeah. That's so funny. Why should\u003C/p>\u003Cp>Speaker 2: I spend 15 minutes to do the thing that I need to do when I could spend 3 hours to automate the thing.\u003C/p>\u003Cp>Speaker 0: Oh, no. No. It was good. We used it for I mean, a quick aside, but I will explain. So weddings have, like, quite a lot of variability to them.\u003C/p>\u003Cp>So we had if you consider a ticket. Right? A ticket. Right? An RSVP.\u003C/p>\u003Cp>So the first thing is you don't generally send, a ticket per person. You'll send it a group and the group will RSVP for individuals and they might not RSVP the same way, then we, at least, our ceremony could only hold like 80 people, but our reception had like 200 people at it. So, we had variable ticket types and then some ticket types allowed a plus one, some didn't, so all this variability. So backing that with data was really useful. So you had people, then you had invites of multiple people, and then you had RSVPs against the invite.\u003C/p>\u003Cp>And then we also used it for comms, so like blasting out emails or texts to everyone. That's how we did it. And we also used it to manage the shopping list as, because we DIY'd the wedding as people RSVP. So that's that's why it needed hours and hours and hours of, of time.\u003C/p>\u003Cp>Speaker 2: Just like Benny said, you just have to get married a couple times, then the effort was totally worse.\u003C/p>\u003Cp>Speaker 1: I mean, I'm never doing that again. One time was enough.\u003C/p>\u003Cp>Speaker 2: So, yeah, I think it's it's regarding the This is\u003C/p>\u003Cp>Speaker 1: year 30 for us.\u003C/p>\u003Cp>Speaker 2: Oh, damn. Nice.\u003C/p>\u003Cp>Speaker 1: My partner's been a long time. Congratulations.\u003C/p>\u003Cp>Speaker 2: So, Alright. Let's summarize a little bit because we have, you know, 2 issues. So the first one with the general email translatability is not that complex in itself, but should be doable also. And with Kevin's mentioned, like, translation strings, if we could use those, that would be very cool, very nice. With for the system emails, if we could reuse the crowd in, stuff or, like, the static hosting of the string so we can reuse, you know, services and stuff, that would also be very nice.\u003C/p>\u003Cp>And now I think like, if nobody has anything else regarding that\u003C/p>\u003Cp>Speaker 0: Could I just add could I could I add a question? Very interesting. Which is simply Sure. Of course. Can you not use translation strings in custom templates and custom liquid templates today?\u003C/p>\u003Cp>It feels like you should be able to. No. You can't. Okay. Fine.\u003C/p>\u003Cp>Speaker 2: I don't think you can.\u003C/p>\u003Cp>Speaker 1: That's not what I know of.\u003C/p>\u003Cp>Speaker 0: No. No. No. I I defer to you. I've not tried to do it.\u003C/p>\u003Cp>I made an assumption that it was supported. So, yeah, good to know.\u003C/p>\u003Cp>Speaker 2: As far as I know, like, the the only thing that you can do in there is, like, use data that's get passed in, you know, with the keys. So, you could do that with flows. Right? Like, if you would send if you could fetch some data or decide in a flow, like, what that specific e will hold. So, like, for an English user, it will hold hello, and for a German user, it would say, like, or something.\u003C/p>\u003Cp>Then you could reuse the same key, and it works, you know, for multiple languages. But you can't actually use the language thingies, the other thingies. Oh, yeah. And and another nice point from Tim. What about right to left languages?\u003C/p>\u003Cp>Speaker 0: Oh, they don't exist. We're gonna forget about right to left lang I mean, sure. But, you know, that that is a nuance in an implementation, I think. I think don't you set it on the HTML attributes? So you would just set it in the email.\u003C/p>\u003Cp>Speaker 2: I mean, it it look. I'm I'm really not sure because I don't speak or read languages that go from right to left.\u003C/p>\u003Cp>Speaker 0: But more but more than anything, we need to think about that.\u003C/p>\u003Cp>Speaker 2: Yeah. Like like a button, like like a, verification button or something. Should it also be on the other side, for example, in the email template? So, like, not only text, but also, like, buttons. Do the does the language affect those as well?\u003C/p>\u003Cp>I think so. Right?\u003C/p>\u003Cp>Speaker 0: I think my my very limp with pleading complete ignorance here, I'm pretty sure basically whole UIs flip accordingly. But, yes, I think Tim Tim makes a good point though in that it would not simply be a translation string, but you would intrinsically need to know is this email a right to left or left to right email. And, you know, change the I think I genuinely think it's just an attribute in the HTML tag, but, you know, change that\u003C/p>\u003Cp>Speaker 2: accordingly. Yeah. But but in the HTML tag, for example, like, it would need to with, like, c s like, CSS in emails is always very tricky. Like, it's very nitpicky. Something works in an email client, something doesn't, and that's, like, super archaic.\u003C/p>\u003Cp>If if anybody had to deal with this, I'm sorry for you.\u003C/p>\u003Cp>Speaker 0: We support some right to left languages though. Right? Like, in the data studio.\u003C/p>\u003Cp>Speaker 2: I believe so. I have never used them, to be honest. So please don't look at me for further info in the context.\u003C/p>\u003Cp>Speaker 0: Given given that the default language collection gets created with We do.\u003C/p>\u003Cp>Speaker 1: We do.\u003C/p>\u003Cp>Speaker 2: Yep. Definitely.\u003C/p>\u003Cp>Speaker 1: So if you look, we have Arabic Yeah. For 1. So if you scroll where's my language Uh-oh. I don't know what\u003C/p>\u003Cp>Speaker 2: Don't lose the button.\u003C/p>\u003Cp>Speaker 1: I'm good enough navigating around. Worst case, I have DirecTV access to this guy. Fix it. But it's all good. So you get your Arabic.\u003C/p>\u003Cp>So we do have the translations for it.\u003C/p>\u003Cp>Speaker 0: It's individual strings, though. Yeah. Yeah. Exactly. Exactly.\u003C/p>\u003Cp>As Tim said here in the chat, I'm not convinced this is a true right to left implementation for languages. It likely will do, but will do is not necessarily the best or optimal. But in any case, that's just worth noticing too.\u003C/p>\u003Cp>Speaker 1: Would the reality actually be that this this should flip over here in the right to left? Like, the module bar, like, the whole screen would, like, invert? Oh, that makes my head hurt.\u003C/p>\u003Cp>Speaker 0: I I am not sure that's correct. So I I feel the need to add, I believe, the 3 of us here, plus Tim and the child Oh, Benny said, yes. Okay. Someone had a degree of confidence because I was like, if we've not done it, we don't know and there are degrees of implementation. Right?\u003C/p>\u003Cp>But, at the very least, consider the way people read left to right in terms of priority and flow. They would wanna be flipped.\u003C/p>\u003Cp>Speaker 2: Yeah. But we also have to keep in mind that, Benny is from Australia, so his layout is also different. His navigation is on the bottom, and everything appears on the top. It's flipped.\u003C/p>\u003Cp>Speaker 0: You know what would be so funny if, yeah, everything was upside down or, like, even subtle things like like the the spinner stand in the other direction like the toilet water.\u003C/p>\u003Cp>Speaker 2: Yeah. Oh, that would be good. Alrighty. I think I think that's for for now, enough about emails because the other issues are also very interesting, and I think we should we should look into them.\u003C/p>\u003Cp>Speaker 1: Definitely. So sounds like we have a couple of solutions to think through on how we'd wanna implement this. I was looking, Kevin, with regards to your question. It does look like if you use a custom template, so so my custom template so if you have a custom liquid template, you can define variables inside that template. So first name, you then pass that as data from a flow.\u003C/p>\u003Cp>Now this is flow implementation of that template. But, technically\u003C/p>\u003Cp>Speaker 0: Yeah. I mean, technically, being able to and it being in any way optimal. There's a very, very wide chasm between the 2, and I think this is up on one end. Right?\u003C/p>\u003Cp>Speaker 1: Yeah. So once again, what you would do is you would have a body and a subject here that you were looking up from that translation user side content translation. So you'd have a user table with a translations interface, that has your subject and your body and just simply translate the whole thing. You could variabilize or anything that you wanted inside that template itself. Flows would have to handle these, you know, use a run script.\u003C/p>\u003Cp>I did something like this recently for page templates. So defining a page template, content gets built in the user tables, and then when the user's ready to say, you know, for my my location page template, generate the location page, substitute in. I've got a flow that looks for those variables, substitutes them in, generates the page instance for that location, and I'm standing for that. So you could do those kinds of things. But again, it's a lot of work upfront, for the developers and the, you know, the administrators.\u003C/p>\u003Cp>So Yeah. Figuring out a way to make this I think, again, the system making it accessible via the admin in some way, shape, or form, I think is the ideal solution. Like it. Agreed. Cool.\u003C/p>\u003Cp>Speaker 2: And check.\u003C/p>\u003Cp>Speaker 1: Alright. System flows. What do you wanna talk about there, mister Daniel?\u003C/p>\u003Cp>Speaker 2: Oh, this this one is very cool. This is very interesting. It's, the idea and I'm I'm I I'm always very in favor of dogfooding, like, our application. So if we could use like, integrate flows deeper into our app itself, it would, like, in a way, force us to actually improve flows a little bit for ourselves. And so we got it's not I mean, really like them.\u003C/p>\u003Cp>Speaker 1: I know. We we we talked about flows 2.0 a little while back. Right? But yes. Go ahead.\u003C/p>\u003Cp>Speaker 2: Yeah. And, yeah. So this one is pretty interesting. So, the idea is that you replace our current, like, internal behavior of, like, what even happens if somebody presses, forgot password. You know, like like, for example, maybe I would like to send a couple emails.\u003C/p>\u003Cp>I would like to send one email to the to the admin and say, like, oh, warning. This user has, requested a password request or something, you know, just as a random example. You can also do, like,\u003C/p>\u003Cp>Speaker 1: more product updates. Record above and beyond what's in the system already. Maybe you've got soft compliance or ISO compliance things where you get that logged to a system somewhere. Yeah. That's not our app.\u003C/p>\u003Cp>Interesting.\u003C/p>\u003Cp>Speaker 2: Exactly. So maybe you want to customize this. And, I think, I haven't read the entire issue yet, but I think the idea was just to start with, like, invites and password, resets. But we can expand on this a little bit. And, you know, like, depending on how we structure this, how the user experience is, like, how do you override that?\u003C/p>\u003Cp>We have to have a fallback. What happens in the UI? And and you're not allowed to delete the thing. What happens if somebody deletes the thing? You know, like, there's there's a couple things that can go wrong, but, technically, I think this is very exciting and a very smart idea.\u003C/p>\u003Cp>I I really like this.\u003C/p>\u003Cp>Speaker 1: Clever. Well written too. Well defined. Well thought through. Linking to related concepts and tickets.\u003C/p>\u003Cp>David David Zacharias. Excellent. Excellent work. I love the\u003C/p>\u003Cp>Speaker 0: Yeah. Read only read only flows. Must have read only flows AKA system flows. Additional which kinda makes sense here. You have system collections and you can extend system collections as well.\u003C/p>\u003Cp>So it could be that thing where it's like, hey, you know what? These first two steps or the first and last, you can't change those. You can either put stuff in the middle or stuff at the end, kind of like you can do with fields today in collections. So Mhmm. The core behavior stays exactly the same that you can tack on.\u003C/p>\u003Cp>I would be more inclined then to just have an event, though, that an event based hook can hook into.\u003C/p>\u003Cp>Speaker 2: I don't know. No. That's a good point, actually.\u003C/p>\u003Cp>Speaker 0: Because if you have read only flows and you have these new additional scopes, then they kind of negate the need for one another, I think.\u003C/p>\u003Cp>Speaker 2: Now that that's a good point. Yeah. That is a good point.\u003C/p>\u003Cp>Speaker 0: And our paradigm today is\u003C/p>\u003Cp>Speaker 2: because, like, oh, let me based. Even if somebody wants to just break the flow, for example, maybe somebody doesn't want the, like, even the functionality to to be there. You know? Like, maybe you want to forbid actually password requests, like, reset requests.\u003C/p>\u003Cp>Speaker 0: Yeah. Or you just want it to come to the internal team and never go back out to them automatically. Like, that is a a managed process. Yeah. Interesting.\u003C/p>\u003Cp>Yep.\u003C/p>\u003Cp>Speaker 2: Yeah. Also Disabling your Reasonable to me.\u003C/p>\u003Cp>Speaker 1: Disabling a password reset? Interesting.\u003C/p>\u003Cp>Speaker 2: Yeah. I mean, for you know, it's it's always such a, you know, contrived example and stuff, but take like, there's so many different environments and and regulations and\u003C/p>\u003Cp>Speaker 0: Get in touch with your system administrator. Managers and Your IT team\u003C/p>\u003Cp>Speaker 2: or whatever. For example. Yeah. So I can definitely see the use case there. And and this is, like like, super interesting to me.\u003C/p>\u003Cp>Speaker 0: The the the lowest\u003C/p>\u003Cp>Speaker 2: Like, I have not thought about this.\u003C/p>\u003Cp>Speaker 0: Neither have I. The lowest hanging fruit version of this is additional scopes that allow you to execute additional logic on top of the core logic for things like inviting and password resets. These I I almost feel like that is a separate thing, which is smaller and much easier to to implement. Pop that to the side. The system flows is is diff is the thing here.\u003C/p>\u003Cp>I just find that in that addition interesting.\u003C/p>\u003Cp>Speaker 1: Yep. Nope. And so, again, it was extensible, you know, in the sense of it'd be nice to have the translation capability, but also, again, move. We're gonna touch some of these things, potentially moving some of that logic to where it's more accessible or modifiable.\u003C/p>\u003Cp>Speaker 0: Oh, to modify the business logic, users can duplicate the flow and adapt to their needs. The system flow would then be inactive, but could be activated anytime to restore the default functionality. That UX, for me, does not feel good, but it is an interesting way to think about it.\u003C/p>\u003Cp>Speaker 2: Yeah. I I think\u003C/p>\u003Cp>Speaker 1: Well, the system side scope\u003C/p>\u003Cp>Speaker 2: in the event yeah. If you can just, like, have a filter hook, and that would do the thing. Like, you can then cancel. Like, if you throw, the hook will be canceled, and then the email won't be sent out. And if you just pass it through, the normal behavior will take place, which is just sending an email.\u003C/p>\u003Cp>Right? Like, and you can do stuff in between. Yeah. I feel like this would be a way better solution than having, like, read only flows and copying and then, you know, fallbacks and something is missing and whatever. Like, maybe just\u003C/p>\u003Cp>Speaker 0: And the And the critical thing is it matches the paradigms we have today. We have event based, event based triggers, and then we have actions and filters which run before or after the the operation happens, the the database operation happens. And yeah. So that feels that feels kinda better to me. And, yeah, you can always find a way to break it out of completing.\u003C/p>\u003Cp>And if you're if it's blocking, then the end part never happens.\u003C/p>\u003Cp>Speaker 2: Yeah. Oh, this is cool. If somebody could work on this, that would be nice. This is pretty cool.\u003C/p>\u003Cp>Speaker 1: Well, that's why we're reviewing so we can Could we determine, does it make sense and what additional things do we need to think through. Right?\u003C/p>\u003Cp>Speaker 0: Can we split this into 2 feature requests? Because that core nugget we've discussed is feeling very viable. Also, it feels like not a huge lift. And it is not system. It is not system flows.\u003C/p>\u003Cp>It is the introduction of new events, and then system flows is is something in and of its own. Right. I suppose.\u003C/p>\u003Cp>Speaker 2: But, like, if we have that event, like, do we even need system flows then?\u003C/p>\u003Cp>Speaker 0: I I I would argue not. I I would argue not. But that isn't what this. It's hard because I don't feel inclined to change people's feature requests into the 10% one liner that they've introduced, in it. But that nugget can be dealt with in isolation.\u003C/p>\u003Cp>Speaker 1: Yeah.\u003C/p>\u003Cp>Speaker 0: Yeah. Saw that.\u003C/p>\u003Cp>Speaker 2: Okay. So so that that's that's pretty neat. That would be nice. Yeah. Very cool.\u003C/p>\u003Cp>Yeah. Then okay. So for the events, then there would be a password request reset request. I always\u003C/p>\u003Cp>Speaker 0: It's a reset it's a reset request and then a reset.\u003C/p>\u003Cp>Speaker 2: Yeah. And a oh, oh, right. Those are 2 things. Okay. The the request and the actual reset.\u003C/p>\u003Cp>Maybe you want to act on that too. Okay. Those 2. The invite would be the 3rd.\u003C/p>\u003Cp>Speaker 0: Oh, I'm hang on. So I can just tell you because I've just written this section of our new documentation. So there is invite request and invite accept. Right, actually creating a user off the back of an invite. Then there is, password reset request and password reset.\u003C/p>\u003Cp>And then there's a set maybe around SSO specifically, but maybe not. They are that out. And then if we're talking about if we're thinking about the whole breadth of kind of off, there is also 2fa on off. Like, they are the that is the set of kind of operations that exist beyond just logging in, locking out, logging out, refreshing.\u003C/p>\u003Cp>Speaker 2: And since this week, there will be maybe coming then, register request and register request accepted, maybe?\u003C/p>\u003Cp>Speaker 0: Yes.\u003C/p>\u003Cp>Speaker 2: Something. Ah, and the email verification might also be interesting. Somebody might need that.\u003C/p>\u003Cp>Speaker 0: I would sooner add a a a, you know, like, a bundle of new events and go, well, there's the bit the biggest degree of flexibility for\u003C/p>\u003Cp>Speaker 2: That would be neat. Okay. Yeah. This this this sounds very exciting. This sounds actually very cool to me.\u003C/p>\u003Cp>Because we introduced to also new events for the content versioning. There was the promotion Promotion. Or promote or promotion, which is also useful. So this doesn't sound that far and unreasonable. This sounds very reasonable, actually.\u003C/p>\u003Cp>Speaker 0: So, Jonathan, so while you're typing, there's a there's a couple more. So there is invite, and then there's invite accept. There's password reset request and password reset, And there is 2 FA on and off. And they are the only 2 in my mind that I'm not sure how what like, I'm not sure what the impact of adding those in particular are, but they are there as well. And then I don't I don't have enough knowledge around single sign on to be confident in in listing events around that.\u003C/p>\u003Cp>Speaker 2: Cool.\u003C/p>\u003Cp>Speaker 1: Yep.\u003C/p>\u003Cp>Speaker 2: Yeah.\u003C/p>\u003Cp>Speaker 1: Yeah. And, again, being able to extend being able to extend these would be really powerful. Right? Because if we've got a lot of clients that run us as a SaaS, we operate as a SaaS. There are system and service emails that, again, we do it through user templates and other things today, the way that we define and the way we've talked about.\u003C/p>\u003Cp>But making this kind of built in where you can create and manage your own system emails and service emails, would love it.\u003C/p>\u003Cp>Speaker 0: Yeah. And the the thing is we already because we this only were basically, taking this feature request around or the other feature request rather, which is, oh, sorry. I see you're in this issue. Sorry. We're in the wrong one.\u003C/p>\u003Cp>Sorry. So emails that go out are not all of those. Sorry. Emails are just the, the yes. The password reset request and the invite.\u003C/p>\u003Cp>They are the only 2 system emails. Sorry. They are the auth events, though. Or the the potential auth you know, they are the points at which we could implement events. The fact that we already have filters and actions as blocking and non blocking, you know, code, you know, logic business logic means immediately you can effectively extend Directus' core functionality without needing to do anything else but expose these new events, which is really cool.\u003C/p>\u003Cp>Speaker 2: Yes. Agreed.\u003C/p>\u003Cp>Speaker 0: It's nice when you have a system that kind of already has the building blocks. Right? There's no new building blocks that need to be built for this one.\u003C/p>\u003Cp>Speaker 2: Yes. Only the integration and making sure that we can actually, like, cancel, for example, like, what happens if if the thing canceled and stuff. But other than that, you're completely right. Yes. So just as a reminder for everybody else in the chat because there's still a couple people here, the show will be over very soon.\u003C/p>\u003Cp>So if you have any questions that you would like to ask, now is the time. Now is the time.\u003C/p>\u003Cp>Speaker 1: Quiet group today.\u003C/p>\u003Cp>Speaker 0: Nothing wrong with that at all.\u003C/p>\u003Cp>Speaker 2: Alright. Yeah.\u003C/p>\u003Cp>Speaker 1: Means maybe we did our job right.\u003C/p>\u003Cp>Speaker 0: And and not right any other\u003C/p>\u003Cp>Speaker 1: time.\u003C/p>\u003Cp>Speaker 0: Yeah. That's really cool. I like the idea of system system flows. I, ultimately, like the idea of that, of exposing a set of functionality as flows in a UI that could be extended on the end. But I I, personally believe it's just simply unnecessary.\u003C/p>\u003Cp>It's just exposing the new events. And with actions and filters, it facilitates, I think, the goals of this.\u003C/p>\u003Cp>Speaker 2: Yep. Yeah. Exactly. I fully agree. So people let your let your questions out in last minute.\u003C/p>\u003Cp>1, 2. Yeah. If you have enjoyed this, or want to partake in the next one, you take a look in Discord in the events. You will see that we will be doing another episode on the 23rd May Yeah. Will be about configurable API errors.\u003C/p>\u003Cp>So errors are always very important. You might wanna let your voice be heard there. So please join in. Tune in if you like. And if you enjoyed this and want to revisit this or other episodes, head on over to directors.iotv.\u003C/p>\u003Cp>And, there will be lots of other shows as well, which are quite interesting and fun. So please check it out. Let's say it again, directors.io/tv.\u003C/p>\u003Cp>Speaker 0: Yep. We hope\u003C/p>\u003Cp>Speaker 2: you enjoyed. Oh, there's the link.\u003C/p>\u003Cp>Speaker 0: Thank you. And all other episodes of request review bar the last one because I messed up the recording, you can find right there.\u003C/p>","Talk about what this feature is. I'll I'll I'll in I'll intro because Daniel's gonna have to do all the hard hard work to do. Oh, no. So, we initially I think this was initially flagged as the make email templates translatable. So currently, especially for the system templates, not support translations on those. So things like your invite email, those are kind of hard coded as English only. You can do some things with the liquid templates, but even those are without some work are not really translatable. Mhmm. So the content, you have to do some variableization. Now with flows, I think with the advent of flows, you can do some things where you look up the data, pull your you know, based on content or other things, but be nice to make some modifications to the way that this platform is working to use either, the current translation strings or other capabilities, the translation solutions, interfaces on the platform itself, find a way to solve this problem so that emails can be multilingual more easily. So that's our goal. But I also saw that there I I found this that actually has an RFC spec. This was our our lovely CTO, mister Reich, creating this one liner, back in the day before we hardenforced RFCs. But then someone actually did a nice write up, and they referenced this particular ticket along with some other things. But they had some other capabilities on some event hooks and user invites and some things to actually extend the capabilities of the email solutions a little better. So we thought we'd use this as our our driving factor since it actually has RFC and it references the email translations. So that's the goal today, talk about how do we make the emails, the the platform emails, as well as other email things more easily translatable and easier to use and extend on the platform. Miss anything, Daniel? Anything here? That's a nice, initial summary because, the pain of actually having a multilingual app, comes up quite quickly. As as soon as you got a couple different users from a couple different countries, you're you will run into this. So, this is largely only a problem for multilingual apps right now because, as of right now, you do have the ability to override our system email templates. So, technically, if you only have one language, you could just go into the, email template folder. And I even have looked up a link beforehand that I can share with the chat because I'm so organized, apparently, at least today. Alright. You may head on over to our docs under self hosted email templates, which will help you get started with how to do that. It's very, very simple. You have a template folder where you can put in the, the liquid template file, and you're basically done. Depending on what you want to do, of course, you know, you can make it more complicated. You can expand and expand on it, or add other functionality with your instance. But, technically, if you only have one language that you would like to serve, you can very easily do this. It's no problem. And even for the very, very, very brand new feature that was released this week, not sure how many of you guys have seen this because, I did a little thing with the help of the others, of course, because public registration also uses, an email template that you can override yourself. So that's the thing. You can expand on it, but as Jonathan said in the beginning, yeah, multi multilingual apps have a problem. Right? Like, how how do you do that as of right now? There's different, routes that you could go down on. Maybe just the the most most naive way would be to have a template per language that you just prefix with the ISO code of the language, for example. This would be the most naive way that I would not recommend, but, technically, we could do that just as a you know, to throw it in the room for discussion right now. So we could do that. I wouldn't do it that way, but, technically, we could. One thing you mentioned, Jonathan, is you can kinda hack around with, if you build custom templates with, like, liquid tags. Do you have any more information about that? I'm not saying it would be an ideal approach, but how might that be approached now? So liquid is just a, you know, a templating language, and you can you can grab templates in the flows as part of the email, service, that email notification service operation. And I believe if you supply variables, you can actually swap in the variables. So you could in in your Are you using translation strings? Look up the look up the multilingual contents, you know, store your your template data as a record using the current translations interface. So if we were to box here. I think in this particular demo, mail, settings, email templates. So if you were to set up, like, an email template like this. Right? So you can you could make this data driven, where instead of having this body here and the subject here and the name here, you could actually put those into a translation's interface, using the existing translations capabilities, then you manage your content. Now at this point, I can use a flow to grab this data. Now it doesn't work, I think, as Daniel's kind of referring to user invites. You'd have to do your own custom user invite and handle the, you know, on authentication or allow public registration kinds of things to do that. So to send a nonce you know, those are kind of system emails. Now we can override those with a liquid template. I'd have to play around. I think if we created a liquid template with variables in it, I think you could potentially use a flow to inject into that and swap in the multilingual from something like this, where I have the content lingually translated. But I don't have any testing. I mean, you're talking about a naive way of doing this to be to have a template for each language, but why would we not simply store the contents of the email as a string in our translation files and then use that dynamic value? Surely, that's the the optimal way to do this. No? That's That would have been the better way that I wanted to to get into now. Good. So the like, in the beginning, I meant, like, the most naive way just to prefix every, email template with, like, enus.template. Liquid or whatever and then called it in every single language. Like, this is the most naive way that I could think of. But, as you might have guessed, by me emphasizing naive is it's very naive. It's not good because we have a very, very nice, crowd and integration for all of our translations for the app. So it would be very cool if we, instead of making every single template, store the languages directly, we would store like you suggested now, we would store every string inside that we want to translate very similar to our app translations. So we everybody from the community can what what's the what's the Crowded. Everybody can profit off of the, yeah, of the community's breadth and and width. Like, we have people coming from everywhere, every language. So it would be nice if we can do that. It's also for anyone that does not know this, head on over to no. I haven't. Oh, I have I have actually, Locales. What what we're doing? Locales dot directors. Yeah. Yes. If in case anybody did not know this, you can head over to locales.directors.io, and you'll see our app translations. You can contribute, can add your own. Take a look. Yeah. This would be a better solution to the email problem. So we can, automatically generate and pull in those, from user contributions. That would be very nice. But there is something, you know, digging below the surface. That'd be fine for system emails. Like, for system emails, that's totally fine. Just use these. But scratching one level below the surface is the ability to act is to use translation strings maybe in custom templates. That's the one level on because I feel like the solution to make email tran make email templates translatable is fine for system emails. Like, that we have an approach. We have a you know, we have something. We just need to not hard code the values in English. But there's more than that, I suppose. So I think that was one of the comments that was in the one of these tickets already. I think that was in here somewhere. Somebody mentioned that using the translation strings. I think it was the right somewhere along the way. But that is an option. Right? So to me, either something like this. Right? So you can do this yourself with flows for for anything non system email, whether we do the system emails in the translation strings here or whether it's in the actual crowd in files, because it's a system email, you could technically actually have that in all of the languages. I guess, technical question, how do I know what language to send the email in? So when I'm using a user invite, I guess, you set the language for the user, the default language for the user. Is that how you would manage that? Yeah. For invites, I think it's a special case because you don't know the person. You could, under specific circumstances, not know what the person is speaking technically. But, like, for other system emails, we have the user. It's they are already registered, so you can just pick the language from the user or the default language of the instance. But for invites, I think we should fall back to the, default language of the instance Yeah. But also provide the opportunity to set a specific language inside of the, pop up. Yeah. Yeah. Exactly. Exactly. Yeah. We'd add we'd add language here as well. Right? So not just the you know, you'd choose choose your role or whatever you're inviting this user to, but also have a language and set it to the default for the project as the initial. If If they wanna change it, they can choose from one of the languages. And, therefore, those things exist in the crowd inside, but that's part of the you don't have to do any translations inside the app for that. That's not a dynamic kind of thing necessarily. Although, if I'm overriding, then I still want. So that's something that we have to think about as well. Right? So where does this get managed, and how does this get managed? Crowdin is awesome for system, but the reality is I may want to customize the actual liquid tablet for this. And as soon as I do that, I'm now I now fall back outside of how do I manage the translations. You know what would be really cool actually is if in settings, there was a a section called email templates, and our default ones were there and not deletable, and you could create new ones, and then use the built in director's translation interface to enable that. Like, that feels like a really nice flexible way to also potentially stop people needing to build their own liquid templates, like, as separate files and then load them into a directory. For many use cases, just being a just having a WYSIWYG with, well, a WYSIWYG would be plenty. Maybe with, like, a yield or, you know So it it's this. This is fundamentally what you need is a subject and a body. That's all you really have to have for email. For the text, put this in as a, you know I I think again, the the trouble with making it a system side is I I also kind of want content users to be able to do this. So I'd I'd want my marketing teams or my whoever's to be able to manage the content in these. So in a sense, it would be a system user table, possibly, that would show up that you could make accessible to the content side. You could optionally make it as accessible maybe. We don't really we don't really do that with anything today. We don't we don't create anything here other than the languages table gets created if you haven't created it yet, for translations interface. I I question that notion to a degree, though, because if you look at things like creating and editing flows, you can make exactly the same argument there. You wanna expose flows to more people in your organization, and we don't do that. I don't see why like, I think that that would be a bigger conversation about what is exposed versus what isn't and to what degree. So I I would be at peace with the idea that that is an admin setting, but I do get it. It feels a little more editorial perhaps. Okay. That's a separate discussion then, really, I think. So one of because this actually came up this week. Somebody noted that if I make you know, I I'd like to be able to make some role editing capabilities accessible to a user, but not give them data model access or flow access or other things. So potentially being able to enable subsets of the settings for non admin users. So that's a separate discussion. But I do agree. I think email templates would be really awesome in here, so that I don't have to create liquid files or other things behind the scenes. And you don't have access to the directory on professional cloud. That's the other big thing in my mind is, like, to really be a solution that works anywhere Directus is is hosted. It can't rely on dropping files in a persistent directory. It needs to be accessible via the data studio. Some action in the chat. I envision being able to create custom email templates and sending it to a mailing service, specifically something something from directors. Could be separate thing, but would be good if it wasn't limited to settings page and flexible enough to support this. The reality is, though, if I do want my own email templates, I could still create the user stuff for that, and I could mimic whatever was in the system. So, hopefully, over time, maybe there's additional system emails or things that we want for the platform. So we'd wanna be able to have those, and they could be manageable in the setting side if you wanted to replicate. Because Flows already supports, you know, grabbing things, you know, pull your variables, pull your data, pull your content. So, again, in this, like, agent c OS here, we have flows for, you know, sending the email. Right? So when you send the email, we're actually getting data from the trigger hooks and creates and posts and whatever, so we can pull in data from wherever. Right? So if there's data in a template or places that we wanna get it, we could do that. We can grab that ahead of the email. Could you, show the drop down link? Sorry. Could you show the drop down again where it was marked down? What was the other option is attempt just in send email here. The other option is WYSIWYG or a template. Interesting. Because still in my mind, like, to be really flexible, you want templates to be able to have exactly data that is popped in there. So, yeah, I don't see why why you couldn't be authoring those indirectors. You know? I I think we should. I I think it would be awesome because having to go to liquid templates and have to have custom extension deployment capabilities or access to the server. Yeah. Not ideal. Right? So again, part of what I think this overall system flows discussion was about, in general. So moving away from the the simple thing that Raich put in here, this was a little bit more of handling this with some system flows and setups and management, being able to manage this from the front end as opposed to back end. So making it data driven. Right? The the things we enable for everyone else, let's enable this for ourselves and make this data driven. I love it. Yeah. I I really want to go into that, issue as well, the system flows for, for the system actions. But, first, I wanna finish the other, message from the chat that was with the mailing service. Like, I'm not sure if I understand correctly because, like, this should be possible, right, with just the environment variables. You just define which SMTP server you want to use as an email service so you can actually connect to something else. And if you override the template, that is doing what you just said. Or maybe maybe am I missing something? Maybe the ability to send them to custom places per run. Like, there's a few it depends what level of flexibility or abstraction you want to build in, but at its core, that's what happens with the email service In flows, at least. In flows? I don't play with it much, but I don't think you get to choose your mail service here. Right? No. No. But you configure that for the project. And then when emails are sent using this, that's how they get sent. But they get sent with 1 service, not multiple services. Right? Single services. I'm not I'm not sure I see I mean, maybe I'm missing it, but I'm not sure I see the value in being able to set set up multiple. I don't even think you can. I'm not sure you can. It'll be in config options. I mean, technically, because, like, most email services have specific rest APIs that you can just call. So you can probably just set up a webhook event Use a web. Operation thingy. Yep. Send over the the specific payload, and then the email service should take over. So, yeah, technically, should be doable. Nice. But I think we're single email service Yeah. On the scenes or Yes. And I I don't actually know. Hello. The hello. Hello. Hello, Duff. We could should talk because I also built my wedding website with, with Directus and built a set of automations around that. So how funny is that? I wrote a whole blog post about this. It's one of the earliest blog posts on the developer blog. If you wanna go take a look in our docs. Yeah. That's fun. Gosh. Nerds really do like to overcomplicate weddings. Then don't they slash we. Developers are as bad as some of our I mean, I have to deal with complicating things. Right? That's amazing. Yeah. That's so funny. Why should I spend 15 minutes to do the thing that I need to do when I could spend 3 hours to automate the thing. Oh, no. No. It was good. We used it for I mean, a quick aside, but I will explain. So weddings have, like, quite a lot of variability to them. So we had if you consider a ticket. Right? A ticket. Right? An RSVP. So the first thing is you don't generally send, a ticket per person. You'll send it a group and the group will RSVP for individuals and they might not RSVP the same way, then we, at least, our ceremony could only hold like 80 people, but our reception had like 200 people at it. So, we had variable ticket types and then some ticket types allowed a plus one, some didn't, so all this variability. So backing that with data was really useful. So you had people, then you had invites of multiple people, and then you had RSVPs against the invite. And then we also used it for comms, so like blasting out emails or texts to everyone. That's how we did it. And we also used it to manage the shopping list as, because we DIY'd the wedding as people RSVP. So that's that's why it needed hours and hours and hours of, of time. Just like Benny said, you just have to get married a couple times, then the effort was totally worse. I mean, I'm never doing that again. One time was enough. So, yeah, I think it's it's regarding the This is year 30 for us. Oh, damn. Nice. My partner's been a long time. Congratulations. So, Alright. Let's summarize a little bit because we have, you know, 2 issues. So the first one with the general email translatability is not that complex in itself, but should be doable also. And with Kevin's mentioned, like, translation strings, if we could use those, that would be very cool, very nice. With for the system emails, if we could reuse the crowd in, stuff or, like, the static hosting of the string so we can reuse, you know, services and stuff, that would also be very nice. And now I think like, if nobody has anything else regarding that Could I just add could I could I add a question? Very interesting. Which is simply Sure. Of course. Can you not use translation strings in custom templates and custom liquid templates today? It feels like you should be able to. No. You can't. Okay. Fine. I don't think you can. That's not what I know of. No. No. No. I I defer to you. I've not tried to do it. I made an assumption that it was supported. So, yeah, good to know. As far as I know, like, the the only thing that you can do in there is, like, use data that's get passed in, you know, with the keys. So, you could do that with flows. Right? Like, if you would send if you could fetch some data or decide in a flow, like, what that specific e will hold. So, like, for an English user, it will hold hello, and for a German user, it would say, like, or something. Then you could reuse the same key, and it works, you know, for multiple languages. But you can't actually use the language thingies, the other thingies. Oh, yeah. And and another nice point from Tim. What about right to left languages? Oh, they don't exist. We're gonna forget about right to left lang I mean, sure. But, you know, that that is a nuance in an implementation, I think. I think don't you set it on the HTML attributes? So you would just set it in the email. I mean, it it look. I'm I'm really not sure because I don't speak or read languages that go from right to left. But more but more than anything, we need to think about that. Yeah. Like like a button, like like a, verification button or something. Should it also be on the other side, for example, in the email template? So, like, not only text, but also, like, buttons. Do the does the language affect those as well? I think so. Right? I think my my very limp with pleading complete ignorance here, I'm pretty sure basically whole UIs flip accordingly. But, yes, I think Tim Tim makes a good point though in that it would not simply be a translation string, but you would intrinsically need to know is this email a right to left or left to right email. And, you know, change the I think I genuinely think it's just an attribute in the HTML tag, but, you know, change that accordingly. Yeah. But but in the HTML tag, for example, like, it would need to with, like, c s like, CSS in emails is always very tricky. Like, it's very nitpicky. Something works in an email client, something doesn't, and that's, like, super archaic. If if anybody had to deal with this, I'm sorry for you. We support some right to left languages though. Right? Like, in the data studio. I believe so. I have never used them, to be honest. So please don't look at me for further info in the context. Given given that the default language collection gets created with We do. We do. Yep. Definitely. So if you look, we have Arabic Yeah. For 1. So if you scroll where's my language Uh-oh. I don't know what Don't lose the button. I'm good enough navigating around. Worst case, I have DirecTV access to this guy. Fix it. But it's all good. So you get your Arabic. So we do have the translations for it. It's individual strings, though. Yeah. Yeah. Exactly. Exactly. As Tim said here in the chat, I'm not convinced this is a true right to left implementation for languages. It likely will do, but will do is not necessarily the best or optimal. But in any case, that's just worth noticing too. Would the reality actually be that this this should flip over here in the right to left? Like, the module bar, like, the whole screen would, like, invert? Oh, that makes my head hurt. I I am not sure that's correct. So I I feel the need to add, I believe, the 3 of us here, plus Tim and the child Oh, Benny said, yes. Okay. Someone had a degree of confidence because I was like, if we've not done it, we don't know and there are degrees of implementation. Right? But, at the very least, consider the way people read left to right in terms of priority and flow. They would wanna be flipped. Yeah. But we also have to keep in mind that, Benny is from Australia, so his layout is also different. His navigation is on the bottom, and everything appears on the top. It's flipped. You know what would be so funny if, yeah, everything was upside down or, like, even subtle things like like the the spinner stand in the other direction like the toilet water. Yeah. Oh, that would be good. Alrighty. I think I think that's for for now, enough about emails because the other issues are also very interesting, and I think we should we should look into them. Definitely. So sounds like we have a couple of solutions to think through on how we'd wanna implement this. I was looking, Kevin, with regards to your question. It does look like if you use a custom template, so so my custom template so if you have a custom liquid template, you can define variables inside that template. So first name, you then pass that as data from a flow. Now this is flow implementation of that template. But, technically Yeah. I mean, technically, being able to and it being in any way optimal. There's a very, very wide chasm between the 2, and I think this is up on one end. Right? Yeah. So once again, what you would do is you would have a body and a subject here that you were looking up from that translation user side content translation. So you'd have a user table with a translations interface, that has your subject and your body and just simply translate the whole thing. You could variabilize or anything that you wanted inside that template itself. Flows would have to handle these, you know, use a run script. I did something like this recently for page templates. So defining a page template, content gets built in the user tables, and then when the user's ready to say, you know, for my my location page template, generate the location page, substitute in. I've got a flow that looks for those variables, substitutes them in, generates the page instance for that location, and I'm standing for that. So you could do those kinds of things. But again, it's a lot of work upfront, for the developers and the, you know, the administrators. So Yeah. Figuring out a way to make this I think, again, the system making it accessible via the admin in some way, shape, or form, I think is the ideal solution. Like it. Agreed. Cool. And check. Alright. System flows. What do you wanna talk about there, mister Daniel? Oh, this this one is very cool. This is very interesting. It's, the idea and I'm I'm I I'm always very in favor of dogfooding, like, our application. So if we could use like, integrate flows deeper into our app itself, it would, like, in a way, force us to actually improve flows a little bit for ourselves. And so we got it's not I mean, really like them. I know. We we we talked about flows 2.0 a little while back. Right? But yes. Go ahead. Yeah. And, yeah. So this one is pretty interesting. So, the idea is that you replace our current, like, internal behavior of, like, what even happens if somebody presses, forgot password. You know, like like, for example, maybe I would like to send a couple emails. I would like to send one email to the to the admin and say, like, oh, warning. This user has, requested a password request or something, you know, just as a random example. You can also do, like, more product updates. Record above and beyond what's in the system already. Maybe you've got soft compliance or ISO compliance things where you get that logged to a system somewhere. Yeah. That's not our app. Interesting. Exactly. So maybe you want to customize this. And, I think, I haven't read the entire issue yet, but I think the idea was just to start with, like, invites and password, resets. But we can expand on this a little bit. And, you know, like, depending on how we structure this, how the user experience is, like, how do you override that? We have to have a fallback. What happens in the UI? And and you're not allowed to delete the thing. What happens if somebody deletes the thing? You know, like, there's there's a couple things that can go wrong, but, technically, I think this is very exciting and a very smart idea. I I really like this. Clever. Well written too. Well defined. Well thought through. Linking to related concepts and tickets. David David Zacharias. Excellent. Excellent work. I love the Yeah. Read only read only flows. Must have read only flows AKA system flows. Additional which kinda makes sense here. You have system collections and you can extend system collections as well. So it could be that thing where it's like, hey, you know what? These first two steps or the first and last, you can't change those. You can either put stuff in the middle or stuff at the end, kind of like you can do with fields today in collections. So Mhmm. The core behavior stays exactly the same that you can tack on. I would be more inclined then to just have an event, though, that an event based hook can hook into. I don't know. No. That's a good point, actually. Because if you have read only flows and you have these new additional scopes, then they kind of negate the need for one another, I think. Now that that's a good point. Yeah. That is a good point. And our paradigm today is because, like, oh, let me based. Even if somebody wants to just break the flow, for example, maybe somebody doesn't want the, like, even the functionality to to be there. You know? Like, maybe you want to forbid actually password requests, like, reset requests. Yeah. Or you just want it to come to the internal team and never go back out to them automatically. Like, that is a a managed process. Yeah. Interesting. Yep. Yeah. Also Disabling your Reasonable to me. Disabling a password reset? Interesting. Yeah. I mean, for you know, it's it's always such a, you know, contrived example and stuff, but take like, there's so many different environments and and regulations and Get in touch with your system administrator. Managers and Your IT team or whatever. For example. Yeah. So I can definitely see the use case there. And and this is, like like, super interesting to me. The the the lowest Like, I have not thought about this. Neither have I. The lowest hanging fruit version of this is additional scopes that allow you to execute additional logic on top of the core logic for things like inviting and password resets. These I I almost feel like that is a separate thing, which is smaller and much easier to to implement. Pop that to the side. The system flows is is diff is the thing here. I just find that in that addition interesting. Yep. Nope. And so, again, it was extensible, you know, in the sense of it'd be nice to have the translation capability, but also, again, move. We're gonna touch some of these things, potentially moving some of that logic to where it's more accessible or modifiable. Oh, to modify the business logic, users can duplicate the flow and adapt to their needs. The system flow would then be inactive, but could be activated anytime to restore the default functionality. That UX, for me, does not feel good, but it is an interesting way to think about it. Yeah. I I think Well, the system side scope in the event yeah. If you can just, like, have a filter hook, and that would do the thing. Like, you can then cancel. Like, if you throw, the hook will be canceled, and then the email won't be sent out. And if you just pass it through, the normal behavior will take place, which is just sending an email. Right? Like, and you can do stuff in between. Yeah. I feel like this would be a way better solution than having, like, read only flows and copying and then, you know, fallbacks and something is missing and whatever. Like, maybe just And the And the critical thing is it matches the paradigms we have today. We have event based, event based triggers, and then we have actions and filters which run before or after the the operation happens, the the database operation happens. And yeah. So that feels that feels kinda better to me. And, yeah, you can always find a way to break it out of completing. And if you're if it's blocking, then the end part never happens. Yeah. Oh, this is cool. If somebody could work on this, that would be nice. This is pretty cool. Well, that's why we're reviewing so we can Could we determine, does it make sense and what additional things do we need to think through. Right? Can we split this into 2 feature requests? Because that core nugget we've discussed is feeling very viable. Also, it feels like not a huge lift. And it is not system. It is not system flows. It is the introduction of new events, and then system flows is is something in and of its own. Right. I suppose. But, like, if we have that event, like, do we even need system flows then? I I I would argue not. I I would argue not. But that isn't what this. It's hard because I don't feel inclined to change people's feature requests into the 10% one liner that they've introduced, in it. But that nugget can be dealt with in isolation. Yeah. Yeah. Saw that. Okay. So so that that's that's pretty neat. That would be nice. Yeah. Very cool. Yeah. Then okay. So for the events, then there would be a password request reset request. I always It's a reset it's a reset request and then a reset. Yeah. And a oh, oh, right. Those are 2 things. Okay. The the request and the actual reset. Maybe you want to act on that too. Okay. Those 2. The invite would be the 3rd. Oh, I'm hang on. So I can just tell you because I've just written this section of our new documentation. So there is invite request and invite accept. Right, actually creating a user off the back of an invite. Then there is, password reset request and password reset. And then there's a set maybe around SSO specifically, but maybe not. They are that out. And then if we're talking about if we're thinking about the whole breadth of kind of off, there is also 2fa on off. Like, they are the that is the set of kind of operations that exist beyond just logging in, locking out, logging out, refreshing. And since this week, there will be maybe coming then, register request and register request accepted, maybe? Yes. Something. Ah, and the email verification might also be interesting. Somebody might need that. I would sooner add a a a, you know, like, a bundle of new events and go, well, there's the bit the biggest degree of flexibility for That would be neat. Okay. Yeah. This this this sounds very exciting. This sounds actually very cool to me. Because we introduced to also new events for the content versioning. There was the promotion Promotion. Or promote or promotion, which is also useful. So this doesn't sound that far and unreasonable. This sounds very reasonable, actually. So, Jonathan, so while you're typing, there's a there's a couple more. So there is invite, and then there's invite accept. There's password reset request and password reset, And there is 2 FA on and off. And they are the only 2 in my mind that I'm not sure how what like, I'm not sure what the impact of adding those in particular are, but they are there as well. And then I don't I don't have enough knowledge around single sign on to be confident in in listing events around that. Cool. Yep. Yeah. Yeah. And, again, being able to extend being able to extend these would be really powerful. Right? Because if we've got a lot of clients that run us as a SaaS, we operate as a SaaS. There are system and service emails that, again, we do it through user templates and other things today, the way that we define and the way we've talked about. But making this kind of built in where you can create and manage your own system emails and service emails, would love it. Yeah. And the the thing is we already because we this only were basically, taking this feature request around or the other feature request rather, which is, oh, sorry. I see you're in this issue. Sorry. We're in the wrong one. Sorry. So emails that go out are not all of those. Sorry. Emails are just the, the yes. The password reset request and the invite. They are the only 2 system emails. Sorry. They are the auth events, though. Or the the potential auth you know, they are the points at which we could implement events. The fact that we already have filters and actions as blocking and non blocking, you know, code, you know, logic business logic means immediately you can effectively extend Directus' core functionality without needing to do anything else but expose these new events, which is really cool. Yes. Agreed. It's nice when you have a system that kind of already has the building blocks. Right? There's no new building blocks that need to be built for this one. Yes. Only the integration and making sure that we can actually, like, cancel, for example, like, what happens if if the thing canceled and stuff. But other than that, you're completely right. Yes. So just as a reminder for everybody else in the chat because there's still a couple people here, the show will be over very soon. So if you have any questions that you would like to ask, now is the time. Now is the time. Quiet group today. Nothing wrong with that at all. Alright. Yeah. Means maybe we did our job right. And and not right any other time. Yeah. That's really cool. I like the idea of system system flows. I, ultimately, like the idea of that, of exposing a set of functionality as flows in a UI that could be extended on the end. But I I, personally believe it's just simply unnecessary. It's just exposing the new events. And with actions and filters, it facilitates, I think, the goals of this. Yep. Yeah. Exactly. I fully agree. So people let your let your questions out in last minute. 1, 2. Yeah. If you have enjoyed this, or want to partake in the next one, you take a look in Discord in the events. You will see that we will be doing another episode on the 23rd May Yeah. Will be about configurable API errors. So errors are always very important. You might wanna let your voice be heard there. So please join in. Tune in if you like. And if you enjoyed this and want to revisit this or other episodes, head on over to directors.iotv. And, there will be lots of other shows as well, which are quite interesting and fun. So please check it out. Let's say it again, directors.io/tv. Yep. We hope you enjoyed. Oh, there's the link. Thank you. And all other episodes of request review bar the last one because I messed up the recording, you can find right there.","published",[139,150,160],{"people_id":140},{"id":141,"first_name":142,"last_name":143,"avatar":144,"bio":145,"links":146},"07ec688d-251d-4efe-bc17-73848402d43b","Daniel","Biegler","8897b70f-c524-460a-8990-58cc5c3be886","Engineer at Directus",[147],{"url":148,"service":149},"https://directus.io/team/daniel-biegler","website",{"people_id":151},{"id":152,"first_name":153,"last_name":154,"avatar":155,"bio":156,"links":157},"0d906492-75f0-45d9-abf7-ab779bf1ed08","Jonathan","Wagner","5062e4df-a198-4b40-af47-42362d3c0551","Sales Engineering Manager at Directus",[158],{"url":159,"service":149},"https://directus.io/team/jonathan-wagner",{"people_id":161},{"id":162,"first_name":163,"last_name":164,"avatar":165,"bio":166,"links":167},"82b3f7e5-637b-4890-93b2-378b497d5dc6","Kevin","Lewis","a662f91b-1ee9-4277-8c9d-3ac1878e44ad","Director of Developer Experience at Directus",[168],{"url":169,"service":149},"https://directus.io/team/kevin-lewis",[],{"id":172,"number":173,"year":174,"episodes":175,"show":188},"6aa046f1-bd53-4510-9af0-c0f3daaf4415",1,"2024",[176,177,178,179,180,181,122,182,183,184,185,186,187],"daed2c08-703a-43d6-ac97-aacac61be4c0","86fa152b-6a8b-477e-94b5-bd91e1202d21","0b5f4343-1494-455b-b41a-25811c151242","b2b01569-d8c6-49a7-adaa-429fe84f204f","b63afbe1-6418-4e9e-b1da-4890979789f0","69ad81e8-5e1d-4b85-9fa9-3b767a3a3478","243daa59-3772-4ebe-b212-c2a09a4a0b71","d66c1e46-cc57-49fe-a914-2e440bbc1576","12c8f72d-22fa-4ffa-a9d1-57047216fd1a","8896c934-aa2c-43b6-9342-8275682ab8b2","84c7b3ac-fd85-4539-8f39-3247118bcbf2","044b7c89-aaec-43b2-9d6d-6743a0fb5afd",{"title":189,"tile":190},"Request Review","73687d01-3734-4c28-aef7-e6fa8db4cf1e",{"title":8,"meta_description":8},{"id":182,"slug":193,"season":172,"vimeo_id":194,"description":195,"tile":196,"length":197,"resources":198,"people":8,"episode_number":201,"published":202,"title":203,"video_transcript_html":204,"video_transcript_text":205,"content":8,"seo":206,"status":137,"episode_people":207,"recommendations":211},"strictness-of-api-errors","950779919","In this recording of our live event on May 23 2024, Daniel, Jonathan, and Rick discuss making the strictness of API errors","6d44c6a0-aaa7-4ad2-bbcb-d6e0f1de2829",54,[199],{"name":130,"url":200},"https://github.com/directus/directus/discussions/4368",8,"2024-05-30","Strictness of API errors","\u003Cp>Speaker 0: Thank you for joining for this week's, session of request review. We have a fascinating topic for you, which is a little deeper than you think from the from the title. But thanks for joining. We hope you like it. This week's topic is about the strictness of API errors.\u003C/p>\u003Cp>So what does that even mean? Let's kick off with that. Everybody knows software doesn't work always quite how you want it to work, so sometimes you will receive errors in your application, and Directus is no exception. So as you might have noticed, we are very strict as of right now. So if you do something wrong, chances are high that you're just gonna see the message forbidden.\u003C/p>\u003Cp>Like, even when the message, even when the error isn't actually that type of error. So why do we even do that? Throw throwing the, throwing the conversation ball to mister Reich over there, please.\u003C/p>\u003Cp>Speaker 1: To me? Oh, man. Why do we do that? Well, it stems from a security report back in those days in that if you, let's let's use let's use a, a a 4 zero four as an example because it's it's an easy one to think about. So let's say you're fetching a collection that doesn't exist.\u003C/p>\u003Cp>Right? So you should get a 404 because the thing doesn't exist, so there's no route for it. No endpoint. If we would return a 404 for those, you could theoretically extract what the data model looks like by brute forcing your way through the whole API and checking which things return a 404 and which things return a 403, which, you know, to some security minded folks or setups, that's an issue because you don't wanna expose what type of data that you're managing in this system for security reasons. Now this is immediately where we're kicking off with opinions.\u003C/p>\u003Cp>Speaker 0: Let's go.\u003C/p>\u003Cp>Speaker 1: Because you can definitely, you know, you you can see it from both sides. You can easily say, well, that just makes the developer experience very annoying. And that's also why I created this feature request the moment I built it in this way. But it makes sense from a security perspective. Right?\u003C/p>\u003Cp>It's a similar reason why, during login, it'll say wrong email password or account doesn't exist, but you never exposed which of the 2 it is just to make sure that people can't slate, you know, what users exist. And the same goes for, you know, individual items in records. It's like you wanna, you wanna make sure that you can't just say, okay, slash item slash xyz and just keep scraping all of the different numbers, and now you know exactly what records exist, etcetera, etcetera. Somebody rightfully called out, you know, nobody does that in this trustworthy world, which I think sort of concludes this discussion then. Just ignore it.\u003C/p>\u003Cp>Speaker 0: Lying on the Internet? What? Who does that? No. What?\u003C/p>\u003Cp>No. No. No. We don't do that. And a perfect example.\u003C/p>\u003Cp>So leaking of information is a very, very big thing. You don't want to expose stuff that you don't have to expose, but as you just said, it would be nice, for example, like the most basic, setup when you're just developing locally, it would be nice if you could actually see what went wrong. Because currently, it's just okay, forbidden. Nope. No information for you.\u003C/p>\u003Cp>So then the question comes. Oh, La Prima asked, can we go on stage? Sure thing. I think so. But let's let's first, summarize a little bit.\u003C/p>\u003Cp>So There should be\u003C/p>\u003Cp>Speaker 1: a button somewhere.\u003C/p>\u003Cp>Speaker 0: The question is. Yeah. Yeah. Exactly. So, immediately the question comes to mind.\u003C/p>\u003Cp>Okay. So if you would have multiple errors in that case, a strict one which just says forbidden and another one that has more information, Okay. How many different errors would you even like to have? So are we happy with just 2? Because, I guess this is opinion alert or something, or maybe even a useful thing for many other people.\u003C/p>\u003Cp>Like, what what do you guys think about this? Like, how many error, different error, messages would be nice, would be good. Useful.\u003C/p>\u003Cp>Speaker 1: I mean, that is very much a sliding scale, isn't it? Because you really go from, from like, just give me every single thing. It's like, oh, I was trying to read the articles collection that I don't have access to. So I'm gonna get a forbidden error that says, well, articles exist, but you can't read it. And that's because of this permission that you don't have.\u003C/p>\u003Cp>For development purposes, that's really nice. But if you go to production with that, it's tricky because now you're just exposing a bunch of information about, you know, your setup. So, yeah, I'd I'd say it it there's a large range there's a large range in in that. And even within that forbidden error, you know, we can, think about how we wanna return stuff. So one thing I've been working on recently as part of this the the rules and permissions sprint that we'll we'll ship in soon is the, is that forbidden errors now include a little bit more information as in, you know, the field x that you were trying to query, you know, you don't have access to it or it doesn't exist, but at least it's a little bit more explicit while not necessarily exposing the schema, but just exposing what part of your query is wrong.\u003C/p>\u003Cp>Right? Which is not leaking any of that information but still being a little bit more useful.\u003C/p>\u003Cp>Speaker 0: Alright. That does sound exciting, to be honest. Like, it's such a developer thing, like, normal users. I don't know if how how many people care about this, but this is such a nice thing for developers. So I gather from your answer right now maybe 3 types of messages.\u003C/p>\u003Cp>At least from your answer, it sounded like 2 in the beginning, like, one strict one with a little bit of information, but that could be counted as 3, right? Like the very strict one with nothing, a strict one with a little bit, and another one with very much information. Are we happy with 3?\u003C/p>\u003Cp>Speaker 1: No? Yes. Asterisk. There's one more tricky thing in that the output type signature of the errors themselves change between those modes Right? So for example, if you're trying to fetch a thing that doesn't exist, we want to return a 404 status code with a not found error message.\u003C/p>\u003Cp>If you then program your app around that, you know, that it on your front end, it shows, you know, something because it checks 4 Oh, it was a 404 status. Therefore, do this logic. Right? The moment you now change that option to be strict when you go to production, that messes up again. Because now it's returning a 403 with a forbidden instead of a 404 with a not found.\u003C/p>\u003Cp>Cricky.\u003C/p>\u003Cp>Speaker 0: So Let's see. May maybe maybe after we dive in a little bit deeper, we have more clarity around this. But so far, like, I personally like would like, I think, 3 modes. Let let's call them levels. Do we call them levels, modes?\u003C/p>\u003Cp>I think levels. Right?\u003C/p>\u003Cp>Speaker 1: Yeah. It depends. As per usual, it's\u003C/p>\u003Cp>Speaker 0: great.\u003C/p>\u003Cp>Speaker 1: It it depends on, you know, are we considering multiple different flavors at the same time, or is it just an on and off? Because somebody in the chat just now rightfully mentioned, maybe it's just a single production versus development flag that changes that. Right? It could also be a setting within the app. Maybe it's even a setting on a per collection basis that you wanna say, well, this collection is not as, you know, private as the other ones.\u003C/p>\u003Cp>So for this one I wanna have, you know, nice and friendly, messages. And for the rest, we wanna keep private. There's options. It's tricky. It's it's when it comes to a single sort of Boolean flag that says production yes or no, I would be a little bit worried about getting into a point where undoubtedly 3 months later, somebody will show up and be like, well, we kinda need something in between.\u003C/p>\u003Cp>You know?\u003C/p>\u003Cp>Speaker 0: Exactly. You\u003C/p>\u003Cp>Speaker 1: can't really\u003C/p>\u003Cp>Speaker 2: Or more importantly, somebody develops in development mode thinking they've got those messages, then they move that code to production, and suddenly nothing works.\u003C/p>\u003Cp>Speaker 1: Right. Which is gonna be a bit of an issue no matter what because\u003C/p>\u003Cp>Speaker 0: Agreed.\u003C/p>\u003Cp>Speaker 1: We're not talking about, you know, changing the output errors. You could consider those, part of, the the what what would you call it? The public API contract of the APIs that you're building against. Right? Because because, you know, in your intro then, you said stuff sometimes crashes, and that's just part of part of software life.\u003C/p>\u003Cp>Stuff crashes on purpose. Right? In this case, like when validation fails, it's we're still throwing an error, but it's not an unexpected error or anything else. It's, you know, we stop you from doing something on purpose, so we throw an error, which is, you know, a bit of a difference there as well, of course. Because when it comes to exposing internal server errors, it's a whole different question.\u003C/p>\u003Cp>Because right now we do a similar thing, right, where we say you just get internal server error. That's it. Like regular users don't see anything else. If you're an admin user though, that's today, that's the difference. It'll show, you know, the stack trace of the actual JavaScript error, with where in the source codes it it it happened.\u003C/p>\u003Cp>So but exposing internal server errors to non authenticated users, that's gonna be that could easily become an issue. Right? Because now you find some sort of vulnerability path or whatever that's you don't wanna expose that.\u003C/p>\u003Cp>Speaker 0: Definitely.\u003C/p>\u003Cp>Speaker 1: From the chat, somebody has a suggestion here. It says, what about setting a trusted IP address? So you can just say, okay. When I am the one from my home to do this, I get all the pretty errors and otherwise, I can't. I think you're gonna have a similar issue with, you know, def versus prod, strict versus friendly error types, and how do you handle those in your app.\u003C/p>\u003Cp>It's gonna be a tricky tricky thing to figure out. And then somebody else said, the only thing where that really isn't enough is when there's a delete constraint. Users trying to delete items, and they can be deleted because they're still a referencing item. Admins already get a little oh, yeah. That's yeah.\u003C/p>\u003Cp>Because right now that is technically, a database error that bubbles up. So it comes out as an internal server error that for admins, it'll show the the SQL error underneath. But for regular users, they just get a whoops, you know, internal server error.\u003C/p>\u003Cp>Speaker 0: Also with the IP, since currently currently, IP access, is attributed to roles. Right? You you customize that on a role with that access. So we could use that, but it does, like, sound very, very, very likely to me that you wouldn't be interested in, like, giving this to every single user of that role. You would like to have something more granular than that.\u003C/p>\u003Cp>So how about this also comes from, someone from the community. How about we attach this to permissions themselves? So let's say this user can access this and that, and that user is authorized to receive better errors than other users. For example, like, the simplest example, like admin users. Right?\u003C/p>\u003Cp>Mhmm. Like, all admin users are allowed to get the best errors that you can, But users other users get strict errors. Okay? But that, yeah. That's that, like, I I I'm not really sure how exactly complicated that would be to do at every single point in the in the code base.\u003C/p>\u003Cp>Speaker 1: Well, I mean, in in terms of implementation, how like, they currently were throwing forbidden errors all over the place, but the way this would work in implementation is right at the end where we return the error in the APIs, that's where the sort of translation layer kicks in. Right? So all over the code base, it should just throw the actual errors or the actual error types and the correct ones. So if you're trying to hit something that doesn't exist, it should throw a not found error. And then all the way at the end in the middleware stack in the error response handler, it should say, oh, if your current, you know, e n v setting for errors or if the, which I'm gonna call it, if the the error permissions or whatever you wanna call it are set to a certain state, then it translates them into a different way.\u003C/p>\u003Cp>I think that's the way that that will be implemented realistically speaking.\u003C/p>\u003Cp>Speaker 0: Yeah. That does sound quite good. Adjust intercept as a middleware. Okay. You are allowed to see this or not.\u003C/p>\u003Cp>Okay.\u003C/p>\u003Cp>Speaker 1: Yeah. Because we're doing that on a case by case. It's gonna be insane. Just trying to do it every single time that we throw an error. That's that's too much.\u003C/p>\u003Cp>We currently already do a similar thing, right, where the the error handling stack, we check was this a direct as error type or any other error and that chooses between internal server error or, sort of the the expected error output.\u003C/p>\u003Cp>Speaker 0: Very reasonable. I think, if somebody from the chat also has another recommendation or anything, please let us know. There's quite a quite a bit of action today, which is pretty nice, actually. Like, seeing the chat scroll up and down is pretty nice. Somebody says, I can't explain it in the chat, but I'll try.\u003C/p>\u003Cp>That's lovely.\u003C/p>\u003Cp>Speaker 2: Well, well, La Prema can if if you you guys are okay with it, La Prema can join us on the stage.\u003C/p>\u003Cp>Speaker 1: Yeah. I'm down. There should be a button somewhere that just says, I\u003C/p>\u003Cp>Speaker 2: can invite them or they can add they can request, but we can also do an invite.\u003C/p>\u003Cp>Speaker 1: How is that often with these?\u003C/p>\u003Cp>Speaker 2: They asked to speak earlier, so I'm inviting. Or have they does Kevin have that blocked in this event?\u003C/p>\u003Cp>Speaker 1: Oh, boy. With this guy.\u003C/p>\u003Cp>Speaker 2: Nope. Nope. There we go.\u003C/p>\u003Cp>Speaker 1: I got the invite. I got Does it work? I got\u003C/p>\u003Cp>Speaker 2: him up here.\u003C/p>\u003Cp>Speaker 3: I'm trying to\u003C/p>\u003Cp>Speaker 0: see if I wait. Weekend.\u003C/p>\u003Cp>Speaker 3: Yeah. It works. Okay.\u003C/p>\u003Cp>Speaker 1: It works. Hello. Welcome. Hello.\u003C/p>\u003Cp>Speaker 3: As always, sorry for my accent. I'm I'm Frenchy. But, like, I Don't worry.\u003C/p>\u003Cp>Speaker 1: You're you're chatting with a Dutch and a German, so we we know you need to know the guy.\u003C/p>\u003Cp>Speaker 3: Oh, yeah. But your English is better than mine. I don't know if you remember, but we used to talk to Get A Rich for about the the error management system. And at that time, I was working for a company who uses directors, but I leave them. So after, what I haven't the time to to follow the the the case of the the server management system.\u003C/p>\u003Cp>But, I think the the main problem, we had is is, dealing with the the the, yeah, the, like, the granular system of a thing. And what I proposed at the that time was, like, adding in the the collection a a new, column, like, you already have, edit and all that stuff. And, in permission, column where you will have, like, 1 or 2 level no. Mostly 2, but 2 or 3 level of, strictness of the error. Like, do do do I wanna send them all on that collection or not?\u003C/p>\u003Cp>Because, yeah, I I think that would be the I'm sorry. But because it's user related, it would be better for the companies. Because, if you if you, like, have only one private, instance of directors, I think it's not a problem to throw error to people because they are part of the company. So if they don't, respect them, their permissions, you they can have, like, legal, troubles, and this is possible. But if you have, like, an public API and also a private one, On one instances, if it's just a mode, I think it won't fit because a lot of data can be really sensitive.\u003C/p>\u003Cp>And, same goes with the developer mode. Yeah. I I totally agree with, like, developing something, in a in a development mode and then going to production, it doesn't work. That would not be a solution for me. And, as well that the the this this production and development mode would be probably used somewhere else, at another time.\u003C/p>\u003Cp>And, in a long term view, that will just lean in people active in the development, mode every time and, development searching, be thrown everywhere.\u003C/p>\u003Cp>Speaker 1: Yeah. That's right. It's such a tricky, and I I think if that, what was that nodes flag called? It node e and v or something as taught as anything is that you should not rely on a single dev and prod flag. Yeah.\u003C/p>\u003Cp>No. It's a good one. It's the the doing it on a collection level, on a permissions level for for collections, basically. It's it's a good angle. It's it raises some different interesting questions though as per usual.\u003C/p>\u003Cp>Everything does, Especially around requests that touch multiple collections at the same time, right? Where in REST, you can technically go, you know, nest it and then you end up with you could have an error that's thrown in the sort of nested layer of the same request. Or in a GraphQL request, you can technically request multiple collections at the root level at the same time. And then it gets a little interesting because now you can get, you know, a different error output from that request based on which of the data points of that request crashed, I guess, instead of, you know, the request itself,\u003C/p>\u003Cp>Speaker 3: which opens a little\u003C/p>\u003Cp>Speaker 1: bit of a different kind of words.\u003C/p>\u003Cp>Speaker 3: Yeah. But I I think in some case, that's what we want. Because, in the the app I I was working on, we we used to to to get in that kind of problem where, we have people, who had access to certain company, but not certain order. And we retrieve that data in one block. And if some points fails, the the whole, request will fail, so we'll have to deal with that.\u003C/p>\u003Cp>And, what what we can do at that point, I guess, it's when you you have the error only in one part of the query, it's just returning not an error not an error code, but just like in the API, an error, tag in, that object and not all the thing. But I I know that would be, like, a big breaking change. So it can be hard to implement for some people. But, I guess, by default, if we just disable all the error, like, the the basic settings will just be, what Direct just currently does, it won't be a problem because the people who turn the the feature on, would know what they do or or would have ways to find out.\u003C/p>\u003Cp>Speaker 1: I think that is\u003C/p>\u003Cp>Speaker 0: You're you're just assuming that people know what they do. I barely know what I'm doing. And when when I add features oh, no.\u003C/p>\u003Cp>Speaker 1: I think, it it there's a there's a new question hidden in there that we hadn't touched on yet, nor in our notes right now, which is if an error is thrown somewhere nested, let's say, you know, the let's use the GraphQL as an example because it's it's easy to reason about. Let's let's say you're requesting 5 collections of data at the same time. Right? And one of the 5 crashes. The question is, do you return an error and the whole thing failed, or do you return the data that worked and then have an additional error flag that just indicates this is the path of your query that didn't work?\u003C/p>\u003Cp>But return what you have and leave the rest.\u003C/p>\u003Cp>Speaker 3: And, I think for that opinions. Yeah. I think for that, the the the best way to to do it is just, like, throwing the error only for what phase. Because, if we use these directives, I assume it's that we just want to cut the the front end. And and so, it it would be, most of the time, like, an interactive app or a single page application on or something of of that kind.\u003C/p>\u003Cp>And those kind of errors are really easy to to treat with that method. So and and, I I think most of the time, if we activate the the feature, it's what we want because, that will lead in, less, just big crashes, like internal server error proof and nothing worse. So I don't know.\u003C/p>\u003Cp>Speaker 1: It's it's an interesting one because, somebody in Chet just now says it too. It's a rather fundamental question. But, you know, you see both in the wild, and both are kinda alright. It's it's, there's also an implicit difference between, you know, reading and write operations there as well, of course, that if you do a large nested update, you kinda want it to work as a single unit, as a single transaction to make sure that if, you know, part of the insertion doesn't work or part of the update, the whole thing shouldn't, you know, go through because otherwise you have that sort of half half done state. But for read queries, it could be different, but that really depends on, you know, what are you reading, and is it all tightly coupled, or are you reading multiple things at the same time for convenience sake?\u003C/p>\u003Cp>Because if I'm not mistaken, in in sort of the GraphQL spec, Like, it allows you to return partial data and then errors for other root fields. But I feel like if there's an error in a nested field in the query, it'll still fill that one sort of root query as a whole. But that is that is something I love to double check.\u003C/p>\u003Cp>Speaker 0: Yeah. Yeah. I'm I'm I'm not sure either. Like, my my gut feeling was, no. I I I don't wanna say something wrong right now.\u003C/p>\u003Cp>So, okay. Another another suggestion from the chat. How about hashing the error plus a UUID, I assume that is? Only admins will be able to see what the error really is. Well, I mean, I mean, you are probably talking about, like, error codes.\u003C/p>\u003Cp>Like, for example, many different other apps just say, okay this is error 3, 1,172 or something. And then you can look up internally what that exactly means, But, you would still be missing out on, like, more information, like the stack trace or which which file failed or something something that you would have to encode in that thing, but I don't think that's always possible or\u003C/p>\u003Cp>Speaker 1: even Or to just think very divergently. You know, an alternative approach is that you basically encrypt the whole error, save that to a database, and then only return the ID of the error that it was. So then the admin can log in and look up what the full stack was and and and and sort of decrypt it and see the whole the whole contents and everything else. But that that feels like a way to sort of persist to security and give more information at the same time. But it also feels like another skip and a hop and a skip for regular users to be able to just, you know, use do the errors that are output.\u003C/p>\u003Cp>Speaker 0: That's an interesting thing. I have never thought about it like that. People do that? Do their apps do that? That's interesting.\u003C/p>\u003Cp>Speaker 3: Yes. Some app does. I I used to work on one with the that. But I think it's the it's a problem because, like, most of the time, you you wanna informing in some way your user of what the error is because you don't want to to get someone just to to create a ticket to the IT support just every time they need, something, didn't work. But they just didn't have the permission in the first time.\u003C/p>\u003Cp>So, yeah, I don't think, it's a global sit solution. But maybe in some case in some case, if if we add, like, an EMV approach, that would be a a great option for some people that maybe need that.\u003C/p>\u003Cp>Speaker 1: And I could see it work like that for for internal server errors. Right? Well, that's one of those things where we wanna hide away the stack trace no matter what for for the API output and then save it elsewhere. But at the same time, you know, saving every error could easily blow up in your face too, because if you just have, you know, an unauthenticated user that tries to access some data there's no permissions for, we don't wanna save those. Right?\u003C/p>\u003Cp>Because now you could easily just blow up a database by a public malicious actor that just spams your API knowing that they're gonna get errors. So there is there is definitely a a difference between sort of expected and unexpected errors there and which ones are saved. Have AI blocked them. AI solves all of our problems.\u003C/p>\u003Cp>Speaker 0: Sure. Just do what I want. Parentheses open, parenthesis close. That should do it. Right?\u003C/p>\u003Cp>If only if only it would be that easy. K. So let's let's summarize a little bit because we have been, you know, throwing stuff out there. Maybe we should get back to reality a little bit. So, okay.\u003C/p>\u003Cp>So so let's say we have, we like to use the Moscow list. Right? The must have, should have, could have, type of stuff. So for must haves, let's let's get on the same train. Like like, what do we need?\u003C/p>\u003Cp>What what must we include? So, yeah. How granular do we make this? Oh, no. It has to be actionable, man.\u003C/p>\u003Cp>It has to be actionable. Okay.\u003C/p>\u003Cp>Speaker 3: I I think the the point where we've been stuck the the last time was reached is, like, we we both agree on the fact we need a granular system, but we both must agree, on the fact that it would be so long to to make and to think and all that stuff that maybe it would be nice to have something, before that. But,\u003C/p>\u003Cp>Speaker 1: Right. Yeah.\u003C/p>\u003Cp>Speaker 3: Yeah.\u003C/p>\u003Cp>Speaker 1: Yeah.\u003C/p>\u003Cp>Speaker 3: But, I I think, that's, my my point was, like, directors, is a tool who is proposing things like a a different way than the other, tools in the same category. Like, you you just want it to to be a little more, accessible and, and usable. And not just, like, being that's the way you should do that and, just follow it. And and so I I think that's the problem there. If we did something else, than a granular system, you would, follow a path that doesn't feels like directors to me.\u003C/p>\u003Cp>And I don't know that that's totally your point, but but, I I'm not sure if that's what people need. Otherwise, they they will lose WordPress or or something like that.\u003C/p>\u003Cp>Speaker 1: You you you sound like a team member in the sense of when the choice arises, let's do it the difficult way and make it unopinionated wherever we can. So no. I I do agree though. I do agree because it's it it is at the end of the day, this is a very opinionated thing, where for some apps, it's acceptable to have it a little looser or even want it. For some, you know, security minded folks, it's absolutely not, and we can't really make that decision for people at that global level, even though we have right now.\u003C/p>\u003Cp>Oops. Right now we are on the side of safety first, which, you know, it's it's defendable, but not ideal. Like, that's also why I created that feature request. It feels like decades ago, but it was 3 years. I think that the the main unresolved question now, and and that is sort of to answer your point, Dan, before we can really fill in the blanks for what are the must haves, is what is that ideal approach to how do you configure this?\u003C/p>\u003Cp>And then from from a developer experience first and then secondly does that make sense from an implementation perspective. Because doing it on the collection scope level makes sense for errors that are thrown on the collection scope, but not all errors are thrown within the context of a single collection. So that that that opens up, you know, new questions. And the opposite is true for doing it globally on the e and v level. Right?\u003C/p>\u003Cp>Because, you know, it's a low pretty much excellent point. It's not that black on white that you say make it insecure for the whole API. You know, you wanna do it on a case by case basis. That is a that's a that's just a statement with no no answer.\u003C/p>\u003Cp>Speaker 3: No. I don't another, I don't know what to say it in English, but, like, another thing we, think about at time was, like, just, if if we take, like, the the point, if we need the error, it's just to treat them. One, thing, which, was a a little bit, between the two option is just like, having some kind of way to tell directors just, okay. This error, I want you to to throw it, and other errors I don't want. So that would be a little bit more granular, but that would be more like, just a techy approach.\u003C/p>\u003Cp>And and then not, being, so universal.\u003C/p>\u003Cp>Speaker 0: Oh, Rike, you're muted, by the way.\u003C/p>\u003Cp>Speaker 1: I hit the wrong mute button. That's what you get when you have 2. I I muted myself here, and then I and then I muted myself to unmute on this. Oh, man. I mean, we've only been doing this remote thing for about 5 years now, so I'm I'll I'll get it.\u003C/p>\u003Cp>Yeah. Because I what what I was trying to say is that that approach to say, okay, which error types do we expose? That is something we could do on the role level or soon to be policy level, And it is a system where we can still have one translation middleware that handles that error no matter where is thrown from, which is you if you compare that to the sort of collection idea that we had earlier, that one is a little trickier to implement in that sense, because now you have to make sure that you know what the collection context is for every single function call that may or may not throw an error. And then do that sort of translation piece, in in situ of where those errors are created, That makes sense. Whereas with, you know, an approach where you say, okay, not found errors can be exposed.\u003C/p>\u003Cp>If that's your setting, we can do that on one middleware at the end of the stack to just say, okay. Was the error\u003C/p>\u003Cp>Speaker 0: Again, I'm not sure if if I can wrap my head around every single edge case, but, like, it does sound and may maybe I understood it a little incorrectly, but it does sound quite easy to mess that up. Like, it to to to bubble to bubble something up that does not get blocked then. And somehow I am exposing a random error that I have not thought about, like, on on an error level.\u003C/p>\u003Cp>Speaker 1: It's it's the difference of an allow list versus a deny list is basically what you're saying. Right? So if you have a filter where you're denying individual pieces, it's easy to miss something. Whereas we do the opposite way where it's blocked by default and you allow more and more stuff, it's you can't really accidentally expose anything because nothing is exposed by default. Which is similar to what, Andre Andre Andre Key are.\u003C/p>\u003Cp>Just gonna assume that's how you pronounce it. Just said in the chat as well as, you know, if you really want it super granular, effectively just make it a rule based system, right. Where you run a filter against the error object that is thrown. Like if the error types is XYZ and the extensions contain blah, blah, blah, collections, such and such, then expose it, which will be one way to make that super granular, but also a little harder to configure at that point. Because I could, I mean, at the end of the day, I could also imagine that, you know, for just local development, that is gonna be a pain in the ass because now you have to configure so much stuff to get the errors out.\u003C/p>\u003Cp>Speaker 3: I I think we what we could do is just like doing an EMV, variable just for real development case, then that system for the production mode. And later on, if we think it's too difficult or, then we can add the system, with the the permissions because that would make, like, stacks on top of each each other, with the the the the first step would be, like, the programmatic approach on the error throwing and all. And then, the the we could add later on just, instead of just throwing nothing if the error do not doesn't match, any case. Like, is it a permission error? Then is there a plain way to treat it?\u003C/p>\u003Cp>So it's made steps, and we have something, in between no permission management and on the other end, full thing.\u003C/p>\u003Cp>Speaker 2: What this makes me think of is the we've got a client that we're playing around within a proof of concept. They're they're actually have an error table for their front end, and they've actually got translations. So they've actually got a translations table on that so that they can have the language the errors translated to multiple languages. If we went something like that where we have, like, a direct us errors collection, right, as part of the system tables with a set of, you know, with translation, then we have native translations capabilities right there. So if you wanna adjust the error message, so we could have the defaults.\u003C/p>\u003Cp>Right? The I don't know. We've got a dozen or whatever that we have now, with some variable support maybe even there so that if you wanted collection information or you want that stack trace information to percolate up, you could. And you could determine at least in the short term, we can we can avoid the permissions for the short term, give you just control over your error messages. You wanna show something that we don't do natively by default, well, you can override the messaging as a thought.\u003C/p>\u003Cp>And I think that would actually help with things like flows as well. We have the same problem in flows. We can't percolate an error message to the user even though we're throwing and controlling the erroring of the of the workflow. Potentially, if we think about it in that context as well where we've got we could use that same error structure\u003C/p>\u003Cp>Speaker 1: Mhmm.\u003C/p>\u003Cp>Speaker 2: Idea. Not a not necessarily the solution or the right solution, but\u003C/p>\u003Cp>Speaker 1: here's the new rabbit hole, multilingual error output for the API. Oh, boy. Oh, boy. I mean, sort of it's an interesting angle to to sort of noodle on a little bit. The the general idea of errors as it stands right now is that you basically return an error code that is one of the standard sort of known error types.\u003C/p>\u003Cp>And then in whatever app that you're building, you can basically just say, you know, oh, if the error is not found, then use my own translations and my own front end and all that kind of stuff. But yeah. I mean, it it does raise I mean, we we know we need some sort of translations in the API no matter what for email templating, which has been that'll be a fun discussion for another day. But, yeah, it's it's an interesting idea to have errors as a sort of system collection that you can then modify. Although, we would have to hard code in what the keys are because otherwise, we don't know what error to throw still.\u003C/p>\u003Cp>That's a tricky one.\u003C/p>\u003Cp>Speaker 2: But we can hard set the keys. Right? And then if you create your own key or we lock the table, you're not allowed to add to it, you know, without core functionality. Or, again, you've got your hard set core pieces. Core knows what they are, and they can't be changed.\u003C/p>\u003Cp>Their primary key or whatever we wanna do around that Yeah. Gives us that level of flexibility. But then if users want to code other error messages for other applications and data, they can actually still maintain it in all in one place. I'm not smart enough to solve it. I just I just know that's an implementation I saw recently where we're for a front end application and the error messages that they wanna generate for that front end application.\u003C/p>\u003Cp>They're actually doing that in a set of tables. Kinda cool.\u003C/p>\u003Cp>Speaker 0: Oh, I think, like, lots of interesting things that we can do, but I think we're still stuck on where do we want to control this or configure this. Let's just go to the one extreme. I mean, very That would be the easiest thing.\u003C/p>\u003Cp>Speaker 1: You have\u003C/p>\u003Cp>Speaker 0: to And you get errors. Okay. Done. That's the easiest thing that we can do. That leaves a couple of things that we would like to have open.\u003C/p>\u003Cp>Right? Like, we ideally, or at least in my mind, it would be really useful if, for example, like, admins actually get no like, good, context rich errors and normal users don't, which would you know, it's not really that possible with that environment variable approach.\u003C/p>\u003Cp>Speaker 1: Yeah. So so it feels to me like out of everything that we've discussed so far, the direction where you effectively set up and allow list of errors on the role or soon policy level gives you, so far everything that we sort of want to achieve. Right? Where you can say, okay, you can allow one admin role to just see all of the errors with some sort of wild card flick, accept everything, allow everything. You could use the filter rules to be more granular that you could say, oh, if the error contains a collection, flag, then filter against that.\u003C/p>\u003Cp>So So you can you can do sort of the collection by collection, filtering. And, yeah. You you get to choose role by role, so make it admin only or make it make it, a different, you know. So the users can only see the granular errors for a a collection that's already public or something like that.\u003C/p>\u003Cp>Speaker 3: The the only problem there, it's for the flows. But, like, what do we do with them?\u003C/p>\u003Cp>Speaker 0: Mhmm. Mhmm.\u003C/p>\u003Cp>Speaker 3: For flow, that's\u003C/p>\u003Cp>Speaker 0: a grant. Like\u003C/p>\u003Cp>Speaker 3: Because I I think we could have\u003C/p>\u003Cp>Speaker 0: close the\u003C/p>\u003Cp>Speaker 3: The same system, like, in the in the settings of the flows. But is it gonna be the same way?\u003C/p>\u003Cp>Speaker 1: But we could also say that if you trigger a flow, you are a user or the public role. So therefore, the same settings of your role should still kick in.\u003C/p>\u003Cp>Speaker 3: Yeah. Yeah. Because they access the data. So that's good point. But, like, what if you want to, bypass a permission on a specific flow?\u003C/p>\u003Cp>Like, then you'll because in some case, may maybe it's the the old data you don't want to access, but just one field you want to show someone?\u003C/p>\u003Cp>Speaker 2: As long as the filtering's happening on the API level, that's still all API under you know, flows is still API under the hood. So if you uplift an operation, read CRUD, whatever, this the erroring although, I guess, it's still gonna then it would percolate to that user\u003C/p>\u003Cp>Speaker 1: type I think the the the underlying question is what if you want that error to be returned no matter what the settings are in your flow? Because your flow is effectively a custom endpoint or, you know, hook or whatever.\u003C/p>\u003Cp>Speaker 2: Yeah. That's what I think flows when you're throwing an error inside flows, that should be its own code. And if you throw a custom message back to that, then you should be able to get that custom message. That's the key thing that I always want. Right?\u003C/p>\u003Cp>Or that it the the initially, I thought was, oh, I'm gonna throw an error here and I wanna I wanna send back to the user, you know, you did x y z wrong, you know, or this this isn't allowed because you failed to give me the appropriate information instead of a internal server error, which means nothing. You know, I can't I can't tell the user what they've done wrong or what they're violating that I'm validating inside of my flow. I can't say, mister user, you must have your status in the right thing or, you know, these three fields have to be populated to be able to use this or\u003C/p>\u003Cp>Speaker 1: And and that's where the allow list approach for error filtering gets tricky again, because now you need to make sure that you allow list your custom errors from your flow in the policy or in the role, so it makes its way to the app.\u003C/p>\u003Cp>Speaker 2: Or I think, again, flows the error code or codes that are returned by flows, that there is a specific code, and you simply take the message that the user's providing out of the out of the, you know, exception. Yeah.\u003C/p>\u003Cp>Speaker 3: Yeah.\u003C/p>\u003Cp>Speaker 2: Yeah. Right. And so it's it's a known code. We know what it is. Right?\u003C/p>\u003Cp>We know\u003C/p>\u003Cp>Speaker 1: Oh, so here's here's another thing we haven't really message. Yeah. Another thing we haven't really touched on in with regards to flows is that, you know, you have a very explicit error handling path that every operation has a okay. If the error fill or if the operation fails, do something else. So maybe there should just be an explicit operation that says return the error, and otherwise it just defaults to, you know, hidden by default, but then it becomes it it no longer is a a permissions or roles thing.\u003C/p>\u003Cp>It just becomes a flow thing, where you just very exclusively have to end your flow with return the actual error. And at that point, it's up to the, you know, the admin to configure that flow and and whatever they want to return.\u003C/p>\u003Cp>Speaker 2: Love it.\u003C/p>\u003Cp>Speaker 3: Me too. Convinced. But\u003C/p>\u003Cp>Speaker 1: yeah. Yeah. Okay. Problem solved.\u003C/p>\u003Cp>Speaker 3: We would have we would have to to, like, also have a a custom error block, I think, if you if we do that that way.\u003C/p>\u003Cp>Speaker 1: Yeah. Yeah. Just an operation that says make a new error, whatever that error is.\u003C/p>\u003Cp>Speaker 3: Yeah. And you can pass the a variable or a text or something. Yeah.\u003C/p>\u003Cp>Speaker 1: Which is then a funny new completely unrelated question. But if you have a block that says create an error, is the good path the error or is the bad path the error?\u003C/p>\u003Cp>Speaker 3: I I think you can you can do this.\u003C/p>\u003Cp>Speaker 2: There is no path. You don't get to you don't get to leave. That's the end. You're finished. Termination note.\u003C/p>\u003Cp>Speaker 3: I think the I don't I don't know. Like, I think there you called it, like, grade pass and, error pass, but I think in so in some time, like, on the if call, trigger, the error pass is not really an error pass. So\u003C/p>\u003Cp>Speaker 1: Right. Yeah.\u003C/p>\u003Cp>Speaker 3: That would be sense to be both, really.\u003C/p>\u003Cp>Speaker 1: Yeah. So in the create an error operation, both both are the same. It just calls both.\u003C/p>\u003Cp>Speaker 3: Yeah. And then\u003C/p>\u003Cp>Speaker 1: Now you have oh, woah. Woah. But if it calls both, now you have a way to do parallel split paths because it's now\u003C/p>\u003Cp>Speaker 0: a trick.\u003C/p>\u003Cp>Speaker 1: That's a discussion\u003C/p>\u003Cp>Speaker 2: for the problem.\u003C/p>\u003Cp>Speaker 3: I I think that would be nice, but yeah. That would be a lot for that,\u003C/p>\u003Cp>Speaker 2: more importantly, I want merge capability. Be able to rejoin back into logic.\u003C/p>\u003Cp>Speaker 1: Oh, good lord. Now you have to wait for both to be done and oh, boy. Okay. Let's definitely not get into this.\u003C/p>\u003Cp>Speaker 2: We've got we've got very divergent. We have about 9 minutes left, folks.\u003C/p>\u003Cp>Speaker 1: Yeah. This this feels like another 6 hour discussion if we go that route.\u003C/p>\u003Cp>Speaker 2: It is a very, very long discussion. Flows 2.0. We're already doing some research on that, so figure out what that looks like long term. Okay. Back to API errors.\u003C/p>\u003Cp>Do we have an idea of what our must have\u003C/p>\u003Cp>Speaker 3: should have?\u003C/p>\u003Cp>Speaker 1: Yeah. I think my my current going direction is is basically the on the role level, there is an allow list type configuration that allows you to say there needs to be a wild card that says do everything for def purposes and otherwise you can use the sort of filter structure that we have to say these are the error types that we allow you to see, and these are the and then based on whatever the error contains, you can filter it more granularly if you want to. That's for me closer to a should have than a must have. The error types is really the main the heart must have MVP. Because then because our error objects, they are, you know, they have additional extensions is what I called them right now, which is basically, a not found error can have a collection flag that says this is the collection that was not found.\u003C/p>\u003Cp>And therefore, you can use those flags inside of the filter as well to just make it real granular. And then for for flows specific specifically, just to reiterate what we just said, is you wanna have some sort of operation that it it's almost like the end of the line operation that should that sort of allows you to say, okay. This is what we're returning now, in the output. And then that could be that can have a couple of settings like, what is the status? What is the, what is the message?\u003C/p>\u003Cp>What's the body? And then that body can be generated using a, sort of create error operation. So I think that would be a nice setup if we have 2 additional operations. 1 to create an error that this custom, but uses the direct as error format and everything else to make it system standard. Therefore, you can use it in things like manual triggers to output an error to the client.\u003C/p>\u003Cp>And then we need to have some sort of operation that you can use at the end of your chain to say return the data and then just format it in this in a certain way, to really allow you a lot of control about the request.\u003C/p>\u003Cp>Speaker 0: Okay. Rest of energetic typing.\u003C/p>\u003Cp>Speaker 3: This is satisfying.\u003C/p>\u003Cp>Speaker 1: It was very nice to listen to.\u003C/p>\u003Cp>Speaker 3: ASMR.\u003C/p>\u003Cp>Speaker 0: Now we switch to the ASMR section of the Can you just show\u003C/p>\u003Cp>Speaker 1: this video?\u003C/p>\u003Cp>Speaker 0: Everybody quiet. Everybody quiet.\u003C/p>\u003Cp>Speaker 1: Oh, no. Oh, no.\u003C/p>\u003Cp>Speaker 0: Let's not do this. Okay.\u003C/p>\u003Cp>Speaker 1: Alright. This this feels pretty good so far. Did we we I know we we chatted a little bit before we hopped on this session. Did we have any other questions that we prepared for this? I think I think we have for most of it.\u003C/p>\u003Cp>Let me pull it off here on the second screen that is off camera. Why are we returning for threes? Went through that. How many lovelies? We kinda figured that one out.\u003C/p>\u003Cp>It could be an environment variable, but you kinda just just figure that out. And then business logic existing scripts expected specific error code. Yeah. I mean yeah. I mean, I think we're gonna we've answered all the points that we sort of note note beforehand.\u003C/p>\u003Cp>Speaker 3: I I just have and I I know I think someone in the chat is just saying, like, for what about the extensions? But I think for the extensions, we can just have, like, a a JavaScript function with with the same as the one in flows and just like custom chats.\u003C/p>\u003Cp>Speaker 1: Yeah. Because we do we we haven't really documented it too properly yet because it's sort of it was a little bit in flux still, but we made that direct as error specage with a function that you can use to make a new sort of, like, error a direct as error that direct as then like, what's the right word? Recognizes as a direct as error that you can then throw to make sure that no d ep and API behave in the same way as the system would so for for custom extensions that should sort of be solved, which is good. Put that direction. Yeah.\u003C/p>\u003Cp>With that being said, I think we have a for the first time ever in one of these sessions, we left it off with a pretty clear idea of what we actually wanna do. It's usually, we go way too deep and then have a sort of couple ideas. But\u003C/p>\u003Cp>Speaker 0: But it's we're trying. This is my\u003C/p>\u003Cp>Speaker 1: No. No.\u003C/p>\u003Cp>Speaker 3: I I was going to to say it's just because it's a refurbish version of what we already think of later.\u003C/p>\u003Cp>Speaker 1: Yeah. Well, you'd you'd think that about every time we do a feature request review, and it's it's never never the case.\u003C/p>\u003Cp>Speaker 3: Like, a 2 years old idea or something. Okay. Maybe Yeah. Not 2 years, but, oh, yeah. It must be.\u003C/p>\u003Cp>Like, I I see 2022. So yeah. Kinda.\u003C/p>\u003Cp>Speaker 1: How passionate people got in the actual feature request that somebody said. It's like, this is the single worst thing about I hate it so much. My whole month is ruined because Directus' errors are too strict.\u003C/p>\u003Cp>Speaker 3: No. But, sir, seriously, like\u003C/p>\u003Cp>Speaker 0: I wish. I wish. Yeah. I wish director's errors were the worst thing in directors. Then directors would be very nice.\u003C/p>\u003Cp>Speaker 1: Yeah. That's actually a good point. Yeah. Ouch. If this is if this is the worst problem to talk about, then we're doing real well.\u003C/p>\u003Cp>Speaker 0: Then we're really golden. We're good and then.\u003C/p>\u003Cp>Speaker 1: Alright. Well, we're we're slipping a little bit, but let's call it for now. I wanna say as per use, thanks everybody for watching. This will be going live on direct TV, which is direct. Io/tv.\u003C/p>\u003Cp>If you haven't seen it yet, book market sends to your friends, your family, everybody else, Share it on LinkedIn, Twitter, x, whatever you whatever your flavor of the week is. We'll be back in, I think, 2 weeks. Am I saying that right? Uh-oh. Should have prepped this.\u003C/p>\u003Cp>Yes. We'll be back in 2 weeks exact, June 6th with another one. Oh, hello. Another one of these party full sessions. But until then, thanks for watching, and we'll see you next time.\u003C/p>\u003Cp>Speaker 2: Happy coding.\u003C/p>\u003Cp>Speaker 1: Bye. Like and subscribe. Save me from the algorithm.\u003C/p>","Thank you for joining for this week's, session of request review. We have a fascinating topic for you, which is a little deeper than you think from the from the title. But thanks for joining. We hope you like it. This week's topic is about the strictness of API errors. So what does that even mean? Let's kick off with that. Everybody knows software doesn't work always quite how you want it to work, so sometimes you will receive errors in your application, and Directus is no exception. So as you might have noticed, we are very strict as of right now. So if you do something wrong, chances are high that you're just gonna see the message forbidden. Like, even when the message, even when the error isn't actually that type of error. So why do we even do that? Throw throwing the, throwing the conversation ball to mister Reich over there, please. To me? Oh, man. Why do we do that? Well, it stems from a security report back in those days in that if you, let's let's use let's use a, a a 4 zero four as an example because it's it's an easy one to think about. So let's say you're fetching a collection that doesn't exist. Right? So you should get a 404 because the thing doesn't exist, so there's no route for it. No endpoint. If we would return a 404 for those, you could theoretically extract what the data model looks like by brute forcing your way through the whole API and checking which things return a 404 and which things return a 403, which, you know, to some security minded folks or setups, that's an issue because you don't wanna expose what type of data that you're managing in this system for security reasons. Now this is immediately where we're kicking off with opinions. Let's go. Because you can definitely, you know, you you can see it from both sides. You can easily say, well, that just makes the developer experience very annoying. And that's also why I created this feature request the moment I built it in this way. But it makes sense from a security perspective. Right? It's a similar reason why, during login, it'll say wrong email password or account doesn't exist, but you never exposed which of the 2 it is just to make sure that people can't slate, you know, what users exist. And the same goes for, you know, individual items in records. It's like you wanna, you wanna make sure that you can't just say, okay, slash item slash xyz and just keep scraping all of the different numbers, and now you know exactly what records exist, etcetera, etcetera. Somebody rightfully called out, you know, nobody does that in this trustworthy world, which I think sort of concludes this discussion then. Just ignore it. Lying on the Internet? What? Who does that? No. What? No. No. No. We don't do that. And a perfect example. So leaking of information is a very, very big thing. You don't want to expose stuff that you don't have to expose, but as you just said, it would be nice, for example, like the most basic, setup when you're just developing locally, it would be nice if you could actually see what went wrong. Because currently, it's just okay, forbidden. Nope. No information for you. So then the question comes. Oh, La Prima asked, can we go on stage? Sure thing. I think so. But let's let's first, summarize a little bit. So There should be a button somewhere. The question is. Yeah. Yeah. Exactly. So, immediately the question comes to mind. Okay. So if you would have multiple errors in that case, a strict one which just says forbidden and another one that has more information, Okay. How many different errors would you even like to have? So are we happy with just 2? Because, I guess this is opinion alert or something, or maybe even a useful thing for many other people. Like, what what do you guys think about this? Like, how many error, different error, messages would be nice, would be good. Useful. I mean, that is very much a sliding scale, isn't it? Because you really go from, from like, just give me every single thing. It's like, oh, I was trying to read the articles collection that I don't have access to. So I'm gonna get a forbidden error that says, well, articles exist, but you can't read it. And that's because of this permission that you don't have. For development purposes, that's really nice. But if you go to production with that, it's tricky because now you're just exposing a bunch of information about, you know, your setup. So, yeah, I'd I'd say it it there's a large range there's a large range in in that. And even within that forbidden error, you know, we can, think about how we wanna return stuff. So one thing I've been working on recently as part of this the the rules and permissions sprint that we'll we'll ship in soon is the, is that forbidden errors now include a little bit more information as in, you know, the field x that you were trying to query, you know, you don't have access to it or it doesn't exist, but at least it's a little bit more explicit while not necessarily exposing the schema, but just exposing what part of your query is wrong. Right? Which is not leaking any of that information but still being a little bit more useful. Alright. That does sound exciting, to be honest. Like, it's such a developer thing, like, normal users. I don't know if how how many people care about this, but this is such a nice thing for developers. So I gather from your answer right now maybe 3 types of messages. At least from your answer, it sounded like 2 in the beginning, like, one strict one with a little bit of information, but that could be counted as 3, right? Like the very strict one with nothing, a strict one with a little bit, and another one with very much information. Are we happy with 3? No? Yes. Asterisk. There's one more tricky thing in that the output type signature of the errors themselves change between those modes Right? So for example, if you're trying to fetch a thing that doesn't exist, we want to return a 404 status code with a not found error message. If you then program your app around that, you know, that it on your front end, it shows, you know, something because it checks 4 Oh, it was a 404 status. Therefore, do this logic. Right? The moment you now change that option to be strict when you go to production, that messes up again. Because now it's returning a 403 with a forbidden instead of a 404 with a not found. Cricky. So Let's see. May maybe maybe after we dive in a little bit deeper, we have more clarity around this. But so far, like, I personally like would like, I think, 3 modes. Let let's call them levels. Do we call them levels, modes? I think levels. Right? Yeah. It depends. As per usual, it's great. It it depends on, you know, are we considering multiple different flavors at the same time, or is it just an on and off? Because somebody in the chat just now rightfully mentioned, maybe it's just a single production versus development flag that changes that. Right? It could also be a setting within the app. Maybe it's even a setting on a per collection basis that you wanna say, well, this collection is not as, you know, private as the other ones. So for this one I wanna have, you know, nice and friendly, messages. And for the rest, we wanna keep private. There's options. It's tricky. It's it's when it comes to a single sort of Boolean flag that says production yes or no, I would be a little bit worried about getting into a point where undoubtedly 3 months later, somebody will show up and be like, well, we kinda need something in between. You know? Exactly. You can't really Or more importantly, somebody develops in development mode thinking they've got those messages, then they move that code to production, and suddenly nothing works. Right. Which is gonna be a bit of an issue no matter what because Agreed. We're not talking about, you know, changing the output errors. You could consider those, part of, the the what what would you call it? The public API contract of the APIs that you're building against. Right? Because because, you know, in your intro then, you said stuff sometimes crashes, and that's just part of part of software life. Stuff crashes on purpose. Right? In this case, like when validation fails, it's we're still throwing an error, but it's not an unexpected error or anything else. It's, you know, we stop you from doing something on purpose, so we throw an error, which is, you know, a bit of a difference there as well, of course. Because when it comes to exposing internal server errors, it's a whole different question. Because right now we do a similar thing, right, where we say you just get internal server error. That's it. Like regular users don't see anything else. If you're an admin user though, that's today, that's the difference. It'll show, you know, the stack trace of the actual JavaScript error, with where in the source codes it it it happened. So but exposing internal server errors to non authenticated users, that's gonna be that could easily become an issue. Right? Because now you find some sort of vulnerability path or whatever that's you don't wanna expose that. Definitely. From the chat, somebody has a suggestion here. It says, what about setting a trusted IP address? So you can just say, okay. When I am the one from my home to do this, I get all the pretty errors and otherwise, I can't. I think you're gonna have a similar issue with, you know, def versus prod, strict versus friendly error types, and how do you handle those in your app. It's gonna be a tricky tricky thing to figure out. And then somebody else said, the only thing where that really isn't enough is when there's a delete constraint. Users trying to delete items, and they can be deleted because they're still a referencing item. Admins already get a little oh, yeah. That's yeah. Because right now that is technically, a database error that bubbles up. So it comes out as an internal server error that for admins, it'll show the the SQL error underneath. But for regular users, they just get a whoops, you know, internal server error. Also with the IP, since currently currently, IP access, is attributed to roles. Right? You you customize that on a role with that access. So we could use that, but it does, like, sound very, very, very likely to me that you wouldn't be interested in, like, giving this to every single user of that role. You would like to have something more granular than that. So how about this also comes from, someone from the community. How about we attach this to permissions themselves? So let's say this user can access this and that, and that user is authorized to receive better errors than other users. For example, like, the simplest example, like admin users. Right? Mhmm. Like, all admin users are allowed to get the best errors that you can, But users other users get strict errors. Okay? But that, yeah. That's that, like, I I I'm not really sure how exactly complicated that would be to do at every single point in the in the code base. Well, I mean, in in terms of implementation, how like, they currently were throwing forbidden errors all over the place, but the way this would work in implementation is right at the end where we return the error in the APIs, that's where the sort of translation layer kicks in. Right? So all over the code base, it should just throw the actual errors or the actual error types and the correct ones. So if you're trying to hit something that doesn't exist, it should throw a not found error. And then all the way at the end in the middleware stack in the error response handler, it should say, oh, if your current, you know, e n v setting for errors or if the, which I'm gonna call it, if the the error permissions or whatever you wanna call it are set to a certain state, then it translates them into a different way. I think that's the way that that will be implemented realistically speaking. Yeah. That does sound quite good. Adjust intercept as a middleware. Okay. You are allowed to see this or not. Okay. Yeah. Because we're doing that on a case by case. It's gonna be insane. Just trying to do it every single time that we throw an error. That's that's too much. We currently already do a similar thing, right, where the the error handling stack, we check was this a direct as error type or any other error and that chooses between internal server error or, sort of the the expected error output. Very reasonable. I think, if somebody from the chat also has another recommendation or anything, please let us know. There's quite a quite a bit of action today, which is pretty nice, actually. Like, seeing the chat scroll up and down is pretty nice. Somebody says, I can't explain it in the chat, but I'll try. That's lovely. Well, well, La Prema can if if you you guys are okay with it, La Prema can join us on the stage. Yeah. I'm down. There should be a button somewhere that just says, I can invite them or they can add they can request, but we can also do an invite. How is that often with these? They asked to speak earlier, so I'm inviting. Or have they does Kevin have that blocked in this event? Oh, boy. With this guy. Nope. Nope. There we go. I got the invite. I got Does it work? I got him up here. I'm trying to see if I wait. Weekend. Yeah. It works. Okay. It works. Hello. Welcome. Hello. As always, sorry for my accent. I'm I'm Frenchy. But, like, I Don't worry. You're you're chatting with a Dutch and a German, so we we know you need to know the guy. Oh, yeah. But your English is better than mine. I don't know if you remember, but we used to talk to Get A Rich for about the the error management system. And at that time, I was working for a company who uses directors, but I leave them. So after, what I haven't the time to to follow the the the case of the the server management system. But, I think the the main problem, we had is is, dealing with the the the, yeah, the, like, the granular system of a thing. And what I proposed at the that time was, like, adding in the the collection a a new, column, like, you already have, edit and all that stuff. And, in permission, column where you will have, like, 1 or 2 level no. Mostly 2, but 2 or 3 level of, strictness of the error. Like, do do do I wanna send them all on that collection or not? Because, yeah, I I think that would be the I'm sorry. But because it's user related, it would be better for the companies. Because, if you if you, like, have only one private, instance of directors, I think it's not a problem to throw error to people because they are part of the company. So if they don't, respect them, their permissions, you they can have, like, legal, troubles, and this is possible. But if you have, like, an public API and also a private one, On one instances, if it's just a mode, I think it won't fit because a lot of data can be really sensitive. And, same goes with the developer mode. Yeah. I I totally agree with, like, developing something, in a in a development mode and then going to production, it doesn't work. That would not be a solution for me. And, as well that the the this this production and development mode would be probably used somewhere else, at another time. And, in a long term view, that will just lean in people active in the development, mode every time and, development searching, be thrown everywhere. Yeah. That's right. It's such a tricky, and I I think if that, what was that nodes flag called? It node e and v or something as taught as anything is that you should not rely on a single dev and prod flag. Yeah. No. It's a good one. It's the the doing it on a collection level, on a permissions level for for collections, basically. It's it's a good angle. It's it raises some different interesting questions though as per usual. Everything does, Especially around requests that touch multiple collections at the same time, right? Where in REST, you can technically go, you know, nest it and then you end up with you could have an error that's thrown in the sort of nested layer of the same request. Or in a GraphQL request, you can technically request multiple collections at the root level at the same time. And then it gets a little interesting because now you can get, you know, a different error output from that request based on which of the data points of that request crashed, I guess, instead of, you know, the request itself, which opens a little bit of a different kind of words. Yeah. But I I think in some case, that's what we want. Because, in the the app I I was working on, we we used to to to get in that kind of problem where, we have people, who had access to certain company, but not certain order. And we retrieve that data in one block. And if some points fails, the the whole, request will fail, so we'll have to deal with that. And, what what we can do at that point, I guess, it's when you you have the error only in one part of the query, it's just returning not an error not an error code, but just like in the API, an error, tag in, that object and not all the thing. But I I know that would be, like, a big breaking change. So it can be hard to implement for some people. But, I guess, by default, if we just disable all the error, like, the the basic settings will just be, what Direct just currently does, it won't be a problem because the people who turn the the feature on, would know what they do or or would have ways to find out. I think that is You're you're just assuming that people know what they do. I barely know what I'm doing. And when when I add features oh, no. I think, it it there's a there's a new question hidden in there that we hadn't touched on yet, nor in our notes right now, which is if an error is thrown somewhere nested, let's say, you know, the let's use the GraphQL as an example because it's it's easy to reason about. Let's let's say you're requesting 5 collections of data at the same time. Right? And one of the 5 crashes. The question is, do you return an error and the whole thing failed, or do you return the data that worked and then have an additional error flag that just indicates this is the path of your query that didn't work? But return what you have and leave the rest. And, I think for that opinions. Yeah. I think for that, the the the best way to to do it is just, like, throwing the error only for what phase. Because, if we use these directives, I assume it's that we just want to cut the the front end. And and so, it it would be, most of the time, like, an interactive app or a single page application on or something of of that kind. And those kind of errors are really easy to to treat with that method. So and and, I I think most of the time, if we activate the the feature, it's what we want because, that will lead in, less, just big crashes, like internal server error proof and nothing worse. So I don't know. It's it's an interesting one because, somebody in Chet just now says it too. It's a rather fundamental question. But, you know, you see both in the wild, and both are kinda alright. It's it's, there's also an implicit difference between, you know, reading and write operations there as well, of course, that if you do a large nested update, you kinda want it to work as a single unit, as a single transaction to make sure that if, you know, part of the insertion doesn't work or part of the update, the whole thing shouldn't, you know, go through because otherwise you have that sort of half half done state. But for read queries, it could be different, but that really depends on, you know, what are you reading, and is it all tightly coupled, or are you reading multiple things at the same time for convenience sake? Because if I'm not mistaken, in in sort of the GraphQL spec, Like, it allows you to return partial data and then errors for other root fields. But I feel like if there's an error in a nested field in the query, it'll still fill that one sort of root query as a whole. But that is that is something I love to double check. Yeah. Yeah. I'm I'm I'm not sure either. Like, my my gut feeling was, no. I I I don't wanna say something wrong right now. So, okay. Another another suggestion from the chat. How about hashing the error plus a UUID, I assume that is? Only admins will be able to see what the error really is. Well, I mean, I mean, you are probably talking about, like, error codes. Like, for example, many different other apps just say, okay this is error 3, 1,172 or something. And then you can look up internally what that exactly means, But, you would still be missing out on, like, more information, like the stack trace or which which file failed or something something that you would have to encode in that thing, but I don't think that's always possible or even Or to just think very divergently. You know, an alternative approach is that you basically encrypt the whole error, save that to a database, and then only return the ID of the error that it was. So then the admin can log in and look up what the full stack was and and and and sort of decrypt it and see the whole the whole contents and everything else. But that that feels like a way to sort of persist to security and give more information at the same time. But it also feels like another skip and a hop and a skip for regular users to be able to just, you know, use do the errors that are output. That's an interesting thing. I have never thought about it like that. People do that? Do their apps do that? That's interesting. Yes. Some app does. I I used to work on one with the that. But I think it's the it's a problem because, like, most of the time, you you wanna informing in some way your user of what the error is because you don't want to to get someone just to to create a ticket to the IT support just every time they need, something, didn't work. But they just didn't have the permission in the first time. So, yeah, I don't think, it's a global sit solution. But maybe in some case in some case, if if we add, like, an EMV approach, that would be a a great option for some people that maybe need that. And I could see it work like that for for internal server errors. Right? Well, that's one of those things where we wanna hide away the stack trace no matter what for for the API output and then save it elsewhere. But at the same time, you know, saving every error could easily blow up in your face too, because if you just have, you know, an unauthenticated user that tries to access some data there's no permissions for, we don't wanna save those. Right? Because now you could easily just blow up a database by a public malicious actor that just spams your API knowing that they're gonna get errors. So there is there is definitely a a difference between sort of expected and unexpected errors there and which ones are saved. Have AI blocked them. AI solves all of our problems. Sure. Just do what I want. Parentheses open, parenthesis close. That should do it. Right? If only if only it would be that easy. K. So let's let's summarize a little bit because we have been, you know, throwing stuff out there. Maybe we should get back to reality a little bit. So, okay. So so let's say we have, we like to use the Moscow list. Right? The must have, should have, could have, type of stuff. So for must haves, let's let's get on the same train. Like like, what do we need? What what must we include? So, yeah. How granular do we make this? Oh, no. It has to be actionable, man. It has to be actionable. Okay. I I think the the point where we've been stuck the the last time was reached is, like, we we both agree on the fact we need a granular system, but we both must agree, on the fact that it would be so long to to make and to think and all that stuff that maybe it would be nice to have something, before that. But, Right. Yeah. Yeah. Yeah. But, I I think, that's, my my point was, like, directors, is a tool who is proposing things like a a different way than the other, tools in the same category. Like, you you just want it to to be a little more, accessible and, and usable. And not just, like, being that's the way you should do that and, just follow it. And and so I I think that's the problem there. If we did something else, than a granular system, you would, follow a path that doesn't feels like directors to me. And I don't know that that's totally your point, but but, I I'm not sure if that's what people need. Otherwise, they they will lose WordPress or or something like that. You you you sound like a team member in the sense of when the choice arises, let's do it the difficult way and make it unopinionated wherever we can. So no. I I do agree though. I do agree because it's it it is at the end of the day, this is a very opinionated thing, where for some apps, it's acceptable to have it a little looser or even want it. For some, you know, security minded folks, it's absolutely not, and we can't really make that decision for people at that global level, even though we have right now. Oops. Right now we are on the side of safety first, which, you know, it's it's defendable, but not ideal. Like, that's also why I created that feature request. It feels like decades ago, but it was 3 years. I think that the the main unresolved question now, and and that is sort of to answer your point, Dan, before we can really fill in the blanks for what are the must haves, is what is that ideal approach to how do you configure this? And then from from a developer experience first and then secondly does that make sense from an implementation perspective. Because doing it on the collection scope level makes sense for errors that are thrown on the collection scope, but not all errors are thrown within the context of a single collection. So that that that opens up, you know, new questions. And the opposite is true for doing it globally on the e and v level. Right? Because, you know, it's a low pretty much excellent point. It's not that black on white that you say make it insecure for the whole API. You know, you wanna do it on a case by case basis. That is a that's a that's just a statement with no no answer. No. I don't another, I don't know what to say it in English, but, like, another thing we, think about at time was, like, just, if if we take, like, the the point, if we need the error, it's just to treat them. One, thing, which, was a a little bit, between the two option is just like, having some kind of way to tell directors just, okay. This error, I want you to to throw it, and other errors I don't want. So that would be a little bit more granular, but that would be more like, just a techy approach. And and then not, being, so universal. Oh, Rike, you're muted, by the way. I hit the wrong mute button. That's what you get when you have 2. I I muted myself here, and then I and then I muted myself to unmute on this. Oh, man. I mean, we've only been doing this remote thing for about 5 years now, so I'm I'll I'll get it. Yeah. Because I what what I was trying to say is that that approach to say, okay, which error types do we expose? That is something we could do on the role level or soon to be policy level, And it is a system where we can still have one translation middleware that handles that error no matter where is thrown from, which is you if you compare that to the sort of collection idea that we had earlier, that one is a little trickier to implement in that sense, because now you have to make sure that you know what the collection context is for every single function call that may or may not throw an error. And then do that sort of translation piece, in in situ of where those errors are created, That makes sense. Whereas with, you know, an approach where you say, okay, not found errors can be exposed. If that's your setting, we can do that on one middleware at the end of the stack to just say, okay. Was the error Again, I'm not sure if if I can wrap my head around every single edge case, but, like, it does sound and may maybe I understood it a little incorrectly, but it does sound quite easy to mess that up. Like, it to to to bubble to bubble something up that does not get blocked then. And somehow I am exposing a random error that I have not thought about, like, on on an error level. It's it's the difference of an allow list versus a deny list is basically what you're saying. Right? So if you have a filter where you're denying individual pieces, it's easy to miss something. Whereas we do the opposite way where it's blocked by default and you allow more and more stuff, it's you can't really accidentally expose anything because nothing is exposed by default. Which is similar to what, Andre Andre Andre Key are. Just gonna assume that's how you pronounce it. Just said in the chat as well as, you know, if you really want it super granular, effectively just make it a rule based system, right. Where you run a filter against the error object that is thrown. Like if the error types is XYZ and the extensions contain blah, blah, blah, collections, such and such, then expose it, which will be one way to make that super granular, but also a little harder to configure at that point. Because I could, I mean, at the end of the day, I could also imagine that, you know, for just local development, that is gonna be a pain in the ass because now you have to configure so much stuff to get the errors out. I I think we what we could do is just like doing an EMV, variable just for real development case, then that system for the production mode. And later on, if we think it's too difficult or, then we can add the system, with the the permissions because that would make, like, stacks on top of each each other, with the the the the first step would be, like, the programmatic approach on the error throwing and all. And then, the the we could add later on just, instead of just throwing nothing if the error do not doesn't match, any case. Like, is it a permission error? Then is there a plain way to treat it? So it's made steps, and we have something, in between no permission management and on the other end, full thing. What this makes me think of is the we've got a client that we're playing around within a proof of concept. They're they're actually have an error table for their front end, and they've actually got translations. So they've actually got a translations table on that so that they can have the language the errors translated to multiple languages. If we went something like that where we have, like, a direct us errors collection, right, as part of the system tables with a set of, you know, with translation, then we have native translations capabilities right there. So if you wanna adjust the error message, so we could have the defaults. Right? The I don't know. We've got a dozen or whatever that we have now, with some variable support maybe even there so that if you wanted collection information or you want that stack trace information to percolate up, you could. And you could determine at least in the short term, we can we can avoid the permissions for the short term, give you just control over your error messages. You wanna show something that we don't do natively by default, well, you can override the messaging as a thought. And I think that would actually help with things like flows as well. We have the same problem in flows. We can't percolate an error message to the user even though we're throwing and controlling the erroring of the of the workflow. Potentially, if we think about it in that context as well where we've got we could use that same error structure Mhmm. Idea. Not a not necessarily the solution or the right solution, but here's the new rabbit hole, multilingual error output for the API. Oh, boy. Oh, boy. I mean, sort of it's an interesting angle to to sort of noodle on a little bit. The the general idea of errors as it stands right now is that you basically return an error code that is one of the standard sort of known error types. And then in whatever app that you're building, you can basically just say, you know, oh, if the error is not found, then use my own translations and my own front end and all that kind of stuff. But yeah. I mean, it it does raise I mean, we we know we need some sort of translations in the API no matter what for email templating, which has been that'll be a fun discussion for another day. But, yeah, it's it's an interesting idea to have errors as a sort of system collection that you can then modify. Although, we would have to hard code in what the keys are because otherwise, we don't know what error to throw still. That's a tricky one. But we can hard set the keys. Right? And then if you create your own key or we lock the table, you're not allowed to add to it, you know, without core functionality. Or, again, you've got your hard set core pieces. Core knows what they are, and they can't be changed. Their primary key or whatever we wanna do around that Yeah. Gives us that level of flexibility. But then if users want to code other error messages for other applications and data, they can actually still maintain it in all in one place. I'm not smart enough to solve it. I just I just know that's an implementation I saw recently where we're for a front end application and the error messages that they wanna generate for that front end application. They're actually doing that in a set of tables. Kinda cool. Oh, I think, like, lots of interesting things that we can do, but I think we're still stuck on where do we want to control this or configure this. Let's just go to the one extreme. I mean, very That would be the easiest thing. You have to And you get errors. Okay. Done. That's the easiest thing that we can do. That leaves a couple of things that we would like to have open. Right? Like, we ideally, or at least in my mind, it would be really useful if, for example, like, admins actually get no like, good, context rich errors and normal users don't, which would you know, it's not really that possible with that environment variable approach. Yeah. So so it feels to me like out of everything that we've discussed so far, the direction where you effectively set up and allow list of errors on the role or soon policy level gives you, so far everything that we sort of want to achieve. Right? Where you can say, okay, you can allow one admin role to just see all of the errors with some sort of wild card flick, accept everything, allow everything. You could use the filter rules to be more granular that you could say, oh, if the error contains a collection, flag, then filter against that. So So you can you can do sort of the collection by collection, filtering. And, yeah. You you get to choose role by role, so make it admin only or make it make it, a different, you know. So the users can only see the granular errors for a a collection that's already public or something like that. The the only problem there, it's for the flows. But, like, what do we do with them? Mhmm. Mhmm. For flow, that's a grant. Like Because I I think we could have close the The same system, like, in the in the settings of the flows. But is it gonna be the same way? But we could also say that if you trigger a flow, you are a user or the public role. So therefore, the same settings of your role should still kick in. Yeah. Yeah. Because they access the data. So that's good point. But, like, what if you want to, bypass a permission on a specific flow? Like, then you'll because in some case, may maybe it's the the old data you don't want to access, but just one field you want to show someone? As long as the filtering's happening on the API level, that's still all API under you know, flows is still API under the hood. So if you uplift an operation, read CRUD, whatever, this the erroring although, I guess, it's still gonna then it would percolate to that user type I think the the the underlying question is what if you want that error to be returned no matter what the settings are in your flow? Because your flow is effectively a custom endpoint or, you know, hook or whatever. Yeah. That's what I think flows when you're throwing an error inside flows, that should be its own code. And if you throw a custom message back to that, then you should be able to get that custom message. That's the key thing that I always want. Right? Or that it the the initially, I thought was, oh, I'm gonna throw an error here and I wanna I wanna send back to the user, you know, you did x y z wrong, you know, or this this isn't allowed because you failed to give me the appropriate information instead of a internal server error, which means nothing. You know, I can't I can't tell the user what they've done wrong or what they're violating that I'm validating inside of my flow. I can't say, mister user, you must have your status in the right thing or, you know, these three fields have to be populated to be able to use this or And and that's where the allow list approach for error filtering gets tricky again, because now you need to make sure that you allow list your custom errors from your flow in the policy or in the role, so it makes its way to the app. Or I think, again, flows the error code or codes that are returned by flows, that there is a specific code, and you simply take the message that the user's providing out of the out of the, you know, exception. Yeah. Yeah. Yeah. Right. And so it's it's a known code. We know what it is. Right? We know Oh, so here's here's another thing we haven't really message. Yeah. Another thing we haven't really touched on in with regards to flows is that, you know, you have a very explicit error handling path that every operation has a okay. If the error fill or if the operation fails, do something else. So maybe there should just be an explicit operation that says return the error, and otherwise it just defaults to, you know, hidden by default, but then it becomes it it no longer is a a permissions or roles thing. It just becomes a flow thing, where you just very exclusively have to end your flow with return the actual error. And at that point, it's up to the, you know, the admin to configure that flow and and whatever they want to return. Love it. Me too. Convinced. But yeah. Yeah. Okay. Problem solved. We would have we would have to to, like, also have a a custom error block, I think, if you if we do that that way. Yeah. Yeah. Just an operation that says make a new error, whatever that error is. Yeah. And you can pass the a variable or a text or something. Yeah. Which is then a funny new completely unrelated question. But if you have a block that says create an error, is the good path the error or is the bad path the error? I I think you can you can do this. There is no path. You don't get to you don't get to leave. That's the end. You're finished. Termination note. I think the I don't I don't know. Like, I think there you called it, like, grade pass and, error pass, but I think in so in some time, like, on the if call, trigger, the error pass is not really an error pass. So Right. Yeah. That would be sense to be both, really. Yeah. So in the create an error operation, both both are the same. It just calls both. Yeah. And then Now you have oh, woah. Woah. But if it calls both, now you have a way to do parallel split paths because it's now a trick. That's a discussion for the problem. I I think that would be nice, but yeah. That would be a lot for that, more importantly, I want merge capability. Be able to rejoin back into logic. Oh, good lord. Now you have to wait for both to be done and oh, boy. Okay. Let's definitely not get into this. We've got we've got very divergent. We have about 9 minutes left, folks. Yeah. This this feels like another 6 hour discussion if we go that route. It is a very, very long discussion. Flows 2.0. We're already doing some research on that, so figure out what that looks like long term. Okay. Back to API errors. Do we have an idea of what our must have should have? Yeah. I think my my current going direction is is basically the on the role level, there is an allow list type configuration that allows you to say there needs to be a wild card that says do everything for def purposes and otherwise you can use the sort of filter structure that we have to say these are the error types that we allow you to see, and these are the and then based on whatever the error contains, you can filter it more granularly if you want to. That's for me closer to a should have than a must have. The error types is really the main the heart must have MVP. Because then because our error objects, they are, you know, they have additional extensions is what I called them right now, which is basically, a not found error can have a collection flag that says this is the collection that was not found. And therefore, you can use those flags inside of the filter as well to just make it real granular. And then for for flows specific specifically, just to reiterate what we just said, is you wanna have some sort of operation that it it's almost like the end of the line operation that should that sort of allows you to say, okay. This is what we're returning now, in the output. And then that could be that can have a couple of settings like, what is the status? What is the, what is the message? What's the body? And then that body can be generated using a, sort of create error operation. So I think that would be a nice setup if we have 2 additional operations. 1 to create an error that this custom, but uses the direct as error format and everything else to make it system standard. Therefore, you can use it in things like manual triggers to output an error to the client. And then we need to have some sort of operation that you can use at the end of your chain to say return the data and then just format it in this in a certain way, to really allow you a lot of control about the request. Okay. Rest of energetic typing. This is satisfying. It was very nice to listen to. ASMR. Now we switch to the ASMR section of the Can you just show this video? Everybody quiet. Everybody quiet. Oh, no. Oh, no. Let's not do this. Okay. Alright. This this feels pretty good so far. Did we we I know we we chatted a little bit before we hopped on this session. Did we have any other questions that we prepared for this? I think I think we have for most of it. Let me pull it off here on the second screen that is off camera. Why are we returning for threes? Went through that. How many lovelies? We kinda figured that one out. It could be an environment variable, but you kinda just just figure that out. And then business logic existing scripts expected specific error code. Yeah. I mean yeah. I mean, I think we're gonna we've answered all the points that we sort of note note beforehand. I I just have and I I know I think someone in the chat is just saying, like, for what about the extensions? But I think for the extensions, we can just have, like, a a JavaScript function with with the same as the one in flows and just like custom chats. Yeah. Because we do we we haven't really documented it too properly yet because it's sort of it was a little bit in flux still, but we made that direct as error specage with a function that you can use to make a new sort of, like, error a direct as error that direct as then like, what's the right word? Recognizes as a direct as error that you can then throw to make sure that no d ep and API behave in the same way as the system would so for for custom extensions that should sort of be solved, which is good. Put that direction. Yeah. With that being said, I think we have a for the first time ever in one of these sessions, we left it off with a pretty clear idea of what we actually wanna do. It's usually, we go way too deep and then have a sort of couple ideas. But But it's we're trying. This is my No. No. I I was going to to say it's just because it's a refurbish version of what we already think of later. Yeah. Well, you'd you'd think that about every time we do a feature request review, and it's it's never never the case. Like, a 2 years old idea or something. Okay. Maybe Yeah. Not 2 years, but, oh, yeah. It must be. Like, I I see 2022. So yeah. Kinda. How passionate people got in the actual feature request that somebody said. It's like, this is the single worst thing about I hate it so much. My whole month is ruined because Directus' errors are too strict. No. But, sir, seriously, like I wish. I wish. Yeah. I wish director's errors were the worst thing in directors. Then directors would be very nice. Yeah. That's actually a good point. Yeah. Ouch. If this is if this is the worst problem to talk about, then we're doing real well. Then we're really golden. We're good and then. Alright. Well, we're we're slipping a little bit, but let's call it for now. I wanna say as per use, thanks everybody for watching. This will be going live on direct TV, which is direct. Io/tv. If you haven't seen it yet, book market sends to your friends, your family, everybody else, Share it on LinkedIn, Twitter, x, whatever you whatever your flavor of the week is. We'll be back in, I think, 2 weeks. Am I saying that right? Uh-oh. Should have prepped this. Yes. We'll be back in 2 weeks exact, June 6th with another one. Oh, hello. Another one of these party full sessions. But until then, thanks for watching, and we'll see you next time. Happy coding. Bye. Like and subscribe. Save me from the algorithm.","ddd81e25-4534-4c87-af7e-f25e6832a128",[208,209,210],"146b3a54-018d-4b79-8190-a52ea1afe20c","1d6a4684-3c50-4af4-bc64-3ee590527d12","e13ded04-2af2-4552-a441-a39c373fe008",[],{"reps":213},[214,270],{"name":215,"sdr":8,"link":216,"countries":217,"states":219},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[218],"United States",[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,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269],"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":271,"link":272,"countries":273},"Michelle Riber","https://meetings.hubspot.com/mriber",[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,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,251,462,463],"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",1773850447398]