Svelte • Template syntax

await

On this page

As of Svelte 5.36, you can use the await keyword inside your components in three places where it was previously unavailable:

  • at the top level of your component’s <script>
  • inside $derived(...) declarations
  • inside your markup

This feature is currently experimental, and you must opt in by adding the experimental.async option wherever you configure Svelte, usually svelte.config.js:

export default {
	
compilerOptions: {
    experimental: {
        async: boolean;
    };
}
compilerOptions: {
    experimental: {
        async: boolean;
    };
}
compilerOptions
: {
experimental: {
    async: boolean;
}
experimental: {
    async: boolean;
}
experimental
: {
async: booleanasync: booleanasync: true } } };

The experimental flag will be removed in Svelte 6.

Synchronized updates

When an await expression depends on a particular piece of state, changes to that state will not be reflected in the UI until the asynchronous work has completed, so that the UI is not left in an inconsistent state. In other words, in an example like this…

<script lang="ts">
	let a = $state(1);
	let b = $state(2);

	async function add(a, b) {
		await new Promise((f) => setTimeout(f, 500)); // artificial delay
		return a + b;
	}
</script>

<input type="number" bind:value={a}>
<input type="number" bind:value={b}>

<p>{a} + {b} = {await add(a, b)}</p>
<script>
	let a = $state(1);
	let b = $state(2);

	async function add(a, b) {
		await new Promise((f) => setTimeout(f, 500)); // artificial delay
		return a + b;
	}
</script>

<input type="number" bind:value={a}>
<input type="number" bind:value={b}>

<p>{a} + {b} = {await add(a, b)}</p>

…if you increment a, the contents of the <p> will not immediately update to read this —

<p>2 + 2 = 3</p>

— instead, the text will update to 2 + 2 = 4 when add(a, b) resolves.

Updates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing.

Concurrency

Svelte will do as much asynchronous work as it can in parallel. For example if you have two await expressions in your markup…

<p>{await one(x)}</p>
<p>{await two(y)}</p>

…both functions will run at the same time, as they are independent expressions, even though they are visually sequential.

This does not apply to sequential await expressions inside your <script> or inside async functions — these run like any other asynchronous JavaScript. An exception is that independent $derived expressions will update independently, even though they will run sequentially when they are first created:

