kmail.at
contact

elsewhere

EnglishEN
← learning

gradio · difficulty ◆

gradio Interface — wrap any Python function in a working web demo

fn, inputs, outputs — that's the whole contract. Since Gradio 6 the API endpoint is named after your function, not /predict.

Every Hugging Face Space you have ever played with started life as three arguments: fn, inputs, outputs.

AI-generated draft · reviewed and editedThis text was generated with AI as a first draft, then reviewed and edited by the author, who holds editorial responsibility for it.

2026-09-13 · 6 min read

$ gr.Interface(fn, inputs, outputs)
Feb 19, 2019
First release

gradio 0.1.0 on PyPI; verified against the PyPI API

Nov 21, 2025
Gradio 6.0

same day as 5.50 — the release that ships deprecation warnings for 6.x removals

39
Constructor parameters

38 named parameters plus **kwargs — 39 as inspect.signature reports it, on 6.27.0

48
launch() parameters

from gr.Blocks.launch signature, 6.27.0

>= 3.10
Requires Python

from PyPI metadata for gradio 6.27.0

5.28.0
MCP server mode

'Let Gradio apps also be MCP Servers', April 29, 2025

What it does

gr.Interface wraps a single Python function in a complete web UI: input components on the left, output components on the right, Submit, Clear and Flag buttons generated for you. You pass fn (the function), inputs (one component or a list, one per argument), and outputs (one per return value). Strings are accepted as shortcuts — "text" is gr.Textbox(), "image" is gr.Image(), "number" is gr.Number(). Gradio handles the HTTP server, the queue and the JSON plumbing; you only write the function.

What changed in Gradio 6

Gradio 6.0 (2025-11-21) standardized the API and moved app-wide settings out of the constructor: theme, css, css_paths, js, head and head_paths now belong to .launch(), not gr.Interface/gr.Blocks. The API endpoint for an Interface is no longer /predict — it is named after your function, so fn=classify exposes /classify (the old name was kept only for Blocks consistency and MCP tools). show_api is gone: launch() takes footer_links=["api","gradio","settings"] and event listeners take api_visibility="public"|"undocumented"|"private". cache_examples now accepts only True/False, with cache_mode="eager"|"lazy" alongside it. Gradio 6 requires Python >= 3.10.

Why it matters

It is the lowest-energy path from "I have a function" to "people can use it in a browser", which is why it became the default format for model demos on Hugging Face Spaces. Each event listener is auto-named after your function, so the same demo serves the browser UI, gradio_client calls, and — via launch(mcp_server=True) — MCP tools an LLM agent can call. One Interface, three doors into your model.

Example

$ import gradio as gr

def reverse_text(text: str) -> str:
    return text[::-1]

demo = gr.Interface(
    fn=reverse_text,
    inputs=gr.Textbox(label="Your text", placeholder="Type here..."),
    outputs=gr.Textbox(label="Reversed"),
    title="Text Reverser",
    examples=[["Gradio makes ML demos easy"]],
)

demo.launch(prevent_thread_lock=True)
* Running on local URL:  http://127.0.0.1:7861

Rendered UI (read from /config on 6.27.0):
- centered Markdown title "Text Reverser"
- left column: Textbox labeled "Your text" (placeholder "Type here...")
- below it: Clear and Submit buttons
- right column: Textbox labeled "Reversed", below it a Flag button
- bottom: an examples table "Your text | Gradio makes ML demos easy"
Event listener: targets=[Submit.click, Textbox.submit] inputs=[textbox] outputs=[textbox], api_name="/reverse_text"
End-to-end: gradio_client predict("Hello Gradio", api_name="/reverse_text") -> 'oidarG olleH'

Verified on gradio 6.27.0 (Python 3.12). In Gradio 6 the endpoint is named after the function — /gradio_api/info reports "/reverse_text" and no /predict. Submit binds to .click AND Enter (.submit) on the input.

$ import gradio as gr
from PIL import Image
import numpy as np

def classify(img: Image.Image):
    if img is None:
        return {}
    arr = np.asarray(img.convert("L"), dtype=float)
    brightness = arr.mean() / 255.0
    if brightness > 0.6:
        return {"sky": 0.83, "cloud": 0.12, "night": 0.05}
    elif brightness > 0.3:
        return {"forest": 0.61, "meadow": 0.27, "sky": 0.12}
    else:
        return {"night": 0.77, "cave": 0.18, "forest": 0.05}

