Tkinter Entry Widget: Get, Set, Validate, Placeholder and Password (With Examples)

The Tkinter Entry widget is a single-line text box for user input such as a name, a number or a password. Create it with tk.Entry(parent), read the text with get(), change it with delete() and insert(), and control what users can type with validatecommand. This guide covers each of these, plus placeholder text, password fields, the Enter key, ttk.Entry and the errors beginners run into.

Every example is a complete program, tested with Python 3.12.5 with Tk 8.6.13 on Windows 11. The screenshots are the real windows. For the full reference, see the tkinter.ttk documentation.

Tkinter Entry example

import tkinter as tk

root = tk.Tk()
root.title("Entry")
root.geometry("320x150")

tk.Label(root, text="Your name:").pack(pady=(15, 2))
name_entry = tk.Entry(root, width=30)
name_entry.pack()

greeting = tk.Label(root, text="")
greeting.pack(pady=5)

def say_hello():
    name = name_entry.get()                  # the text in the Entry, always a str
    greeting.config(text=f"Hello, {name}!")

tk.Button(root, text="Greet", command=say_hello).pack()
root.mainloop()
Tkinter Entry widget example: a name Entry with Maria typed in and a Greet button showing Hello, Maria!
Type a name, click Greet, and the Label shows the text from get().

get() always returns a string. It’s only useful inside a function that runs later (a button click or a key press), because when the program starts the Entry is still empty. More ways to read the text: get text from a Tkinter Entry.

Entry options: width, font, colour and state

import tkinter as tk

root = tk.Tk()
root.title("Entry options")
root.configure(padx=20, pady=15)

examples = [
    ("width=12", dict(width=12)),
    ("font", dict(font=("Segoe UI", 14))),
    ("justify='center'", dict(justify="center")),
    ("bg / fg", dict(bg="#fff8dc", fg="#1f4e79")),
    ("relief='solid'", dict(relief="solid", bd=1)),
    ("highlight border", dict(highlightthickness=2, highlightcolor="#306998", highlightbackground="#9ab")),
    ("state='disabled'", dict(state="disabled")),
    ("state='readonly'", dict(state="readonly")),
]
for row, (label, options) in enumerate(examples):
    tk.Label(root, text=label, anchor="w", width=16).grid(row=row, column=0, sticky="w", pady=3)
    entry = tk.Entry(root, **options)
    entry.grid(row=row, column=1, sticky="w")
    if options.get("state"):                      # insert() is ignored while disabled/readonly
        entry.config(state="normal")
        entry.insert(0, "Python Guides")
        entry.config(state=options["state"])
    else:
        entry.insert(0, "Python Guides")

root.mainloop()
Tkinter Entry options compared: width, font, justify center, background and foreground colour, solid relief, highlight border, disabled and readonly state
The same text with different Entry options.
OptionWhat it does
widthWidth in characters (not pixels). Default 20.
fontFor example ("Segoe UI", 14). A bigger font also makes the Entry taller; Entry has no height option.
justify"left", "center" or "right" (handy for numbers).
bg, fgBackground and text colour. insertbackground sets the cursor colour.
relief, bdBorder style ("flat", "solid", "sunken"…) and width.
highlightthickness, highlightcolorA focus ring that changes colour when the Entry has the cursor.
showCharacter to display instead of the text, e.g. "*" for passwords.
state"normal", "disabled" (greyed out, can’t select) or "readonly" (can select and copy, can’t edit).
textvariableA StringVar linked to the text.
validate, validatecommandCheck each change before it happens (see below).

Get the Entry value (and convert it to a number)

To use the input as a number, convert it and handle the case where it isn’t one:

import tkinter as tk

root = tk.Tk()
age_entry = tk.Entry(root)
age_entry.pack()

def read_age():
    text = age_entry.get().strip()
    try:
        return int(text)
    except ValueError:
        print(f"Not a whole number: {text!r}")
        return None

for typed in ["42", " 7 ", "", "4.5"]:
    age_entry.delete(0, tk.END)
    age_entry.insert(0, typed)
    print(repr(age_entry.get()), "->", read_age())
root.destroy()

Output:

