diff --git a/.changeset/ten-onions-talk.md b/.changeset/ten-onions-talk.md
new file mode 100644
index 000000000000..7351c4ac6086
--- /dev/null
+++ b/.changeset/ten-onions-talk.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': minor
+---
+
+feat: provide `PageProps` and `LayoutProps` types
diff --git a/documentation/docs/20-core-concepts/10-routing.md b/documentation/docs/20-core-concepts/10-routing.md
index 836b93221aab..3ebe9c9035ec 100644
--- a/documentation/docs/20-core-concepts/10-routing.md
+++ b/documentation/docs/20-core-concepts/10-routing.md
@@ -42,7 +42,7 @@ Pages can receive data from `load` functions via the `data` prop.
```svelte
@@ -51,7 +51,9 @@ Pages can receive data from `load` functions via the `data` prop.
```
> [!LEGACY]
-> In Svelte 4, you'd use `export let data` instead
+> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` property manually with `PageData` instead, see [$types](#\$types).
+>
+> In Svelte 4, you'd use `export let data` instead.
> [!NOTE] SvelteKit uses `` elements to navigate between routes, rather than a framework-specific `` component.
@@ -212,7 +214,7 @@ We can create a layout that only applies to pages below `/settings` (while inher
```svelte
@@ -227,6 +229,9 @@ We can create a layout that only applies to pages below `/settings` (while inher
{@render children()}
```
+> [!LEGACY]
+> `LayoutProps` was added in 2.16.0. In earlier versions, you had to [type the properties manually instead](#\$types).
+
You can see how `data` is populated by looking at the `+layout.js` example in the next section just below.
By default, each layout inherits the layout above it. Sometimes that isn't what you want - in this case, [advanced layouts](advanced-routing#Advanced-layouts) can help you.
@@ -255,7 +260,7 @@ Data returned from a layout's `load` function is also available to all its child
```svelte
```
+> [!NOTE]
+> The `PageProps` and `LayoutProps` types, added in 2.16.0, are a shortcut for typing the `data` prop as `PageData` or `LayoutData`, as well as other props, such as `form` for pages, or `children` for layouts. In earlier versions, you had to type these properties manually. For example, for a page:
+>
+> ```js
+> /// file: +page.svelte
+> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
+> let { data, form } = $props();
+> ```
+>
+> Or, for a layout:
+>
+> ```js
+> /// file: +layout.svelte
+> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */
+> let { data, children } = $props();
+> ```
+
In turn, annotating the `load` function with `PageLoad`, `PageServerLoad`, `LayoutLoad` or `LayoutServerLoad` (for `+page.js`, `+page.server.js`, `+layout.js` and `+layout.server.js` respectively) ensures that `params` and the return value are correctly typed.
If you're using VS Code or any IDE that supports the language server protocol and TypeScript plugins then you can omit these types _entirely_! Svelte's IDE tooling will insert the correct types for you, so you'll get type checking without writing them yourself. It also works with our command line tool `svelte-check`.
diff --git a/documentation/docs/20-core-concepts/20-load.md b/documentation/docs/20-core-concepts/20-load.md
index 269d9078dd14..933d38563875 100644
--- a/documentation/docs/20-core-concepts/20-load.md
+++ b/documentation/docs/20-core-concepts/20-load.md
@@ -24,7 +24,7 @@ export function load({ params }) {
```svelte
@@ -33,7 +33,14 @@ export function load({ params }) {
```
> [!LEGACY]
-> In Svelte 4, you'd use `export let data` instead
+> Before version 2.16.0, the props of a page and layout had to be typed individually:
+> ```js
+> /// file: +page.svelte
+> /** @type {{ data: import('./$types').PageData }} */
+> let { data } = $props();
+> ```
+>
+> In Svelte 4, you'd use `export let data` instead.
Thanks to the generated `$types` module, we get full type safety.
@@ -88,7 +95,7 @@ export async function load() {
```svelte
@@ -111,6 +118,14 @@ export async function load() {
```
+> [!LEGACY]
+> `LayoutProps` was added in 2.16.0. In earlier versions, properties had to be typed individually:
+> ```js
+> /// file: +layout.svelte
+> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */
+> let { data, children } = $props();
+> ```
+
Data returned from layout `load` functions is available to child `+layout.svelte` components and the `+page.svelte` component as well as the layout that it 'belongs' to.
```svelte
@@ -118,7 +133,7 @@ Data returned from layout `load` functions is available to child `+layout.svelte
@@ -511,7 +526,7 @@ This is useful for creating skeleton loading states, for example:
```svelte
@@ -652,7 +667,7 @@ export async function load({ fetch, depends }) {
@@ -152,7 +152,14 @@ export const actions = {
```
> [!LEGACY]
-> In Svelte 4, you'd use `export let data` and `export let form` instead to declare properties
+> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` and `form` properties individually:
+> ```js
+> /// file: +page.svelte
+> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
+> let { data, form } = $props();
+> ```
+>
+> In Svelte 4, you'd use `export let data` and `export let form` instead to declare properties.
### Validation errors
@@ -339,7 +346,7 @@ The easiest way to progressively enhance a form is to add the `use:enhance` acti
@@ -390,7 +397,7 @@ If you return a callback, you may need to reproduce part of the default `use:enh
@@ -427,7 +434,7 @@ We can also implement progressive enhancement ourselves, without `use:enhance`,
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
- /** @type {{ form: import('./$types').ActionData }} */
+ /** @type {import('./$types').PageProps} */
let { form } = $props();
/** @param {SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}} event */
diff --git a/documentation/docs/20-core-concepts/50-state-management.md b/documentation/docs/20-core-concepts/50-state-management.md
index 9c0ab1b1764a..0d56c8cc66e1 100644
--- a/documentation/docs/20-core-concepts/50-state-management.md
+++ b/documentation/docs/20-core-concepts/50-state-management.md
@@ -89,7 +89,7 @@ You might wonder how we're able to use `page.data` and other [app state]($app-st
diff --git a/documentation/docs/98-reference/54-types.md b/documentation/docs/98-reference/54-types.md
index 2b24ed69034c..eb76c2fa7ea3 100644
--- a/documentation/docs/98-reference/54-types.md
+++ b/documentation/docs/98-reference/54-types.md
@@ -84,6 +84,37 @@ export async function load({ params, fetch }) {
}
```
+The return types of the load functions are then available through the `$types` module as `PageData` and `LayoutData` respectively, while the union of the return values of all `Actions` is available as `ActionData`. Starting with version 2.16.0, two additional helper types are provided. `PageProps` defines `data: PageData`, as well as `form: ActionData`, when there are actions defined. `LayoutProps` defines `data: LayoutData`, as well as `children: Snippet`:
+
+```svelte
+
+
+```
+
+> [!LEGACY]
+> Before 2.16.0:
+> ```svelte
+>
+>
+> ```
+>
+> Using Svelte 4:
+> ```svelte
+>
+>
+> ```
+
> [!NOTE] For this to work, your own `tsconfig.json` or `jsconfig.json` should extend from the generated `.svelte-kit/tsconfig.json` (where `.svelte-kit` is your [`outDir`](configuration#outDir)):
>
> `{ "extends": "./.svelte-kit/tsconfig.json" }`
diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js
index e57b00183e6f..308d566606f8 100644
--- a/packages/kit/src/core/sync/write_types/index.js
+++ b/packages/kit/src/core/sync/write_types/index.js
@@ -270,6 +270,12 @@ function update_types(config, routes, route, to_delete = new Set()) {
'export type Actions | void = Record | void> = Kit.Actions'
);
}
+
+ if (route.leaf.server) {
+ exports.push('export type PageProps = { data: PageData; form: ActionData }');
+ } else {
+ exports.push('export type PageProps = { data: PageData }');
+ }
}
if (route.layout) {
@@ -333,6 +339,10 @@ function update_types(config, routes, route, to_delete = new Set()) {
if (proxies.server?.modified) to_delete.delete(proxies.server.file_name);
if (proxies.universal?.modified) to_delete.delete(proxies.universal.file_name);
+
+ exports.push(
+ 'export type LayoutProps = { data: LayoutData; children: import("svelte").Snippet }'
+ );
}
if (route.endpoint) {