async function function one(x: number): Promise<number>function one(x: number): Promise<number>one(x: numberx: numberx: number) { return x: numberx: numberx; }
async function function two(y: number): Promise<number>function two(y: number): Promise<number>two(y: numbery: numbery: number) { return y: numbery: numbery; }
let let x: numberlet x: numberx = 
function $state<1>(initial: 1): 1 (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
function $state<1>(initial: 1): 1 (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
$state
(1);
let let y: numberlet y: numbery =
function $state<2>(initial: 2): 2 (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
function $state<2>(initial: 2): 2 (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
$state
(2);
// cut // `b` will not be created until `a` has resolved, // but once created they will update independently // even if `x` and `y` update simultaneously let let a: numberlet a: numbera =
function $derived<number>(expression: number): number
namespace $derived

Declares derived state, i.e. one that depends on other state variables. The expression inside $derived(...) should be free of side-effects.

Example:

let double = $derived(count * 2);
@see{@link https://svelte.dev/docs/svelte/$derived Documentation}@paramexpression The derived state expression
function $derived<number>(expression: number): number
namespace $derived

Declares derived state, i.e. one that depends on other state variables. The expression inside $derived(...) should be free of side-effects.

Example:

let double = $derived(count * 2);
@see{@link https://svelte.dev/docs/svelte/$derived Documentation}@paramexpression The derived state expression
$derived
(await function one(x: number): Promise<number>function one(x: number): Promise<number>one(let x: numberlet x: numberx));
let let b: numberlet b: numberb =
function $derived<number>(expression: number): number
namespace $derived

Declares derived state, i.e. one that depends on other state variables. The expression inside $derived(...) should be free of side-effects.

Example:

let double = $derived(count * 2);
@see{@link https://svelte.dev/docs/svelte/$derived Documentation}@paramexpression The derived state expression
function $derived<number>(expression: number): number
namespace $derived

Declares derived state, i.e. one that depends on other state variables. The expression inside $derived(...) should be free of side-effects.

Example:

let double = $derived(count * 2);
@see{@link https://svelte.dev/docs/svelte/$derived Documentation}@paramexpression The derived state expression
$derived
(await function two(y: number): Promise<number>function two(y: number): Promise<number>two(let y: numberlet y: numbery));
/** @param {number} x */
async function function one(x: number): Promise<number>
@paramx
function one(x: number): Promise<number>
@paramx
one
(x: number
@paramx
x: number
@paramx
x
) { return x: number
@paramx
x: number
@paramx
x
; }
/** @param {number} y */ async function function two(y: number): Promise<number>
@paramy
function two(y: number): Promise<number>
@paramy
two
(y: number
@paramy
y: number
@paramy
y
) { return y: number
@paramy
y: number
@paramy
y
; }
let let x: numberlet x: numberx =
function $state<1>(initial: 1): 1 (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
function $state<1>(initial: 1): 1 (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
$state
(1);
let let y: numberlet y: numbery =
function $state<2>(initial: 2): 2 (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
function $state<2>(initial: 2): 2 (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
$state
(2);
// cut // `b` will not be created until `a` has resolved, // but once created they will update independently // even if `x` and `y` update simultaneously let let a: numberlet a: numbera =
function $derived<number>(expression: number): number
namespace $derived

Declares derived state, i.e. one that depends on other state variables. The expression inside $derived(...) should be free of side-effects.

Example:

let double = $derived(count * 2);
@see{@link https://svelte.dev/docs/svelte/$derived Documentation}@paramexpression The derived state expression
function $derived<number>(expression: number): number
namespace $derived

Declares derived state, i.e. one that depends on other state variables. The expression inside $derived(...) should be free of side-effects.

Example:

let double = $derived(count * 2);
@see{@link https://svelte.dev/docs/svelte/$derived Documentation}@paramexpression The derived state expression
$derived
(await function one(x: number): Promise<number>
@paramx
function one(x: number): Promise<number>
@paramx
one
(let x: numberlet x: numberx));
let let b: numberlet b: numberb =
function $derived<number>(expression: number): number
namespace $derived

Declares derived state, i.e. one that depends on other state variables. The expression inside $derived(...) should be free of side-effects.

Example:

let double = $derived(count * 2);
@see{@link https://svelte.dev/docs/svelte/$derived Documentation}@paramexpression The derived state expression
function $derived<number>(expression: number): number
namespace $derived

Declares derived state, i.e. one that depends on other state variables. The expression inside $derived(...) should be free of side-effects.

Example:

let double = $derived(count * 2);
@see{@link https://svelte.dev/docs/svelte/$derived Documentation}@paramexpression The derived state expression
$derived
(await function two(y: number): Promise<number>
@paramy
function two(y: number): Promise<number>
@paramy
two
(let y: numberlet y: numbery));

If you write code like this, expect Svelte to give you an await_waterfall warning

Indicating loading states

To render placeholder UI, you can wrap content in a <svelte:boundary> with a pending snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.

After the contents of a boundary have resolved for the first time and have replaced the pending snippet, you can detect subsequent async work with $effect.pending(). This is what you would use to display a “we’re asynchronously validating your input” spinner next to a form field, for example.

You can also use settled() to get a promise that resolves when the current update is complete:

let let color: stringlet color: stringcolor = 'red';
let let answer: numberlet answer: numberanswer = -1;
let let updating: booleanlet updating: booleanupdating = false;
// cut
import { function tick(): Promise<void>

Returns a promise that resolves once any pending state changes have been applied.

function tick(): Promise<void>

Returns a promise that resolves once any pending state changes have been applied.

tick
, function settled(): Promise<void>

Returns a promise that resolves once any state changes, and asynchronous work resulting from them, have resolved and the DOM has been updated

@since5.36
function settled(): Promise<void>

Returns a promise that resolves once any state changes, and asynchronous work resulting from them, have resolved and the DOM has been updated

@since5.36
settled
} from 'svelte';
async function function onclick(): Promise<void>function onclick(): Promise<void>onclick() { let updating: booleanlet updating: booleanupdating = true; // without this, the change to `updating` will be // grouped with the other changes, meaning it // won't be reflected in the UI await function tick(): Promise<void>

Returns a promise that resolves once any pending state changes have been applied.

function tick(): Promise<void>

Returns a promise that resolves once any pending state changes have been applied.

tick
();
let color: stringlet color: stringcolor = 'octarine'; let answer: numberlet answer: numberanswer = 42; await function settled(): Promise<void>

Returns a promise that resolves once any state changes, and asynchronous work resulting from them, have resolved and the DOM has been updated

@since5.36
function settled(): Promise<void>

Returns a promise that resolves once any state changes, and asynchronous work resulting from them, have resolved and the DOM has been updated

@since5.36
settled
();
// any updates affected by `color` or `answer` // have now been applied let updating: booleanlet updating: booleanupdating = false; }

Error handling

Errors in await expressions will bubble to the nearest error boundary.

Server-side rendering

Svelte supports asynchronous server-side rendering (SSR) with the render(...) API. To use it, simply await the return value:

import { 
function render<Comp extends SvelteComponent<any> | Component<any>, Props extends ComponentProps<Comp> = ComponentProps<Comp>>(...args: {} extends Props ? [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options?: {
    props?: Omit<Props, "$$slots" | "$$events">;
    context?: Map<any, any>;
    idPrefix?: string;
    csp?: Csp;
    transformError?: (error: unknown) => unknown | Promise<unknown>;
}] : [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options: {
    props: Omit<Props, "$$slots" | "$$events">;
    context?: Map<any, any>;
    idPrefix?: string;
    csp?: Csp;
    transformError?: (error: unknown) => unknown | Promise<unknown>;
}]): RenderOutput

Only available on the server and when compiling with the server option. Takes a component and returns an object with body and head properties on it, which you can use to populate the HTML when server-rendering your app.

function render<Comp extends SvelteComponent<any> | Component<any>, Props extends ComponentProps<Comp> = ComponentProps<Comp>>(...args: {} extends Props ? [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options?: {
    props?: Omit<Props, "$$slots" | "$$events">;
    context?: Map<any, any>;
    idPrefix?: string;
    csp?: Csp;
    transformError?: (error: unknown) => unknown | Promise<unknown>;
}] : [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options: {
    props: Omit<Props, "$$slots" | "$$events">;
    context?: Map<any, any>;
    idPrefix?: string;
    csp?: Csp;
    transformError?: (error: unknown) => unknown | Promise<unknown>;
}]): RenderOutput

Only available on the server and when compiling with the server option. Takes a component and returns an object with body and head properties on it, which you can use to populate the HTML when server-rendering your app.

render
} from 'svelte/server';
import
type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App
from './App.svelte';
const { const head: string

HTML that goes into the <head>

const head: string

HTML that goes into the <head>

head
, const body: string

HTML that goes somewhere into the <body>

const body: string

HTML that goes somewhere into the <body>

body
} = await
render<SvelteComponent<Record<string, any>, any, any>, Record<string, any>>(component: ComponentType<SvelteComponent<Record<string, any>, any, any>>, options?: {
    props?: Omit<Record<string, any>, "$$slots" | "$$events"> | undefined;
    context?: Map<any, any>;
    idPrefix?: string;
    csp?: Csp;
    transformError?: ((error: unknown) => unknown | Promise<unknown>) | undefined;
} | undefined): RenderOutput

Only available on the server and when compiling with the server option. Takes a component and returns an object with body and head properties on it, which you can use to populate the HTML when server-rendering your app.

render<SvelteComponent<Record<string, any>, any, any>, Record<string, any>>(component: ComponentType<SvelteComponent<Record<string, any>, any, any>>, options?: {
    props?: Omit<Record<string, any>, "$$slots" | "$$events"> | undefined;
    context?: Map<any, any>;
    idPrefix?: string;
    csp?: Csp;
    transformError?: ((error: unknown) => unknown | Promise<unknown>) | undefined;
} | undefined): RenderOutput

Only available on the server and when compiling with the server option. Takes a component and returns an object with body and head properties on it, which you can use to populate the HTML when server-rendering your app.

render
(const App: LegacyComponentTypeconst App: LegacyComponentTypeApp);

If you’re using a framework like SvelteKit, this is done on your behalf.

If a <svelte:boundary> with a pending snippet is encountered during SSR, that snippet will be rendered while the rest of the content is ignored. All await expressions encountered outside boundaries with pending snippets will resolve and render their contents prior to await render(...) returning.

In the future, we plan to add a streaming implementation that renders the content in the background.

Forking

The fork(...) API, added in 5.42, makes it possible to run await expressions that you expect to happen in the near future. This is mainly intended for frameworks like SvelteKit to implement preloading when (for example) users signal an intent to navigate.

<script lang="ts">
	import { function fork(fn: () => void): Fork

Creates a 'fork', in which state changes are evaluated but not applied to the DOM. This is useful for speculatively loading data (for example) when you suspect that the user is about to take some action.

Frameworks like SvelteKit can use this to preload data when the user touches or hovers over a link, making any subsequent navigation feel instantaneous.

The fn parameter is a synchronous function that modifies some state. The state changes will be reverted after the fork is initialised, then reapplied if and when the fork is eventually committed.

When it becomes clear that a fork will not be committed (e.g. because the user navigated elsewhere), it must be discarded to avoid leaking memory.

@since5.42
fork
} from 'svelte';
import
type Menu = SvelteComponent<Record<string, any>, any, any>
const Menu: LegacyComponentType
Menu
from './Menu.svelte';
import type { Fork } from 'svelte'; let let open: booleanopen =
function $state<false>(initial: false): false (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
$
function $state<false>(initial: false): false (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);
@see{@link https://svelte.dev/docs/svelte/$state Documentation}@paraminitial The initial value
state
(false);
let let pending: Fork | nullpending: Fork | null = null; function function (local function) preload(): voidpreload() { let pending: Fork | nullpending ??= function fork(fn: () => void): Fork

Creates a 'fork', in which state changes are evaluated but not applied to the DOM. This is useful for speculatively loading data (for example) when you suspect that the user is about to take some action.

Frameworks like SvelteKit can use this to preload data when the user touches or hovers over a link, making any subsequent navigation feel instantaneous.

The fn parameter is a synchronous function that modifies some state. The state changes will be reverted after the fork is initialised, then reapplied if and when the fork is eventually committed.

When it becomes clear that a fork will not be committed (e.g. because the user navigated elsewhere), it must be discarded to avoid leaking memory.

@since5.42
fork
(() => {
let open: booleanopen = true; }); } function function (local function) discard(): voiddiscard() { let pending: Fork | nullpending?.Fork.discard(): void

Discard the fork

discard
();
let pending: Fork | nullpending = null; } </script> <button onfocusin={function (local function) preload(): voidpreload} onfocusout={function (local function) discard(): voiddiscard} onpointerenter={function (local function) preload(): voidpreload} onpointerleave={function (local function) discard(): voiddiscard} onclick={() => { let pending: Fork | nullpending?.Fork.commit(): Promise<void>

Commit the fork. The promise will resolve once the state change has been applied

commit
();
let pending: Fork | nullpending = null; // in case `pending` didn't exist // (if it did, this is a no-op) let open: booleanopen = true; }} >open menu</button> {#if let open: booleanopen} <!-- any async work inside this component will start as soon as the fork is created --> <const Menu: LegacyComponentTypeMenu onclose={() => let open: booleanopen = false} /> {/if}
<script>
	import { fork } from 'svelte';
	import Menu from './Menu.svelte';

	let open = $state(false);

	/** @type {import('svelte').Fork | null} */
	let pending = null;

	function preload() {
		pending ??= fork(() => {
			open = true;
		});
	}

	function discard() {
		pending?.discard();
		pending = null;
	}
</script>

<button
	onfocusin={preload}
	onfocusout={discard}
	onpointerenter={preload}
	onpointerleave={discard}
	onclick={() => {
		pending?.commit();
		pending = null;

		// in case `pending` didn't exist
		// (if it did, this is a no-op)
		open = true;
	}}
>open menu</button>

{#if open}
	<!-- any async work inside this component will start
	     as soon as the fork is created -->
	<Menu onclose={() => open = false} />
{/if}

Caveats

As an experimental feature, the details of how await is handled (and related APIs like $effect.pending()) are subject to breaking changes outside of a semver major release, though we intend to keep such changes to a bare minimum.

Breaking changes

Effects run in a slightly different order when the experimental.async option is true. Specifically, block effects like {#if ...} and {#each ...} now run before an $effect.pre or beforeUpdate in the same component, which means that in very rare situations it is possible to update a block that should no longer exist, but only if you update state inside an effect, which you should avoid.