'42' -> 42
' 7 ' -> 7
Not a whole number: ''
'' -> None
Not a whole number: '4.5'
'4.5' -> None

strip() removes spaces users type by accident. For a float, use float(text) the same way.

Set, clear and default value

There’s no set() method on an Entry. Clear the text with delete(0, tk.END) and add new text with insert(), or link a StringVar with textvariable and call set() on that:

import tkinter as tk

root = tk.Tk()

# 1) insert / delete
city = tk.Entry(root)
city.insert(0, "London")                 # default value
print("start:", city.get())
city.delete(0, tk.END)                   # clear it
city.insert(0, "New York")               # set new text
print("after set:", city.get())
city.insert(tk.END, ", USA")             # append
print("after append:", city.get())

# 2) textvariable: set and get through a StringVar
city_var = tk.StringVar(value="Paris")
city2 = tk.Entry(root, textvariable=city_var)
print("StringVar default:", city2.get())
city_var.set("Tokyo")
print("after city_var.set():", city2.get())
root.destroy()

Output:

start: London
after set: New York
after append: New York, USA
StringVar default: Paris
after city_var.set(): Tokyo

Inserting text right after creating the Entry gives it a default value. More options (including a default that disappears on click) in Tkinter Entry default value.

Placeholder text

Tkinter has no placeholder option. This small subclass shows grey hint text while the Entry is empty, clears it when the user clicks in, and makes get() return an empty string while the hint is showing:

import tkinter as tk

class PlaceholderEntry(tk.Entry):
    """An Entry that shows grey hint text while it is empty and not focused."""

    def __init__(self, master, placeholder, **kwargs):
        super().__init__(master, **kwargs)
        self.placeholder = placeholder
        self.normal_fg = self.cget("fg")
        self.bind("<FocusIn>", self._clear)
        self.bind("<FocusOut>", self._show)
        self._show()

    def _show(self, event=None):
        if not super().get():
            self.insert(0, self.placeholder)
            self.config(fg="grey")

    def _clear(self, event=None):
        if self.cget("fg") == "grey":
            self.delete(0, tk.END)
            self.config(fg=self.normal_fg)

    def get(self):
        """The real value: an empty string while the placeholder is showing."""
        return "" if self.cget("fg") == "grey" else super().get()

root = tk.Tk()
root.title("Sign up")
root.configure(padx=20, pady=15)

email = PlaceholderEntry(root, "you@example.com", width=30)
email.pack(pady=4)
phone = PlaceholderEntry(root, "Phone number", width=30)
phone.pack(pady=4)
tk.Button(root, text="Submit", command=lambda: print(repr(email.get()), repr(phone.get()))).pack(pady=6)
root.mainloop()
Tkinter Entry placeholder text: grey hint you@example.com in an empty email Entry, and a phone Entry with 555-0142 typed in
The email field still shows its grey placeholder; the phone field has been filled in.

Before and after filling in the phone number, get() returns:

'' ''
'' '555-0142'

Password field: show=”*”

import tkinter as tk

root = tk.Tk()
root.title("Password")
root.configure(padx=20, pady=15)

tk.Label(root, text="Password:").grid(row=0, column=0, sticky="w")
password = tk.Entry(root, show="*", width=24)       # every character is displayed as *
password.grid(row=0, column=1, padx=6)

visible = tk.BooleanVar(value=False)

def toggle():
    password.config(show="" if visible.get() else "*")

tk.Checkbutton(root, text="Show password", variable=visible, command=toggle).grid(
    row=1, column=1, sticky="w", pady=4)
root.mainloop()
Tkinter password Entry using show='*' so the typed password appears as asterisks, with a Show password checkbox
show="*" hides the characters.
Tkinter password Entry with the Show password checkbox ticked, displaying the password Sup3rSecret! as plain text
Ticking the checkbox sets show="" to reveal the password.

show only changes what is displayed:

get() returns the real text: Sup3rSecret!

Run a function when the user presses Enter

An Entry has no command option:

import tkinter as tk

root = tk.Tk()
entry = tk.Entry(root, command=lambda: print("Enter"))
_tkinter.TclError: unknown option "-command"

Use bind("<Return>", ...) instead. The bound function receives an event argument, so give it a default of None and the same function works for a button too:

