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

fn, inputs, outputs — that's the whole contract, and Gradio builds the app around it.

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

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.

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 (fn=classify becomes a public API endpoint /classify), so the same demo serves the browser UI, gradio_client calls, and — since 5.28 — MCP tools. One Interface, three doors into your model.

Examples

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
* To create a public link, set `share=True` in `launch()`.

Rendered UI (verified via /config):
- 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") -> 'oidarG olleH'

Verified on gradio 6.27.0 (Python 3.12). The Submit button binds to .click AND Enter (.submit) on the input; the API endpoint is named after the function, not 'predict'.

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"], ["examples/night.jpg"]],
    cache_examples=False,
)

demo.launch(prevent_thread_lock=True)
* Running on local URL:  http://127.0.0.1:7861
* To create a public link, set `share=True` in `launch()`.

Rendered UI (verified via /config):
- Markdown title, then description line under it
- left column: Image input (sources: upload, webcam, clipboard)
- right column: Label output "Prediction" (num_top_classes=3)
- bottom: examples dataset with thumbnails for beach.jpg and night.jpg
Event listener: Image.click(Submit) -> api_name="/classify"
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-with-confidence-scores return value is the contract gr.Label understands; the component renders each label 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
* To create a public link, set `share=True` in `launch()`.

Rendered UI (verified via /config):
- left column: Image input "Photo" + Slider "Grayscale strength" (min 0, max 1, step 0.05, default 1.0)
- right column: Image output "Result"
- NO Submit button anywhere — live=True removed it
Event listener: targets=[[image, 'change'], [slider, 'change']] inputs=[image, slider] outputs=[image]
Function logic verified: strength 1.0 -> mode L, fully gray; strength 0.5 -> pixel blend (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.

Flags

FlagMeaning
fn / inputs / outputsThe 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=TruePre-computes example outputs at launch so clicking one is instant; use cache_mode='lazy' to compute on first use instead.
live=TrueRuns fn on every input change and removes the Submit button — ideal for cheap transforms, painful for slow models.
title / description / articleMarkdown 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.
additional_inputsExtra components tucked into a collapsed accordion below the main inputs, passed to fn after the primary ones.

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 (Abubakar Abid, Ali Abid and team) 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 (released 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 — because for one-function demos it remains the shortest path from code to URL.

Under the hood: one function, two conversions

Each component sits between the browser and your fn via two methods: preprocess() turns the 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). When you call launch(), Interface also 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 result streams back over SSE. fn never knows a web server exists.

Fun facts

Pros

Cons

Takeaways