demo = gr.Interface(
    fn=classify,
    inputs=gr.Image(type="pil", label="Input image"),
    outputs=gr.Label(num_top_classes=3, label="Prediction"),
    title="Scenery Classifier",
    description="Upload a photo, get a (toy) scene label.",
    examples=[["examples/beach.jpg"]],
    cache_examples=False,
)

demo.launch(prevent_thread_lock=True)
* Running on local URL:  http://127.0.0.1:7861

Rendered UI (verified via /config):
- Markdown title, then the description line under it
- left column: Image input with sources: upload, webcam, clipboard
- right column: Label output "Prediction" (num_top_classes=3)
- bottom: examples dataset listing examples/beach.jpg
Event listener: targets=[[Submit, "click"]] inputs=[image] outputs=[label], api_name="/classify" — an Image input gets no Enter (.submit) binding, unlike a Textbox
Function logic verified: bright input -> {'sky': 0.83, 'cloud': 0.12, 'night': 0.05}; dark input -> {'night': 0.77, 'cave': 0.18, 'forest': 0.05}

The dict-of-confidence-scores return value is the contract gr.Label understands; each key renders with a bar. Swap the toy logic for a transformers pipeline and nothing else changes.

$ import gradio as gr
from PIL import Image

def to_grayscale(img: Image.Image, strength: float):
    if img is None:
        return None
    gray = img.convert("L")
    if strength >= 0.99:
        return gray
    return Image.blend(img.convert("RGB"), gray.convert("RGB"), strength)

demo = gr.Interface(
    fn=to_grayscale,
    inputs=[
        gr.Image(type="pil", label="Photo"),
        gr.Slider(0, 1, value=1.0, step=0.05, label="Grayscale strength"),
    ],
    outputs=gr.Image(type="pil", label="Result"),
    title="Grayscale Studio",
    live=True,  # re-runs on every input change, no Submit button
)

demo.launch(prevent_thread_lock=True)
* Running on local URL:  http://127.0.0.1:7861

Rendered UI (verified via /config on 6.27.0):
- left column: Image input "Photo" + Slider "Grayscale strength" (min 0, max 1, step 0.05, default 1.0)
- right column: Image output "Result"
- buttons present: Clear, Flag — NO Submit button anywhere, live=True removed it
Event listener: targets=[[image,'change'], [slider,'change']] inputs=[image, slider] outputs=[image], api_name="/to_grayscale"
Function logic verified: strength 1.0 -> mode L (fully gray); strength 0.5 -> Image.blend gives (147,72,72) for input (200,50,50)

live=True is the difference between a form and an instrument. Great for filters and sliders, expensive for slow models — every slider tick is a queued run.

$ # The Gradio 6 idiom: app-wide settings move to launch()
import gradio as gr

def summarize(text: str, max_words: int):
    words = text.split()
    return " ".join(words[:max_words]) + ("…" if len(words) > max_words else "")

demo = gr.Interface(
    fn=summarize,
    inputs=[gr.Textbox(label="Article", lines=4),
            gr.Slider(3, 30, value=8, step=1, label="Max words")],
    outputs=gr.Textbox(label="Summary"),
    title="Truncating Summarizer",
    flagging_mode="never",       # Gradio 5's allow_flagging= is GONE
)

demo.launch(
    theme=gr.themes.Soft(),                              # moved off the constructor
    css=".gradio-container { max-width: 720px !important; }",  # moved too
    footer_links=["gradio", "settings"],                 # replaces show_api=False
)
* Running on local URL:  http://127.0.0.1:7860

Rendered UI (verified on 6.27.0 via /config):
- theme Soft() applied, container capped at 720px by the injected CSS
- footer shows only the "Built with Gradio" and settings links — no API link
- right column: Textbox "Summary"; Clear + Submit buttons (no Flag: flagging_mode='never')
Event listener: api_name="/summarize"
Function logic verified: summarize("one two three four five six", 4) -> 'one two three four…'; summarize("short text", 8) -> 'short text'

If you write theme=/css= on gr.Interface or gr.Blocks in Gradio 6 you get a deprecation warning telling you to pass them to launch() instead — they still work on the constructor, but launch() is where they belong now.

