{"id":38389,"date":"2025-09-03T07:00:00","date_gmt":"2025-09-03T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=38389"},"modified":"2025-11-06T17:14:16","modified_gmt":"2025-11-07T01:14:16","slug":"signed-urls-the-why-and-how","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how","title":{"rendered":"Signed URLs With Cloudinary: The Why and How"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>When uploading images to Cloudinary programmatically, you can do it from the frontend or the backend, depending on the app\u2019s needs. Frontend uploads are more common in customer-facing apps, where speed is critical, and you want the image to be on a server somewhere as soon as possible without any proxies in between. Backend uploads, on the other hand, are used when you want the image to be passed through your API or in asynchronous operations and scheduled jobs.<\/p>\n<p>Imagine an AI-based image transformation app where users upload images they want to be transformed using AI. An AI transformation may take quite a bit of time already, so you want to optimize the speed wherever possible. By uploading to Cloudinary from the frontend client, you\u2019ll upload your image to a geographically close region, which is then replicated to the vast number of Cloudinary CDN servers automatically.<\/p>\n<p>Sometimes, there are other factors involved, too. Perhaps you\u2019re limited by the payload size of your serverless platform and can\u2019t send large images. Or you just want to save on bandwidth. In such cases, you\u2019ll want the images to be uploaded to Cloudinary by the frontend, so you can just send a URL to your backend API.<\/p>\n<p>In all cases, images should be uploaded to Cloudinary securely using your Cloudinary API secret. When uploading from the backend, that\u2019s an easier task. You only need to use the <a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_sdks\">Cloudinary SDK<\/a>, initialize it with the API key and secret, and then use the upload API to upload the image. On the frontend, things aren\u2019t so simple. Everything shipped to the browser is theoretically readable by the user, so you don\u2019t want to expose your API secret to the user. Otherwise, a malicious user might use it to upload any images to your Cloudinary account, spending your plan\u2019s tokens, which is not something that you intended.<\/p>\n<p>There\u2019s a workaround combining the security of the backend with the upload flexibility of the frontend, generating signed URLs for upload. In this blog post, we\u2019ll show you how this can be done.<\/p>\n<h2>Generating Signed Upload URLs<\/h2>\n<p>The easiest way to generate signed upload URLs is to use the Cloudinary SDK. We\u2019ll show you an example using Next.js and the Node SDK.<\/p>\n<p>For simplicity, we\u2019ll use Next.js server functions to perform code meant to be executed by the backend. If you use some alternative backend implementations, you can still use this as a guideline. For those of you not familiar with Next.js <a href=\"https:\/\/nextjs.org\/docs\/app\/building-your-application\/data-fetching\/server-actions-and-mutations\">server functions<\/a>, they are almost like RPCs (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Remote_procedure_call\">Remote Procedure Calls<\/a>) exposed to the client running in the browser. When a server function is called, the client sends a POST request to the Next.js backend in the background.<\/p>\n<p>As with any publicly accessible API, protecting it is always a good idea. The first step is to check if an authorized user has made the request. The next step is to initialize the Cloudinary SDK by providing it with your cloud name, API key, and API secret. You can find your Cloudinary cloud name by logging into your Cloudinary account and navigating to the settings page of your dashboard in <a href=\"https:\/\/console.cloudinary.com\/settings\/api-keys\">API Keys<\/a>. The cloud name will be noted at the top of the page. This is also the place to obtain your API key and secret. All of this information should be stored securely in environment variables on the server.<\/p>\n<p>After initializing the SDK, the next step is to create a cryptographic signature that will be used for image uploads. Without the signature, anonymous users cannot upload to your Cloudinary account. The signature takes some parameters, such as timestamp, upload folder, and tags (if you would like to tag your images). These parameters, in addition to the signature, are then provided to the frontend. The only piece not provided to the frontend is your Cloudinary secret; as its name says, this should be kept secret and not exposed outside the back end.<\/p>\n<p>The complete code is below. The module is annotated with the <code>use server<\/code> directive to mark it as a module that contains server functions exposed to the client.<\/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\"><span class=\"hljs-string\">\"use server\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getCurrentUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/lib\/auth.ts'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> CLOUDINARY_CLOUD_NAME = process.env.CLOUDINARY_CLOUD_NAME || <span class=\"hljs-string\">\"\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> CLOUDINARY_API_KEY = process.env.CLOUDINARY_API_KEY || <span class=\"hljs-string\">\"\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> CLOUDINARY_API_SECRET = process.env.CLOUDINARY_API_SECRET || <span class=\"hljs-string\">\"\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> CLOUDINARY_UPLOAD_FOLDER = process.env.CLODINARY_UPLOAD_FOLDER || <span class=\"hljs-string\">\"\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> generateCloudinarySignatureAction = <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-comment\">\/\/ The implementation of getCurrentUser is not relevant for this blog post<\/span>\n  <span class=\"hljs-comment\">\/\/ However, this is only to show you that you need to protect your server actions<\/span>\n  <span class=\"hljs-comment\">\/\/ if image upload is only for authenticated users<\/span>\n  <span class=\"hljs-keyword\">const<\/span> currentUser = <span class=\"hljs-keyword\">await<\/span> getCurrentUser();\n  <span class=\"hljs-keyword\">if<\/span> (!currentUser) {\n    <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">\"User not authenticated\"<\/span>) \n  }\n  \n  <span class=\"hljs-comment\">\/\/ Configure your Cloudinary instance with the properties obtained from the environment<\/span>\n  cloudinary.config({\n    <span class=\"hljs-attr\">cloud_name<\/span>: CLOUDINARY_CLOUD_NAME,\n    <span class=\"hljs-attr\">api_key<\/span>: CLOUDINARY_API_KEY,\n    <span class=\"hljs-attr\">api_secret<\/span>: CLOUDINARY_API_SECRET,\n  });\n\n  <span class=\"hljs-comment\">\/\/ Every signature is parametrized for the specific upload needed<\/span>\n  <span class=\"hljs-keyword\">const<\/span> paramsToSign = {\n    <span class=\"hljs-attr\">timestamp<\/span>: <span class=\"hljs-built_in\">Math<\/span>.floor(<span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Date<\/span>().getTime() \/ <span class=\"hljs-number\">1000<\/span>), <span class=\"hljs-comment\">\/\/ Unix timestamp in seconds<\/span>\n    <span class=\"hljs-attr\">folder<\/span>: CLOUDINARY_UPLOAD_FOLDER, <span class=\"hljs-comment\">\/\/ The folder to upload the image to<\/span>\n    <span class=\"hljs-attr\">tags<\/span>: <span class=\"hljs-string\">\"avatar-image\"<\/span>, <span class=\"hljs-comment\">\/\/ Optionally, tags to add to the image, comma separated<\/span>\n  };\n\n  <span class=\"hljs-comment\">\/\/ Call the Cloudinary SDK to sign the parameters<\/span>\n  <span class=\"hljs-keyword\">const<\/span> signature = cloudinary.utils.api_sign_request(\n    paramsToSign,\n    CLOUDINARY_API_SECRET,\n  );\n\n  <span class=\"hljs-comment\">\/\/ All of the following properties are needed on the frontend to perform the upload<\/span>\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">signature<\/span>: signature,\n    <span class=\"hljs-attr\">apiKey<\/span>: CLOUDINARY_API_KEY,\n    <span class=\"hljs-attr\">cloudName<\/span>: CLOUDINARY_CLOUD_NAME,\n    <span class=\"hljs-attr\">timestamp<\/span>: paramsToSign.timestamp,\n    <span class=\"hljs-attr\">folder<\/span>: paramsToSign.folder,\n  };\n};\n\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>On the client, we\u2019ll show how this works by working on top of an imaginary <code>Avatar<\/code> component, which provides an <code>onClickUpload<\/code> handler called when the user wants to change their avatar. In the handler, we first invoke the server function. In the background, Next.js makes a POST request to the backend and returns the signature result object.<\/p>\n<p>We\u2019ll then create a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/FormData\">FormData<\/a> object and attach the properties from the signature and the image file. We can then upload the image to Cloudinary using a plain <code>fetch<\/code> call. The response will be an object with the <code>secure_url<\/code> property, which contains the URL of the uploaded image. We can then send this URL to the backend or use it to update the client\u2019s avatar immediately.<\/p>\n<p>The code is below:<\/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\"><span class=\"hljs-string\">\"use client\"<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Home, Settings, User } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"lucide-react\"<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Avatar } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/avatar\"<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Button } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/ui\/button\"<\/span>\n<span class=\"hljs-keyword\">import<\/span> { generateCloudinarySignatureAction, updateUserAvatar } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/actions\"<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Sidebar<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;avatarUrl, setAvatarUrl] = useState&lt;string | <span class=\"hljs-literal\">null<\/span>&gt;(<span class=\"hljs-literal\">null<\/span>)\n  <span class=\"hljs-keyword\">const<\/span> &#91;isUploading, setIsUploading] = useState(<span class=\"hljs-literal\">false<\/span>)\n\n  <span class=\"hljs-keyword\">const<\/span> handleAvatarUpload = <span class=\"hljs-keyword\">async<\/span> (imageFile: File) =&gt; {\n    <span class=\"hljs-keyword\">try<\/span> {\n      setIsUploading(<span class=\"hljs-literal\">true<\/span>)\n\n      <span class=\"hljs-comment\">\/\/ Generate a signed upload URL from Cloudinary<\/span>\n      <span class=\"hljs-keyword\">const<\/span> signatureResult = <span class=\"hljs-keyword\">await<\/span> generateCloudinarySignatureAction()\n\n      <span class=\"hljs-comment\">\/\/ Create form data for the upload<\/span>\n      <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">new<\/span> FormData()\n      formData.append(<span class=\"hljs-string\">\"file\"<\/span>, imageFile)\n      <span class=\"hljs-comment\">\/\/ Alternatively, you can also read this from something like process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY<\/span>\n      formData.append(<span class=\"hljs-string\">\"api_key\"<\/span>, signatureResult.apiKey);\n      formData.append(<span class=\"hljs-string\">\"timestamp\"<\/span>, <span class=\"hljs-string\">`<span class=\"hljs-subst\">${signatureResult.timestamp}<\/span>`<\/span>);\n      formData.append(<span class=\"hljs-string\">\"signature\"<\/span>, signatureResult.signature);\n      formData.append(<span class=\"hljs-string\">\"folder\"<\/span>, signatureResult.folder);\n      formData.append(<span class=\"hljs-string\">\"tags\"<\/span>, <span class=\"hljs-string\">\"avatar-image\"<\/span>);\n\n      <span class=\"hljs-comment\">\/\/ Upload to Cloudinary<\/span>\n      <span class=\"hljs-keyword\">const<\/span> uploadResponse = <span class=\"hljs-keyword\">await<\/span> fetch(uploadUrl, {\n        <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n        <span class=\"hljs-attr\">body<\/span>: formData,\n      })\n\n      <span class=\"hljs-keyword\">const<\/span> uploadResult = <span class=\"hljs-keyword\">await<\/span> uploadResponse.json()\n\n      <span class=\"hljs-keyword\">if<\/span> (uploadResult.secure_url) {\n        <span class=\"hljs-comment\">\/\/ Update the user's avatar in the database<\/span>\n        <span class=\"hljs-keyword\">await<\/span> updateUserAvatarAction(uploadResult.secure_url)\n        setAvatarUrl(uploadResult.secure_url)\n      }\n    } <span class=\"hljs-keyword\">catch<\/span> (error) {\n      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error uploading avatar\"<\/span>, error)\n    } <span class=\"hljs-keyword\">finally<\/span> {\n      setIsUploading(<span class=\"hljs-literal\">false<\/span>)\n    }\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      { \/** Other components **\/ }\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Avatar<\/span> <span class=\"hljs-attr\">url<\/span>=<span class=\"hljs-string\">{avatarUrl}<\/span> <span class=\"hljs-attr\">onClickUpload<\/span>=<span class=\"hljs-string\">{handleAvatarUpload}<\/span> <span class=\"hljs-attr\">isUploading<\/span>=<span class=\"hljs-string\">{isUploading}<\/span> \/&gt;<\/span>\n      { \/** Other components **\/ }\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  )\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>It\u2019s also important to note at this point that the above code shows how to upload the image manually, in case you would want to have complete control over the entire process. If you\u2019d just like to get running as soon as possible, you can also use <a href=\"https:\/\/cloudinary.com\/documentation\/upload_widget#signed_uploads\">Cloudinary\u2019s upload widget<\/a> (although the signature generation part on the backend is the same).<\/p>\n<h2>Important Points to Note<\/h2>\n<p>Once a signature is generated with certain parameters, it\u2019s only valid for those parameters. This is an important point to consider when making changes to the backend API. Imagine, for example, that you have a long-running frontend app that uses backend-generated signatures to upload images to a user gallery. Then, at some point, you want to introduce tagging for such images by tagging each uploaded image with <code>gallery<\/code>. The backend is deployed, and you start seeing failed user uploads in your logs. This means some users obtained the signature from the old API and then attempted to upload. Since signatures are also used to validate that no parameters originally meant for the image were tampered with (a good security measure!), those older upload URLs are no longer valid.<\/p>\n<p>To avoid this, always make sure to do proper change management of your API, via API versioning, for example.<\/p>\n<h2>Conclusion<\/h2>\n<p>Cloudinary\u2019s signed upload URLs are another tool you can use with Cloudinary to upload your assets. With them, image uploads aren\u2019t only restricted to backend services but can be used directly by the end-user, which securely provides speed and simplicity.<\/p>\n<p><a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up for a free Cloudinary account<\/a> to try it out for yourself. Cloudinary offers a free-for-life individual access tier, so you can get all of Cloudinary forever starting today.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":38390,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[370,264],"class_list":["post-38389","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-image","tag-security"],"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>Secure Image Uploads With Cloudinary Signed URLs<\/title>\n<meta name=\"description\" content=\"Learn how to use the Cloudinary signed URL feature works to enable secure and fast image uploads directly from the frontend, without exposing your API secret.\" \/>\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\/signed-urls-the-why-and-how\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Signed URLs With Cloudinary: The Why and How\" \/>\n<meta property=\"og:description\" content=\"Learn how to use the Cloudinary signed URL feature works to enable secure and fast image uploads directly from the frontend, without exposing your API secret.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-09-03T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-07T01:14:16+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Signed URLs With Cloudinary: The Why and How\",\"datePublished\":\"2025-09-03T14:00:00+00:00\",\"dateModified\":\"2025-11-07T01:14:16+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how\"},\"wordCount\":8,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA\",\"keywords\":[\"Image\",\"Security\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how\",\"url\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how\",\"name\":\"Secure Image Uploads With Cloudinary Signed URLs\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA\",\"datePublished\":\"2025-09-03T14:00:00+00:00\",\"dateModified\":\"2025-11-07T01:14:16+00:00\",\"description\":\"Learn how to use the Cloudinary signed URL feature works to enable secure and fast image uploads directly from the frontend, without exposing your API secret.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA\",\"width\":2000,\"height\":1100,\"caption\":\"Image showing a plated croissant with the cloudinary logo and representing the Cloudinary signed URL process\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Signed URLs With Cloudinary: The Why and How\"}]},{\"@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\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Secure Image Uploads With Cloudinary Signed URLs","description":"Learn how to use the Cloudinary signed URL feature works to enable secure and fast image uploads directly from the frontend, without exposing your API secret.","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\/signed-urls-the-why-and-how","og_locale":"en_US","og_type":"article","og_title":"Signed URLs With Cloudinary: The Why and How","og_description":"Learn how to use the Cloudinary signed URL feature works to enable secure and fast image uploads directly from the frontend, without exposing your API secret.","og_url":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how","og_site_name":"Cloudinary Blog","article_published_time":"2025-09-03T14:00:00+00:00","article_modified_time":"2025-11-07T01:14:16+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Signed URLs With Cloudinary: The Why and How","datePublished":"2025-09-03T14:00:00+00:00","dateModified":"2025-11-07T01:14:16+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how"},"wordCount":8,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA","keywords":["Image","Security"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how","url":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how","name":"Secure Image Uploads With Cloudinary Signed URLs","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA","datePublished":"2025-09-03T14:00:00+00:00","dateModified":"2025-11-07T01:14:16+00:00","description":"Learn how to use the Cloudinary signed URL feature works to enable secure and fast image uploads directly from the frontend, without exposing your API secret.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA","width":2000,"height":1100,"caption":"Image showing a plated croissant with the cloudinary logo and representing the Cloudinary signed URL process"},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/signed-urls-the-why-and-how#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Signed URLs With Cloudinary: The Why and How"}]},{"@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":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1755643233\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog\/Signed_URLs_With_Cloudinary__The_Why_and_How-blog.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38389","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\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=38389"}],"version-history":[{"count":2,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38389\/revisions"}],"predecessor-version":[{"id":39168,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38389\/revisions\/39168"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/38390"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=38389"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=38389"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=38389"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}