DEV Community

Mate Technologies
Mate Technologies

Posted on

πŸ–₯️ Build a Local URL Shortener with Python & Tkinter (Step-by-Step)

In this tutorial, we’ll build a local URL shortener desktop app using Python + Tkinter.
No internet. No backend. Everything is stored locally in a JSON file.

By the end, you’ll have:

A clean GUI

URL shortening & expanding

Clipboard support

Dark mode

Persistent local storage

🧰 Requirements

Before we start, install the required packages:

pip install sv-ttk

Tkinter comes bundled with Python, so no extra install needed.

πŸ“¦ Step 1: Import Required Modules

We’ll start by importing everything our app needs.

import sys
import os
import json
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
import random
Enter fullscreen mode Exit fullscreen mode

What each module does:

tkinter / ttk β†’ GUI widgets

messagebox β†’ pop-up alerts

json β†’ store URLs locally

random β†’ generate short codes

sv_ttk β†’ modern UI theme

os / sys β†’ file path handling

🧠 Step 2: Helper Functions
πŸ“ Handle file paths safely (important for packaging)

def resource_path(file_name):
    base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, file_name)
Enter fullscreen mode Exit fullscreen mode

This ensures your app works both:

normally

when bundled with PyInstaller

πŸ“’ Update the status bar

def set_status(msg):
    status_var.set(msg)
    root.update_idletasks()
Enter fullscreen mode Exit fullscreen mode

This gives users real-time feedback (e.g. β€œURL shortened successfully”).

🎲 Generate a simple short code

def generate_easy_code():
    prefixes = ["go", "url", "link", "up", "web"]
    prefix = random.choice(prefixes)
    number = random.randint(10, 999)
    return f"{prefix}{number}"
Enter fullscreen mode Exit fullscreen mode

Example output:

go123
link88
web402

Simple and readable for beginners.

πŸ’Ύ Step 3: Local Storage with JSON
πŸ“„ Define storage file
STORAGE_FILE = resource_path("short_urls.json")

πŸ”„ Load existing data (if any)

if os.path.exists(STORAGE_FILE):
    with open(STORAGE_FILE, "r", encoding="utf-8") as f:
        url_mapping = json.load(f)
else:
    url_mapping = {}
Enter fullscreen mode Exit fullscreen mode

This keeps your shortened URLs even after closing the app.

πŸ’Ύ Save mappings to disk

def save_mapping():
    with open(STORAGE_FILE, "w", encoding="utf-8") as f:
        json.dump(url_mapping, f, indent=4)
    update_url_list()
Enter fullscreen mode Exit fullscreen mode

πŸ”— Step 4: Core URL Logic
βœ‚οΈ Shorten a URL

def shorten_url():
    original = url_var.get().strip()
Enter fullscreen mode Exit fullscreen mode

We first read the input field and clean whitespace.

⚠️ Validate input

if not original:
    messagebox.showwarning("Input Required", "Enter a URL to shorten.")
    return
Enter fullscreen mode Exit fullscreen mode

♻️ Avoid duplicates

for code, url in url_mapping.items():
    if url == original:
        short_var.set(code)
        set_status("URL already shortened.")
        return
Enter fullscreen mode Exit fullscreen mode

Same URL = same short code.

πŸ†• Generate a new code

code = generate_easy_code()
while code in url_mapping:
    code = generate_easy_code()
Enter fullscreen mode Exit fullscreen mode

Ensures uniqueness.

πŸ’Ύ Save result

url_mapping[code] = original
save_mapping()
short_var.set(code)
set_status("URL shortened successfully.")
Enter fullscreen mode Exit fullscreen mode

πŸ” Expand a short code

def expand_url():
    code = short_var.get().strip()

if not code:
    messagebox.showwarning("Input Required", "Enter a short code to expand.")
    return

original = url_mapping.get(code)
if original:
    url_var.set(original)
    set_status("URL expanded successfully.")
else:
    messagebox.showerror("Not Found", "Short code does not exist.")
Enter fullscreen mode Exit fullscreen mode

πŸ“‹ Step 5: Clipboard Support

def copy_to_clipboard(entry_var):
    root.clipboard_clear()
    root.clipboard_append(entry_var.get())
    set_status("Copied to clipboard!")
