<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ghofrane Baaziz</title>
    <description>The latest articles on DEV Community by Ghofrane Baaziz (@ghofrane_baaziz_aea1d4056).</description>
    <link>https://dev.to/ghofrane_baaziz_aea1d4056</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3597051%2F7f2a7127-8972-4166-b034-78780ed99179.png</url>
      <title>DEV Community: Ghofrane Baaziz</title>
      <link>https://dev.to/ghofrane_baaziz_aea1d4056</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ghofrane_baaziz_aea1d4056"/>
    <language>en</language>
    <item>
      <title>Why Retool's PDF Component Doesn't Render Your Signed S3 URL (And How to Fix It)</title>
      <dc:creator>Ghofrane Baaziz</dc:creator>
      <pubDate>Mon, 20 Apr 2026 12:39:58 +0000</pubDate>
      <link>https://dev.to/ghofrane_baaziz_aea1d4056/why-retools-pdf-component-doesnt-render-your-signed-s3-url-and-how-to-fix-it-dfk</link>
      <guid>https://dev.to/ghofrane_baaziz_aea1d4056/why-retools-pdf-component-doesnt-render-your-signed-s3-url-and-how-to-fix-it-dfk</guid>
      <description>&lt;p&gt;Original author: Arsany Milad - Engineer @ Stackdrop &lt;/p&gt;

&lt;p&gt;Retool's PDF component uses &lt;code&gt;fetch()&lt;/code&gt; to load files. Browsers enforce CORS on fetch requests. If your S3 bucket isn't configured to allow requests from Retool's origin, the request gets blocked silently and the component renders nothing. Adding Retool's domain to the bucket's CORS configuration fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does a signed S3 URL load correctly in a browser tab but fail silently inside Retool's PDF component?
&lt;/h2&gt;

&lt;p&gt;On a random Friday I found myself working on an invoicing feature: add expense info, upload files, save, done. But when I tried to open the uploaded PDF inside the app it just showed "PDF couldn't be loaded."&lt;/p&gt;

&lt;p&gt;![PDF could not be loaded error in Retool]&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ck4khb40gxd9m9yuisv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ck4khb40gxd9m9yuisv.png" alt=" " width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Retool console wasn't helpful, so I checked the browser console and found this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access to fetch at 'https://&amp;lt;s3-endpoint&amp;gt;/&amp;lt;path&amp;gt;/document.pdf?&amp;lt;signed-url-params&amp;gt;'
from origin 'https://app.retool.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signed URL was valid — pasting it directly into the browser tab opened the PDF without issue. The problem was specific to the PDF component.&lt;/p&gt;

&lt;p&gt;Here's why. When you paste a URL into the address bar, the browser performs a top-level navigation request. CORS rules don't apply to navigation. When Retool's PDF component loads a file, it calls &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" rel="noopener noreferrer"&gt;fetch()&lt;/a&gt; internally. Fetch requests are cross-origin requests, and the browser enforces CORS on them. If the S3 bucket's CORS configuration doesn't include Retool's domain in &lt;code&gt;AllowedOrigins&lt;/code&gt;, the browser blocks the response before the component receives anything.&lt;/p&gt;

&lt;p&gt;Both the URL and the signature are valid. The bucket just hasn't been told that Retool's domain is allowed to make cross-origin requests to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you configure S3 CORS to allow Retool's PDF component to load signed URLs?
&lt;/h2&gt;

