---
title: url
description: Return a URL to fetch a key - a signed GetObject where the adapter can sign, or a direct CDN/public URL where one is configured.
related:
  - /guides/gcs-signed-urls-without-service-account-key
---

`files.url(key, options?)`

Returns a URL the caller can use to fetch `key`. Every adapter returns the most direct URL it can produce. Signing adapters (S3 and the S3-compatible catalog — R2 over HTTP plus every regional / budget / decentralised wrapper — alongside Google Cloud Storage, Firebase Storage, Azure (shared-key or user-delegation SAS), Supabase, UploadThing in `private` mode, Vercel Blob in `access: "private"` mode, and the R2 binding when HTTP credentials are also configured) sign a read URL - defaulting to a 1-hour expiry, override per-call via `{ expiresIn }` or per-adapter via `defaultUrlExpiresIn`. If the adapter is constructed with a `publicBaseUrl` (CDN, custom domain, `r2.dev`, Bunny Pull Zone) or UploadThing's `public-read` ACL, a plain `url(key)` returns that permanent link without signing ([`capabilities.publicUrl`](/docs/capabilities#publicurl) is `true`).

`expiresIn` always means "give me a link that expires". On an adapter that can sign, passing it returns a signed URL even when a `publicBaseUrl` is configured. On one that can't (Vercel Blob in public mode, UploadThing `public-read`, Convex, Appwrite, the filesystem), it throws an `Unsupported` [`FilesError`](/docs/api/errors) rather than handing back a permanent link that ignores it. Leave it out to get the adapter's default. `expiresIn` is a whole number of seconds: `0`, a negative or fractional value, or `NaN` throws an `Invalid` `FilesError` before anything is signed, instead of returning a URL that never works.

Adapters with no URL primitive throw unless you configure a public URL for them: an R2 Workers binding without either `publicBaseUrl` or HTTP credentials; Bunny Storage, FTP, SFTP, and WebDAV without `publicBaseUrl`; Google Drive, OneDrive, and SharePoint without `publicByDefault: true`; and Appwrite without `public: true`. Netlify Blobs has no URL primitive at all, so `url()` always throws there. [Provider gaps](/docs/provider-gaps#url-requires-explicit-public-mode-configuration) covers each one, and [`files.capabilities.signedUrl`](/docs/capabilities#signedurl) tells you up front whether the adapter can sign.

```ts lineNumbers
// One call, every adapter. S3 and the S3-compatible catalog (R2 over HTTP
// plus every regional / budget / decentralised wrapper) sign a GetObject (1h
// default, override with { expiresIn }); Google Cloud Storage and Azure sign
// a read URL natively with the same default; Supabase signs via createSignedUrl
// (or returns the public URL when constructed with public:true); Vercel Blob
// (public), UploadThing (public-read), and Bunny Storage with publicBaseUrl
// return their CDN URLs. If you configured `publicBaseUrl` on the adapter, a
// plain url() returns that permanent link.
const url = await files.url("avatars/abc.png");
// An expiring link: signed even when `publicBaseUrl` is set; an `Unsupported`
// error on an adapter that only has permanent links.
const short = await files.url("avatars/abc.png", { expiresIn: 60 });

// Force download (defeat stored XSS from user-uploaded HTML/SVG).
// Forces signing even if `publicBaseUrl` is configured - a permanent
// CDN URL has no signature to bind the override into, and silently
// dropping a security ask would be a regression.
const safe = await files.url("avatars/abc.png", {
  responseContentDisposition: "attachment",
});
```

## Options

<AutoTypeTable path="../../packages/files-sdk/src/index.ts" name="UrlOptions" />
