Query

Load data from the server and subscribe to any changes of fields we detect from mutations, subscriptions, and other queries.

src/routes/myRoute/+page.gql
query MyProfileInfo {
    viewer {
        firstName
        avatar
    }
}
src/routes/myRoute/+page.svelte
<script lang="ts">
    import type { PageData } from './$houdini'
    export let data: PageData

    // pull the store reference from the route props
    $: ({ MyProfileInfo } = data)
</script>

{$MyProfileInfo.data.viewer.firstName}
src/routes/myRoute/+page.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    // pull the store reference from the route props
    $: ({ MyProfileInfo } = data)
</script>

{$MyProfileInfo.data.viewer.firstName}

This example takes advantage of Houdini’s powerful load generator and is just one of many ways to get access to a store that can drive your server-side rendered routes. For more information, check out the Working with GraphQL guide.

Store Fields

A query store holds an object with the following fields that accessed like $store.data:

  • data contains the result of the query. It’s value will update as mutations, subscriptions, and other queries provide more recent information.
  • loading contains the loading state (true or false) for a query found outside of a route component (ie, not defined in src/routes)
  • errors contains any error values that occur for a query found outside of a route component (ie, not defined in src/routes). If you want to use this for managing your errors, you should enable the quietQueryError configuration option.
  • partial contains a boolean that indicates if the result has a partial match

Methods

A query store has the following methods that invoked without the $, ie store.fetch(...):

  • fetch is a function that can be used to load the most recent data from your API (subject to its Cache Policy). If you want to force the request to always resolve against the API, set policy: 'NetworkOnly'.

Automatic Loading

As described in the Working with GraphQL guide, there are 3 things that you can do to get houdini to create a load function for your route:

  • export _houdini_load from your +page.js file
  • an inline query in +page.svelte
  • +page.gql file in your route directory

Regardless of which pattern you are using, you will quickly need to customize your query’s behavior. This could be because you need to add variables to the query, or maybe you need to perform some logic before and/or after the data is fetched.

Query Variables

The simplest way to pass values for your query inputs is to use route parameters. If houdini sees that the name for a route paramter matches the name of a query’s input, it will attempt to parse the value and pass it to your query.

Sometimes your query variables cannot be directly pulled from your route’s path. In those cases, you can export a function named after your query from +page.js files to perform whatever logic you need. This function takes the same arguments passed to the load function described in the SvelteKit docs. To return an error, or force a redirect, you can use the same utilities exported from @sveltejs/kit. Here is a modified example from the source repository:

src/routes/[filter]/+page.ts
import { graphql } from '$houdini'
import { error } from '@sveltejs/kit'
import type { AllItemsVariables } from './$houdini'

export const _houdini_load = graphql(`
    query AllItems($completed: Boolean) {
        items(completed: $completed) {
            id
            text
        }
    }
`)

// This is the function for the AllItems query.
// Query variable functions must be named _<QueryName>Variables.
export const _AllItemsVariables : AllItemsVariables = (event) => {
    // make sure we recognize the value
    if (!['active', 'completed'].includes(event.params.filter)) {
        throw error(400, 'invalid filter')
    }

    return {
        completed: event.params.filter === 'completed'
    }
}
src/routes/[filter]/+page.js
import { graphql } from '$houdini'
import { error } from '@sveltejs/kit'

export const _houdini_load = graphql(`
    query AllItems($completed: Boolean) {
        items(completed: $completed) {
            id
            text
        }
    }
`)

// This is the function for the AllItems query.
// Query variable functions must be named _<QueryName>Variables.
/* @type { import('./$houdini').AllItemsVariables } */
export const _AllItemsVariables = (event) => {
    // make sure we recognize the value
    if (!['active', 'completed'].includes(event.params.filter)) {
        throw error(400, 'invalid filter')
    }

    return {
        completed: event.params.filter === 'completed',
    }
}

Hooks

Sometimes you will need to add additional logic to a component’s query. For example, you might want to check if the current session is valid before a query is sent to the server. In order to support this, Houdini will look for hook functions defined in your +page.js files.

_houdini_beforeLoad

Called before Houdini executes load queries against the server. You can expect the same arguments as SvelteKit’s load function.

src/routes/myRoute/+page.ts
import { graphql } from '$houdini'
import { redirect } from '@sveltejs/kit'
import type { BeforeLoadEvent } from './$houdini'

export const _houdini_load = graphql(`
    query AllItems($completed: Boolean) {
        items(completed: $completed) {
            id
            text
        }
    }
`)

export function _houdini_beforeLoad({ page, session }: BeforeLoadEvent) {
    if (!session.authenticated) {
        throw redirect(302, '/login')
    }

    return {
        message: 'Number of items:'
    }
}
src/routes/myRoute/+page.js
import { graphql } from '$houdini'
import { redirect } from '@sveltejs/kit'

export const _houdini_load = graphql(`
    query AllItems($completed: Boolean) {
        items(completed: $completed) {
            id
            text
        }
    }
`)

/**
 * @param { import('./$houdini').BeforeLoadEvent }
 */