&lt;p&gt;This is a CORS configuration change on the S3 bucket. No IAM changes, no bucket policy edits, no changes to how you generate signed URLs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Open the bucket in AWS&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log into the AWS Console&lt;/li&gt;
&lt;li&gt;Navigate to S3&lt;/li&gt;
&lt;li&gt;Select the bucket your Retool app is reading from&lt;/li&gt;
&lt;li&gt;Go to Permissions&lt;/li&gt;
&lt;li&gt;Scroll to CORS configuration&lt;/li&gt;
&lt;li&gt;Click Edit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Add the CORS configuration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedMethods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedOrigins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"https://app.retool.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ExposeHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"ETag"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"MaxAgeSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Retool instance runs on a custom subdomain (e.g. &lt;code&gt;https://yourcompany.retool.com&lt;/code&gt;), replace &lt;code&gt;https://app.retool.com&lt;/code&gt; with your actual domain. Use only the specific domains you control — wildcards like &lt;code&gt;*.retool.com&lt;/code&gt; cover every Retool-hosted app across all customers, not just yours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What each field does:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AllowedOrigins&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tells S3 which domains are permitted to make cross-origin requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AllowedMethods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restricts which HTTP methods those origins can use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AllowedHeaders&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Permits the headers Retool includes in its fetch requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ExposeHeaders&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Makes file metadata — content type, disposition, ETag — readable by the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MaxAgeSeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls how long the browser caches the preflight response&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on PUT:&lt;/strong&gt; The template above uses &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt; only, which is enough for rendering PDFs. If your app also uploads files directly to S3 from the browser via presigned upload URLs — as in my case with invoice uploads — add &lt;code&gt;"PUT"&lt;/code&gt; to &lt;code&gt;AllowedMethods&lt;/code&gt;. If uploads go through your backend, you don't need it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Save and verify&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Save the configuration. Hard-refresh Retool with &lt;code&gt;Ctrl+Shift+R&lt;/code&gt;. Open DevTools → Network tab, reload the PDF component. The S3 response should now include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Access-Control-Allow-Origin: https://app.retool.com
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;![PDF rendering correctly after CORS fix]&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqqqkoh41aq9fmggtxt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqqqkoh41aq9fmggtxt1.png" alt=" " width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How should S3 CORS be configured when Retool has separate production and staging environments?
&lt;/h2&gt;

&lt;p&gt;List each domain explicitly in &lt;code&gt;AllowedOrigins&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"AllowedOrigins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"https://yourapp.retool.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"https://yourapp-staging.retool.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep this list as narrow as possible. A wildcard (&lt;code&gt;"*"&lt;/code&gt;) on a private bucket allows cross-origin requests from any domain. Explicit origins are always the right call on a bucket serving private documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  When does an S3 CORS configuration for Retool need PUT or POST in AllowedMethods?
&lt;/h2&gt;

&lt;p&gt;For rendering PDFs and images, &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt; are sufficient. You only need &lt;code&gt;PUT&lt;/code&gt; if the browser is uploading files directly to S3 via presigned upload URLs initiated from the frontend. If uploads go through your backend or a Retool resource connection, the browser never makes the upload request, so &lt;code&gt;PUT&lt;/code&gt; isn't needed.&lt;/p&gt;

&lt;p&gt;Only add methods you actively use. Each one you include extends the surface area of what cross-origin requests can do on that bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the most common mistakes that cause S3 CORS issues to persist in Retool after applying a fix?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Treating the symptom as a signing issue.&lt;/strong&gt; The signed URL works in the browser, so the signature is fine. CORS is a separate layer. The URL can be perfectly valid and still get blocked at the fetch level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;"*"&lt;/code&gt; as the allowed origin.&lt;/strong&gt; This removes the origin restriction entirely. On a private bucket serving sensitive documents, explicit domains are the right call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;*.retool.com&lt;/code&gt; as the allowed origin.&lt;/strong&gt; This wildcard covers every Retool-hosted app across all customers, not just yours. Always use your specific Retool domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping the hard refresh.&lt;/strong&gt; Browsers cache preflight responses for the duration set in &lt;code&gt;MaxAgeSeconds&lt;/code&gt;. If you test immediately after saving without a hard refresh, the browser may still be acting on the cached response from before the fix. &lt;code&gt;Ctrl+Shift+R&lt;/code&gt; clears this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Editing the wrong bucket.&lt;/strong&gt; If production and staging use different buckets, confirm which one your Retool app is reading from before making the change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the workaround for loading private S3 files in Retool when the bucket CORS configuration cannot be modified?
&lt;/h2&gt;

&lt;p&gt;Two options.&lt;/p&gt;

&lt;p&gt;The first is to use Retool's built-in S3 resource to read the file server-side. Because the request originates from Retool's backend rather than the browser, CORS doesn't apply. The tradeoff is memory management — PDFs are large and you'll need to handle query cleanup after use to avoid memory issues.&lt;/p&gt;

&lt;p&gt;The second is to proxy the file through your own backend: fetch the object server-side, encode it as base64, and return it to Retool as a data URI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data:application/pdf;base64,...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same principle — server-to-server requests aren't subject to browser CORS enforcement. The memory and latency cost scales with document size, which becomes significant with large files or high request volume. Modifying the bucket CORS configuration is the better long-term solution where access permits it.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why does a signed S3 URL open correctly in a browser tab but fail to load inside Retool's PDF component?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you open a signed URL directly in a browser tab, the browser performs a top-level navigation request. CORS rules don't apply to navigation. When Retool's PDF component loads the same URL, it uses &lt;code&gt;fetch()&lt;/code&gt; internally. Fetch requests are cross-origin requests and the browser enforces CORS on them. If the S3 bucket's CORS configuration doesn't include Retool's domain in &lt;code&gt;AllowedOrigins&lt;/code&gt;, the browser blocks the response before the component receives anything. The PDF component renders nothing and gives no visible error in the UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I fix a CORS error blocking a signed S3 URL from loading in the Retool PDF component?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add your specific Retool domain to the CORS configuration on the S3 bucket serving the files. In the AWS Console, go to S3 → your bucket → Permissions → CORS configuration → Edit, and add &lt;code&gt;https://app.retool.com&lt;/code&gt; (or your custom subdomain) to &lt;code&gt;AllowedOrigins&lt;/code&gt;. Save the config and hard-refresh Retool with &lt;code&gt;Ctrl+Shift+R&lt;/code&gt;. This is a bucket-level change — no IAM or bucket policy edits are required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I updated the S3 CORS configuration but Retool's PDF component is still blocked. What should I check?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most common cause is a cached preflight response. Browsers cache CORS preflight results for the duration set in &lt;code&gt;MaxAgeSeconds&lt;/code&gt;. If you test without a hard refresh (&lt;code&gt;Ctrl+Shift+R&lt;/code&gt;), the browser may still be acting on the cached response from before the fix. If a hard refresh doesn't resolve it, open DevTools → Network and confirm &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; is now present in the S3 response. If it's still missing, verify you edited the correct bucket — production and staging buckets are separate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the S3 CORS fix for Retool's PDF component also apply to image components and other file types?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any Retool component that loads files using &lt;code&gt;fetch()&lt;/code&gt; can be blocked by missing CORS headers. The PDF component is the most common case because PDFs are typically served from private buckets via signed URLs. If you encounter the same symptom with another component type — the URL works in a browser but the component renders nothing — the fix is the same: add Retool's domain to &lt;code&gt;AllowedOrigins&lt;/code&gt; on the S3 bucket serving those files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a workaround for loading private S3 files in Retool when the bucket CORS configuration cannot be modified?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. You can use Retool's built-in S3 resource to fetch the file server-side, or proxy it through your own backend and return it as a base64 data URI (&lt;code&gt;data:application/pdf;base64,...&lt;/code&gt;). Both approaches bypass browser CORS enforcement because the request originates from a server rather than the browser. The memory and latency cost scales with document size — modifying the bucket CORS configuration is the better long-term solution where access permits it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Arsany Milad is a developer at &lt;a href="https://stackdrop.co/?utm_medium=social&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;Stackdrop&lt;/a&gt;, a Retool-certified agency building governed internal tools for mid-market and enterprise clients across EMEA.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>aws</category>
      <category>lowcode</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a custom range slider for Retool with a histogram built in</title>
      <dc:creator>Ghofrane Baaziz</dc:creator>
      <pubDate>Mon, 24 Nov 2025 13:24:21 +0000</pubDate>
      <link>https://dev.to/ghofrane_baaziz_aea1d4056/i-built-a-custom-range-slider-for-retool-with-a-histogram-built-in-5491</link>
      <guid>https://dev.to/ghofrane_baaziz_aea1d4056/i-built-a-custom-range-slider-for-retool-with-a-histogram-built-in-5491</guid>
      <description>&lt;h2&gt;
  
  
  I built a custom range slider for Retool with a histogram built in
&lt;/h2&gt;

&lt;p&gt;Range sliders in Retool are great until you need to understand your data. They let you pick min and max values, but they don’t tell you anything about the distribution itself.&lt;/p&gt;

&lt;p&gt;I kept running into this when building internal tools, so I built a custom range slider that includes a histogram, handles uneven distributions and exposes clean values. It’s written in TypeScript and works as a native Retool component.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;GitHub repo:&lt;/strong&gt; &lt;a href="https://github.com/StackdropCO/custom-range-slider-retool-component" rel="noopener noreferrer"&gt;https://github.com/StackdropCO/custom-range-slider-retool-component&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;More Awesome Retool components here:&lt;/strong&gt; &lt;a href="https://github.com/StackdropCO/awesome-retool-components" rel="noopener noreferrer"&gt;https://github.com/StackdropCO/awesome-retool-components&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s why I built it and how to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;A real example that pushed me to do this:&lt;/p&gt;

&lt;p&gt;I needed a filter for “years of experience” inside a Retool app. Most candidates had between 0 and 20 years, but a few outliers had 45 years. With the standard slider, you get min and max, but no clue whether the middle ranges are empty or dense.&lt;/p&gt;

&lt;p&gt;It forces you to guess, or worse, manually inspect the dataset.&lt;/p&gt;

&lt;p&gt;To show what I mean:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retool’s default slider (no visibility):&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3gg22k2ksgnkcyq5xoi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3gg22k2ksgnkcyq5xoi.png" alt="Retool's default range slider without distribution visibility" width="688" height="152"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Custom Range Slider with distribution visible:&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0jnvm8ckrfh1c64o5n1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0jnvm8ckrfh1c64o5n1.png" alt="Custom range slider component with histogram showing data distribution overlay" width="634" height="294"&gt;&lt;/a&gt;&lt;br&gt;
The histogram makes it obvious where your data actually sits. In my case, the “20 to 45 years” range was basically empty. With a logarithmic scale, even large skewed values become readable.&lt;/p&gt;

&lt;p&gt;This small change makes filtering more honest and more useful.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I focused on while building it
&lt;/h2&gt;

&lt;p&gt;Here are the engineering decisions that mattered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Histogram built in&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Shows distribution directly on the slider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple scales&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Linear, logarithmic and square root for different distribution shapes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clean values&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Access the selected range through &lt;code&gt;selectedRange.start&lt;/code&gt; and &lt;code&gt;selectedRange.end&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexible inputs&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Arrays, number lists, query outputs — anything numeric.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Click-to-select&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Click any histogram bar to jump to that range.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom colors&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Fully themeable inside Retool.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Negative values supported&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Useful for datasets that include offsets or deltas.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Clone the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/StackdropCO/custom-range-slider-retool-component.git

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install dependencies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;custom-range-slider
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Log in to Retool and initialize
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx retool-ccl login
npx retool-ccl init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start development mode
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx retool-ccl dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy the component
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx retool-ccl deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Using it in Retool
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drag&lt;/strong&gt; the Range Slider component onto the canvas.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Define start and end values:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;start: 0&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;end: 50&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom formatting:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 year&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; years`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bind distribution data:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;{{ query2.dataArray }}&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access the selected range:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;customRangeSlider1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedRange&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Switch scales if your data is skewed:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Linear (default)&lt;/li&gt;
&lt;li&gt;Logarithmic&lt;/li&gt;
&lt;li&gt;Square root&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;published version&lt;/strong&gt; for production&lt;/li&gt;
&lt;li&gt;Test locally with &lt;code&gt;npx retool-ccl dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deploy and &lt;strong&gt;pin the version&lt;/strong&gt; in your Retool app&lt;/li&gt;
&lt;li&gt;For multi-instance setups, use &lt;code&gt;npx retool-ccl sync&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;A few improvements I'm exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-generated histograms from simple arrays&lt;/li&gt;
&lt;li&gt;More control over spacing and styling&lt;/li&gt;
&lt;li&gt;Optional statistics like mean and median&lt;/li&gt;
&lt;li&gt;Additional scale types&lt;/li&gt;
&lt;li&gt;General cleanup and refinements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Get involved
&lt;/h2&gt;

&lt;p&gt;I built this to make filtering inside Retool more &lt;strong&gt;accurate&lt;/strong&gt;, especially with uneven distributions. It's fully &lt;strong&gt;open source&lt;/strong&gt;, and I'd love feedback, ideas or pull requests.&lt;/p&gt;

&lt;p&gt;If you try it, let me know how it works for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy building!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Interested in learning more about this component? Check out the &lt;a href="https://stackdrop.com/components/range-slider?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=component_library" rel="noopener noreferrer"&gt;full component library and documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>lowcode</category>
    </item>
  </channel>
</rss>