import tkinter as tk

root = tk.Tk()
root.title("Search")
root.configure(padx=20, pady=15)

search = tk.Entry(root, width=30)
search.pack()
search.focus_set()                        # the cursor starts in the Entry

result = tk.Label(root, text="Type and press Enter")
result.pack(pady=6)

def run_search(event=None):               # event is passed by bind(), not by the button
    result.config(text=f"Searching for: {search.get()}")

search.bind("<Return>", run_search)       # Enter key on the main keyboard
search.bind("<KP_Enter>", run_search)     # Enter key on the number pad
tk.Button(root, text="Search", command=run_search).pack()
root.mainloop()
Tkinter Entry bound to the Enter key: after typing tkinter entry and pressing Enter the label shows Searching for: tkinter entry
Pressing Enter runs the same function as the Search button.

Pressing Enter in the test gives:

Searching for: tkinter entry

Validate input: only numbers, max length

Set validate="key" and a validatecommand. Tkinter calls the function before each change; returning False blocks it. root.register() turns the function into a Tcl command, and "%P" passes the text the Entry would contain if the change were allowed:

import tkinter as tk

root = tk.Tk()
root.title("Validation")
root.configure(padx=20, pady=15)

def only_digits(new_text):
    """new_text (%P) is what the Entry would contain if this change were allowed."""
    return new_text == "" or new_text.isdigit()

def max_length(new_text):
    return len(new_text) <= 5

def on_invalid():
    root.bell()                                    # beep when a key is rejected

digits_cmd = (root.register(only_digits), "%P")
zip_cmd = (root.register(max_length), "%P")

tk.Label(root, text="Quantity (digits only):").grid(row=0, column=0, sticky="w")
quantity = tk.Entry(root, validate="key", validatecommand=digits_cmd,
                    invalidcommand=root.register(on_invalid))
quantity.grid(row=0, column=1, pady=3)

tk.Label(root, text="ZIP (max 5 chars):").grid(row=1, column=0, sticky="w")
zip_code = tk.Entry(root, validate="key", validatecommand=zip_cmd)
zip_code.grid(row=1, column=1, pady=3)
root.mainloop()
Tkinter Entry validatecommand allowing only numbers: quantity Entry shows 125 after letters were rejected, ZIP Entry limited to 5 characters
Letters are rejected in the first field; the second stops at 5 characters.

Validation also applies to insert() and pasted text, which makes it easy to test:

typed '1'  -> '1'
typed '2'  -> '12'
typed 'a'  -> '12'
typed '5'  -> '125'
typed '-'  -> '125'
ZIP paste of 6 chars -> ''

A paste is checked as one change: pasting 6 characters into the ZIP field is rejected completely, not cut to 5.

For decimals and negative numbers, allow the partial values users type on the way ("-", ".") and check the rest with float():

import tkinter as tk

root = tk.Tk()

def is_number(new_text):
    if new_text in ("", "-", ".", "-."):          # allow partial input while typing
        return True
    try:
        float(new_text)
        return True
    except ValueError:
        return False

price = tk.Entry(root, validate="key", validatecommand=(root.register(is_number), "%P"))
for text in ["-", "-1", "-1.", "-1.5", "-1.5.", "1e3", "abc"]:
    price.delete(0, tk.END)
    price.insert(0, text)
    print(f"{text!r:8} {'accepted' if price.get() == text else 'rejected'}")
root.destroy()

Output:

'-'      accepted
'-1'     accepted
'-1.'    accepted
'-1.5'   accepted
'-1.5.'  rejected
'1e3'    accepted
'abc'    rejected

float() accepts scientific notation like 1e3; use a regular expression if you need to block it.

CodePassed to your function
%PThe text if the change is allowed (most useful)
%SThe text being inserted or deleted
%sThe text before the change
%d1 insert, 0 delete, -1 other
%iIndex where the change happens
%VWhich event triggered it (key, focusout…)
%WThe widget name

Other validate values are "focusout" (check when the user leaves the field), "focus" and "all". Don’t change the Entry’s text from inside the validate function: Tk then switches validation off. Deeper coverage: allow only numbers in a Tkinter Entry and Tkinter form validation.

React to every change with StringVar