export function _houdini_beforeLoad({ page, session }) {
    if (!session.authenticated) {
        throw redirect(302, '/login')
    }

    return {
        message: 'Number of items:',
    }
}

_houdini_afterLoad

Called after Houdini executes load queries against the server. You can expect the same arguments as SvelteKit’s load function, plus an additional data property referencing query result data. Keep in mind that if you define this hook, Houdini will have to block requests to the route in order to wait for the result. For more information about blocking during navigation, check out the deep dive in this section of the query store docs.

src/routes/myRoute/+page.ts
import { graphql } from '$houdini'
import { error } from '@sveltejs/kit'
import type { AfterLoadEvent, MyProfileVariables } from './$houdini'

export const _houdini_load = graphql(`
    query MyProfile($id: ID!) {
        profile(id: $id) {
            name
        }
    }
`)

export const _MyProfileVariables: MyProfileVariables = ({ params }) => {
    return { id: params.id }
}

export function _houdini_afterLoad({ data }: AfterLoadEvent) {
    if (!data.MyProfile) {
        throw  error(404)
    }

    return {
        message: "Hello I'm"
    }
}
src/routes/myRoute/+page.js
import { graphql } from '$houdini'
import { error } from '@sveltejs/kit'

export const _houdini_load = graphql(`
    query MyProfile($id: ID!) {
        profile(id: $id) {
            name
        }
    }
`)

/* @type { import('./$houdini').MyProfileVariables } */
export const _MyProfileVariables = ({ params }) => {
    return { id: params.id }
}

/**
 * @param { import('./$houdini').AfterLoadEvent }
 */
export function _houdini_afterLoad({ data }) {
    if (!data.MyProfile) {
        throw error(404)
    }

    return {
        message: "Hello I'm",
    }
}

_houdini_onError

If defined, the load function will invoked this function instead of throwing an error when an error is received. It receives three inputs: the load event, the inputs for each query, and the error that was encountered. Just like the other hooks, onError can return an object that provides props to the route. If you do define this hook, Houdini will have to block requests to the route in order to wait for the result. For more information about blocking during navigation, check out the deep dive in this section of the query store docs.

src/routes/myRoute/+page.ts
import { graphql } from '$houdini'
import type { OnErrorEvent } from './$houdini'

export const _houdini_load = graphql(`
    query MyProfile {
        profile {
            name
        }
    }
`)

export function _houdini_onError({ error }: OnErrorEvent) {
    throw this.redirect(307, '/login')
}
src/routes/myRoute/+page.js
import { graphql } from '$houdini'

export const _houdini_load = graphql(`
    query MyProfile {
        profile {
            name
        }
    }
`)

/**
 * @param { import('./$houdini').OnErrorEvent }
 */
export function _houdini_onError({ error }) {
    throw this.redirect(307, '/login')
}

Disabling Inline Query Loading

Sometimes it’s useful to have a lazy query that only fires when you call fetch (for example in a search input). For these scenarios (whether in a route or component), you will want to turn off the generated load using the @manual_load directive:

<script lang="ts">
    import { graphql } from '$houdini'

    const result = graphql(`
        query MyQuery @manual_load {
            viewer {
                id
            }
        }
    `)
</script>
<script>
    import { graphql } from '$houdini'
    
    const result = graphql(`
        query MyQuery @manual_load {
            viewer {
                id
            }
        }
    `)
</script>

Manual Loading

If you are writing your route’s load functions by hand, you’ll want to instantiate a store, calls its fetch method and return the store to your route. In order to streamline this, houdini provides a function for each of your stores that you can use to render your route on the server. These functions take the same parameters as fetch:

import { load_MyQuery } from '$houdini'
import type { PageLoad } from './$houdini'

export const load: PageLoad = async (event) => {
    return {
        ...(await load_MyQuery({ event }))
    }
}
import { load_MyQuery } from '$houdini'

/* @type { import('./$houdini').PageLoad } */
export const load = async (event) => {
    return {
        ...(await load_MyQuery({ event })),
    }
}

In case you were wondering, the load_ prefix is there so you can autocomplete your loads by just typing load_<tab>. Anyway, with this in place, your route will recieve props for each of the stores that you have loaded:

src/routes/myRoute.svelte
<script lang="ts">
    import type { PageData } from './$houdini'
    export let data: PageData

    $: ({ MyQuery } = data)
</script>

{$MyQuery.data.value}
src/routes/myRoute.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    $: ({ MyQuery } = data)
</script>

{$MyQuery.data.value}

If your query has variables, you can pass them straight to the loader:

import { load_MyQuery } from '$houdini'
import type { PageLoad } from './$houdini'

export const load: PageLoad = async (event) => {
  return {
    ...await load_MyQuery({
        event,
        variables: { variable1: 'value' }
    })
  }
}
import { load_MyQuery } from '$houdini'

/* @type { import('./$houdini').PageLoad } */
export const load = async (event) => {
    return {
        ...(await load_MyQuery({
            event,
            variables: { variable1: 'value' },
        })),
    }
}

If you are defining a manual load and need to look at the result of the fetch, we recommend not using the load_ utility but instead defining your load explicitly:

import { MyQueryStore } from '$houdini'
import type { PageLoad } from './$houdini'

