Web endpoint URLs

User-specified URLs

Users have partial control over the URL of a web endpoint. For the web_endpoint, asgi_app and wsgi_app decorators, an optional label keyword argument can be provided, which allocates the URL https://<workspace>--<label>.modal.run for your endpoint if your Modal Environment has no web suffix, and https://<workspace>-<env>--<label>.modal.run if it does.

For example, within a Modal workspace with slug my-workspace, and an Environment with no web suffix, the following code will deploy an endpoint at URL https://my-workspace--foo-bar.modal.run:

from modal import App, web_endpoint

app = App(name="xyz")  # Note: prior to April 2024, "app" was called "stub"

@app.function()
@web_endpoint(label="foo-bar")
def my_f():
    ...

Auto-generated URLs

If a label is not specified, web endpoints receive a generated URL under the modal.run domain. These generated URLs contain a subdomain label that is unique to the running endpoint.

Deployed apps

Web endpoints served by a deployed application have subdomains composed of the following parts:

  • Workspace name slug: ECorpecorp
  • Environment web suffix slug: prodprod
  • App name slug: text_to_speechtext-to-speech
  • Function name slug: flask_appflask-app

The deployed web endpoint URL for this example is https://ecorp-prod--text-to-speech-flask-app.modal.run.

This URL is reserved for the deployed application’s web endpoint. Running the application during development will not replace the deployed application’s association with the deployed URL.

One Modal environment is allowed to have an empty web suffix, in which case the environment web suffix part would be absent: ecorp--text-to-speech-flask-app.

Ephemeral apps

Web endpoints run in ephemeral apps have predictable, unique subdomains, distinct from the subdomain of any associated deployment.

The subdomain of a web endpoint running in an ephemeral app is composed of the following parts:

  • Workspace name slug: ECorpecorp
  • Environment web suffix slug: mainmain
  • App name slug: text_to_speechtext-to-speech
  • Function name slug: flask_appflask-app
  • A -dev suffix, calling out that this web endpoint is not deployed.

Combining the example parts, the ephemeral web endpoint’s URL would be https://ecorp-main--text-to-speech-flask-app-erikbern-dev.modal.run.

The components of an ephemeral web endpoint subdomain ensure URL predictability during development and testing, in both personal and shared workspaces. Multiple copies of a web endpoint can be active when a Modal app is run in two or more terminal windows, or when multiple developers are iterating on the same application codebase. Every copy will be uniquely addressable while running.

Stealing

If an emphemeral web endpoint is running and another ephemeral web endpoint is created seeking the same web endpoint label, the new web endpoint function will steal the running web endpoint’s label.

This ensures the latest iteration of an ephemeral web endpoint function is serving requests, while older ones stop recieving web traffic.

(Previously concurrently running ephemeral app web endpoints would include conflict-avoiding hashes.)

Truncation

If a generated subdomain label is longer than 63 characters, it will be truncated.

For example, the following subdomain label is too long, 67 characters: ecorp--text-to-speech-really-really-realllly-long-function-name-dev.

The truncation happens by calculating a SHA-256 hash of the overlong label, then taking the first 6 characters of this hash. The overlong subdomain label is truncated to 56 characters, and then joined by a dash to the hash prefix.

The combination of the label hashing and truncation provides a unique list of 63 characters, complying with both DNS system limits and uniqueness requirements.

Custom domains

Custom domains are available on our Team and Enterprise plans.

You can use your own domain names with Modal web endpoints. If your plan supports custom domains, visit the Domains tab in your workspace settings to add a domain name to your workspace.

You can use three kinds of domains with Modal:

  • Apex: root domain names like example.com
  • Subdomain: single subdomain entries such as my-app.example.com, api.example.com, etc.
  • Wildcard domain: either in a subdomain like *.example.com, or in a deeper level like *.modal.example.com

You’ll be asked to update your domain DNS records with your domain name registrar and then validate the configuration in Modal. Once the records have been properly updated and propagated, your custom domain will be ready to use.

You can assign any Modal web endpoint to any registered domain in your workspace with the custom_domains argument.

from modal import App, web_endpoint

app = App("custom-domains-example")  # Note: prior to April 2024, "app" was called "stub"


@app.function()
@web_endpoint(custom_domains=["api.example.com"])
def hello(message: str):
    return {"message": f"hello {message}"}

You can then run modal deploy to put your web endpoint online, live.

$ curl -s https://api.example.com?message=world
{"message": "hello world"}

Note that Modal automatically generates and renews TLS certificates for your custom domains. Since we do this when your domain is first accessed, there may be an additional 1-2s latency on the first request. Additional requests use a cached certificate.

You can also register multiple domain names and associate them with the same web endpoint.

from modal import App, web_endpoint

app = App("custom-domains-example-2")  # Note: prior to April 2024, "app" was called "stub"


@app.function()
@web_endpoint(custom_domains=["api.example.com", "api.example.net"])
def hello(message: str):
    return {"message": f"hello {message}"}

For Wildcard domains, Modal will automatically resolve arbitrary custom endpoints (and issue TLS certificates). For example, if you add the wildcard domain *.example.com, then you can create any custom domains under example.com:

import random
from modal import App, web_endpoint

app = App("custom-domains-example-2")  # Note: prior to April 2024, "app" was called "stub"

random_domain_name = random.choice(range(10))


@app.function()
@web_endpoint(custom_domains=[f"{random_domain_name}.example.com"])
def hello(message: str):
    return {"message": f"hello {message}"}

Custom domains can also be used with ASGI or WSGI apps using the same custom_domains argument.