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:
ECorp
→ecorp
- Environment web suffix slug:
prod
→prod
- App name slug:
text_to_speech
→text-to-speech
- Function name slug:
flask_app
→flask-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:
ECorp
→ecorp
- Environment web suffix slug:
main
→main
- App name slug:
text_to_speech
→text-to-speech
- Function name slug:
flask_app
→flask-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.