export const load: PageLoad = async (event) => {
    const MyQuery = new MyQueryStore()

    const result = await MyQuery.fetch({ event })

    console.log('do something with', result)

    return { MyQuery }
}
import { MyQueryStore } from '$houdini'

/* @type { import('./$houdini').PageLoad } */
export const load = async (event) => {
    const MyQuery = new MyQueryStore()

    const result = await MyQuery.fetch({ event })

    console.log('do something with', result)

    return { MyQuery }
}

Components Queries

Not every query will be inside of a route. In this situations, you can either work with your query stores directly, or use the same graphql function to instruct the plugin to generate a client-side equivalent of the load function.

src/lib/MyComponent.svelte
<script lang="ts">
    import { graphql } from '$houdini'
    import type { MyComponentQueryVariables } from './$houdini'

    export const _MyComponentQueryVariables: MyComponentQueryVariables = ({ props }) => {
        return { id: props.id }
    }

    const store = graphql(`
        query MyComponentQuery($id: ID!) {
            user(id: $id) {
                id
            }
        }
    `)
</script>

{$store.data.value}
src/lib/MyComponent.svelte
<script>
    import { graphql } from '$houdini'
    
    /* @type { import('./$houdini').MyComponentQueryVariables } */
    export const _MyComponentQueryVariables = ({ props }) => {
        return { id: props.id }
    }
    
    const store = graphql(`
        query MyComponentQuery($id: ID!) {
            user(id: $id) {
                id
            }
        }
    `)
</script>

{$store.data.value}

Endpoints

Using a query store inside of an endpoint looks very similar to the load function: just pass the event you are handed in your route function:

import { MyQueryStore } from '$houdini'
import type { PageServerLoad } from './$houdini'

export const load: PageServerLoad = async (event) => {
    const myQuery = new MyQueryStore()
    const { data } = await myQuery.fetch({ event })

    return { data }
}
import { MyQueryStore } from '$houdini'

/* @type { import('./$houdini').PageServerLoad } */
export const load = async (event) => {
    const myQuery = new MyQueryStore()
    const { data } = await myQuery.fetch({ event })

    return { data }
}

Passing Metadata

Sometimes you need to do something very custom for a specific route. Maybe you need special headers or some other contextual information. Whatever the case may be, you can pass a metadata parameter to fetch:

import type { PageLoad } from './$houdini'

export const load: PageLoad = async (event) => {
    return {
        ...(await load_MyStore({event, metadata: { key: 'value' }}))
    }
}
/* @type { import('./$houdini').PageLoad } */
export const load = async (event) => {
    return {
        ...(await load_MyStore({ event, metadata: { key: 'value' } })),
    }
}

This value will get forwarded to the network function in your client definition, usually found in src/client.js:

import type { RequestHandler } from './$houdini'

const requestHandler: RequestHandler = async ({ fetch, text, variables, session, metadata }) => {
    // do anything with metadata inside of here
}

// Export the Houdini client
export default new HoudiniClient(requestHandler)
/* @type { import('./$houdini').RequestHandler } */
const requestHandler = async ({ fetch, text, variables, session, metadata }) => {
    // do anything with metadata inside of here
}

// Export the Houdini client
export default new HoudiniClient(requestHandler)

Paginated Queries

If the query contains the pagination directive then the generated store will have extra fields/methods according to the pagination strategy and direction. For more information about pagination in general, check out this guide.

Forward cursor pagination

If the decorated field implements cursor-based pagination and provides a first argument, the query store will be generated with an extra method that loads more data and a field pointing to a store with the current pageInfo object. This extra field can be used to track if there are more pages to load:


query MyFriends {
  viewer {
    friends(first: 10) @paginate( name: "My_Friends") {
      edges {
        node {
          id
        }
      }
  }
}


type MyFriendsStore = QueryStore & {
  loadNextPage(
    pageCount?: number,
    after?: string | number
    houdiniContext?: HoudiniContext,
  ): Promise<void>,

  pageInfo: Readable<PageInfo>
}

Backwards cursor pagination

If the decorated field implements cursor-based pagination and provides a last argument, the query store will be generated with an extra method that loads more data and a field pointing to a store with the current pageInfo object. This extra field can be used to track if there are more pages to load:


query MyFriends {
  viewer {
    friends(last: 10) @paginate(name: "My_Friends") {
      edges {
        node {
          id
        }
      }
  }
}


type MyFriendsStore = QueryStore & {
  loadPreviousPage(
    pageCount?: number,
    before?: string | number
    houdiniContext?: HoudiniContext,
  ): Promise<void>,

  pageInfo: Readable<PageInfo>
}

Offset/limit Pagination

If the decorated field implements offset/limit pagination and provides a limit argument, the query store will be generated with an extra methods that lets it load more pages after the one the current one:


query MyFriends {
  viewer {
    friends(limit: 10) @paginate(name: "My_Friends") {
      id
    }
  }
}


type MyFriendsStore = QueryStore & {
  loadNextPage(
    limit?: number,
    offset?: number
    houdiniContext?: HoudiniContext,
  ): Promise<void>,
}