 All right, the nice lady has told me that we are recording. I will usually have my slides up and running when we get started, but I have forgotten to do that today. So that's terrible of me. So welcome everybody, as you are joining, please do, if you need to get your local WordPress environment set up and ready if you are wanting to code along with me today. We don't have any plugin downloads for today because I'm going to be writing the code from scratch. It's just a few snippets of code here and there. But we're also going to be using the postman testing tool. So if you need to download that, please go ahead and download that so long. But I will give folks an opportunity to download that while we get started. And then if you are ready and you're ready to rock and roll, please do consider letting us know in the chat where you are joining us from. And maybe something interesting about your country, your state or city that you're in today. Maybe you can tell me what the weather is like. It's extremely hot in Cape Town at the moment because we're in the middle of summer. And so, yeah, let us know in the chat where you're from and something interesting if you'd like to. While everybody's doing that, I'll introduce myself. My name is Jonathan. I am from Cape Town in South Africa, as I've just mentioned. I used to write code for a living. Now I do these things. I present tutorials and I do all those kind of things. And I work with the training team. I'm a full-time sponsor contributor at a company called Automatic, working with the training team. And I present all of these things, these tutorials, these workshops, sorry, creating tutorials, lesson plans and various other training team related things. So that's my little bit. Today, we are talking more about the WordPress REST API. The last few weeks that has been my focus for these Thursday Let's Code workshops. We have, we started out a few weeks ago with the process of understanding how the REST API works and sort of how we can access the REST API using the background JS client. And then we looked at starting to extend the WordPress REST API a little bit more. Last week, I think it was, we spoke about custom fields, we spoke about authentication. We started using the Postman app. Our walk says Orlando, Florida, sunny and low 60s Fahrenheit this morning. I am going to cheat out outrageously and do a quick Google search on what 60 Fahrenheit is in Celsius. 15 degrees, okay, that's not too hot at all. I wish my brain could translate that much quicker than what it just did. Anyway, so today we're going to be looking at REST API creating custom routes and endpoints. This is a very broad topic because there are many different ways that you can create custom routes and endpoints. Well, when I say that there's one specific way that you register a custom route and then define the endpoints, but there's two different ways that you can code the interaction or the functionality around it. Today, we're going to be focusing on one way and then next week, we're going to be focusing on another way. Today, we're going to be keeping it super simple, but hopefully if you are looking at creating your own custom endpoints, this will give you enough knowledge to start building out your own endpoints and how to work with them and how to interact with them. So that are the goals. Those are the goals, so should I say for today. Erin says, hello from Sacramento, California, 39 Fahrenheit. So that's even colder there. But if I'm not mistaken, California is probably something like five or six AM in the morning. So welcome, those of you who are in the US who are joining us super, super early. Okay, a couple of announcements as always. So first of all, welcome to everybody and thank you for Joshua who's co-hosting with us today. This is Joshua's first time co-hosting. So thank you. I appreciate you being my co-host today. We are presenting in focus mode, but please do feel free to enable your video if you would like me to see your face. Focus mode just means that Joshua and I can see your video but you can't see each other's video and the goal there is just to prevent any kind of Zoom bombing issues which has happened in the past. At any point in time, if you can't see my shared screen. So right now, if you can't see this announcement slide, please let me know and I will just disable the screen share and re-enable it. Basically, there's an issue that we picked up with the Zoom client on Linux servers and screen sharing. So if you ever can't see my screen, my shared screen, let me know in the chat. As always, welcome to ask questions. You're welcome to either post your questions in the chat or unmute if you want to ask questions. I do just ask, if you do unmute to ask a question and you have any kind of background noise, please consider muting yourself again because the background noise can sometimes be disruptive. I do pause as we go through these sessions to allow for questions, but if you do have something specific about what I'm doing on screen, maybe you can't see the code, you're busy copying the code or you have a question about a part of the code, feel free to post that in the chat. Joshua will keep an eye out for those and he will let us know if he sees anything like that. Joshua, just to let you know, I muted you earlier because there was some background noise so you'll just have to unmute if you need to talk, okay? Sure, John, thank you. No problem, okay. All right, then the last reminder to make sure your local install is ready. So if you're going to be coding along with me today, you just need a local WordPress install using any one of the tools that are out there. You will also need some way to access your database just to create some test data early on. I'm kind of pinching my admin tool or some form of accessing the database. It's not a huge requirement because we will be creating an endpoint to actually create data. So if you don't have one, it's not the end of the world. And then if you want to install Postman and get that installed and ready to go, we're going to be using that to test our custom routes. If I'm going too fast, please do let me know. I've been doing this now for almost a year of these online workshops. So hopefully I've learned to not speak too fast. Hopefully this year has given me that experience. And the breaking for Sips of Water helps. It forces me to slow down. So I'm going to do that now. And then lastly, as always, I will be posting the session to WordPress TV afterwards. I will then link the recording to the session in the Meetup event chat. If you're looking for more WordPress focus content, you can go to learn.wordpress.org. That's all the tutorials, lesson plans and these workshops are listed. And then if you have any follow up questions or feedback specifically about this workshop, I know that not everybody uses GitHub, but if you do use GitHub, you can browse to this URL, which I'm going to copy into the chat right now. And you can post any kind of questions around the session. If you're watching this on WordPress TV afterwards, you can post questions or you can get hold of me through my website, which is at the bottom of this slide here. I'll just copy this over. The website has my email address on there. So you're welcome to reach out to me via email as well or my social media is on that website as well. So you're welcome to reach out to me that way. Okay, now that all the announcements are out of the way, let's get on to what we're going to do today. So we're going to learn today how to register a custom WordPress REST API route. Once we know how to register a route, we're then going to learn how to register different endpoints. And if you remember from some of the earlier sessions we did, a route is the specific URL that we access. And the endpoint is either the get request or the post request or the delete request to perform certain actions. So the URL stays the same, but whichever method, HTTP method you send it determines which code gets fired and which endpoint triggers and the action of the endpoint. We're then going to learn how to access request data in your custom route. So if somebody is posting data to the route or you're posting data to the route and various other things, we can learn how to do that. We're going to chat briefly about protecting your routes requiring authentication. In last week's session, we discussed how you can set up an application password to force users to be logged in. So this is going to be how we create our custom routes to require a logged in user or leave it as public. And then we're going to talk about adding and using path variables. And I'll show you what a path variable is and how it works and how you can use it in your custom routes. And if you're wondering why this might be useful, one way that all of this can be useful is if you want to look at replacing your admin Ajax requests with the REST API routes and endpoints, this will be the perfect workshop for you. Using the REST API in my opinion is easier. There's less to set up and less to worry about. And in my opinion, just works better. Okay, and our specific objectives today is we're going to be creating a very simple form submissions REST API plugin. So what the plugin will do is it will create a custom table to store form submissions. And it's just going to be an email and sorry, a name and email field. If you've never worked with custom tables or plugin before, I'm going to share the code with you on how that works, but I'm not going to dive into it too much. But you can go and read more about it in the handbook later. Then we're going to register three different, one route with three different endpoints. And I just realized this should be endpoint and that one should be endpoint. But anyway, we'll get the idea. So it's one route with three endpoints. The first route will be an endpoint to fetch submissions. The second will be to post submissions. I'm going to just update this on the fly so that it covers both. And then the third one will be to fetch a single submission. So we'll be able to pass an ID in the root URL to fetch a single submission. And that should cover most of the general use cases that one might use custom REST API endpoints for or routes and endpoints for. Okay, so that's our goals for today. I'm going to take another break if anybody has any questions on all of that that we're going to be covering today. If not, we will get into the coding and the things while I refresh my voice quickly with another sip of water. Yeah, I think everybody's good to go. So we can crack on. All right, so I'm going to start by, in my LearnPress environment, I'm going to start by creating a brand new plugin today. So I'm just going to create a folder and I'm going to call it WP Learn Form Submissions Conspell API. And inside of that folder, I'm going to create a single PHP file. And I'm just going to call that WP-LearnFormSubmissions-API. I don't mention the hyphens all the time even though that's part of the file name. There we go. WP-Learn-FormSubmissions-API.PHP. And then I'm going to open my PHP tags because this is a PHP file and everything we're going to be doing is in the single PHP file today. So there's no JavaScript today. We're going to be using Postman to test the endpoints. So we won't be using any JavaScript to test them and we'll just be setting things up in PHP. Okay. As we all know, as many of us do know, some of you might not have built a plugin before, but to register a custom plugin, you need a simple plugin header and you do that using the PHP comments block. So this is what it looks like. It's going to be a forward slash and two stars and you'll see VS Code automatically closes it for me. So I'm going to pop that down onto a new line. So that's kind of what it looks like. And then I'm just going to give the plugin a name. So it's plugin name, colon. Yes, that's the colon. And we'll just call it WP-LearnFormSubmissions-API. I'm going to copy this code out if anybody needs to grab this from the chat. That's what it looks like. In fact, let's add the PHP to it as well. So that's all we have right now in our file. Just for the sake of making my life easy, what I also like to do is I like to add the version. I do this whenever I'm building example plugins or whatever, just so that I can start with the version 001 and then I increment the version as it goes along. But that's all I'm going to do there. I'm not going to worry about the author and the author, your line, the text domain and those things today. I just need the PHP file, the plugin name and for my purpose is the version. Now, with that created, if I switch on over to my learn press, my local learn press, learn press dot test, oh, lean press, no, not lean press, learn press. And I log into the dashboard there, which I don't think I've done yet, so let me do that. And in case anybody's wondering, my username and password for my local sites is simply just admin password. I keep it super, super simple because nobody's ever going to access these sites. In my plugins list, I now have a learn form submissions API. There it is. It's sitting on version one right now, which is what we want. And I can activate this plugin and it's not going to do anything. It's just going to be active, but at least now I know it's whatever I do, it'll start working, it'll start doing things. So it's all good to go. There's no errors, so I'm happy with that. So we're ready to rock and roll here. Okay. So creating custom endpoints is a case of using a simple function called, we just find it register rest root. And if you have a look at the last slide in the slides, if you've grabbed them from the meetup event chat, that's the URL we're looking at. And I'll paste it in the chat. It's register rest root. And there's some things that we need for it, but let's keep that documentation open for later. In the race API handbook, there are also two chapters that I can recommend you read. The one is the chapter on adding custom endpoints, which is mostly what we're going to be focusing on today. And the other one is a chapter on roots and endpoints. Now a lot of what exists, I'll paste that into the chat as well, a lot of what exists in these two chapters is duplicated across these chapters. So the adding custom endpoints goes through some basic examples, explains the basics. So it explains the namespacing, it explains the arguments, the return value, the permissions callback, and all of those things. It also explains something called the controller pattern. The controller pattern we're not going to cover today, I'm planning that for a future workshop. Today we're gonna be focusing on the namespace and the arguments, the return value and the permissions callback. But this is a good document to read through to understand how things work. And a lot of the code we're gonna use today is coming from this handbook doc. The roots and endpoints one covers roots and endpoints and how they work and how the URLs work. So, and then it's also, again, talks about namespaces. This document covers the path variables. So if you're looking about how the path variables work, it's in that document. It talks about the different methods, it talks about the different callbacks. And then it also talks about validation and sanitizing of arguments, which we're not covering today, because that is another separate topic. So I do recommend if you're learning to extend your REST API endpoints, you're learning to build custom ones, that you read through both of these pieces of documentation to understand how they work. Okay, before we do any of that though, we need some table to store this data. Now, I could have used a custom post type today. So we could have registered a custom post type, but then the problem with the custom post type is if you register a custom post type, and let's actually go and have a look at the, I think it's just registered custom post type, or it's registered post type, I think it is. Yeah, registered post type function. I'm gonna pop that in the chat if you wanna open that on your side. The register post type function is the WordPress function that allows you to register a custom post type. We used it last week to register a book. And if you scroll down and you look at the arguments array, you will see that somewhere down the bottom here, let me just find it quickly. There is a show in REST argument. And if you specify show in REST to true, then your custom end point, your custom, sorry, your custom post type will just be available in the REST API. There will be get end points, there will be post end points. So you don't have to write code, it's all gonna just work for you. So you're only really gonna use a custom end point if you don't have a custom post type or if you want to do something over and above what the default functionality around the REST API how it works with custom post types. So that's why I thought let's use a more simple example where we register a custom table. And one of the most common examples I can think of is writing a forms plugin. A forms plugin. Yes, there are plugins out there that do it for you. But I personally find that for my own purposes generally when I'm building form plugins for myself, the form plugins that exist are generally overkill. I generally just want two or three fields on a contact form on my website. And I wanted to post some data some way to store it in the database and then maybe trigger an email to me to say that it's come through. That's all I need. And so my options are either using something like Jetpack which has a built-in form block I think it is which works pretty well or something like ninja forms, gravity forms all of those forms plugins but they generally are just overkill for my purposes. So I tend to actually build my own custom plugins. I'll either use a short code with a simple form and I've reused my own form plugin codes so many times because it's just quick and easy and I know what it's doing and if it breaks, I don't have to bug anybody. So I find building these kind of small niche plugins very useful, especially if it's for your own use or for client work, I had a customer a while ago that I used to work with they built a series of custom widgets that they were using for restaurant blogs. The most of their clients were restaurant bloggers sorry, not restaurant, sorry, food blogs and they had certain widgets that they just reused every time and they've had this custom code and they just let it down every time and it was great. So that's why we're building a form plugin today. Okay, so we said in our objectives that we want to create this form submissions REST API plugin and the first thing that we want to do is we want to create a custom table to store form submissions. Now in WordPress, if you've never seen it before there's this thing called DB Delta. So let me share that handbook doc with you as well. And basically DB Delta is a function that you can call to either modify to sorry, to modify the database based on a specified SQL statement. If you've never seen SQL before, don't stress I'm gonna show you some example code today I'll share it in the chat as well. But this is basically how you can go about creating a custom table. I'm not gonna code this. I'm basically doing what I do. Every time I use this code I'm stealing it from another repository that I have somewhere. So I always reuse the same code but basically it looks like this. I'm gonna share it in the chat as well. So you can copy paste it into your plugin if you would like to. And what you're basically doing is you're registering an activation hook. So we're gonna have to deactivate and reactivate the plugin for this to fire. You're saying this file. So this is the file that we're in now. When this file is activated so this plugin specifically then run the WP learn setup table function. So it's similar to registering a normal hook or filter. But we pass in if you look at the code for register activation hook. Let me find that quickly. Register activation hook, there it is. I'll pop this into the chat as well. The first parameter is the file. So it has to be in the file that you are filing. And the second one is the callback. So a lot of times when you see activation hooks, sorry, I'm missing my closure. A lot of the times when you'll see activation hooks being registered, it's in the main plugin file in the core plugin file. Sometimes folks will hook it up within a class but generally it's in the main plugin file because you need to pass the file in. And then it says when the plugin is activated when this file, this plugin is activated, run this code. This code is the function over here. And what it does is it gets the global WordPress database variable or object, if you will. It uses that to get the prefix of the database because the prefix could be different depending on how the install, the WordPress install was run. And then it depends the name of your table. In our case, it's form underscore submissions. Then we write the SQL query. If you've never seen SQL before, SQL before don't stress, but it's basically create table. You give it a table name and then you specify fields. In our case, we want an ID field, which is going to be our main integer, our main primary key. It's how we're going to look up data. We're then going to have a name field, which is going to be a voucher or a variable character and an email field, which is also going to be a variable character. And then at the bottom, we specify the primary key is ID. And that's what your DB Delta SQL query needs to look like. And then you require this file, this upgrade.php file using the absolute path variable. And the reason we do this is because this is registering on activation of the plugin, which happens before a whole bunch of other things happen. So we need to include this file specifically. And then we run DB Delta and we run the SQL query. Now, this workshop is not about how this works. So I don't want to dive into this too much about how it works and how it runs and all those things. But very quickly, are there any questions around how this code works? Anything that isn't super, super clear for you at this point in time. If there is, let me know in the chat. Otherwise, we will deactivate and reactivate our plugin and make sure that the table has been created. Yeah, while folks are thinking if they have any questions, I am going to pop on over to my local WordPress site and I'm going to check in the database if that table is not there because I may have been doing some tests. No, not WP Admin. HP, no, this is not gonna work. I don't think, let's see if this works. Hey, it does work. Because I may have been doing some tests on my local install. So let's see, there's the learn press table. Okay, yeah, there is the full sufficient table. I've already run this code on this database. I'm very quickly going to delete this table so that we can actually see it be created. Okay, I don't see any questions. I think everybody has either seen this code before, they understand what it's doing. So this is our learn press database. I use PHP Admin locally. It's a free PHP-based MySQL database navigator. I've used it for years. So I've just stuck to it. There are others out there. There are things like, I think it's TablePress or TablePlus or something. Many other tools that you can use. Most local WordPress environments come with either PHP MyAdmin or DBAdmin or one of these things. So there should be a way that you can access your database. And if you go to your database for your site, you'll see something along the lines like this. If it's a clean install, it'll be a simple set of data. If you've added plugins that are created to tables, you'll see more tables, should I say. But as we can see there now, I've deleted that form of submissions table so we can test whether this code is working. So I'm going to deactivate the plugin because I need that activation hook to run. And now I'm going to activate it again and it hasn't thrown any errors, which is a good thing. And if I refresh my database, I should now see that that table reappears. There it is, WP form submissions. So that code has run, it's created the table. If I click on the structure, there is my name and my email field. So that's all working perfectly fine. Also while I'm here, and if you're doing this along with me, if you've run this code and created these tables, let's add some test data quickly. So I'm just going to click on inserts on my thing. I'm going to leave the ID field empty because it's going to auto-generate it for me when I add the name and email. And I'm just going to name, going to add my name and my email address, which I don't mind sharing with you all. It's Jonathan Bassenger at gmail.com and we hit go. And it runs the query to insert the data. You'll see it passes null to the ID, but the data is set up. That's already created the ID field as number one. There it is, ID one, Jonathan Bassenger and my email. So I've got some data that I can play with. Okay, so that's created my table. Now I want to register a root to fetch this information. So now I can look at using register-rest-root. Okay, so register-rest-root, let's just actually copy this whole line of code out of here quickly. This is in the register-rest-root function, which I pasted earlier. I'm going to copy this as it is. And I'm going to pop this, where are we now? In my VS Code window over here. And that's what it looks like. So the things that it needs, it needs the namespace, it needs the root, and then it needs the array of arguments. Okay, it doesn't need to override specifically unless you're using that property. We're not going to chat about that property today, but these are the three fields that it needs to be able to work. The other thing that it needs is it does say specifically in the documentation, and let's pop back on over to the adding custom endpoints documentation. If you scroll down to, where is it? Here we go. Just underneath the bare basics code, it says to make this available via the API, we need to register a root. This tells the API to respond, et cetera, et cetera. We do this through a function called registerRestRoot, which should be called in a callback on REST API net to avoid doing extra work when the API is not loaded. So what that means is, if I just register this root as I have done here, every time a WordPress request is made to any post or page or dashboard or whatever, it's going to run this code. That's not ideal because I only want my root to be run when the REST API is requested. So I make sure that I wrap my REST API registration, my root registration, inside a callback that is hooked into the REST API net action. So the way that works, if you haven't seen actions before, there is a video that I did a while ago on how to use actions and filters in WordPress, but it's essentially add action as the function. And then we pass in the action that we want to hook this code onto, and then we pass in or we create at least a callback function. So in our case, we're going to go to WP, learn and let's just say register roots. Just keep it nice and simple. So what that'll do is when the REST API action runs within WordPress execution, it'll call our code and our code can then be used to register the roots. So now we need to create that function. So we go function, register, learn, register REST roots, open that function, open the curly braces for that function code, and then we put our register REST root inside of that. So that's how we register roots. There's a four there, which is why I'm getting an error, getting an error there because I haven't put the semicolon in. I love the fact that coded is to tell you when you're writing bad code. So that's what the code looks like. So we always have to use REST API in it action. We always have to hook into it and then we create our custom function and then we inside of that function, we register our roots. Okay. Any questions on all of that while I take a sip of water, otherwise we'll talk about namespaces, roots and arguments. Yeah, there don't seem to be questions. So let's look at an existing API root. I have got my work press collection in postman setup. You'll see I was testing this the other day, but let's talk about, let's actually do another one. So we make things a little bit simpler. So let me just create a new request here. You don't have to do this with me. I just want to show you this and we'll make this a get posts request and we'll say get and then let's use learn press. And if you've seen the REST API before or you've watched any of the videos, you'll know that the REST API is WP JSON, WP V2 posts. If I remember correctly, yes, that is correct. Okay. Let me make this a little bit bigger so we can break down what's what. So in any WordPress REST API root, in other words, the URL, there is of course the domain that the site is running on. And then there is the WP JSON part. So that's the fact that we're now in the REST API. The next part of that URL is the namespace. So what the namespace means is all of the roots registered in this namespace belong to this namespace. It is recommended to not register your custom roots under the WP namespace because that is reserved for core roots. So if you register a namespace, sorry, a root under WP called, I don't know, let's say get users. And then later on core decides to create one called get users. It's gonna override yours and your code is gonna break. So it's always recommended to register your roots with a custom namespace. Usually what is recommended is to tie it to your plugin code. Now, I thought it might be interesting for folks to see this if it isn't, I apologize. But while I was putting this all together, I remembered the last time that I worked on REST API code. And I went and had a look and it was around about 2018 to 2020. I was working on a plugin called Seriously Simple Podcasting and we were adding REST API support to the plugin. I'll share this in the chat. And you can see some of my code there from three years and two years ago. And you'll see that we registered this endpoint under the, I think it was this SS podcasting or something, let me just find the roots. Here we go, there we go. So our namespace was SSPV1. That was the namespace that we used. So sorry, that actually clarifies the thing that I meant. So not just WP, but WP and this version, that's the namespace. You can just have, for example, in our case, we could have just had SSP, that's also fine. But it's a good idea to also version your REST API because you might have a version one, which is what WordPress had, it had a version one. And then version two came along and the updated roots sort of replaced the old ones, but they still needed the old ones to work. So there are V1 roots, I'm not gonna go looking for them now. There might be a V1 post-root. Let's actually see if there is. Now that now I'm interested, let's see if there's a V1 post-root, it should work. Okay, that post, that root has already been deprecated. But obviously at some point there was a V1, it now switched to V2. So you'll see here, when I was creating these for SSP podcasting, we were working on version one. So I left the namespace as SSP slash V1. You don't have to include the version number, but it's one of those sort of recommended things that makes your life easier if you decide to have a version two or a version three. And then the root itself is the second parameter. So in this case, it's podcast. It could be in our case form submissions, whatever the case may be. And then in the array, we have what method it is, the callback that handles the code and the permissions callback. So those are the things that we need. Okay. So for the purposes of our code, I'm going to follow the recommendations. And so for namespace, I'm going to go, let me just check my notes here. There we go. So I'm going to go WP learn form submissions. And my general process to follow is I make the namespace of my REST API roots the same as my text domain for translation. It's usually the name of the plugin, all hyphenated. So you'll see it's not exactly the same I've missed out the API part. So you'll see this is the same as the plugin slug, is the same as the text domain. I kind of keep those things all the same. And then I'm also going to append the V1. So it's like that. So WP learn form solutions slash V1. You'll notice I don't have slashes on either side. If you do that, it throws an error because now you're adding an extra slash in the namespace. So you keep it like that. If you're going to use the slash in between the namespace and the version, that's the right way to do it. Then for the root itself, I need to start up with the slash because that's going to be after the V1 part of the URL. And in my case, I'm just going to call it form submissions because I'm going to be wanting to use this to retrieve all submissions. So it's WP learn form submissions API. There we go. And then forward slash form submissions. You can put the slash at the end if you want to. It doesn't break if you do. If you don't and a user puts in the slash, it will still work. But that's the basics of what you need. Then in the array, the top three things that you need are, number one, the methods. Let me just go back to register where we register restrict. Here we go. So the argument is either an array of options or an array of arrays for multiple methods. So let's have a look at an example down here. Let's actually find, I think this one's got some code. Maybe not this one. Yeah, here we go. So methods get callback again. So that's what we'll use. All right, so methods. We'll specify this one is the get method. Then we need to set a callback. And the callback is basically the function that will run when this root is accessed on that method. So this is very similar to when we add an action callback, we give it a name in a string and then we need to actually define that function. So for our purposes, we can just say WP learn and I tend to be a little bit over verbose. I'll say WP learn rest, get form submissions. You don't have to be as wordy as I am. That's just how I tend to do things. Sorry, I just realized I'm using equals and not equals with a little arrow because this is an array. So we need that there. That's why Visual Code Studio is giving me some problems. And then finally, you're supposed to set a permission callback. Now let me chat about that very quickly. In the documentation, it does talk about permissions callback down here. Here we go. And it says you must register permissions callback for the endpoint. This is the function that checks if the user can perform the action. This allows the API to tell the client what actions they can perform on a given URL without needing to attempt the request first. Then further on down it says, there's an example here. Here we go. If your rest API endpoint is public, you can use the return true function as the permission callback. So this is a built-in function in WordPress underscore, underscore return, underscore true. And it effectively just returns true. So because it's a callback function, you're supposed to specify a function. So to save you from creating a separate function which just returns true, WordPress has a built-in return true function. So in our code for the permissions callback, we can just specify return true as the string and that is what our root looks like. So let me copy all of this code out quickly into the chat so folks can see it if they're busy coding along. We've registered the root. What we haven't done is registered the callback function. But let's see what happens if we try and access this route anyway. So in postman, let's open that up. And I'm going to delete this one quickly because we don't need that anymore. And I'm going to close this one. I'm going to create a new collection. And this new collection is for my learnPrest site. Just so you can see I'm not cheating. And inside of this collection, I'm going to add a request and it's going to be the get form submissions request. I'm gonna leave it as a get request and then I need to get the URL of the route. So it's learnPrest.test, in my case, whatever your URL is for your local site. And then it's going to be WPJSON. So we know it's in the rest API. It's our namespace and our route at this point. So let me switch back to the code to see what that looks like. So our namespace is WP learn form submissions API v1. So there it is there. And then we made it, let me go back to my code. There we go slash form submissions. So that is what the whole, where is postman gone? There it is. That is what the whole route looks like. So there we go. So it's learnPrest.test, WPIPJSON, our full namespace and then the final name for the route. That should work. We should get some kind of activity if we save that incentive. So let's send that request and let's see what kind of response we get. We probably won't get a positive response but we should get some kind of response. Okay, so there we go. So that means that it's been registered. The route is registered, WordPress knows about it but it says the handler for the route is invalid. So that means that the callback function doesn't exist. It doesn't know what to do. So when you make a request to me, I don't know what to do about your information. Okay, so that's at least a good thing. If we got some kind of other error then that would mean there's a problem with our code somewhere. Okay. The code for the response can be very, very simple. In our case, we're working with this custom table. So it can be as simple as just getting the data from that table and returning it. So let me create that callback function. So down here, I'll say function get form submissions. What I wanna do is I'm trying to space this out a little bit so we can see what's what here. I'm gonna add some comments as well in a second. So that's my get form submissions function. And again, because I'm dealing with a custom table, I'm gonna be using the global WPD variable. So global WPDB, DB. I'm gonna get the table name again. So we can actually just grab this here from our learn setup table code. So that'll give us the right table name. And then I'm gonna create a query. So it'll be something along the lines of, if you've never seen this before, don't stress will be select all from table name. So that's the query. And then we can use the WPDB get results function. So it looks like this is WPDB. And then it's an arrow like that that shows us an object. So it's a method on this object, get results. And we can pass this query in like this. Now, if you're watching this going, he's not doing any kind of data sanitization. I agree with you, I'm not. It's a select, so it should be safe. But yes, if you're doing any kind of inserting of data which we'll do in a second, you should be doing data sanitization. You should be using, there's a special method in WPD that does it for you, which I'll show you in a second. But yes, we should always think about security. So that will get the results and that will return an array of results. So we can just pop that in the front. So we can say results is WPD get results, select all from table name, that's great. And now one of the very cool things about registering your rest routes is you don't have to format the response. You can basically pass almost anything back, sort of return anything from a callback function hooked into a REST API, a custom REST API route. What the REST API process then does is it takes that data, checks what kind of variable is. So is it an array or is it an object? In other words, a collection of data or is it a single item, like a single integer or single string? And it then converts that into a JSON object for you to be returned to your REST API response, which is great. It means you don't have to worry about. So in a second, I'll be showing you how we do that using just a single response. But for now, doing it this way is perfectly fine. So I'm gonna copy this into the chat so folks who need this can see. Somebody said WPD line 44. Oh, I can't spell today. Thank you. Thank you for picking up my errors in my code there. Great. Let me delete this last message because now we know it's wrong. So let's copy. Let me just actually format this one as well and we'll copy that out and we'll pop that back in there. So we've got the right code. Excellent. Thank you. Let's say Clay. So I hope I'm pronouncing your name right there. So let's now see back in our postman callback now what we get when we run this. So we hit send again and there we go. Now we're getting a responsive information. So we added one field to the form submissions table. Hi there. Thank you for picking that up. And now we see our response happening. So now we know the code is working. Yay, we've registered a custom route. We're good to go. Okay. That's literally how simple it is. Okay. So besides making sure you spell your variable names correctly, does anybody else have any questions around all of that while I grab a sip of water? All right. There don't seem to be questions. So I'm going to move on to our next item on our objectives. Our next item, if I can find the slide which requires me moving the zoom window. Our next item was that we wanted to register a route endpoint to post submissions. So that would be very cool if we can now create an API endpoint to post submissions which then means forms and various other places. We can create a mobile app that can post a submission. We can build a web form that can post submission. Everything can happen using the API. Doing a custom post submission can be done exactly the same way as we did to get one. There are multiple different ways of doing it. I'm going to keep it very simple today by duplicating some code. But basically we could just take this whole register race route here and we can copy and paste it just below. We can leave the namespace and we can leave the name of the route. That we can leave the same. The change we make as we change the method from get to post. So now it's a different endpoint. The first one was the get endpoint. Now this is the post endpoint. Which means we also need to change the callback because we don't want the get form submissions callback to run when the post request is made. So now it'll be something like WP learn rest, create form submissions maybe or post form submissions, whatever the case may be. For now we'll leave the permission callback as true. Because we might be wanting to create this as a public route that we want people to be able to post to, we might leave it as true. Once you've got this working we'll chat very quickly about permissions. But for now we'll leave it as true. And that's that. We've created a post endpoint. So let's see if that is true. Let's see if our post endpoint does indeed work. So if we pop over to postman we can now take the get one and we can duplicate it. But let me just save it first. Because if I don't save it, I'll just save it, okay excellent. If I don't save and hit duplicate then things get missing. So in postman if we hit duplicate and then click on it we can click on the name at the top and change this from get to post and just remove the copy part. We can leave the root URL as it is because we've registered the same name space and the same root name but change this to a post. That's all we have to do to test this and then we can hit save. And if we now send this request we should see something. So let's see what happens. Okay, we're seeing the same error we saw earlier invalid request handler because we haven't, as we know we haven't created that request handler. Okay, so now we create the request handler. So again, the request handler can be as simple as just storing the fields in the database. But the first question that you might ask me is, okay, but how do I capture those fields from somebody posting them? Now, if you'll remember and I might have that code here somewhere. So let's have a look at the, I do have that code. When we were doing the initial learn REST API plugin and we were using the backbone JS client we were posting when we created a new post, for example we were posting the data as a JSON object and it was titled was the field name and then the value was the variable we got. So we will be posting the same kind of information we're posting an object in the body and we'll be passing in the fields that we need to pass. The cool thing about registering your custom routes using register REST route. So let me close this and pop back over here is that by default there is a request variable that you can set up in your callback functions. And let me show you in the documentation where that's discussed. I have to do a search for request because I can never find this. Here we go. So by default, is it this one? Yes. By default, routes receive all arguments passing from the request. These are merged into a single set of parameters and then added to the request object which is passed in as the first parameter to your endpoint. So you'll see over here this is the my awesome function callback in the example and they're using the WP REST request. I can't remember the name of that but it basically it tells the code what type of object this is. We don't have to use this we can just use the name that's a whole object oriented paradigm which you're not gonna discuss today but we can just set up this variable name and then you can access parameters using default array notation. So if somebody is posting data in the body you'll be able to access the parameters that way. If somebody is passing parameters in the path which I'll show you in a second you could access it that same way. So when you set up your post callback you'll pop the request variable name there in the parameters of the function and then you can access things in that request. So for example, if somebody was posting a value to name you could access name by using a console today as we noticed earlier you can just say request name and that'll be the value of name. If somebody is passing in email you can access it by going request email and then you'll access that email. So to do that, we're gonna use similar code that we used in our get function we're gonna use WPDB again but this I'm gonna copy code so I don't spell it wrong and I don't mind laughing at myself by the way I really don't. Then we're going to do something called WPDB insert to insert the data. So we'll do something like this. So we'll say rows equals WPDB, WPT, can't do it, WPDB and we'll call the insert method. Now, if you've never seen this stuff before don't stress too much it's not the focus of today's workshop I am planning another workshop on how to insert data. So just trust me that this is how it works in WordPress if you're working with custom tables. And then you need to pass at the table name so we'll just pop in table name in the first one and then you can pass in as an array the fields that you want to enter into your custom table. So you can do it like this you can say name equals something but equals equals and arrow. I still don't know what the name of that thing is but it's how arrays work in PHP. And so you can basically just do it like this you can say name is request name and email is request email and that's what that looks like. Okay, gonna leave that for a few seconds there. I'm also going to copy it into the chat so that we can see it in the chat if folks need to review it there. I'm getting an error so let me see if I can fix that error or it's because I've got semicolons instead of commas. So let's delete that last message and post the correct one. I've got another error that's because I don't have a semicolon there and now it's formatted incorrectly so I have got my VS code set up to automatically format like that. There we go. And that's the code we need. So let's copy that code out and that's the create form submissions callback. Okay, so now this should work. We should be able to pass name and email to the request in the body. It should then store the name and email but then we need to respond with something. So the one way you could respond is you could just respond with a number of rows added. You could respond with some kind of message or whatever the case may be for today. I'm just going to go return and I'm going to say post added, sorry, not post form submission. Can't even remember my types of data today form submission added. Okay, now while we're here there should also be some kind of checking was this inserted correctly? If rows was zero, maybe we return an error all those kinds of things you need to think of. Today we're keeping it super simple. We're going to just assume it works and then it returns with a message to say, yeah, everything is happy. Okay, I'm going to copy this updated code into the chat if you need it and then we can go test it. So let's pop back on over to postman. The other thing we'll now need to do is we'll need to actually pass in some data. So we can click on the body tab to do that. And if we click on the raw radio button and switch this to JSON then we can pass in a JSON object and we can say name is and I'm going to make up names today and say Joe soap and his email is joe at soap.com and if you're a Windows API developer and you remember soap you're having a good laugh with me right now. Okay, so that's going to send name and email in the body let's save that and let's post it and let's see what happens. Form submission added. Okay, so now at least we know the code didn't break we didn't get any errors in the code. It doesn't necessarily mean that the form submission has been added so we need to validate that. So if I switch back on over to my database and I go and have a look at the form submissions table and I click browse. Hey, there's Joe soap joe at soap.com it all worked beautifully. Okay, now while we're here yes, we need to also be thinking about as I said, maybe we could do a check if there's no rows we could do some kind of response maybe we want to return that object like when you post to the post endpoint it returns the post object we could do that kind of thing. We might want to do some validation on the email and the name and make sure that that data is correct and all those kinds of things but we're not talking about those subjects today we're just focusing on custom post inputs. Okay, any questions on all of that before we dive into the last one which is where we want to retrieve a single form submission. I'm going to leave this on the screen. That went fast. I apologize. Did you share the post part the post code or the post the testing of the post? The part where you like copy pasted from where you added the rest route or route. Okay, so that one was this one is here the registered one. Yeah, I'm not sure that I got that part. Let me copy paste that. There we go. So that's registering the post route. So it's also inside of the registered rest routes function so that it's all only happening when the rest API is initialized and then the function so the callback which is the WP learn rest create form submissions one is the one down here and I'll paste that as well if you need that code. All right, thanks. Okay, what I also do what I also do have and I will share this right at the end as well if you need to review this later and I do this for every workshop in my GitHub repository list I always put all of this code in a standalone repository so there is the form submissions API repo and if you click on that form all the code is there as well so if you need any of this afterwards if you're going through this later I always do put it up there. I'll share this with you right at the end and you can just grab that quickly there as well. Okay, I always do it because I know live coding workshops it's a lot to follow. So not a problem at all. Jonathan Aaron has a question here. Is there a danger with exposing the post route publicly without requiring authentication? Absolutely, very good question Aaron. So that reminds me about something that I want to mention. So thank you Aaron for that question. Okay, so let's talk about exposing REST API routes publicly. Your first thing that you need to think about when you're creating your route is what are people going to use this for? Am I building it for public consumption? So as an example the, let me find my postman quickly. I think it was under work press. No, it wasn't. So I'll just dummy it quickly. If you go to my site for example and you go to WPJSN and you go to WPV2Posts that is a public route. I think that's done correctly. And there you will see all of my post content in the REST API route and that's because my post content is public. You can browse it on my blog. It's not hidden information. It's all available. So your first thing you need to think about is am I going to make this a public route or not? If it's a get, it's usually okay to make it public. Maybe there's a specific requirement to make it public. But that's the first question. Then the next question is what if I don't want to make this public? How do I then lock things down? And Aaron raises this very good question. So for example, let's say we wanted the form submissions one to be... So in this simple example I might want the form submissions one to be locked down. I might not. I might be happy to let folks post to it. And then inside of this callback, there's a way, and I'm not diving in this today, but there is a way in one of these... I think it's in the routes at endpoints one. They specifically talk about... Maybe it's not even here. But there is a way... Either using the permissions callback or there's another way where you can actually set up non-checking on your REST API endpoints. And you can prevent folks from actually just even accessing the endpoints in the first place. It's in the documentation some way. I'll find it maybe in a second and link to it. But the other simple way that you can do it is you can use the permissions callback callback and actually specify that this requires some form of authentication. So one way to do it... So let's take the post one, for example, and let's take out return to it. And let's say something like wp-learn. I've actually got this in my notes. So let me actually just steal what I've set up. And I'm just going to call it wp-learn require permission. So that's a good example. So in here, we can do a number of things. We can do something like this. We can say very simply return and using the current user can, which is a built-in WordPress function, and we're going to use the permission edit posts. So that's another built-in capability within WordPress. Let me just format this code a little bit. Here we go. Edit, oh, it's edit underscore posts, I apologize. Now only a user who has the permission to edit posts can actually create a form submission. So let's see what that looks like. So now if I try and do this over here and I hit send, I'm not allowed to do this. Okay, so that's one way you can handle this. The other way that you can handle it is you could do something like non-checking and then inside of that code, you would do a non-check or whatever the case may be. You could do something like passing in a parameter and requiring that parameter allows with a certain value. There's many different ways you can do it. So it really depends on your requirements for your API, how you want people to be able to access your API. In last week's session, we discussed authentication. If you haven't seen the authentication video, you can find it on WordPress.tv. I'm actually going to be turning this into a tutorial as well. And if you search for let's code in the search box and you enable the let's code series and you switch to newest, there it is. No, it wasn't modified responses with this one. Let's actually paste this out. So in this one, I talk about one of the ways that you can do authentication. There's a thing called application passwords inside of WordPress. You can create an application password for your customer's API endpoints and that will then require users to be able to post that data. So there are a number of ways that you can restrict access. The using the permission callback is the easiest way to do it and then actually specifying a specific permission and then either giving users permissions or whatever case you would like it to be. For myself, just for testing today, I'm going to leave it as true. I don't want to have to now go and set up the user names and passwords. We'll just leave it as public for now. But yes, when you're working in a production environment, if you don't want your REST API endpoints available to the public, make sure you set your permission callback and then make sure you set your callback function and then use whatever functionality you need to prevent folks from accessing the REST API. Okay. Cool. Then the last thing I want to show you all today is how to create an endpoint that returns one field of data. So for example, if we have a look at our get form submissions, not this one, so I was testing the other day, right now, if I run this, it's going to return all submissions. So if I keep posting submissions and I use only this API endpoint to get submissions, if I just wanted to return one submission, I would have to run this request and then loop through the results and look for the one with the ID of whatever I'm looking for and then display that one. There is a thing in registering your custom routes that you can use called path variables. So you can specify path variables, which then allow you to pass those path variables into your route URLs. If you remember, those of you who may or may not have attended my very first workshop on the REST API, you'll remember we had this fields global parameter that we used and you could pass in ID, you could pass in any of the fields that exist. So I think it was ID title and content we used and it then limits the response and just returns ID title and content. That is basically a path variable. So it's a query string, the ID is fields, or at least the variable is fields and then I can pass data to it. So the example that they show you in the routes and endpoints documentation, if you have a look at path variables section, and I wish this was a little bit easy to read on the screen, but that's very short. But you'll see in the... Let me scroll down to the route registration and let's just... There we go. So here they're registering the REST routes, my shop v1 and then the root name is products and then there's this code at the end here. So it's question mark, which is showing that it's a query string and then the variable is p. And then there's this notation here, which indicates it's the ID field and then there's this, which is a regex. And what that regex means is any numeric. So if we take that and we look at it in a slightly more human readable format, I'll show you on my screen in a second, it would be something like this. So we would specify it as question mark p and then in sort of these kind of HTML tags, if you will, the name that you're wanting to accept. So that name could be ID, title, whatever you define. And then the regex pattern is to match whatever data you're passing in. So it uses that regex to say, well, is this a valid piece of data? So if I'm specifying this as a numeric, in other words, plus D, and I passed in a string, it would fail. So that's the formats, the English sort of human readable format. What it actually looks like in code is this example over here. So I'm going to copy that out and I'm now going to create another REST route using this path variable. And what I'm going to do is I'm going to use my first get endpoint and I'm going to create a second one just below it. So it's register REST route, form submissions API V1 stays the same. The difference here is I'm going to make it form submission, just because I like my routes to match the data that I'm returning. So if I'm returning a collection of responses or multiple things, then I'll make it plural. If I'm just receiving one, then I'll make it singular. Again, that's a personal thing, but I do know that most REST APIs do follow this sort of pattern, if you will. If you're getting multiple responses back in one response, multiple objects, should I say, it's plural. If you're just getting one back at singular, it just makes it easier to remember. We can leave the get the same. We could also leave the callback the same and we could use that callback and then use the path variable to check, but I'm going to create a separate one. We could do it both ways. I just prefer to keep it separate. So I'm going to say WP learn REST get form submission, just for my own purposes. And I'm going to leave the permission callback as true again, because I want it to be public. Then what I need to do is I need to make sure that I append this, this path variable format onto the end of my root name. So there is form submission forward slash and then all of this. So again, I'm expecting it to be ID. So what that means is when I pass a number at the end of my route, it's going to take that number and it's going to combine it into that request object that I showed you earlier under the object array name ID. So that's how I'm going to access it in the request object or sorry, in the request array. I could call it Bob if I wanted to and then I'm just make sure I look for Bob later on. But for our purposes, we're going to keep it as ID. And then the question mark P is just the format that it uses to say this is a path variable. We're expecting to call it ID. So that's how we set that up. Okay. Then obviously we need the get form submission code. So for that, I'm going to copy the sorry, copy the multiple get form submissions. So it's exactly the same, but I'm going to change it to singular. So get form submission. So let me just actually copy this code out first so everybody can see that. So that's our third route for today. So we've got that one there. And that's again inside of that API and net callback. Okay. And then the function. So we're going to, here we go. We're going to get form submission. We need the request object. Sorry, the request array because we need to get that ID from. So then we could do something like this. And we can say the ID is request. And then we've defined it as ID earlier on. So now we look for ID in that request array. So that's the ID. And now we can update our query string. Sorry, our SQL query. So our SQL query can be where ID, the field ID is equal to the ID variable. There we go. And again, we should be checking. We should be doing some kind of validation on it. I think the WordPress recipe does do validation already, but it's still a good idea to validate our own code. I'm not doing that today. And then the only difference then is, let me just get you here. Yes. So now when we return results, it's still going to be an array of results. So instead of returning all the results, we need to return the first item in that array. And so that item, those of you who know arrays, the arrays always start at zero. Hopefully some of you have seen the joke about your first child being called zero, something like that's programming jokes out there. But that's what that code looks like. So let me copy paste that into the chat. Everybody can see that. So just to review, we created the new root. We just changed the root. Sorry. We just changed the root name to form submission, and then we appended the path variable format to the end of it. So it's expecting a numeric to be passed through, which it'll take to ID. We left the method as get. We set the callback function as get form submission. We left the permission as true just for our testing. Then we created get form submission. We made sure we set up the request variable to use, get the ID, same code here to get the table name. Pretty much the same query. The only thing we specified in the query is where the ID equals the ID we're passing. And then the difference there is we return a singular result. So let's see if that works. Where is my postman? So now I'm going to copy the get form submissions one. And we're going to make a get form submission. And I like to name it based on what I'm getting it by. So I say get form submission by ID. That's just a personal thing. We changed this to submission. And now there's multiple ways we can pass the ID in, but I'm going to pass it in as in the URL because we set it up as a path variable. And now if we save and send that, we should get one ID back. Let's try number two, see if that works. Good. We get Joe back. Let's try Joe. Let's see what happens if we pass in Joe. Could not get a response. We get an error because we specified it in that. Let me go back to the code. Because we specifically said using the regex in this part here, we said it must be a number. And it can be any one of numbers. So it can be one, 12, 123, but it must be a numeric. Wordpress.com says, hey, Joe is not a numeric. I don't like it. So I'm going to throw you an error. And that's how you can specify a single rest API route to get a single item. And there we go. Okay. Any questions on all of that before we chat a little bit about rest APIs and working with them? Maybe we don't seem to have questions. I know this is a lot and I know I tend to go fast. It is my sort of advantage. The fact that I've built this before and it's very easy for me to write the code. And I've also got my notes. I'm cheating out registry. But this is one way you can build rest API routes. And you'll notice that in the code that I shared with you earlier when I was working on this in serious simple podcasting, I use register rest routes. And I used, I did it in a class based environment. But here we've got a get rest podcast method. We've got update rest podcast. And then further down, there's a get rest podcast, which does things, update rest, which does things. So that's one way of doing things. The other way that you can do it, which we're going to cover next week, is you can actually create a full class file to manage your rest API requests. And you can hook that up to your routes. And that's when you need to do a lot more than just simple functionality. So what I've shown you today is more along the lines of what you would do if you want to build admin requests, sorry, Ajax requests. And instead of using admin Ajax to just get a list of data or store some simple data, you wanted to use the rest API. And as you can see, and this is my opinion, obviously, but I find this way of doing things to be a lot easier than having to remember how do I register an admin Ajax request? I've got to use the admin underscore Ajax underscore. And then if I want one to be public facing, I've got to do the admin Ajax. I think it's no proof and all of that stuff. And then I've got to remember to do my nonce checking and all those kind of things. I just find it easier to do it this way. There is some built-in validation and checking as you saw earlier when you specify your paths. There is some built-in checking of data in the request. So it's just a lot cleaner, a lot simpler. And I feel a lot easier. You always know what your response is going to be. It's always going to be a JSON response. So when you're working in your JavaScript file or your HTML, you always know what you're working with. So I find these things to be a lot easier than dealing with admin Ajax. The last thing that I wanted to mention is if you are ever creating custom REST API routes that is updating data. We haven't covered updating today. But updating would require you to pass the ID and the name and email field, for example. If you're going to be doing that, make sure that not only your user has permission to update these things, but also is allowed to therefore owns these fields. And what do I mean by that? So I'll share a fun story with you. If you're using IDs and you're checking, can the user create form submissions? But you're not checking, does the user own the form submission? It might be possible for somebody logged in with their account. Let's say you've got a membership site set up. They log in with their accounts. And all you're doing is you're checking, can this user create these things? They could then using either browser tools or there's actually a very cool pen testing tool that you could just download for free. I can't remember what it's called now. And you could then intercept the request and then change the ID being posted and update somebody else's information. So if you're building these things custom and you're wanting to allow folks to update things like that, make sure that you're not only checking can they update these things, but do they have ownership of these things? And generally within member plug-ins or any kind of membership site, there will be some way of checking not only can the user do it, but does the user own it? I can't remember. I think it's called permission escalation is the name of the vulnerability that exists. I think that's the title. And I've seen that in the world. And one way to work around that is to use something called a UUID or a unique user ID, I think it is, which is basically, we'll quickly show you what a UUID looks like. No, not zoom. I'm not going to get the window I want now, am I? UUID, that's one way to do it if you're building custom things. There we go, universally unique identified. That's what it's called. And UUIDs normally look like this. So you might consider passing the UUID around. It's a lot more difficult for somebody to guess. If you've got a table of, you know, it starts at one, it's easy for someone to guess what the next one's two, the next one's three, the next one's four. It's very difficult to guess what the next UUID is going to be. So that's one way of doing it. So then you don't have to do a check, then you just use the UUID. That's another way of doing it. Or if you're just using IDs, make sure that if you're doing it, if you're just using IDs, make sure that if you're working in an environment where multiple people can access this information, but any certain people can access these four files, or posts, or whatever, and certain people can do this, then you're working accordingly. Generally in a WordPress environment, if it's just posts, it's often not a problem. But if you're working any kind of project that has membership content or anything like that that's related to a user, then make sure you're doing most checks. Okay. Anything else? Any other questions? Anything else anybody wants to say or comment on or ask me? Otherwise, we are done for today. I want to thank you, Josh, again, for co-hosting with me today. I hope that you've enjoyed it at least. And I look forward to you maybe hosting one yourself one day. I really enjoyed it, Jonathan. Thank you. Awesome. Awesome. And thank you also, I know, for yourself. I think it's quite late your time. So thank you for joining me quite late your time. Thank you for the folks who are joining from California, joining early their time. It's always lovely to see folks that side of the U.S. As I say, next week we'll be looking at the controller pattern. It's a little bit more in-depth. So make sure you watch this workshop again before we come on to next week. And otherwise, enjoy the rest of your Thursday, enjoy the rest of your week, and I'll see you all next week. Now I have to remember how this works.