Enter fullscreen mode Exit fullscreen mode

Works for both URLs and short codes.

πŸ“ƒ Step 6: Display Stored URLs

def update_url_list():
    for widget in list_frame_inner.winfo_children():
        widget.destroy()
Enter fullscreen mode Exit fullscreen mode

Clears old UI elements before refreshing.

for i, (code, url) in enumerate(url_mapping.items(), 1):
    ttk.Label(
        list_frame_inner,
        text=f"{i}. {code} β†’ {url}",
        font=("Segoe UI", 10),
        anchor="w"
    ).pack(fill="x", padx=5, pady=2)
Enter fullscreen mode Exit fullscreen mode

Creates a clean numbered list.

🎨 Step 7: App Setup & Theme
πŸͺŸ Create the window

root = tk.Tk()
root.title("Local URL Shortener")
root.geometry("1100x650")
sv_ttk.set_theme("light")
Enter fullscreen mode Exit fullscreen mode

πŸ” Global variables

url_var = tk.StringVar()
short_var = tk.StringVar()
dark_mode_var = tk.BooleanVar(value=False)
Enter fullscreen mode Exit fullscreen mode

πŸŒ— Dark mode toggle

def toggle_theme():
    style.theme_use("clam")

bg = "#2E2E2E" if dark_mode_var.get() else "#FFFFFF"
fg = "white" if dark_mode_var.get() else "black"
root.configure(bg=bg)

for entry in [url_entry, short_entry]:
    entry.configure(
        bg="#1e1e1e" if dark_mode_var.get() else "white",
        fg="white" if dark_mode_var.get() else "black",
        insertbackground="white" if dark_mode_var.get() else "black"
    )
Enter fullscreen mode Exit fullscreen mode

πŸŽ›οΈ Step 8: UI Layout
πŸ“Œ Status bar

status_var = tk.StringVar(value="Ready")
ttk.Label(root, textvariable=status_var, anchor="w").pack(side=tk.BOTTOM, fill="x")
Enter fullscreen mode Exit fullscreen mode

🧱 Main container

main = ttk.Frame(root, padding=20)
main.pack(expand=True, fill="both")
Enter fullscreen mode Exit fullscreen mode

🏷️ Input fields

url_entry = tk.Entry(url_frame, textvariable=url_var, font=("Segoe UI", 12))
short_entry = tk.Entry(short_frame, textvariable=short_var, font=("Segoe UI", 12))
Enter fullscreen mode Exit fullscreen mode

🎯 Action buttons

ttk.Button(actions, text="πŸ”— Shorten", command=shorten_url).pack(side="left")
ttk.Button(actions, text="πŸ” Expand", command=expand_url).pack(side="left")
ttk.Button(actions, text="πŸ“‹ Copy URL", command=lambda: copy_to_clipboard(url_var)).pack(side="left")
ttk.Button(actions, text="πŸ“‹ Copy Code", command=lambda: copy_to_clipboard(short_var)).pack(side="left")
Enter fullscreen mode Exit fullscreen mode

πŸ“œ Step 9: Scrollable Stored URLs List

We use a Canvas + Frame + Scrollbar combo:

list_frame_canvas = tk.Canvas(list_frame_outer)
scrollbar = ttk.Scrollbar(list_frame_outer, orient="vertical", command=list_frame_canvas.yview)
Enter fullscreen mode Exit fullscreen mode
list_frame_inner = ttk.Frame(list_frame_canvas)
list_frame_canvas.create_window((0, 0), window=list_frame_inner, anchor="nw")
Enter fullscreen mode Exit fullscreen mode
def on_frame_configure(event):
    list_frame_canvas.configure(scrollregion=list_frame_canvas.bbox("all"))
Enter fullscreen mode Exit fullscreen mode

▢️ Step 10: Run the App

update_url_list()
root.mainloop()
Enter fullscreen mode Exit fullscreen mode

Your app is now live πŸš€

βœ… Final Features Recap

πŸ”— Local URL shortening

πŸ” URL expansion

πŸ’Ύ Persistent JSON storage

πŸŒ— Dark mode toggle

πŸ“‹ Clipboard support

πŸ–₯️ Desktop GUI (no web server)

Local URL Shortener

Top comments (0)