Netlify provides a comprehensive platform for web development with serverless functions (Node.js), edge functions (Deno), and integrated services like Blobs for storage, Image CDN for optimization, and database support through Neon
.netlify
folder is not for user code. Add it to .gitignore
.@netlify/functions
, not @netlify/functions@VERSION
).Access-Control-Allow-Origin
) unless explicitly requested by the user.netlify dev
to start the dev server unless the user requests a different command.@netlify/vite-plugin
is installed for Vite-powered projects (or @netlify/nuxt
for Nuxt), configured, and you're running the framework's dev command directly (e.g., npm run dev
). This enables full local platform primitives emulation.npm install @netlify/functions
Netlify
object that is also accessible.
Netlify.env.*
for interacting with environment variables in code.YOUR_BASE_DIRECTORY/netlify/functions
or a subdirectory.
netlify.toml
:
[functions]
directory = "my_functions"
netlify.toml
settings override UI settings.index.mts
or match the subdirectory name.
netlify/functions/hello.mts
netlify/functions/hello/index.mts
netlify/functions/hello/hello.mts
.mts
enables modern ES module syntax import type { Context, Config } from "@netlify/functions";
export default async (req: Request, context: Context) => {
// user code
return new Response("Hello, world!")
}
export const config: Config = {
// use this path instead of /.netlify/functions/{fnName}
path: "/hello-world"
};
export default async (req, context) => {
// user code
return new Response("Hello, world!")
}
export const config = {
// use this path instead of /.netlify/functions/{fnName}
path: "/hello-world"
};
config
object. This is the structure the config can have:/.netlify/functions/{function_name}
path by default.{
path: string | string[], // Defines the URL path(s) that trigger the function. Can be a single string or an array of paths.
excludedPath?: string | string[], // Optional. Defines paths that should be excluded from triggering the function.
preferStatic?: boolean, // Optional. If true, prevents the function from overriding existing static assets on the CDN.
}
import { Context } from "@netlify/functions";
export default async (req: Request, context: Context) => {
await someLongRunningTask();
console.log("Done");
};
export default async (req, context) => {
await someLongRunningTask();
console.log("Done");
};
next_run
property. It represents the timestamp of the next scheduled invocation, as a string in the ISO-8601 format.netlify.toml
. ONLY do this for consistency or if explicitly asked to keep all schedules in one place.
[functions."test-scheduled-function"]
schedule = "@hourly"
netlify functions:invoke
command to trigger the scheduled function.
example:
netlify functions:invoke myfunction
import type { Config } from "@netlify/functions"
export default async (req: Request) => {
const { next_run } = await req.json()
console.log("Received event! Next invocation at:", next_run)
}
export const config: Config = {
schedule: "@hourly"
}
export default async (req) => {
const { next_run } = await req.json()
console.log("Received event! Next invocation at:", next_run)
}
export const config = {
schedule: "@hourly"
}
ALWAYS use the latest format of an edge function structure.
DO NOT add CORS headers (such as Access-Control-Allow-Origin) unless explicitly asked for them.
if using typescript, ensure types are installed from npm install @netlify/edge-functions
DO NOT put global logic outside of the exported function unless it is wrapped in a function definition
ONLY use vanilla javascript if there are other ".js" files in the functions directory.
ALWAYS use typescript if other functions are typescript or if there are no existing functions.
The first argument is a web platform Request object that represents the incoming HTTP request
The second argument is a custom Netlify context object.
Edge functions have a global Netlify
object that is also accessible.
Netlify.env.*
for interacting with environment variables in code.Place function files in YOUR_BASE_DIRECTORY/netlify/edge-functions
or a subdirectory.
netlify.toml
:
[build]
edge_functions = "my-custom-directory"
Edge functions use Deno as runtime and should attempt to use built-in methods where possible. See the list of available web APIs to know which built-ins to use.
node:
prefix (e.g., import { randomBytes } from "node:crypto"
).import React from "https://esm.sh/react"
or an import map).npm install
and import by package name (e.g., import _ from "lodash"
).deno.json
).import_map.json
):
{
"imports": {
"html-rewriter": "https://ghuc.cc/worker-tools/html-rewriter/index.ts"
}
}
netlify.toml
:
[functions]
deno_import_map = "./path/to/your/import_map.json"
import { HTMLRewriter } from "html-rewriter";
import type { Context, Config } from "@netlify/edge-functions";
export default async (req: Request, context: Context) => {
// user code
return new Response("Hello, world!")
}
export const config: Config = {
path: "/hello-world"
};
export default async (req, context) => {
// user code
return new Response("Hello, world!")
}
export const config = {
path: "/hello-world"
};
{
...ALL OTHER Context fields/methods,
next: (options?: { sendConditionalRequest?: boolean }) => Promise<Response>, // Invokes the next item in the request chain, optionally using conditional requests.
nextRequest: (request: Request, options?: { sendConditionalRequest?: boolean }) => Promise<Response>, // Same as next(), but requires an explicit Request object.
}
config
object. This is the structure the config can have:{
path?: string | string[], // URLPattern expression defining paths where the edge function should run. Must start with '/'.
excludedPath?: string | string[], // Optional. Defines paths to exclude from execution. Must start with '/'.
pattern?: RegExp | RegExp[], // Alternative to `path`. Uses regex for path matching.
excludedPattern?: RegExp | RegExp[], // Optional. Defines regex patterns to exclude certain routes.
method?: string | string[], // Optional. Specifies HTTP methods that should trigger the function (e.g., "GET", ["POST", "PUT"]).
onError?: "continue" | "fail" | "fallback", // Optional. Controls how the function handles errors.
cache?: 'manual', // Optional. Enables response caching if set to 'manual'.
} = {
path: "", // Default value; should be set per function.
};
ONLY Use netlify.toml
for precise function order control instead of inline declarations.
DO NOT use netlify.toml
if there is not edge function ordering requirements.
When controlling order, it's important to include all edge functions for order control.
Declare Edge Functions in netlify.toml
:
Edge Function Properties:
function
: Name of the edge function.path
: URL pattern to trigger the function (must start with /
).excludedPath
: Excludes specific routes from path
(supports string or array).pattern
: Regex-based path matching.excludedPattern
: Excludes specific regex patterns (single or array).cache
: Enables response caching (cached functions run after non-cached ones) set to 'manual' to opt in.Netlify.toml config examples
[[edge_functions]]
path = "/admin"
function = "auth"
[[edge_functions]]
path = "/admin"
function = "injector"
cache = "manual"
[[edge_functions]]
path = "/blog/*"
function = "auth"
[[edge_functions]]
path = "/blog/*"
function = "rewriter"
[[edge_functions]]
pattern = "/products/(.*)"
excludedPattern = "/products/things/(.*)"
function = "highlight"
[[edge_functions]]
path = "/*"
excludedPath = "/img/*"
function = "common"
- **Execution Order for Edge Functions**:
1. **Configuration-based** edge functions (`netlify.toml`) run first.
2. **Framework-generated** edge functions execute before user-defined functions.
3. **Non-cached** edge functions execute before cached functions.
4. **Inline-declared** edge functions override duplicate `netlify.toml` functions.
5. **Multiple inline edge functions** run alphabetically by filename.
- **Caveats & Special Cases**:
- If an edge function returns a response, redirects for that path DO NOT occur.
- Edge functions DO NOT execute for rewritten static routing targets.
- `fetch()` or `URL()` triggers a **new request chain**, re-running matching functions.
- Use `context.next()` to continue processing instead of re-triggering functions.
- Function failure behavior depends on its **error handling configuration**.
#### Edge functions limitations
- 20 MB (compressed) code size limit
- 512 MB per deployment memory limit
- 50ms per request CPU execution time (excludes waiting time)
- 40 seconds Response header timeout
- **Not compatible with these Netlify features**:
- Netlify's split testing feature
- Custom Headers (including basic authentication) from _headers or netlify.toml config
- Netlify prerendering feature on paths served by edge functions
- Be aware that multiple framework adapters may generate conflicting edge functions
- **Restrictions**:
- Can only rewrite requests to same-site URLs (use `fetch()` for external content)
- Cached edge functions override existing static files
- No local caching; HTTP cache headers are ignored in local testing
- Not included in Netlify’s HIPAA-compliant hosting offering
## Netlify Blobs
- Prefer using Netlify Blobs over creating a new database unless a flat file is not appropriate data layer, if explicitly asking to use a tool, or the dev needs to created relational structures or search that's more appropriate for databases
- Blobs do not require configuration to use, everything is automatically provisions, and devs can simply start using them via any Netlify compute. The API will be the same across all compute types.
- ensure `@netlify/blobs` NPM module is installed
- **Local development troubleshooting**: If you see "The environment has not been configured to use Netlify Blobs. To use it manually, supply the following properties when creating a store: siteID, token" error, install `@netlify/vite-plugin` for Vite-based projects to automatically configure the local environment. This does NOT apply to legacy V1 functions which require manual siteID/token configuration.
- Requirements and limits
- Requires Fetch API support (Node.js 18+ recommended) - a fetch function can be provided to the store
- Store names cannot exceed 64 bytes
- Object keys cannot exceed 600 bytes
- Maximum object size: 5GB
- Local development uses a sandboxed store
### Netlify Blobs API
```typescript
export interface BlobMetadata {
[key: string]: any;
}
export interface BlobData<T = string> {
data: T | null;
etag: string;
metadata: BlobMetadata;
}
export interface ListResult {
blobs: { etag: string; key: string }[];
directories?: string[];
}
interface GetKeyOptions {
type?: 'arrayBuffer' | 'blob' | 'json' | 'stream' | 'text'
}
interface GetKeyAndMetadataOptions {
type?: 'arrayBuffer' | 'blob' | 'json' | 'stream' | 'text',
etag?: string;
}
// THESE ARE THE ONLY STORE METHODS. DO NOT MAKE UP NEW ONES
interface Store {
// Creates or overwrites a blob entry.
// example: await store.set('key-name', 'contents-of key');
// - NEVER add metadata unless instructed to.
set(key: string, value: ArrayBuffer | Blob | string, { metadata?: object }): Promise<void>;
// Stores a JSON-serializable object.
// example: await store.setJSON('key-name', {version: 'a', someBoolean: true});
// - NEVER add metadata unless instructed to.
setJSON(key: string, value: any, { metadata?: object }): Promise<void>;
// Retrieves a stored blob.
// example: await store.get('key-name');
// - NEVER add the second arg unless you need an explicit type 'arrayBuffer' | 'blob' | 'json' | 'stream' | 'text'.
// - Instead of using JSON.parse(blob), use store.get('key-name', {type: 'json'})
// - if the blob is missing, it will resolve the promise with a null value
get(key: string, getOpt?: GetKeyOptions): Promise<any | null>;
// Retrieves a blob along with metadata
// example: await store.getWithMetadata('key-name');
// - NEVER add the second getOpts arg unless you need an explicit type or have an etag to check against.
// - AVOID adding it unless it's reliably available but IF an etag is provided, it will only return the blob if the etag is different that what's stored.
// - if the blob is missing, it will resolve the promise with a null value
getWithMetadata(key: string, getOpts?: GetKeyAndMetadataOptions): Promise<{ data: any, etag: string, metadata: object } | null>;
// Retrieves metadata of a blob WITHOUT downloading the data.
// example: await store.getMetadata('key-name');
// - NEVER add the second getOpts arg unless you need an explicit type or have an etag to check against.
// - AVOID adding it unless it's reliably available but IF an etag is provided, it will only return the blob if the etag is different that what's stored.
// - if the blob is missing, it will resolve the promise with a null value
getMetadata(key: string, getOpts?: GetKeyAndMetadataOptions): Promise<{ etag: string, metadata: object } | null>;
// Lists blobs in the store with optional hierarchical browsing.
// example:
// const { blobs } = await store.list()
// // blobs === [ { etag: 'etag1', key: 'some-key' }, { etag: 'etag2', key: 'another-key' } ]
//
// - NEVER add the options arg unless you need an explicit reduce the searched data.
// -- ONLY if you have to reduce searched data, use `prefix: 'some-prefix'` to pull blobs that start with that prefix value. Use `directories: true` to include the full directory path on the `key`
// - By default, the list() method retrieves all pages, meaning you'll always get the full list of results. This can be slow or memory intensive. To paginate, pass the `paginate: true` in the options to turn the response into an AsyncIterator that allows you to for-of loop through the blobs in the store.
// - if store path is empty, the blobs will resolve the promise with an empty array
list(options?: { directories?: boolean, paginate?: boolean. prefix?: string }): Promise<{ blobs: BlobResult[], directories: string[] }> | AsyncIterable<{ blobs: BlobResult[], directories: string[] }>
// Deletes a blob.
// example: await store.delete('key-name');
// - The return value is always resolves to `undefined`, regardless of whether or not there was an object to delete.
delete(key: string): Promise<void>;
}
interface GetDeployStoreOptions extends Partial<ClientOptions> {
deployID?: string;
name?: string;
region?: Region;
}
// Returns a store instance for managing blobs. This is global scoped data across all deploys.
// example: const store = getStore('my-store');
// - ONLY add the options argument if the user needs strong consistency
export function getStore(name: string, options?: { consistency?: 'strong' | 'eventual' }): Store;
// Returns a deploy-specific store instance for managing blobs tied to a deploy.
// example: const store = getDeployStore('my-store');
// - ONLY add the options argument if the user needs strong consistency
declare const getDeployStore: (input?: GetDeployStoreOptions | string) => Store;
interface GetStoreOptions extends Partial<ClientOptions> {
deployID?: string;
name?: string;
}
// Lists all stores available on a project.
// example:
// const { stores } = await listStores();
// // [ "beauty", "construction" ]
// - By default, the listStores() method retrieves all pages, meaning you'll always get the full list of results. This can be slow or memory intensive. To paginate, pass the `paginate: true` in the options to turn the response into an AsyncIterator that allows you to for-of loop through the blobs in the store.
// - DO NOT pass options unless paginating.
declare function listStores(options?: {
paginate?: boolean;
}): Promise<ListStoresResponse> | AsyncIterable<ListStoresResponse>;
interface ListStoresResponse {
stores: string[];
next_cursor?: string;
}
With file-based uploads, write blobs to deploy-specific stores after the project’s build completes. Useful for frameworks and other tools integrating with Netlify as it does not require a build plugin.
Put files in .netlify/blobs/deploy/*
for deploy specific
.netlify/
├─ blobs/
| ├─ deploy/
│ | ├─ beauty/
│ │ | └─ nails.jpg
To attach metadata to a blob via file upload flows, include a JSON file that prefixes the corresponding blob filename with $ and has a .json extension. For example:
├─ blobs/
| ├─ deploy/
│ | ├─ beauty/
│ │ | ├─ nails.jpg
│ │ | └─ $nails.jpg.json
consistency
field to 'strong'
on the store instantiation.Example:
const store = getStore({ name: "animals", consistency: "strong" });
await store.set("dog", "🐶");
const dog = await store.get("dog");
getDeployStore()
is used to interact with deploy specific stores.getStore()
is used for global scope. // basic writing to a deploy store
import { getDeployStore } from "@netlify/blobs";
const store = getDeployStore("construction");
// basic writing to a global store
import { getStore } from "@netlify/blobs";
const store = getStore("construction");
// using global store if in production, otherwise use deploy scope store
import { getStore, getDeployStore } from "@netlify/blobs";
function getBlobStore(...storeOptions){
if((Netlify.context?.deploy.context === 'production'){
return getStore(...storeOptions);
}
return getDeployStore(...storeOptions)
}
const store = getBlobStore("construction");
/.netlify/images
route supported by their project without any additional enablement./.netlify/images
.w
(width) and h
(height) in pixels.contain
, cover
, fill
).top
, bottom
, left
, right
, center
).avif
, jpg
, png
, webp
, gif
, or blurhash
.q
, 1-100, default 75). <!-- get an image hosted on this project and change its size and format -->
<img src="/.netlify/images?url=/image.jpg&w=100&h=100&fit=cover&fm=webp&q=80" />
<!-- get an image hosted externally and change its size and format -->
<img src="/.netlify/images?url=https://example.com/path/to/image&w=40&h=10&fm=jpg&q=80" />
netlify.toml
.
[images]
remote_images = ["https://externalexample.com/.*"]
/.netlify/images
path, a redirect or rewrite can be used to have a different url._redirects
or netlify.toml
files. [[redirects]]
from = "/transform-my-images/*"
to = "/.netlify/images?url=/:splat&w=50&h=50"
status = 200
/transform-all/* /.netlify/images?url=/:splat&w=50&h=50 200
[[headers]]
for = "/source-images/*"
[headers.values]
Cache-Control = "public, max-age=604800, must-revalidate"
/source-images/* Cache-Control: public, max-age=604800, must-revalidate
Netlify Image CDN integrates with frameworks for automatic optimizations:
NgOptimizedImage
component will use Image CDN automatically<Image />
component will use Image CDN automaticallyNETLIFY_IMAGE_CDN=true
and use the Contentful, Drupal, or WordPress source plugins.remotePatterns
in next.config.js
nuxt/image
module will use Image CDN automaticallynetlify.toml
overrides UI/CLI/API variables, and project-specific variables take precedence over shared ones.Variables can be created and managed using:
netlify.toml
): Defines variables at the repository level. ONLY use this for environment variables where the project is not linked yet and the values are not sensitive.env:set
for changes, env:unset
to delete. env:import
to import from a dotenv.env
file. netlify env:set API_KEY "not-a-secret"
netlify env:set API_KEY "secret-value" --secret
netlify.toml
Configuration # Production context: all deploys from the Production branch
# set in your project’s Branches settings in the Netlify UI will inherit
# these settings. You can define environment variables
# here but we recommend using the Netlify UI for sensitive
# values to keep them out of your source repository.
[context.production]
publish = "output/"
command = "make publish"
environment = { NODE_VERSION = "14.15.3" }
# Here is an example of how to define context-specific
# environment variables. Be mindful when using this
# option and avoid committing sensitive values to public
# source repositories.
[context.deploy-preview.environment]
NOT_PRIVATE_ITEM = "not so secret"
# Branch Deploy context: all deploys that are not from
# a pull/merge request or from the Production branch
# will inherit these settings.
[context.branch-deploy.environment]
NODE_ENV = "development"
# Dev context: environment variables set here
# are available for local development environments
# run using Netlify Dev. These values can be
# overwritten on branches that have a more specific
# branch context configured.
[context.dev.environment]
NODE_ENV = "development"
# Specific branch context: all deploys from
# this specific branch will inherit these settings.
[context.staging.environment] # “staging” is a branch name
NODE_ENV = "development"
.env
File Handling.env
files directly.env
variables into Netlify using the UI or CLI (netlify env:import .env
)..env
files via UI or CLI (env:list
)..env
Variables# list the production deploy context values in .env format
netlify env:list --plain --context production
# list the production deploy context values in .env format
# and pipe results into a .env file
netlify env:list --plain --context production > .env
If using Netlify Forms, prevent spam submissions with a honeypot field.
PROJECT_FOLDER/.netlify/state.json
file exists and it has a populated siteId
value. Note that the siteID appears as the project ID in the Netlify UI at app.netlify.com.netlify init
to allow the user to set up the site with Netlify. If the user deploys manually, it will set up the site to use Netlify automatically. If the user decides to set up a repo, they might have to set up the repo first. If the site is already set up on netlify then run netlify link
for the user to input the credentials to link.For projects using Vite or Vite-powered metaframeworks, strongly recommend installing @netlify/vite-plugin
:
npm install -D @netlify/vite-plugin
(or equivalent for whichever package manager the project uses)
Add to the Vite config plugins array. Framework-specific examples:
Standard Vite (vite.config.js/ts):
import { defineConfig } from "vite";
import netlify from "@netlify/vite-plugin";
export default defineConfig({
plugins: [netlify()]
});
Nuxt (nuxt.config.ts):
For Nuxt projects, use @netlify/nuxt
module instead:
npx nuxi module add @netlify/nuxt
This does exactly what @netlify/vite-plugin
does but specifically for Nuxt. It brings the complete Netlify platform directly into your nuxt dev
server: Functions, Edge Functions, Blobs, Image CDN, headers, redirects, environment variables.
Alternatively, you can configure manually by installing as a dev dependency and adding to the modules array:
npm install -D @netlify/nuxt
export default defineNuxtConfig({
modules: ["@netlify/nuxt"]
});
SvelteKit (vite.config.js):
import { sveltekit } from "@sveltejs/kit/vite";
import netlify from "@netlify/vite-plugin";
export default defineConfig({
plugins: [sveltekit(), netlify()]
});
Other metaframeworks: Look for vite
configuration key in the framework's config file (e.g., astro.config.js
, remix.config.js
).
With @netlify/vite-plugin
installed, users can run their regular dev command (npm run dev
, yarn dev
, astro dev
, etc.) instead of netlify dev
.
Vite-powered frameworks include: Astro, SvelteKit, Nuxt 3+ (use @netlify/nuxt
module instead), Remix 2.2.0+ (unless using Classic Compiler), SolidStart, TanStack Start, Analog, Qwik City, VitePress.
This does NOT apply to these frameworks (they do not use Vite): Next.js, Gatsby, Angular.
@netlify/neon
NPM module is required.@netlify/neon
, and run either netlify dev
or netlify build
.netlify env:get NETLIFY_DATABASE_URL
path
to the exported config
object in the Netlify Function, remember not to add the /.netlify/functions/
prefix to the URI in API calls when you make them in the app@netlify/neon
npm module@netlify/neon
is a wrapper around @neondatabase/serverless
so all its methods are available, but there is no need to pass a connection string to the neon() functionimport { neon } from "@netlify/neon";
const sql = neon();
// query
const users = await sql("SELECT * FROM users");
// insert
await sql("INSERT INTO users (name) VALUES ('John Doe')");
// update
await sql("UPDATE users SET name = 'John Doe' WHERE id = 1");
// delete
await sql("DELETE FROM users WHERE id = 1");