trace_add("write", ...) on the textvariable runs a function whenever the text changes, whether the user typed it or the code set it. Useful for live counters, search-as-you-type and enabling a button:

import tkinter as tk

root = tk.Tk()
root.title("Live counter")
root.configure(padx=20, pady=15)

tweet = tk.StringVar()
counter = tk.Label(root, text="0 / 40")

def update_count(*args):
    n = len(tweet.get())
    counter.config(text=f"{n} / 40", fg="red" if n > 40 else "black")

tweet.trace_add("write", update_count)       # runs on every change

tk.Entry(root, textvariable=tweet, width=42).pack()
counter.pack(anchor="e", pady=4)
root.mainloop()
Tkinter Entry with StringVar trace_add updating a live character counter showing 37 / 40
The counter updates as you type.

Entry methods

import tkinter as tk

root = tk.Tk()
entry = tk.Entry(root)
entry.pack()

entry.insert(0, "Hello World")
print("get():", entry.get())
entry.insert(5, ",")                          # insert at index 5
print("insert(5, ','):", entry.get())
entry.delete(0, 7)                            # delete characters 0-6
print("delete(0, 7):", entry.get())
entry.icursor(2)                              # move the text cursor
print("index(INSERT):", entry.index(tk.INSERT))
print("index(END):", entry.index(tk.END))     # length of the text
entry.select_range(0, tk.END)                 # select all
print("selection_present():", entry.selection_present())
print("selected text:", entry.selection_get())
root.destroy()

Output:

get(): Hello World
insert(5, ','): Hello, World
delete(0, 7): World
index(INSERT): 2
index(END): 5
selection_present(): True
selected text: World
MethodDescription
get()Return the text
insert(index, text)Insert text at an index (0, tk.END, tk.INSERT)
delete(first, last=None)Delete characters; delete(0, tk.END) clears everything
icursor(index)Move the text cursor
index(index)Convert an index to a number; index(tk.END) is the length
select_range(start, end)Select text; select_clear() removes the selection
focus_set()Put the cursor in this Entry
config(**options)Change options, e.g. state or show

Read-only Entry

With state="readonly" users can select and copy the text but not edit it. Your code can’t insert either, so switch to "normal" briefly to update it:

import tkinter as tk

root = tk.Tk()
code = tk.Entry(root)
code.insert(0, "ABC-123")
code.config(state="readonly")

code.insert(tk.END, "XYZ")                    # silently ignored
print("readonly:", code.get())

code.config(state="normal")                   # unlock, change, lock again
code.delete(0, tk.END)
code.insert(0, "NEW-456")
code.config(state="readonly")
print("updated:", code.get())
root.destroy()

Output:

readonly: ABC-123
updated: NEW-456

ttk.Entry vs tk.Entry

ttk.Entry (from tkinter.ttk) uses the native theme and is styled with ttk.Style instead of options like bg:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("tk vs ttk")
root.configure(padx=20, pady=15)

style = ttk.Style()
style.theme_use("clam")
style.configure("Big.TEntry", padding=6, fieldbackground="#eef4fb", foreground="#1f4e79")

tk.Label(root, text="tk.Entry").grid(row=0, column=0, sticky="w")
tk.Entry(root, width=26).grid(row=0, column=1, pady=4)
tk.Label(root, text="ttk.Entry").grid(row=1, column=0, sticky="w")
ttk.Entry(root, width=26).grid(row=1, column=1, pady=4)
tk.Label(root, text="ttk.Entry + style").grid(row=2, column=0, sticky="w", padx=(0, 10))
ttk.Entry(root, width=24, style="Big.TEntry", font=("Segoe UI", 11)).grid(row=2, column=1, pady=4)

for child in root.grid_slaves(column=1):
    child.insert(0, "Python Guides")
root.mainloop()
Tkinter tk.Entry compared with ttk.Entry and a ttk.Entry styled with ttk.Style padding and fieldbackground colour
Classic tk.Entry, themed ttk.Entry, and a styled ttk.Entry.

Passing a tk.Entry option to ttk.Entry fails:

_tkinter.TclError: unknown option "-bg"

Methods like get(), insert() and delete() and validation work the same on both.

Example: login form