Common flags

fn / inputs / outputs
The required trio; list lengths must match the function's parameters and return values, in order.
examples=[[...], ...]
Clickable sample inputs shown under the app; each inner list is one row, one entry per input component.
cache_examples / cache_mode
Gradio 6: cache_examples is True|False only. Put the strategy in cache_mode='eager' (default) or 'lazy'. cache_examples='lazy' now raises ValueError.
live=True
Runs fn on every input change and removes the Submit button — ideal for cheap transforms, painful for slow models.
title / description / article
Markdown above and below the interface; title becomes both the header and the browser tab title.
flagging_mode
'manual' (default) shows a Flag button that logs input/output pairs to CSV; 'never' hides it; 'auto' flags every run. Replaces Gradio 5's allow_flagging=.
additional_inputs
Extra components tucked into a collapsed accordion below the main inputs, passed to fn after the primary ones.
api_name / api_visibility
The endpoint name defaults to the function name in Gradio 6. api_visibility='public'|'undocumented'|'private' replaces the removed show_api and api_name=False.
theme / css / js (launch only)
Gradio 6 moved these app-wide settings to .launch(). Passing them to the constructor still works but warns.

History

February 2019: the original idea

gr.Interface is the API Gradio launched with — version 0.1.0 hit PyPI on 2019-02-19, and the whole library was this one abstraction: point it at a function, get a browser UI. The founders wanted sharing an ML demo to take minutes, not a web-dev project. Hugging Face acquired Gradio in 2021, and Interface became the default blueprint for thousands of Spaces.

May 2022: Blocks arrives, Interface stays first

Gradio 3.0 (2022) introduced the 'biggest update ever': gr.Blocks, the low-level API for custom layouts and multi-step flows. Yet the team kept Interface as the front door, and the docs still teach it first — for one-function demos it remains the shortest path from code to URL.

November 2025: Gradio 6 standardizes the API

6.0.0 landed on PyPI on 2025-11-21 (the same day as 5.50, the last 5.x release and the migration on-ramp). The headline breaking changes all push one way — make the Python API predictable: app-wide parameters (theme, css, js, head) moved from the Blocks/Interface constructor to launch(), the default API endpoint switched from /predict to your function's name, show_api was replaced by footer_links and api_visibility, and the Chatbot tuple format was removed in favor of role/content messages. Python 3.10+ is now required.

Under the hood

Under the hood: one function, two conversions

Each component sits between the browser and your fn via two methods: preprocess() turns incoming JSON into the Python type your function wants (an uploaded image into a PIL object or numpy array, depending on type=), and postprocess() turns the return value back into something the frontend can render (a dict of label->confidence into the Label component's bars). launch() emits a /config JSON describing every component and event listener — my probes read the rendered tree straight from it. Clicking Submit joins the queue at /gradio_api/queue/join (the old /queue/status path now 404s; the live one is /gradio_api/queue/status) and results stream back over SSE. fn never knows a web server exists.

Fun facts

Pros & cons

pros

  • + Fastest possible path from a Python function to a shareable UI — three arguments, no HTML, no routes
  • + Buttons, layout, queue and flagging are generated and consistent for free
  • + Every demo is automatically a REST API, and optionally an MCP server, at no extra cost

cons

  • − One fn, one step: anything with branches, stages or custom layout forces you into gr.Blocks
  • − The left-inputs/right-outputs layout is fixed — no repositioning, no nesting
  • − A long signature makes the UI sprawling unless you hide parameters in additional_inputs
  • − Gradio 6 is a hard migration: allow_flagging, show_api and constructor-level theme/css are all removed, not just deprecated

Takeaways

  1. 1Reach for gr.Interface first; graduate to gr.Blocks only when one function is genuinely not enough.
  2. 2On Gradio 6, put theme/css/js on launch(), not the constructor — that is where app-wide settings live now.
  3. 3Your API endpoint is your function name: fn=classify gives /classify, not /predict.
  4. 4cache_examples is boolean in Gradio 6 — use cache_mode='lazy' for deferred computation instead of cache_examples='lazy'.
  5. 5Match list lengths: one input component per function parameter, one output component per return value.
  6. 6live=True for cheap reactive transforms; plain submit for anything model-sized.
  7. 7Name your functions as if they were API endpoints — because they are.

Related commands