{"id":27807,"date":"2022-03-23T22:37:52","date_gmt":"2022-03-23T22:37:52","guid":{"rendered":"http:\/\/Creating-Custom-User-Reports-with-Redwood"},"modified":"2022-03-23T22:37:52","modified_gmt":"2022-03-23T22:37:52","slug":"creating-custom-user-reports-with-redwood","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/","title":{"rendered":"Creating Custom User Reports"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Sometimes you need to generate reports that show users certain metrics around what they do in an app. A report can include anything from images to specific user data and they can give your users meaningful ways to monitor their behavior.<\/p>\n<p>That\u2019s why we\u2019re going to make some custom user reports with Redwood. This little app will let users see their information in a table and then print it to a PDF if they need it offline. They\u2019ll get a product list with quantities and prices included with pictures. We\u2019ll be hosting our images on Cloudinary so we don\u2019t have to worry about keeping them in a local directory.<\/p>\n<h2>Setting up the Redwood app<\/h2>\n<p>In a terminal, run the following command to create a new Redwood app.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn create redwood-app user-reports\n<\/code><\/span><\/pre>\n<p>This will generate all of the files we need to create a robust front-end and back-end connected to a database. The back-end is contained in the <code>api<\/code> folder and the front-end is in the <code>web<\/code> folder.<\/p>\n<p>We\u2019ll start by making the model for this app. It\u2019s usually a good idea to have the business model defined for an app before jumping into very much code.<\/p>\n<h2>Setting up the database model<\/h2>\n<p>The first thing we\u2019ll do is update the connection string to our database instance. We\u2019re using a local Postgres instance to handle our operations. So we need to update the <code>.env<\/code> file.<\/p>\n<p>You can uncomment the <code>DATABASE_URL<\/code> line and update it to the connection string for your instance. Here\u2019s an example of what one might look like.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">DATABASE_URL=postgres:<span class=\"hljs-comment\">\/\/postgres:admin@localhost:5432\/user_reports<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>If you need to set up a local instance of Postgres to get your connection string, <a href=\"https:\/\/www.postgresql.org\/download\/\">check out their docs<\/a>.<\/p>\n<h3>Adding models<\/h3>\n<p>Next, go to <code>api &gt; db<\/code> and open up the <code>schema.prisma<\/code> file. We need to update the <code>provider<\/code> to <code>postgresql<\/code> since that\u2019s the database we\u2019re working with. Now we can delete the example model and replace it with our own.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">model User {\n  id       Int       @id @<span class=\"hljs-keyword\">default<\/span>(autoincrement())\n  email    <span class=\"hljs-built_in\">String<\/span>    @unique\n  name     <span class=\"hljs-built_in\">String<\/span>\n  products Product&#91;]\n}\n\nmodel Product {\n  id       Int    @id @<span class=\"hljs-keyword\">default<\/span>(autoincrement())\n  name     <span class=\"hljs-built_in\">String<\/span>\n  imageUrl <span class=\"hljs-built_in\">String<\/span>\n  price    Float\n  quantity Int\n  User     User?  @relation(fields: &#91;userId], <span class=\"hljs-attr\">references<\/span>: &#91;id])\n  userId   Int?\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>There is one foreign key relationship between these two models. One user can have multiple products associated with it. That\u2019s why we have the <code>userId<\/code> and <code>User<\/code> on the <code>Product<\/code> table. It\u2019s the reference to the <code>User<\/code> table.<\/p>\n<p>With the models in place, we can run a database migration.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw prisma migrate dev\n<\/code><\/span><\/pre>\n<h3>Seeding your database<\/h3>\n<p>When you have relations in your models, it\u2019s usually a good idea to add default values to your database to prevent any errors in the app when you start it. You\u2019ll see a lot of production database seed data like dropdown options, user roles, or initial users.<\/p>\n<p>In the <code>seed.js<\/code> file, in <code>api &gt; db<\/code>, you can delete all of the commented out code in the <code>main<\/code> function because we\u2019ll be adding our own calls.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">await<\/span> <span class=\"hljs-selector-tag\">db<\/span><span class=\"hljs-selector-class\">.user<\/span><span class=\"hljs-selector-class\">.create<\/span>({\n  <span class=\"hljs-attribute\">data<\/span>: { name: <span class=\"hljs-string\">'Mochi'<\/span>, email: <span class=\"hljs-string\">'mochi@test.com'<\/span> },\n})\n\n<span class=\"hljs-selector-tag\">await<\/span> <span class=\"hljs-selector-tag\">db<\/span><span class=\"hljs-selector-class\">.product<\/span><span class=\"hljs-selector-class\">.create<\/span>({\n  <span class=\"hljs-attribute\">data<\/span>: {\n    name: <span class=\"hljs-string\">'Jugs'<\/span>,\n    imageUrl: <span class=\"hljs-string\">'example.com\/jhon.png'<\/span>,\n    price: <span class=\"hljs-number\">7.88<\/span>,\n    quality: <span class=\"hljs-number\">25<\/span>,\n    userId: <span class=\"hljs-number\">1<\/span>,\n  },\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now run this command to seed the database.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw prisma db seed\n<\/code><\/span><\/pre>\n<p>With the database ready to go, we can move to the back-end and front-end.<\/p>\n<h2>Generating the GraphQL and React code with Redwood<\/h2>\n<p>Redwood does a lot of work for us once the model has been migrated. We can get the CRUD for both the front-end and back-end with these two commands.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g scaffold user\nyarn rw g scaffold product\n<\/code><\/span><\/pre>\n<p>These two let us add users and products in this app. That way we can add new products to different users and create those custom reports for them.<\/p>\n<p>You\u2019ll find all of the generated code for the GraphQL server in the <code>api &gt; src<\/code> folder. The types and resolvers are in the <code>graphql<\/code> and <code>services<\/code> folders respectively. All of the front-end code will be in <code>web &gt; src<\/code>. There are quite a few new files and folders for the front-end, so we\u2019re going to focus on just one.<\/p>\n<p>To see what these new pages look like, go ahead and run the app with:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw dev\n<\/code><\/span><\/pre>\n<p>Then go to <code>localhost:8910\/users<\/code> in the browser. You should see something like this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1626399702\/e-603fc55d218a650069f5228b\/t7f7hktmzytwqrdgexnc.png\" alt=\"users table\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"798\"\/><\/p>\n<p>If you go to <code>localhost:8910\/products<\/code>, you\u2019ll see something like this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1626399719\/e-603fc55d218a650069f5228b\/i8kwbylfbyjgranub84h.png\" alt=\"products table\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"631\"\/><\/p>\n<h2>Add some pictures to Cloudinary<\/h2>\n<p>Since we\u2019re going to host our images on Cloudinary, we need to upload a few images. To that, <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">create<\/a> or <a href=\"https:\/\/cloudinary.com\/users\/login\">login<\/a> to your Cloudinary account.<\/p>\n<p>When you log in, you\u2019ll be taken to the dashboard. At the top, navigate to the \u201cMedia Library\u201d. This is where you can upload images and videos. It\u2019ll look similar to this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1626399823\/e-603fc55d218a650069f5228b\/o9azx8cvwjl8g4luekcv.png\" alt=\"Cloudinary media library\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1268\"\/><\/p>\n<p>Use the \u201cUpload\u201d button to upload some product images or any other images you like. None of the images I\u2019ll be using are for any type of product.<\/p>\n<h2>Making the report<\/h2>\n<p>In <code>web &gt; src &gt; components &gt; User &gt; Users<\/code> folder, we\u2019ll open the <code>Users.js<\/code> file because this is where we\u2019ll add the report and a button that will download it for users.<\/p>\n<p>First thing we need to do is add the <code>react-pdf<\/code> package to the <code>web<\/code> directory. So in the <code>web<\/code> directory in your terminal, run:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">yarn<\/span> <span class=\"hljs-selector-tag\">add<\/span> <span class=\"hljs-keyword\">@react-pdf<\/span>\/renderer\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then we\u2019ll need to import that some components from the package at the top of <code>Users.js<\/code>, like this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> {\n  Page,\n  Image,\n  Text,\n  View,\n  Document,\n  PDFDownloadLink,\n  StyleSheet,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@react-pdf\/renderer'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now that we have all of the components imported, we\u2019ll start by adding the styles for the report pages. So right above the <code>UsersList<\/code> component, add this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> styles = StyleSheet.create({\n  <span class=\"hljs-attr\">page<\/span>: {\n    <span class=\"hljs-attr\">flexDirection<\/span>: <span class=\"hljs-string\">'row'<\/span>,\n    <span class=\"hljs-attr\">backgroundColor<\/span>: <span class=\"hljs-string\">'#E4E4E4'<\/span>,\n  },\n  <span class=\"hljs-attr\">section<\/span>: {\n    <span class=\"hljs-attr\">margin<\/span>: <span class=\"hljs-number\">10<\/span>,\n    <span class=\"hljs-attr\">padding<\/span>: <span class=\"hljs-number\">10<\/span>,\n    <span class=\"hljs-attr\">flexGrow<\/span>: <span class=\"hljs-number\">1<\/span>,\n  },\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>It won\u2019t be the fanciest looking report, but feel free to play with the styles as you see fit. Now we should make the actual report. For now, we\u2019ll just show the user\u2019s name. Right below the styles we just created, add the following:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> UserReport = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ user }<\/span>) =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Document<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Page<\/span> <span class=\"hljs-attr\">size<\/span>=<span class=\"hljs-string\">\"A4\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{styles.page}<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">View<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{styles.section}<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Text<\/span>&gt;<\/span>Name: {user.name}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Text<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">View<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Page<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Document<\/span>&gt;<\/span><\/span>\n)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This makes the content that will show in the pdf. We\u2019ll expand this in a bit to return all of the product info associated with a user. First, let\u2019s go ahead and make our download button.<\/p>\n<h3>Download the report with a button click<\/h3>\n<p>People with access to this table should be able to download a pdf for any of the users on the table. So we\u2019re going to add a \u201cDownload\u201d button right after the \u201cDelete\u201d button in the table row for each user.<\/p>\n<p>To do that, add the following code below the last <code>&lt;a&gt;<\/code> element in the <code>UsersList<\/code> component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">&lt;PDFDownloadLink\n  document={&lt;UserReport user={user} \/&gt;}\n  fileName={`user_report_${user.id}`}\n&gt;\n  {({ blob, url, loading, error }) =&gt;\n    loading ? <span class=\"hljs-string\">'Generating report...'<\/span> : <span class=\"hljs-string\">'Download'<\/span>\n  }\n&lt;\/PDFDownloadLink&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019re using the <code>PDFDownloadLink<\/code> component to handle the actual download. We specify the document component we want to use which is <code>UserReport<\/code> and we\u2019re passing in the user data for that row. Then we handle the pdf\u2019s download state inside the component so that we know if the pdf is still being generated.<\/p>\n<p>Now when you run the project in the browser, you\u2019ll see a new button on the row.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1626399755\/e-603fc55d218a650069f5228b\/oomsg443xkukqggeg1ll.png\" alt=\"added download button\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"680\"\/><\/p>\n<h3>Add a new resolver to get user products<\/h3>\n<p>Now that we have the front-end downloading a PDF, we need to create the resolver that will return the products associated with a user. Open <code>users.js<\/code> in <code>api &gt; src &gt; services &gt; users<\/code>. This is where we\u2019ll add the query to get a user\u2019s products. Right below the <code>deleteUser<\/code> mutation, add this query:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getUserProducts = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ id }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> db.product.findMany({\n    <span class=\"hljs-attr\">where<\/span>: {\n      <span class=\"hljs-attr\">userId<\/span>: id,\n    },\n  })\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This queries the <code>product<\/code> table for any products that have the user ID we pass in. We also need to add a type to <code>users.sdl.js<\/code> in <code>api &gt; src &gt; graphql<\/code>. This will make the query available on our server. Let\u2019s add the new type below the <code>user<\/code> query definition.<\/p>\n<p><em>Note: The <code>users.js<\/code> and <code>users.sdl.js<\/code> files were automatically generated when we ran the <code>scaffold<\/code> command. We\u2019re just adding these couple of things to them.<\/em><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">getUserProducts<\/span>(<span class=\"hljs-selector-tag\">id<\/span>: <span class=\"hljs-selector-tag\">Int<\/span>!): <span class=\"hljs-selector-attr\">&#91;Product]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>That\u2019s all for the back-end! All that\u2019s left is using this query on the front-end and a quick update to the document we made.<\/p>\n<h3>Using the product data in the document<\/h3>\n<p>We\u2019ll need to update <code>Users.js<\/code> in the <code>web &gt; src &gt; User &gt; Users<\/code> folder. The first thing we\u2019ll do is import the <code>useQuery<\/code> hook. You can add this to the existing import from \u2018@redwoodjs\/web\u2019.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useMutation, useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>With this import, we can add the query we need to get our data. We\u2019ll do that right below the <code>DELETE_USER_MUTATION<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> GET_USER_PRODUCTS = gql`\n  query GetUserProductsQuery($id: Int!) {\n    getUserProducts(id: $id) {\n      quantity\n      name\n      imageUrl\n      price\n    }\n  }\n`\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will return an array of products associated with the given user ID. The next thing we\u2019ll do is update the <code>UserReport<\/code> so that we can show the product data. Note that we changed the name of the prop we\u2019re passing in.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> UserReport = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ products }<\/span>) =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Document<\/span>&gt;<\/span>\n    {products.map((product) =&gt; (\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Page<\/span> <span class=\"hljs-attr\">size<\/span>=<span class=\"hljs-string\">\"A4\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{styles.page}<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">View<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{styles.section}<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Text<\/span>&gt;<\/span>Name: {product.name}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Text<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Text<\/span>&gt;<\/span>Price: {product.price}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Text<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Text<\/span>&gt;<\/span>Quantity: {product.quantity}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Text<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">View<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">View<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{styles.section}<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{product.imageUrl}<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">View<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Page<\/span>&gt;<\/span>\n    ))}\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Document<\/span>&gt;<\/span><\/span>\n)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019re in the last steps now! All that\u2019s left is to fetch the product data for each user row and generate a report that will be downloadable. Inside of the <code>&lt;tbody&gt;<\/code>, where we map over the users, add this bit of code above the <code>return<\/code> statement.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> { loading, data } = useQuery(GET_USER_PRODUCTS, {\n  <span class=\"hljs-attr\">variables<\/span>: { <span class=\"hljs-attr\">id<\/span>: user.id },\n})\n<span class=\"hljs-keyword\">if<\/span> (loading) {\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is how we will get the product data to pass into our reports. We add a check to see if the data is still loading or else it return prematurely and the app will crash because there\u2019s no data to use.<\/p>\n<p>We need to update the prop we pass to the <code>UserReport<\/code> in the \u201cDownload\u201d button.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UserReport<\/span> <span class=\"hljs-attr\">products<\/span>=<span class=\"hljs-string\">{data.getUserProducts}<\/span> \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now when you run your app and click the download button for your user, you should get a PDF that displays all of the products you create for them!<\/p>\n<h2>Finished code<\/h2>\n<p>You can check out the finished code in <a href=\"https:\/\/github.com\/flippedcoder\/blog-examples\/tree\/main\/user-reports\">this repo on GitHub in the <code>user-reports<\/code> folder<\/a>. You can also see the front-end code in <a href=\"https:\/\/codesandbox.io\/s\/ancient-night-o9uvl\">this Code Sandbox<\/a>.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/ancient-night-o9uvl?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"ancient-night-o9uvl\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Conclusion<\/h2>\n<p>Giving users an easy way to see their data is a common task. Generating dynamic PDFs in JavaScript is a useful skill to have in your toolbox, so feel free to look at approaches that may be better for performance.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":27808,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,177,246,371],"class_list":["post-27807","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-javascript","tag-react","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Creating Custom User Reports<\/title>\n<meta name=\"description\" content=\"Users expect to be able to see their data in a format that&#039;s easy for them to read. When you allow them to download custom reports as PDFs, it gives them something they can use to see their data quickly. In this tutorial, we&#039;ll make a new Redwood app that stores user data and generates reports for them.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Creating Custom User Reports\" \/>\n<meta property=\"og:description\" content=\"Users expect to be able to see their data in a format that&#039;s easy for them to read. When you allow them to download custom reports as PDFs, it gives them something they can use to see their data quickly. In this tutorial, we&#039;ll make a new Redwood app that stores user data and generates reports for them.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:37:52+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Creating Custom User Reports\",\"datePublished\":\"2022-03-23T22:37:52+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/\"},\"wordCount\":4,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"Javascript\",\"React\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/\",\"name\":\"Creating Custom User Reports\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:37:52+00:00\",\"description\":\"Users expect to be able to see their data in a format that's easy for them to read. When you allow them to download custom reports as PDFs, it gives them something they can use to see their data quickly. In this tutorial, we'll make a new Redwood app that stores user data and generates reports for them.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA\",\"width\":6720,\"height\":4480},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Creating Custom User Reports\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Creating Custom User Reports","description":"Users expect to be able to see their data in a format that's easy for them to read. When you allow them to download custom reports as PDFs, it gives them something they can use to see their data quickly. In this tutorial, we'll make a new Redwood app that stores user data and generates reports for them.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/","og_locale":"en_US","og_type":"article","og_title":"Creating Custom User Reports","og_description":"Users expect to be able to see their data in a format that's easy for them to read. When you allow them to download custom reports as PDFs, it gives them something they can use to see their data quickly. In this tutorial, we'll make a new Redwood app that stores user data and generates reports for them.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:37:52+00:00","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/"},"author":{"name":"","@id":""},"headline":"Creating Custom User Reports","datePublished":"2022-03-23T22:37:52+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/"},"wordCount":4,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA","keywords":["Guest Post","Image","Javascript","React","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/","name":"Creating Custom User Reports","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA","datePublished":"2022-03-23T22:37:52+00:00","description":"Users expect to be able to see their data in a format that's easy for them to read. When you allow them to download custom reports as PDFs, it gives them something they can use to see their data quickly. In this tutorial, we'll make a new Redwood app that stores user data and generates reports for them.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA","width":6720,"height":4480},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-custom-user-reports-with-redwood\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Creating Custom User Reports"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681926209\/Web_Assets\/blog\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3\/49c61c5a752237f029ae20e9eb254c89d6a56a38-6720x4480-1_278086d0c3.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/27807","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=27807"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/27807\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/27808"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=27807"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=27807"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=27807"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}