Several Entry features together: show="*", Enter to submit, clearing the password on failure, and moving the cursor back:

import tkinter as tk
from tkinter import messagebox, ttk

USERS = {"admin": "python123"}

root = tk.Tk()
root.title("Login")
root.resizable(False, False)

form = ttk.Frame(root, padding=20)
form.pack()

ttk.Label(form, text="Username").grid(row=0, column=0, sticky="w", pady=4)
username = ttk.Entry(form, width=25)
username.grid(row=0, column=1, pady=4)

ttk.Label(form, text="Password").grid(row=1, column=0, sticky="w", pady=4)
password = ttk.Entry(form, width=25, show="*")
password.grid(row=1, column=1, pady=4)

status = ttk.Label(form, text="", foreground="red")
status.grid(row=3, column=0, columnspan=2)

def login(event=None):
    user, pwd = username.get().strip(), password.get()
    if not user or not pwd:
        status.config(text="Enter a username and password")
    elif USERS.get(user) == pwd:
        status.config(text="")
        messagebox.showinfo("Login", f"Welcome, {user}!")
    else:
        status.config(text="Wrong username or password")
        password.delete(0, tk.END)
        password.focus_set()

ttk.Button(form, text="Log in", command=login).grid(row=2, column=1, sticky="e", pady=8)
root.bind("<Return>", login)                 # Enter logs in from any field
username.focus_set()
root.mainloop()
Tkinter login form built with ttk Entry widgets for username and password and a Log in button showing Wrong username or password
A failed login clears the password and shows a message.

Testing empty fields, a wrong password and the right one:

''       ''           status='Enter a username and password'  password field=''
'admin'  'wrong'      status='Wrong username or password'  password field=''
showinfo: Welcome, admin!
'admin'  'python123'  status=''  password field='python123'

For a bigger form see registration form in Python Tkinter; for suggestions while typing, see Tkinter autocomplete.

Common Entry errors

AttributeError: ‘NoneType’ object has no attribute ‘get’

import tkinter as tk

root = tk.Tk()
entry = tk.Entry(root).pack()        # pack() returns None, so entry is None
print(entry.get())
AttributeError: 'NoneType' object has no attribute 'get'

pack(), grid() and place() return None. Create the Entry and place it on separate lines:

import tkinter as tk

root = tk.Tk()
entry = tk.Entry(root)               # create first...
entry.pack()                         # ...then place it on its own line
entry.insert(0, "works")
print(entry.get())
root.destroy()

Output:

works

get() returns an empty string

import tkinter as tk

root = tk.Tk()
entry = tk.Entry(root)
entry.pack()
value = entry.get()                  # runs once, before the user can type
print(repr(value))
root.destroy()

Output:

''

This code reads the Entry once, when the program starts. Call get() inside the function that runs when the user clicks a button or presses Enter.

ValueError: invalid literal for int()

int(entry.get()) fails for empty or non-numeric input. Wrap it in try/except ValueError as shown above, or block bad input with validation.

Need more than one line?

Entry is single-line only. Use the Text widget for multi-line input; see display data in textboxes with Tkinter.

Frequently asked questions

How do I get the value of a Tkinter Entry?

Call entry.get() inside a function that runs after the user has typed, such as a button command. It returns a string; convert with int() or float() if you need a number.

How do I set the text of an Entry?

Use entry.delete(0, tk.END) and then entry.insert(0, "text"), or link a StringVar with textvariable and call var.set("text").

How do I allow only numbers in a Tkinter Entry?

Use validate="key" and validatecommand=(root.register(func), "%P"), where func returns True only if the new text is empty or isdigit().

How do I run a function when Enter is pressed in an Entry?

Entry has no command option. Use entry.bind("<Return>", func), where func accepts an event argument.

How do I make a password Entry?

Create it with show="*". get() still returns the real text; set show="" to reveal it.

How do I change the height of an Entry?

Entry has no height option. Use a larger font, ipady in pack()/grid(), or padding in a ttk.Style. For multiple lines, use a Text widget.

What is the difference between tk.Entry and ttk.Entry?

ttk.Entry follows the system theme and is styled with ttk.Style; tk.Entry takes options like bg and fg directly. Both have the same methods.