<?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: Ameen Usman</title>
    <description>The latest articles on DEV Community by Ameen Usman (@ameenusman).</description>
    <link>https://dev.to/ameenusman</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4181012%2Fe1324f6d-6551-4ecc-ab3f-784cfd012669.png</url>
      <title>DEV Community: Ameen Usman</title>
      <link>https://dev.to/ameenusman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ameenusman"/>
    <language>en</language>
    <item>
      <title>What a JSON converter drafts from the first object</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Sun, 01 Nov 2026 05:55:29 +0000</pubDate>
      <link>https://dev.to/ameenusman/what-a-json-converter-drafts-from-the-first-object-24b6</link>
      <guid>https://dev.to/ameenusman/what-a-json-converter-drafts-from-the-first-object-24b6</guid>
      <description>&lt;p&gt;I had an API payload in one window and a blank TypeScript file in the other. The payload was a list of people. Each object carried an id, a name, a role, and a meta object with an active flag. I was ready to type the interface by hand and assume the second person would have the same keys as the first. That assumption is the useful part to check. The JSON converter at &lt;a href="https://kepttools.com/json-converter" rel="noopener noreferrer"&gt;https://kepttools.com/json-converter&lt;/a&gt; reads the paste in the browser and drafts the interface from the first object only. The same text can also become a Laravel array, a PHP DTO, or CSV. The blocks below are that sample, run through the converter, not a tidied rewrite of it.&lt;/p&gt;

&lt;p&gt;The Sample button uses this array:&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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"meta"&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="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Linus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"founder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"meta"&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="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;Laravel keeps both people, and it keeps meta nested as a PHP array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ada'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'engineer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'meta'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Linus'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'founder'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'meta'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript stops at Ada. Linus never adds a field. Meta becomes its own interface because the value is an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Meta&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PHP DTO is one class, always named DataTransferObject, and it is also Ada only. Meta is array, not a nested class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataTransferObject&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'role'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'meta'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSV keeps the top-level keys and puts meta in a single cell. The nested object is JSON text, with quotes doubled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id,name,role,meta
1,Ada,engineer,"{""active"":true}"
2,Linus,founder,"{""active"":false}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A shorter array makes the same cut obvious. &lt;code&gt;[{"id":1,"name":"Ada"},{"id":2,"email":"linus@example.com"}]&lt;/code&gt; still becomes two Laravel rows, so &lt;code&gt;email&lt;/code&gt; survives there. The interface and the PHP class keep &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; only. CSV's header is &lt;code&gt;id,name,email&lt;/code&gt;: Ada's row ends on an empty cell, and the next row leaves &lt;code&gt;name&lt;/code&gt; empty.&lt;/p&gt;

