<?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: Maggie B</title>
    <description>The latest articles on DEV Community by Maggie B (@maggieb_ailstudio).</description>
    <link>https://dev.to/maggieb_ailstudio</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%2F4208209%2F3354a7d5-28b9-43df-b70f-c5d23e7fc0d2.png</url>
      <title>DEV Community: Maggie B</title>
      <link>https://dev.to/maggieb_ailstudio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maggieb_ailstudio"/>
    <language>en</language>
    <item>
      <title>How I built a subscription tracker in Google Sheets (formulas to normalize any billing cycle)</title>
      <dc:creator>Maggie B</dc:creator>
      <pubDate>Mon, 26 Oct 2026 08:51:08 +0000</pubDate>
      <link>https://dev.to/maggieb_ailstudio/how-i-built-a-subscription-tracker-in-google-sheets-formulas-to-normalize-any-billing-cycle-5gc7</link>
      <guid>https://dev.to/maggieb_ailstudio/how-i-built-a-subscription-tracker-in-google-sheets-formulas-to-normalize-any-billing-cycle-5gc7</guid>
      <description>&lt;p&gt;Subscriptions are sneaky because they bill on different rhythms. A $139/year Prime membership, a $15.49/month Netflix plan and a $9/week meal kit can't be compared until you put them on the same scale. Here's how I built a tracker that does that automatically. It works the same way in Google Sheets and Excel.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The columns
&lt;/h2&gt;

&lt;p&gt;One row per subscription on a &lt;code&gt;Subscriptions&lt;/code&gt; sheet:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;D&lt;/th&gt;
&lt;th&gt;E&lt;/th&gt;
&lt;th&gt;F&lt;/th&gt;
&lt;th&gt;G&lt;/th&gt;
&lt;th&gt;H&lt;/th&gt;
&lt;th&gt;I&lt;/th&gt;
&lt;th&gt;J&lt;/th&gt;
&lt;th&gt;K&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Service&lt;/td&gt;
&lt;td&gt;Category&lt;/td&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Billing cycle&lt;/td&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;Yearly cost&lt;/td&gt;
&lt;td&gt;Next renewal&lt;/td&gt;
&lt;td&gt;Days left&lt;/td&gt;
&lt;td&gt;Payment method&lt;/td&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;Notes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You type into C, D, G and J. Columns E, F and H are formulas.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A lookup table instead of nested IFs
&lt;/h2&gt;

&lt;p&gt;The tempting approach is &lt;code&gt;IF(D2="Weekly",C2*52/12,IF(D2="Monthly",C2,...))&lt;/code&gt;. It breaks the moment you add a cycle. Instead, make a small &lt;code&gt;Lists&lt;/code&gt; sheet:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cycle (A)&lt;/th&gt;
&lt;th&gt;Payments per year (B)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quarterly&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every 6 months&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yearly&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now &lt;strong&gt;every&lt;/strong&gt; cycle is normalized the same way: &lt;em&gt;cost × payments per year ÷ 12&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Monthly cost (E2)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=IF(C2="","",IF(J2="Cancelled",0,C2*IFERROR(INDEX(Lists!$B$2:$B$6,MATCH(D2,Lists!$A$2:$A$6,0)),0)/12))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading it inside-out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MATCH(D2, Lists!$A$2:$A$6, 0)&lt;/code&gt; finds the row of the billing cycle (exact match).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;INDEX(Lists!$B$2:$B$6, …)&lt;/code&gt; returns payments per year.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IFERROR(…, 0)&lt;/code&gt; keeps a typo or a blank cycle from spraying &lt;code&gt;#N/A&lt;/code&gt; across your totals.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IF(J2="Cancelled", 0, …)&lt;/code&gt; lets you keep cancelled rows for history without counting them.&lt;/li&gt;
&lt;li&gt;The outer &lt;code&gt;IF(C2="","",…)&lt;/code&gt; keeps empty rows blank.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Yearly cost (F2)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=IF(E2="","",E2*12)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is monthly, yearly is trivial. This column is the one that hurts: seeing $14/month as $168/year changes decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Days until renewal (H2)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=IF(G2="","",G2-TODAY())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dates are just numbers, so subtraction gives days. &lt;code&gt;TODAY()&lt;/code&gt; recalculates on open, so the countdown is always current.&lt;/p&gt;

&lt;p&gt;Add conditional formatting to make it pop: a rule on &lt;code&gt;H2:H101&lt;/code&gt; with a custom formula like &lt;code&gt;=AND($H2&amp;gt;=0,$H2&amp;lt;=7)&lt;/code&gt; and a red fill. I also grey out cancelled rows with &lt;code&gt;=$J2="Cancelled"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Data validation
&lt;/h2&gt;

&lt;p&gt;Use dropdowns sourced from the &lt;code&gt;Lists&lt;/code&gt; sheet (cycles in &lt;code&gt;Lists!A2:A6&lt;/code&gt;, categories in &lt;code&gt;D2:D11&lt;/code&gt;, statuses in &lt;code&gt;E2:E5&lt;/code&gt;). This matters more than it looks, because the &lt;code&gt;MATCH&lt;/code&gt; in step 3 needs exact text.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The dashboard
&lt;/h2&gt;

&lt;p&gt;A separate &lt;code&gt;Dashboard&lt;/code&gt; sheet just aggregates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monthly total:            =SUM(Subscriptions!E2:E101)
Yearly total:             =SUM(Subscriptions!F2:F101)
Active subscriptions:     =COUNTIF(Subscriptions!J2:J101,"Active")+COUNTIF(Subscriptions!J2:J101,"Trial")+COUNTIF(Subscriptions!J2:J101,"Review")
Renewing in next 7 days:  =COUNTIFS(Subscriptions!H2:H101,"&amp;gt;=0",Subscriptions!H2:H101,"&amp;lt;=7")
Renewing in next 30 days: =COUNTIFS(Subscriptions!H2:H101,"&amp;gt;=0",Subscriptions!H2:H101,"&amp;lt;=30")
Marked for review ($/mo): =SUMIF(Subscriptions!J2:J101,"Review",Subscriptions!E2:E101)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a per-category breakdown, with category names in column E:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=SUMIF(Subscriptions!B2:B101,E2,Subscriptions!E2:E101)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed that range into a pie chart and you can see where the money goes.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftdagrbgz8o057r46sg6x.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftdagrbgz8o057r46sg6x.png" alt="Dashboard showing monthly and yearly totals, renewal counts, and spend by category" width="799" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;"&amp;gt;=0"&lt;/code&gt; condition in &lt;code&gt;COUNTIFS&lt;/code&gt; excludes past dates, so stale renewals don't count as "upcoming". Update the date after each renewal.&lt;/li&gt;
&lt;li&gt;A "Review" status is a gentle middle ground between Active and Cancelled. The dashboard tells you how much per month is riding on those decisions.&lt;/li&gt;
&lt;li&gt;Excel and Sheets both handle every formula here with no scripts or add-ons.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;That's the whole thing: one lookup table, three formula columns and a summary sheet. Build it yourself in about 20 minutes with the formulas above. If you'd rather skip the setup, I packaged the finished version (Excel + Google Sheets, dashboard and chart included) for $1: &lt;a href="https://ailstudio.gumroad.com/l/qkwkix" rel="noopener noreferrer"&gt;https://ailstudio.gumroad.com/l/qkwkix&lt;/a&gt;&lt;/p&gt;

</description>
      <category>googlesheets</category>
      <category>productivity</category>
      <category>spreadsheets</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
