Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Dynamic livePreview URL for multi-tenant

default discord avatar
bratvanovlast year
5

I'm using the [multi-tenant example](

https://github.com/payloadcms/payload/tree/main/examples/multi-tenant

) and trying to set up livePreview on my Pages collection with a [dynamic URL](

https://payloadcms.com/docs/live-preview/overview#dynamic-urls

) containing the tenant URL as documented.



The

url.data

param returns all document's field/data, and

data.tenant

is just the objectID of the Tenants collection doc, because the [tenantField](

https://github.com/payloadcms/payload/blob/main/examples/multi-tenant/src/collections/Pages/index.ts#L35

) is [a relationship field](

https://github.com/payloadcms/payload/blob/main/examples/multi-tenant/src/fields/TenantField/index.ts#L9

), and that will only [return the objectID by design](

https://payloadcms.com/docs/fields/relationship#has-one

).



The tenant's objectID is not sufficient to construct the dynamic URL, as I actually need the [tenant's slug](

https://github.com/payloadcms/payload/blob/556b8ed393a0b8b6c1b4f6c80d76e3dcd9fe129b/examples/multi-tenant/src/collections/Tenants/index.ts#L41-L47

).



My question is – what's the best way to get the tenant.slug in the

data

param of the livePreview

url

function? Should I add another field to my Pages collection that gets the tenant slug from the relationship field's objectID? Or should I query the local API for the

tenant.slug

? Is it even possible/advisable to run logic/query the API in the Payload config?



For some reason I'm having trouble making sense of the

req

param inside the

url

function to see if I can extract the tenant slug from it somehow.



According to the docs you can use

data.tenant.url

in the

url

function, but

data.tenant

is just a string with the objectID, so perhaps the docs are misleading.



Thanks!



I solved this with another field

tenantSlug

that simply reads the

tenantField

and queries the local API to get the slug of the tenant.



{
    name: 'tenantSlug',
    type: 'text',
    label: 'Tenant Slug',
    admin: {
      readOnly: true,
    },
    hooks: {
      afterRead: [
        async ({ req, data }) => {
          const tenantId = data?.tenant;
          if (!tenantId) return null;
          
          const tenant = await req.payload.findByID({
            collection: 'tenants',
            id: tenantId
          });
          
          return tenant?.slug || null;
        }
      ]
    }
  }


    livePreview: {
      url: ({
        data,
        collectionConfig
      }) => {
        return `http://localhost:3000/${data.tenantSlug}${
          collectionConfig.slug === 'reviews' 
            ? `/review/${data.slug}` 
            : data.slug !== 'index' 
              ? `/${data.slug}` 
              : ''
        }`;
      },
  • default discord avatar
    hellouserlast year

    Good idea. I've been stuck at this too. I also noticed, in my build anyway, that when the slug is updated, the live preview breaks, as it is still calling the url with original slug, as it is not being updated. This is because i was using the page slug in the live preview url, like I think you have above.



    The way I fixed it was to use the page ID in the url instead, similar in the way the Payload website template is



    const previewUrl =

    ${baseUrl}/${data.tenantSlug}/next/preview/page?collection=pages&id=${data.id}&tenant=${data.tenantSlug}
  • default discord avatar
    bratvanovlast year
    @841627797629567028

    that's a good point, and I realized I also had to refine my code to use the id for the preview URL, although for a different reason, as I wanted to have the preview active on a brand new page before it was even published (so, no slug at all).



    I also changed my approach to not use a second field for

    tenantSlug

    and instead just get it from the REST API through the tenant's objectID (

    data.tenant

    ) - something like that:



    livePreview: {
      url: async ({ data }) => {
        const getTenantData = await fetch(`${process.env.PAYLOAD_URL}/api/tenants/${data.tenant}`).then(res => res.json());
        return `${process.env.PAYLOAD_URL}/${getTenantData.slug}/${data.id}?livepreview`;
      },
    },
  • default discord avatar
    hellouserlast year

    great stuff 👍

  • default discord avatar
    mild_fire6 months ago

    i am having a lot of trouble making live preview work with multi-tenant setup even locally. Can you share the repo code please for a working live preview multi-tenant example?



    I tried to make it work, but as i see it the iframe in admin interface and the tenant itself are on different domains and one can not send data to the other due to security restrictions.



    I may be doing something wrong or lack understanding of why it is not working as intended, but i only got the preview to work in a separate window and only after i hard refresh this window with F5. Then i see the unpublished changes. But live preview panel itself does not change as a result of field input at all.



    maybe this is a known limitaion of multi-tenant setup? I tried to find similar topics here but none are multi-tenant so i decided to ask here. Sorryif this is the wrong place, i just could not find any better

  • default discord avatar
    volemrdat84386 months ago

    Im interested in solution too

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.