&lt;p&gt;Two limits sit on top of that sample. If the root is an array, TypeScript and the PHP DTO never look past the first object, so a key that exists only later is missing from the draft. CSV does look at later rows when it builds the header, but a nested object still occupies one cell, not a new column per inner key. Invalid JSON clears the output and shows the parse error. &lt;code&gt;{&lt;/code&gt; leaves the box empty with &lt;code&gt;Expected property name or '}' in JSON at position 1 (line 1 column 2)&lt;/code&gt;. An empty array is valid JSON and still not CSV: the output is cleared and the message is &lt;code&gt;CSV conversion expects an array of objects.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One tip before you paste the draft into a project: open the second object and look for a key the first object does not have. Root will not list it. Add that field yourself, or convert a single object that already contains every key you mean to keep. The PHP constructor also assigns null when a key is absent, including into &lt;code&gt;public int $id&lt;/code&gt;, so treat the class as a draft and rename DataTransferObject.&lt;/p&gt;

&lt;p&gt;If the paste is hard to read, indent it first at &lt;a href="https://kepttools.com/json-formatter" rel="noopener noreferrer"&gt;https://kepttools.com/json-formatter&lt;/a&gt; and then convert. A trailing comma is enough to wipe the output.&lt;/p&gt;

&lt;p&gt;Full guide with the FAQ and the converter: &lt;a href="https://kepttools.com/guides/json-to-typescript-php-dto-laravel" rel="noopener noreferrer"&gt;https://kepttools.com/guides/json-to-typescript-php-dto-laravel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>json</category>
      <category>typescript</category>
      <category>php</category>
      <category>laravel</category>
    </item>
    <item>
      <title>What cron expression runs every 5 minutes?</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Sat, 31 Oct 2026 05:18:12 +0000</pubDate>
      <link>https://dev.to/ameenusman/what-cron-expression-runs-every-5-minutes-1aa</link>
      <guid>https://dev.to/ameenusman/what-cron-expression-runs-every-5-minutes-1aa</guid>
      <description>&lt;p&gt;A cleanup job was supposed to run every five minutes. The crontab was saved at 10:03. The log showed 10:05, then 10:10. That is not a late start. The expression is tied to the clock, at minutes 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, and 55. Saving the file does not start a countdown. Classic cron also does not hold the next start while the previous process is still running, so a six-minute command overlaps itself. What follows is the five-field form used by Vixie cron and cronie, not a systemd timer and not Quartz.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five fields, then the command
&lt;/h2&gt;

&lt;p&gt;A standard line is five time fields and then the command. None of them is a seconds field.&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;Range&lt;/th&gt;
&lt;th&gt;A star means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minute&lt;/td&gt;
&lt;td&gt;0–59&lt;/td&gt;
&lt;td&gt;Every minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hour&lt;/td&gt;
&lt;td&gt;0–23&lt;/td&gt;
&lt;td&gt;Every hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day of month&lt;/td&gt;
&lt;td&gt;1–31&lt;/td&gt;
&lt;td&gt;Every day of the month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Month&lt;/td&gt;
&lt;td&gt;1–12&lt;/td&gt;
&lt;td&gt;Every month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day of week&lt;/td&gt;
&lt;td&gt;0–7&lt;/td&gt;
&lt;td&gt;Every day of the week&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Vixie cron and on cronie, Sunday is both 0 and 7. Monday is 1 and Friday is 5, so &lt;code&gt;1-5&lt;/code&gt; is Monday through Friday. Some other schedulers accept only 0 for Sunday. Read crontab(5) on the host before using 7. Prefer numbers to names such as MON or JAN.&lt;/p&gt;

&lt;p&gt;The fields use the daemon’s timezone, so &lt;code&gt;0 0 * * *&lt;/code&gt; is midnight there, not necessarily UTC. Honor &lt;code&gt;CRON_TZ&lt;/code&gt; only if that host’s crontab(5) says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every 5, 10, 15, and 30 minutes
&lt;/h2&gt;

&lt;p&gt;Each line below is aligned to the clock, and each one includes minute 0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/5 * * * *     # 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55
*/10 * * * *    # 0, 10, 20, 30, 40, 50
*/15 * * * *    # 0, 15, 30, 45
*/30 * * * *    # 0 and 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The edit time is not stored. A line installed at 10:03 still waits for 10:05, then 10:10.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;5-59/5 * * * *&lt;/code&gt; starts the range at 5, so minute 0 is left out. Use it only when you mean to skip the top of the hour. For a plain every-five-minutes job, keep &lt;code&gt;*/5 * * * *&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Drop the step when you want an hour or a day instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 * * * *       # every hour, at minute 0
0 0 * * *       # every day at 00:00
0 9 * * *       # every day at 09:00
0 9 * * 1-5     # Monday through Friday at 09:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0 * * * *&lt;/code&gt; fires 24 times a day, &lt;code&gt;0 0 * * *&lt;/code&gt; once at midnight, and &lt;code&gt;0 9 * * 1-5&lt;/code&gt; on weekdays at 09:00 because day-of-month stays a star. The either-day rule starts only when both day fields are restricted. A day field that starts with &lt;code&gt;*&lt;/code&gt;, including &lt;code&gt;*/2&lt;/code&gt;, is not restricted. See &lt;a href="https://kepttools.com/guides/cron-both-day-fields" rel="noopener noreferrer"&gt;why cron runs on the wrong day&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a step is counted
&lt;/h2&gt;

&lt;p&gt;A step is a range plus &lt;code&gt;/n&lt;/code&gt;. In the minute field, &lt;code&gt;*&lt;/code&gt; means &lt;code&gt;0-59&lt;/code&gt;, so &lt;code&gt;*/5&lt;/code&gt; is 0, then every fifth minute through 55. Twelve runs an hour.&lt;/p&gt;

&lt;p&gt;In the hour field, &lt;code&gt;0 */2 * * *&lt;/code&gt; is minute 0 of every second hour: 00:00, 02:00, 04:00, and so on through 22:00.&lt;/p&gt;

&lt;p&gt;Day of month starts at 1, not 0. On Vixie cron and cronie, &lt;code&gt;*&lt;/code&gt; in that field is &lt;code&gt;1-31&lt;/code&gt;, so &lt;code&gt;0 0 */5 * *&lt;/code&gt; is midnight on the 1st, 6th, 11th, 16th, 21st, 26th, and 31st. It is not the dates divisible by 5 (the 5th, 10th, 15th, 20th, 25th, and 30th). If the daemon is not cronie, test that list. A &lt;a href="https://kepttools.com/cron-generator" rel="noopener noreferrer"&gt;cron expression generator&lt;/a&gt; shows the five fields. It does not simulate one daemon’s calendar, and it does not install the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes that move the run
&lt;/h2&gt;

&lt;p&gt;Copy the five fields, not the sentence beside them. Crontab does not read “every five minutes.”&lt;/p&gt;

&lt;p&gt;Do not add a seconds field in front. The other fields shift right. Quartz uses six fields, or seven with a year, and seconds come first. Spring uses that six-field form. EventBridge’s six fields are minutes, hours, day-of-month, month, day-of-week, and year, with no seconds field. Quartz and EventBridge both use &lt;code&gt;?&lt;/code&gt; in a day field. A five-field line matches neither shape.&lt;/p&gt;

&lt;p&gt;Traditional Vixie cron does not skip a run because the previous process is still going. If the work must not overlap, take a lock in the command, for example with &lt;code&gt;flock&lt;/code&gt;, and leave the schedule alone. systemd timers and queue workers can differ. This warning is about classic cron.&lt;/p&gt;

&lt;p&gt;Cron’s PATH is often shorter than a login shell. Call the program by its absolute path. A stored “next run” in an API is often a Unix timestamp, not an expression. Read that number on the &lt;a href="https://kepttools.com/timestamp" rel="noopener noreferrer"&gt;Unix timestamp converter&lt;/a&gt;. Do not paste it into the minute field.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;*/5&lt;/code&gt; cannot mean “five minutes from now.” Sleep in a process, or use a queue delay. The daemon still starts the command once, when the minute begins, not again at second 10 or 20.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it for one cycle
&lt;/h2&gt;

&lt;p&gt;Point a throwaway line at a file you can delete.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /tmp/cron-every-5.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait until two lines are written. The minutes should be five apart, and each minute should be divisible by 5. Then delete the line. Some hosts log the start with &lt;code&gt;journalctl -u cron&lt;/code&gt;. Others still use syslog. Use the log that machine keeps.&lt;/p&gt;

&lt;p&gt;Full guide with the FAQ and a cron generator: &lt;a href="https://kepttools.com/guides/cron-every-5-minutes" rel="noopener noreferrer"&gt;https://kepttools.com/guides/cron-every-5-minutes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cron</category>
      <category>linux</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to encode a URL and escape HTML in the browser</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Thu, 29 Oct 2026 05:13:54 +0000</pubDate>
      <link>https://dev.to/ameenusman/how-to-encode-a-url-and-escape-html-in-the-browser-1nf1</link>
      <guid>https://dev.to/ameenusman/how-to-encode-a-url-and-escape-html-in-the-browser-1nf1</guid>
      <description>&lt;p&gt;A query string and an HTML page do not escape text the same way.&lt;/p&gt;

&lt;p&gt;This string is fine in a sentence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Hello World&amp;gt; &amp;amp; "KeptTools"?q=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put it in a URL without encoding, and &lt;code&gt;&amp;amp;&lt;/code&gt; starts a new parameter. Put it in HTML without escaping, and &lt;code&gt;&amp;lt;&lt;/code&gt; starts a tag. Those are two different jobs.&lt;/p&gt;

&lt;p&gt;I use a small encoder for both. It runs in the browser, so the string is not uploaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kepttools.com/url-html-encoder" rel="noopener noreferrer"&gt;https://kepttools.com/url-html-encoder&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  URL encoding
&lt;/h2&gt;

&lt;p&gt;URL encoding turns characters that have a special meaning in a URL into &lt;code&gt;%&lt;/code&gt; sequences.&lt;/p&gt;

&lt;p&gt;The tool uses &lt;code&gt;encodeURIComponent&lt;/code&gt;, which is the right function for a query &lt;strong&gt;value&lt;/strong&gt; or a path segment. The sample becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%3CHello%20World%3E%20%26%20%22KeptTools%22%3Fq%3D1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;&lt;/code&gt; is &lt;code&gt;%3C&lt;/code&gt;, a space is &lt;code&gt;%20&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt; is &lt;code&gt;%26&lt;/code&gt;, &lt;code&gt;"&lt;/code&gt; is &lt;code&gt;%22&lt;/code&gt;, and &lt;code&gt;?&lt;/code&gt; is &lt;code&gt;%3F&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;encodeURI&lt;/code&gt; is the other common function, and it is not the same. It leaves &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, and &lt;code&gt;=&lt;/code&gt; alone, because those characters structure a full URL. Use that when you already have a complete address. Use &lt;code&gt;encodeURIComponent&lt;/code&gt; when the text is one value you are about to drop into a query string.&lt;/p&gt;

&lt;p&gt;Do not run &lt;code&gt;encodeURIComponent&lt;/code&gt; on a whole URL. It will also encode &lt;code&gt;https://&lt;/code&gt; and the &lt;code&gt;?&lt;/code&gt;, and the link will break.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML escaping
&lt;/h2&gt;

&lt;p&gt;HTML escaping is for text that should be shown, not parsed.&lt;/p&gt;

&lt;p&gt;The same sample, on HTML Encode, becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;lt;Hello World&amp;amp;gt; &amp;amp;amp; &amp;amp;quot;KeptTools&amp;amp;quot;?q=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;&lt;/code&gt; becomes &lt;code&gt;&amp;amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt; becomes &lt;code&gt;&amp;amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt; becomes &lt;code&gt;&amp;amp;amp;&lt;/code&gt;, and &lt;code&gt;"&lt;/code&gt; becomes &lt;code&gt;&amp;amp;quot;&lt;/code&gt;. A browser then displays the characters instead of treating them as markup. An apostrophe is escaped as &lt;code&gt;&amp;amp;#39;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the step you want before you insert user text into HTML. URL encoding does not do it. &lt;code&gt;%3C&lt;/code&gt; is not the same thing as &lt;code&gt;&amp;amp;lt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoding
&lt;/h2&gt;

&lt;p&gt;Each mode has a reverse.&lt;/p&gt;

&lt;p&gt;URL Decode turns the &lt;code&gt;%&lt;/code&gt; sequences back into characters. A broken sequence, such as a &lt;code&gt;%&lt;/code&gt; that is not followed by two hex digits, does not decode. The page reports that the encoding is invalid.&lt;/p&gt;

&lt;p&gt;HTML Decode turns the entities back into &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, and quotes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays on the machine
&lt;/h2&gt;

&lt;p&gt;Paste the text, or load the sample, pick URL Encode, URL Decode, HTML Encode, or HTML Decode, and copy the result. Nothing is sent to a server.&lt;/p&gt;

&lt;p&gt;If you are building a link, encode the value. If you are showing the value on a page, escape it for HTML. Do one, then the other, when you need both.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>html</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to decode a JWT in the browser</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Wed, 28 Oct 2026 04:33:04 +0000</pubDate>
      <link>https://dev.to/ameenusman/how-to-decode-a-jwt-in-the-browser-10li</link>
      <guid>https://dev.to/ameenusman/how-to-decode-a-jwt-in-the-browser-10li</guid>
      <description>&lt;p&gt;A JWT looks secret. It is not. The header and the payload are only Base64url text. Anyone who has the token can read the claims. The signature is the part that proves the token was issued by someone who holds the key, and that check does not happen by decoding it.&lt;/p&gt;

&lt;p&gt;This is the distinction I keep having to make while debugging login. A readable &lt;code&gt;exp&lt;/code&gt; or &lt;code&gt;sub&lt;/code&gt; is useful. It is not proof the token is valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the three parts are
&lt;/h2&gt;

&lt;p&gt;A JWT is three pieces separated by dots:&lt;/p&gt;

&lt;p&gt;header.payload.signature&lt;/p&gt;

&lt;p&gt;The header names the algorithm, usually &lt;code&gt;HS256&lt;/code&gt; or &lt;code&gt;RS256&lt;/code&gt;. The payload holds the claims: &lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;role&lt;/code&gt;, &lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;exp&lt;/code&gt;, and whatever else the issuer put there. The signature is the third part. A decoder shows it. It does not check it.&lt;/p&gt;

&lt;p&gt;Base64url is not encryption. Changing &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt; back to &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt;, padding with &lt;code&gt;=&lt;/code&gt;, and running a Base64 decode gives you JSON. No secret is required for that step.&lt;/p&gt;

&lt;h2&gt;
  
  
  A token you can read by eye
&lt;/h2&gt;

&lt;p&gt;The sample on the KeptTools JWT decoder is shaped like a real token, with a placeholder in the signature slot so you can see the three parts:&lt;/p&gt;

&lt;p&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IktlcHRUb29scyBVc2VyIiwiaWF0IjoxNzA5ODAwMDAwLCJleHAiOjE3Mzk4MDAwMDAsInJvbGUiOiJkZXZlbG9wZXIifQ.signature-placeholder&lt;/p&gt;

&lt;p&gt;The header decodes to:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "alg": "HS256",&lt;br&gt;
  "typ": "JWT"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The payload decodes to:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "sub": "1234567890",&lt;br&gt;
  "name": "KeptTools User",&lt;br&gt;
  "iat": 1709800000,&lt;br&gt;
  "exp": 1739800000,&lt;br&gt;
  "role": "developer"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;code&gt;iat&lt;/code&gt; and &lt;code&gt;exp&lt;/code&gt; are Unix timestamps in seconds. &lt;code&gt;1709800000&lt;/code&gt; is the issued-at time. &lt;code&gt;1739800000&lt;/code&gt; is the expiry. Compare &lt;code&gt;exp&lt;/code&gt; with the current time before you treat the session as active. An expired token still decodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What decoding does not tell you
&lt;/h2&gt;

&lt;p&gt;A decoded payload does not mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the signature matches the header and payload&lt;/li&gt;
&lt;li&gt;the token came from your server&lt;/li&gt;
&lt;li&gt;the token has not been edited&lt;/li&gt;
&lt;li&gt;the session is still valid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signature checks belong on the server that holds the HMAC secret or the public key. For &lt;code&gt;HS256&lt;/code&gt;, that secret must never be in frontend code. For &lt;code&gt;RS256&lt;/code&gt;, the browser can hold a public key, but verification is still a separate step from reading the JSON.&lt;/p&gt;

&lt;p&gt;If you only need to see why a request is failing, decoding is enough. If you are deciding whether to trust the token, it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do it without uploading the token
&lt;/h2&gt;

&lt;p&gt;Paste the token into a page that decodes it locally, or do the same two Base64url decodes in your own console. Either way, a production access token or a refresh token should not be sent to a server you do not control.&lt;/p&gt;

&lt;p&gt;I use the decoder at &lt;a href="https://kepttools.com/jwt-decoder" rel="noopener noreferrer"&gt;https://kepttools.com/jwt-decoder&lt;/a&gt; for this. The token stays in the tab. The page shows the header, the payload, and the signature string. It does not verify the signature, and it does not load analytics on that page.&lt;/p&gt;

&lt;p&gt;Paste a token, or load the sample. Read &lt;code&gt;alg&lt;/code&gt; in the header and &lt;code&gt;exp&lt;/code&gt; in the payload. Then verify the signature in the app that issued the token.&lt;/p&gt;

</description>
      <category>jwt</category>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Turn PDF pages into JPG images in your browser</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Mon, 26 Oct 2026 10:26:08 +0000</pubDate>
      <link>https://dev.to/ameenusman/turn-pdf-pages-into-jpg-images-in-your-browser-mei</link>
      <guid>https://dev.to/ameenusman/turn-pdf-pages-into-jpg-images-in-your-browser-mei</guid>
      <description>&lt;p&gt;A PDF page is often the wrong format for a slide, a chat message, or a blog image. Uploading the whole document just to export one page is a poor trade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kepttools.com/pdf-to-jpg" rel="noopener noreferrer"&gt;PDF to JPG&lt;/a&gt; does that conversion in the browser. The file stays on your device. Nothing is uploaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to export the pages
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the PDF.&lt;/li&gt;
&lt;li&gt;Choose all pages, or enter a range such as &lt;code&gt;1-3, 5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Pick High, Medium, or Low quality.&lt;/li&gt;
&lt;li&gt;Convert, preview the pages, then download one JPG or a ZIP of all of them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A 5-page deck on High comes back as five images. Preview them before you download the ZIP.&lt;/p&gt;

&lt;p&gt;The limit shown on the page is 40 pages and 25 MB. A password-protected PDF has to be unlocked first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the JPG is
&lt;/h2&gt;

&lt;p&gt;Each page becomes a flat image. Text that you could select in the PDF cannot be selected in the JPG. Use this when you need a picture. Keep the original PDF when you still need selectable text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it runs
&lt;/h2&gt;

&lt;p&gt;The page is drawn in the browser and saved as a JPG from there. I do not receive the file.&lt;/p&gt;

&lt;p&gt;Tool: &lt;a href="https://kepttools.com/pdf-to-jpg" rel="noopener noreferrer"&gt;https://kepttools.com/pdf-to-jpg&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Merge, split, and compress PDFs in your browser</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Sun, 25 Oct 2026 07:55:38 +0000</pubDate>
      <link>https://dev.to/ameenusman/merge-split-and-compress-pdfs-in-your-browser-54gg</link>
      <guid>https://dev.to/ameenusman/merge-split-and-compress-pdfs-in-your-browser-54gg</guid>
      <description>&lt;p&gt;I kept avoiding random PDF websites for contracts, invoices, and reports. Those files should not leave the laptop just to merge two exports or pull out one page.&lt;br&gt;
So I added PDF tools to &lt;a href="https://kepttools.com" rel="noopener noreferrer"&gt;KeptTools&lt;/a&gt;. They run in the browser. Nothing is uploaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Merge a few PDFs into one
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://kepttools.com/pdf-merge" rel="noopener noreferrer"&gt;Merge PDF&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add two or more PDFs.&lt;/li&gt;
&lt;li&gt;Put them in the order you want. Remove any you do not need.&lt;/li&gt;
&lt;li&gt;Merge and download one PDF.
Example: add &lt;code&gt;report-a.pdf&lt;/code&gt; and &lt;code&gt;report-b.pdf&lt;/code&gt;, put A first, then download &lt;code&gt;kepttools-merged.pdf&lt;/code&gt; with every page in that order.
You can merge up to 20 files, 25 MB each. Password-protected PDFs have to be unlocked first.
## Pull pages out of a long PDF
Open &lt;a href="https://kepttools.com/pdf-split" rel="noopener noreferrer"&gt;Split PDF&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Choose one PDF.&lt;/li&gt;
&lt;li&gt;Enter ranges, or split every page.&lt;/li&gt;
&lt;li&gt;Download one PDF, or a ZIP when there is more than one result.
Ranges look like &lt;code&gt;1-3, 5&lt;/code&gt; or &lt;code&gt;1-3, 7-10&lt;/code&gt;. Page numbers start at 1.
Example: a 10-page PDF with &lt;code&gt;1-3, 7-10&lt;/code&gt; comes back as two files in a ZIP. Pages 1–3 in one, pages 7–10 in the other.
## Shrink the file, keep the text
&lt;a href="https://kepttools.com/pdf-compress" rel="noopener noreferrer"&gt;Compress PDF&lt;/a&gt; recompresses images inside the PDF. Light, Medium, and Strong change image quality. The text stays real text, so you can still select it.
A text-only PDF may barely get smaller. That is an honest result, not a broken tool. Use it when the PDF has photos or scans and you need a smaller file for email.
## Turn pages into images, or images into a PDF
&lt;a href="https://kepttools.com/pdf-to-jpg" rel="noopener noreferrer"&gt;PDF to JPG&lt;/a&gt; exports pages as JPG. Pick all pages or a range, choose quality, preview, then download one image or a ZIP. After this step the page is a picture, so the text is no longer selectable.
&lt;a href="https://kepttools.com/jpg-to-pdf" rel="noopener noreferrer"&gt;JPG to PDF&lt;/a&gt; does the other direction. Add JPG, PNG, or WebP images, reorder them, then set page size (A4, Letter, or fit), orientation, and margins. Fit inside keeps the photo proportions. Stretch can distort them.
## What stays on your machine
The file is read in the browser and the result is downloaded from there. I do not receive the PDF.
If you try one, tell me whether you needed merge, split, or compress first.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>kepttools</category>
      <category>javascript</category>
      <category>pdf</category>
    </item>
    <item>
      <title>Unix Timestamp Converter That Runs in Your Browser</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Sat, 24 Oct 2026 07:48:52 +0000</pubDate>
      <link>https://dev.to/ameenusman/unix-timestamp-converter-that-runs-in-your-browser-47i9</link>
      <guid>https://dev.to/ameenusman/unix-timestamp-converter-that-runs-in-your-browser-47i9</guid>
      <description>&lt;p&gt;I still paste Unix timestamps into random converters more often than I want to admit.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;created_at: 1729000000&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;exp: 1735689600&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The number is fine. The worry is the same every time: does this page upload what I paste?&lt;/p&gt;

&lt;h2&gt;
  
  
  What I needed
&lt;/h2&gt;

&lt;p&gt;A tiny converter that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accepts epoch &lt;strong&gt;seconds&lt;/strong&gt; or &lt;strong&gt;milliseconds&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;shows &lt;strong&gt;UTC (ISO)&lt;/strong&gt; and &lt;strong&gt;local&lt;/strong&gt; time&lt;/li&gt;
&lt;li&gt;lets me copy the ISO string&lt;/li&gt;
&lt;li&gt;has a &lt;strong&gt;Use now&lt;/strong&gt; button for the current timestamp&lt;/li&gt;
&lt;li&gt;does the work &lt;strong&gt;in the browser&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I added one to KeptTools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://kepttools.com/timestamp" rel="noopener noreferrer"&gt;Unix Timestamp Converter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
text
1729000000
→ 2024-10-15T13:46:40.000Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>productivity</category>
      <category>kepttools</category>
    </item>
    <item>
      <title>Cron Generator Online: Build Expressions in Your Browser</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Tue, 20 Oct 2026 15:45:13 +0000</pubDate>
      <link>https://dev.to/ameenusman/cron-generator-online-build-expressions-in-your-browser-239n</link>
      <guid>https://dev.to/ameenusman/cron-generator-online-build-expressions-in-your-browser-239n</guid>
      <description>&lt;h1&gt;
  
  
  Cron Generator Online: Build Expressions in Your Browser
&lt;/h1&gt;

&lt;p&gt;Cron schedules are simple… until you’re staring at five fields and guessing.&lt;/p&gt;

&lt;p&gt;I built a &lt;strong&gt;Cron Expression Generator&lt;/strong&gt; inside &lt;a href="https://kepttools.com" rel="noopener noreferrer"&gt;KeptTools&lt;/a&gt; so you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pick a preset (hourly, daily, weekdays 9am, etc.)&lt;/li&gt;
&lt;li&gt;edit minute / hour / day / month / weekday&lt;/li&gt;
&lt;li&gt;read a plain-English summary&lt;/li&gt;
&lt;li&gt;copy the expression into crontab&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preset: &lt;strong&gt;Weekdays 9am&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Expression: &lt;code&gt;0 9 * * 1-5&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Browser-only
&lt;/h2&gt;

&lt;p&gt;Processing stays in your browser. Nothing is uploaded.&lt;/p&gt;

&lt;p&gt;Try it: &lt;a href="https://kepttools.com/cron-generator" rel="noopener noreferrer"&gt;https://kepttools.com/cron-generator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full toolkit (JSON, JWT, regex, and more): &lt;a href="https://kepttools.com" rel="noopener noreferrer"&gt;https://kepttools.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use cron a lot, what preset do you want added next?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>linux</category>
      <category>kepttools</category>
    </item>
    <item>
      <title>I built KeptTools so your JWT, SQL, and Base64 never leave the browser</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Mon, 19 Oct 2026 15:50:32 +0000</pubDate>
      <link>https://dev.to/ameenusman/i-built-kepttools-so-your-jwt-sql-and-base64-never-leave-the-browser-5cho</link>
      <guid>https://dev.to/ameenusman/i-built-kepttools-so-your-jwt-sql-and-base64-never-leave-the-browser-5cho</guid>
      <description>&lt;h1&gt;
  
  
  I built KeptTools so your JWT, SQL, and Base64 never leave the browser
&lt;/h1&gt;

&lt;p&gt;Most “free online tools” are convenient until you paste something you shouldn’t.&lt;/p&gt;

&lt;p&gt;A JWT from staging.&lt;br&gt;&lt;br&gt;
A SQL dump with real emails.&lt;br&gt;&lt;br&gt;
A Base64 string that still holds a secret.&lt;/p&gt;

&lt;p&gt;If that page uploads your paste, you’ve already shared more than you meant to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;I kept needing small utilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;format / validate JSON
&lt;/li&gt;
&lt;li&gt;format SQL
&lt;/li&gt;
&lt;li&gt;decode a JWT
&lt;/li&gt;
&lt;li&gt;encode / decode Base64
&lt;/li&gt;
&lt;li&gt;generate hashes
&lt;/li&gt;
&lt;li&gt;tweak images
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…but I didn’t want to trust random sites with the paste.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://kepttools.com" rel="noopener noreferrer"&gt;KeptTools&lt;/a&gt;&lt;/strong&gt; — a set of browser-only developer tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Processing runs in your browser.&lt;/p&gt;

&lt;p&gt;Nothing you paste is uploaded for processing.&lt;/p&gt;

&lt;p&gt;On the more sensitive pages (JWT, Base64, hash, password generator):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no analytics
&lt;/li&gt;
&lt;li&gt;no ads
&lt;/li&gt;
&lt;li&gt;no third-party scripts
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tip: open &lt;strong&gt;DevTools → Network&lt;/strong&gt; while you use those pages. You should not see a request carrying your paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s included
&lt;/h2&gt;

&lt;p&gt;KeptTools currently covers everyday work like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON formatter / converter
&lt;/li&gt;
&lt;li&gt;SQL formatter
&lt;/li&gt;
&lt;li&gt;JWT decoder
&lt;/li&gt;
&lt;li&gt;Base64 encode / decode
&lt;/li&gt;
&lt;li&gt;Hash generator (MD5, SHA-1, SHA-256, SHA-512)
&lt;/li&gt;
&lt;li&gt;URL &amp;amp; HTML encode / decode
&lt;/li&gt;
&lt;li&gt;Regex tester
&lt;/li&gt;
&lt;li&gt;Image convert / compress / crop / background tools
&lt;/li&gt;
&lt;li&gt;and more
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All free to use: &lt;a href="https://kepttools.com" rel="noopener noreferrer"&gt;https://kepttools.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I’m sharing this
&lt;/h2&gt;

&lt;p&gt;I’m not claiming this replaces your local CLI or IDE.&lt;/p&gt;

&lt;p&gt;I’m claiming something simpler:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a tool can run in the browser, it shouldn’t need your paste on a server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you try it, I’d love blunt feedback:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is the privacy angle clear on first visit?
&lt;/li&gt;
&lt;li&gt;Which tool would you use weekly?
&lt;/li&gt;
&lt;li&gt;What’s missing?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thanks for reading — and if you try it, open Network and verify it yourself.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>privacy</category>
      <category>javascript</category>
      <category>kepttools</category>
    </item>
    <item>
      <title>How to prove an online JWT tool isn’t stealing your token</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Sun, 18 Oct 2026 15:28:56 +0000</pubDate>
      <link>https://dev.to/ameenusman/how-to-prove-an-online-jwt-tool-isnt-stealing-your-token-72g</link>
      <guid>https://dev.to/ameenusman/how-to-prove-an-online-jwt-tool-isnt-stealing-your-token-72g</guid>
      <description>&lt;p&gt;Most free JWT decoders ask you to paste a token.&lt;/p&gt;

&lt;p&gt;That’s a trust problem. Tokens can include emails, roles, and session data. If the page uploads it, you just gave secrets to someone else’s server.&lt;/p&gt;

&lt;p&gt;Here’s the fastest way to check any tool:&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the Network tab
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open DevTools (F12)&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Network&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Paste a &lt;strong&gt;sample&lt;/strong&gt; token&lt;/li&gt;
&lt;li&gt;Watch for requests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If decoding fires a request with your token, leave.&lt;/p&gt;

&lt;p&gt;A privacy claim is marketing. An empty Network tab is evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try a local decoder
&lt;/h2&gt;

&lt;p&gt;I built a browser-only JWT decoder here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kepttools.com/jwt-decoder" rel="noopener noreferrer"&gt;https://kepttools.com/jwt-decoder&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No account. Nothing uploaded. Open Network while you decode and confirm it yourself.&lt;/p&gt;

&lt;p&gt;If you try it, tell me bluntly: is the privacy story clear in 5 seconds?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>kepttools</category>
    </item>
    <item>
      <title>That API Timestamp Looks Like Nonsense — Here’s How I Read It in the Browser</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Fri, 16 Oct 2026 06:20:24 +0000</pubDate>
      <link>https://dev.to/ameenusman/that-api-timestamp-looks-like-nonsense-heres-how-i-read-it-in-the-browser-23ac</link>
      <guid>https://dev.to/ameenusman/that-api-timestamp-looks-like-nonsense-heres-how-i-read-it-in-the-browser-23ac</guid>
      <description>&lt;p&gt;If you work with APIs long enough, you eventually get a field like this:&lt;/p&gt;

&lt;p&gt;1729000000&lt;/p&gt;

&lt;p&gt;Is that seconds? Milliseconds? Yesterday? Next week?&lt;/p&gt;

&lt;p&gt;I got tired of guessing — and tired of pasting values into random websites.&lt;/p&gt;

&lt;p&gt;So I added a Unix timestamp converter to &lt;strong&gt;KeptTools&lt;/strong&gt;. It runs in the browser. Nothing is uploaded.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://kepttools.com/timestamp" rel="noopener noreferrer"&gt;https://kepttools.com/timestamp&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Paste a Unix timestamp&lt;/li&gt;
&lt;li&gt;See &lt;strong&gt;UTC&lt;/strong&gt; and &lt;strong&gt;local&lt;/strong&gt; time&lt;/li&gt;
&lt;li&gt;Copy the ISO value&lt;/li&gt;
&lt;li&gt;Or tap &lt;strong&gt;Use now&lt;/strong&gt; for the current epoch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: &lt;code&gt;1729000000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;UTC: &lt;code&gt;2024-10-15T13:46:40.000Z&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful when you’re reading &lt;code&gt;created_at&lt;/code&gt;, writing a test, or checking an expiry field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why browser-only matters here
&lt;/h2&gt;

&lt;p&gt;Timestamps themselves aren’t always sensitive.&lt;/p&gt;

&lt;p&gt;But the habit is: once you start pasting API payloads into third-party sites, it’s easy to paste a token next.&lt;/p&gt;

&lt;p&gt;I want the everyday tools on the same page as the privacy promise: work happens on your device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other tools in the suite
&lt;/h2&gt;

&lt;p&gt;If timestamps are only one part of your day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kepttools.com/json-formatter" rel="noopener noreferrer"&gt;JSON Formatter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kepttools.com/jwt-decoder" rel="noopener noreferrer"&gt;JWT Decoder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kepttools.com/json-converter" rel="noopener noreferrer"&gt;JSON Converter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full set: &lt;a href="https://kepttools.com" rel="noopener noreferrer"&gt;https://kepttools.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick question
&lt;/h2&gt;

&lt;p&gt;When an API returns epoch time, do you usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;convert it in code,&lt;/li&gt;
&lt;li&gt;use a browser tool, or&lt;/li&gt;
&lt;li&gt;guess from the length?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tell me in the comments — I’m iterating based on real workflows.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>kepttools</category>
      <category>javascript</category>
      <category>api</category>
    </item>
    <item>
      <title>I Got Tired of “Free Tools” That Upload My Files — So I Built Browser-Only Ones</title>
      <dc:creator>Ameen Usman</dc:creator>
      <pubDate>Thu, 15 Oct 2026 11:57:59 +0000</pubDate>
      <link>https://dev.to/ameenusman/i-got-tired-of-free-tools-that-upload-my-files-so-i-built-browser-only-ones-4b3p</link>
      <guid>https://dev.to/ameenusman/i-got-tired-of-free-tools-that-upload-my-files-so-i-built-browser-only-ones-4b3p</guid>
      <description>&lt;p&gt;Most “free online tools” ask you to paste text or upload a photo.&lt;/p&gt;

&lt;p&gt;What they don’t always make clear: that content often leaves your device.&lt;/p&gt;

&lt;p&gt;For JSON with client data, a JWT, a draft you’re not ready to publish, or a passport-style photo — that’s a bad trade.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;KeptTools&lt;/strong&gt;: a small set of developer and image tools that run in the browser. No account. No “upload to process.”&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://kepttools.com" rel="noopener noreferrer"&gt;https://kepttools.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What “browser-only” means here
&lt;/h2&gt;

&lt;p&gt;When you open a tool page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The work happens on the page (JavaScript in your browser)&lt;/li&gt;
&lt;li&gt;You can format, convert, crop, or decode without creating an account&lt;/li&gt;
&lt;li&gt;Closing the tab ends the session — nothing is stored as “your file” on our servers for processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few tools download a small model the first time (for example, portrait background replacement). That download is the model, not your photo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s live
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Developer tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON formatter / validator&lt;/li&gt;
&lt;li&gt;JSON converter (CSV, Laravel, TypeScript, PHP, and more)&lt;/li&gt;
&lt;li&gt;SQL formatter&lt;/li&gt;
&lt;li&gt;JWT decoder&lt;/li&gt;
&lt;li&gt;Base64, hash, URL/HTML encode&lt;/li&gt;
&lt;li&gt;Regex tester, cron generator, UUID/slug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Image tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PNG/JPG → WebP&lt;/li&gt;
&lt;li&gt;Compress&lt;/li&gt;
&lt;li&gt;Crop&lt;/li&gt;
&lt;li&gt;Image → SVG&lt;/li&gt;
&lt;li&gt;Change background (white / blue / custom / transparent)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Text utilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Word counter&lt;/li&gt;
&lt;li&gt;Case converter (camelCase, snake_case, kebab-case, …)&lt;/li&gt;
&lt;li&gt;And more on the site&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I care about this
&lt;/h2&gt;

&lt;p&gt;I use these utilities every week. I don’t want a random site keeping a copy of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a token&lt;/li&gt;
&lt;li&gt;a production-ish JSON blob&lt;/li&gt;
&lt;li&gt;an ID photo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Privacy shouldn’t be a premium feature for basic tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack (short)
&lt;/h2&gt;

&lt;p&gt;Laravel + Vite + Tailwind + Alpine. Tools are client-side where it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d love feedback on
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Is “browser-only / no upload” a strong enough reason to switch tools?&lt;/li&gt;
&lt;li&gt;Which tool would you open first from the homepage?&lt;/li&gt;
&lt;li&gt;What’s missing that would make you bounce?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you try something and it feels unclear, tell me — I’m iterating from real use.&lt;/p&gt;

&lt;p&gt;Site: &lt;a href="https://kepttools.com" rel="noopener noreferrer"&gt;https://kepttools.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>privacy</category>
      <category>security</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
