Common Types

Link

The discriminated union returned anywhere a connector or component schema yields a navigable target. Five variants, one resolver.

A breadcrumb item points somewhere. A menu item points somewhere. A "shop now" CTA on a section points somewhere. The shape connectors and editors return for all of these is the same Link discriminated union: a type field plus the data that variant needs. The storefront's linkResolver turns the object into the actual URL based on the current language and market.

import type { Link } from '@laioutr-core/core-types/common';

type Link = LinkReference | LinkUrl | LinkAnchor | LinkPage | LinkPageType;

The variants exist because the storefront resolves them differently. A reference link goes through the page-type registry to find the right detail page. A URL link is returned as-is. An anchor link becomes a #fragment. Page and page-type links plug into vue-router. Picking the right variant is what lets one link object work across markets, languages, and storefronts that route products under /p/:slug versus /products/:slug.

Choosing a variant

VariantUse it forResolver behavior
referenceLinking to a canonical entity (Product, Category, Brand, BlogCollection, BlogPost)Looks up the page type that resolves for the entity type, fills in slug
urlExternal URLs, or any pre-baked path you don't want the resolver to touchReturned verbatim
anchorSame-page anchorsBecomes #fragment
pageLinking to a specific Studio page by ID (e.g. a single landing page instance)Looks up the route by page ID
pageTypeLinking to whichever page of a given type exists (e.g. "the cart page", "the search results page")Looks up the route by page type

If you're a connector returning a link to an entity, use reference. If you're an editor in a link picker, the schema field gives you all five.

Variants

LinkReference

{
  type: 'reference';
  reference: {
    type: WellKnownReferenceType; // 'Product' | 'Category' | 'Brand' | 'BlogCollection' | 'BlogPost' | (string & {})
    slug: string;
    id?: string;
  };
  query?: LinkQuery;
}

Use this whenever a connector returns a link to an entity. The resolver finds the page type whose resolveFor includes the reference type, then fills in slug (and any other reference fields used in the route's path params).

id is optional but worth setting when the connector has it. Some page types use id instead of slug, and including both lets the storefront pick whichever its routes need.

The type field on reference is widened to string & {} so custom storefronts can introduce their own well-known types beyond the built-in five. Built-in entities should stick to the canonical names.

LinkUrl

{
  type: 'url';
  href: string;
  target?: '_blank';
  query?: LinkQuery;
}

Use this for external URLs. The resolver returns href unchanged, so localization, market prefixes, and base paths are not applied; whatever you put in is what the user navigates to.

It's tempting to use url for internal links by hardcoding a path. Don't (see For connector authors).

LinkAnchor

{
  type: 'anchor';
  fragment: string; // no leading '#'
  query?: LinkQuery;
}

Use this for jumps within the current page. The resolver returns #${fragment}. Per the URL spec, the fragment value must not include the leading #.

LinkPage

{
  type: 'page';
  page: { id: string };
  params?: LinkParams; // Record<string, string>
  query?: LinkQuery;
}

Use this when you want a specific Studio page instance, typically a static page like a particular landing page. The resolver finds the route whose meta.metaPage.id matches and applies params to fill any route placeholders.

LinkPageType

{
  type: 'pageType';
  pageType: string; // e.g. 'core/cart', 'ecommerce/search-results'
  params?: LinkParams;
  query?: LinkQuery;
}

Use this when you want whichever page of a given type the storefront has ("the cart page", "the search results page"). The resolver finds the route whose meta.metaPage.type matches.

This is the right variant for header/footer links pointing at structural pages: there's exactly one cart page per storefront, but you don't know its ID up front.

A Link object is just data. Turning it into a URL string the browser can navigate to is the job of linkResolver.resolve(), which is auto-imported in storefronts and reads the active language and market to produce a localized path.

components/SomeComponent.vue
<script setup lang="ts">
import type { Link } from '@laioutr-core/core-types/common';
const props = defineProps<{ link: Link }>();
</script>

<template>
  <NuxtLink :to="linkResolver.resolve(props.link)">Go</NuxtLink>
</template>
linkResolver.resolve() reads the current language and market domain to produce its result. When called bare in setup(), the returned string will not update when the language or market changes (the same reactivity caveat that applies to nuxt-i18n's $t() outside of templates and computed()). Use useResolvedLink() when you need a reactive result.

useResolvedLink(link) wraps linkResolver.resolve() in a computed() so the resolved path re-evaluates automatically when the language or market changes:

const resolved = useResolvedLink(props.link); // ComputedRef<string>

To override resolution behavior (for example, to route certain reference types through a custom path), register a link resolver hook. When the hook sets result.value, the default resolution is skipped entirely.

If the resolver can't find a route for a link, it returns a fallback URL like #unknown-route?pageType=... and logs a warning. Treat those as bugs in either the link's shape or the storefront's page-type setup.

Query and params

All variants accept an optional query field of type Record<string, string | number | null | (string | number | null)[]>, matching vue-router's LocationQueryRaw. Page and page-type variants also accept params: Record<string, string> for route placeholders.

{
  type: 'reference',
  reference: { type: 'Category', slug: 'shoes' },
  query: { sort: 'price-asc', filters: ['size:42', 'color:red'] },
}

For connector authors

This is the section that prevents the most common bug in connector code: returning links the storefront can't route correctly. The rule is short:

When a query handler returns a link to an entity (a breadcrumb item linking back to a category, a menu item linking to a product), use LinkReference. Never construct the URL string yourself.

Wrong

// Sylius connector, breadcrumb handler
return {
  name: taxon.name,
  link: { href: '/c/' + taxon.slug, type: 'internal' }, // ❌
};

This breaks for three independent reasons:

  1. type: 'internal' is not a valid Link variant. The discriminated union has five tags (reference, url, anchor, page, pageType). The resolver hits the unknown-link branch and returns #unknown-link?....
  2. href is hardcoded to /c/. The storefront might route categories under /category/:slug, /categories/:slug, or /:locale/c/:slug. The connector cannot know.
  3. No locale or market prefix. Even if the URL shape were right, multi-market and multi-language deployments need the resolver's path-prefix logic.
// Sylius connector, breadcrumb handler
return {
  name: taxon.name,
  link: {
    type: 'reference',
    reference: {
      type: 'Category',
      slug: taxon.slug,
      id: taxon.code, // optional, useful for storefronts that route by id
    },
  },
};

The connector says "this links to a Category named shoes." The storefront's page-type registry decides which page renders categories and what URL pattern that page lives at. Multi-language, multi-market, and storefront-specific URL schemes all stay where they belong: in the storefront.

The same rule applies to product breadcrumb tails, menu items pointing at brands, cart items linking back to product pages, and anywhere else a connector hands the storefront a link to a canonical entity.

When LinkUrl is the right answer for a connector

LinkUrl is correct when the URL is genuinely external (a third-party support article, an off-site brand page) or when the backend has already produced a fully-resolved URL the storefront should not touch (a CMS page editor that lets the editor type a path manually). For anything that points to a canonical entity in the same storefront, LinkReference is the answer.

Copyright © 2026 Laioutr GmbH