[{"data":1,"prerenderedAt":425},["ShallowReactive",2],{"serp-preview-navigation":3,"mdc--4zkox4-key":20,"serp-preview-version":53,"\u002Fdocs\u002Fserp-preview\u002Fformatters":54,"\u002Fdocs\u002Fserp-preview\u002Fformatters-surround":422},[4],{"title":5,"path":6,"stem":7,"children":8,"icon":19},"SERP Preview","\u002Fdocs\u002Fserp-preview","1.docs\u002F7.serp-preview\u002F1.index",[9,11,15],{"title":10,"path":6,"stem":7},"Getting Started",{"title":12,"path":13,"stem":14},"Configuration","\u002Fdocs\u002Fserp-preview\u002Fconfiguration","1.docs\u002F7.serp-preview\u002F2.configuration",{"title":16,"path":17,"stem":18},"Custom Formatters","\u002Fdocs\u002Fserp-preview\u002Fformatters","1.docs\u002F7.serp-preview\u002F3.formatters","i-ri-search-eye-line",{"data":21,"body":22},{},{"type":23,"children":24},"root",[25],{"type":26,"tag":27,"props":28,"children":29},"element","p",{},[30,33,42,44,51],{"type":31,"value":32},"text","We love Kirby – our plugins wouldn't exist without it. Help us improve: share feedback in ",{"type":26,"tag":34,"props":35,"children":39},"a",{"href":36,"rel":37},"https:\u002F\u002Fgithub.com\u002Fkirby-tools\u002Fcommunity\u002Fdiscussions",[38],"nofollow",[40],{"type":31,"value":41},"Discussions",{"type":31,"value":43}," or report bugs via ",{"type":26,"tag":34,"props":45,"children":48},{"href":46,"rel":47},"https:\u002F\u002Fgithub.com\u002Fkirby-tools\u002Fcommunity\u002Fissues",[38],[49],{"type":31,"value":50},"Issues",{"type":31,"value":52},".",null,{"id":55,"title":16,"body":56,"description":417,"extension":418,"meta":419,"navigation":115,"path":17,"seo":420,"stem":18,"__hash__":421},"docs\u002F1.docs\u002F7.serp-preview\u002F3.formatters.md",{"type":57,"value":58,"toc":412},"minimark",[59,62,67,83,252,255,259,266,269,388,394,398,405,408],[27,60,61],{},"The section clamps a long title to one line and a long description to two, the way Google does. A formatter changes the text itself instead – truncate it at the length a search result actually shows, strip the HTML a textarea leaves behind, or vary it per page.",[63,64,66],"h2",{"id":65},"defining-a-formatter","Defining a Formatter",[27,68,69,70,74,75,78,79,82],{},"Formatters live under ",[71,72,73],"code",{},"johannschopplich.serp-preview.formatters",", one for ",[71,76,77],{},"title"," and one for ",[71,80,81],{},"description",". Each is a closure that receives the resolved value and the page, and returns what the snippet should draw:",[84,85,91],"pre",{"className":86,"code":87,"filename":88,"language":89,"meta":90,"style":90},"language-php shiki shiki-themes github-light github-light github-dark","use Kirby\\Toolkit\\Str;\n\nreturn [\n    'johannschopplich.serp-preview' => [\n        'formatters' => [\n            'title' => fn (string $value, \\Kirby\\Cms\\Page $page) => Str::short($value, 60),\n            'description' => fn (string $value, \\Kirby\\Cms\\Page $page) => Str::short(strip_tags($value), 160)\n        ]\n    ]\n];\n","site\u002Fconfig\u002Fconfig.php","php","",[71,92,93,110,117,126,138,148,193,234,240,246],{"__ignoreMap":90},[94,95,98,102,106],"span",{"class":96,"line":97},"line",1,[94,99,101],{"class":100},"s8jYJ","use",[94,103,105],{"class":104},"sBjJW"," Kirby\\Toolkit\\Str",[94,107,109],{"class":108},"sxrX7",";\n",[94,111,113],{"class":96,"line":112},2,[94,114,116],{"emptyLinePlaceholder":115},true,"\n",[94,118,120,123],{"class":96,"line":119},3,[94,121,122],{"class":100},"return",[94,124,125],{"class":108}," [\n",[94,127,129,133,136],{"class":96,"line":128},4,[94,130,132],{"class":131},"sIIMD","    'johannschopplich.serp-preview'",[94,134,135],{"class":100}," =>",[94,137,125],{"class":108},[94,139,141,144,146],{"class":96,"line":140},5,[94,142,143],{"class":131},"        'formatters'",[94,145,135],{"class":100},[94,147,125],{"class":108},[94,149,151,154,156,159,162,165,168,171,174,177,180,184,187,190],{"class":96,"line":150},6,[94,152,153],{"class":131},"            'title'",[94,155,135],{"class":100},[94,157,158],{"class":100}," fn",[94,160,161],{"class":108}," (",[94,163,164],{"class":100},"string",[94,166,167],{"class":108}," $value, ",[94,169,170],{"class":104},"\\Kirby\\Cms\\Page",[94,172,173],{"class":108}," $page) => ",[94,175,176],{"class":104},"Str",[94,178,179],{"class":100},"::",[94,181,183],{"class":182},"snPdu","short",[94,185,186],{"class":108},"($value, ",[94,188,189],{"class":104},"60",[94,191,192],{"class":108},"),\n",[94,194,196,199,201,203,205,207,209,211,213,215,217,219,222,225,228,231],{"class":96,"line":195},7,[94,197,198],{"class":131},"            'description'",[94,200,135],{"class":100},[94,202,158],{"class":100},[94,204,161],{"class":108},[94,206,164],{"class":100},[94,208,167],{"class":108},[94,210,170],{"class":104},[94,212,173],{"class":108},[94,214,176],{"class":104},[94,216,179],{"class":100},[94,218,183],{"class":182},[94,220,221],{"class":108},"(",[94,223,224],{"class":104},"strip_tags",[94,226,227],{"class":108},"($value), ",[94,229,230],{"class":104},"160",[94,232,233],{"class":108},")\n",[94,235,237],{"class":96,"line":236},8,[94,238,239],{"class":108},"        ]\n",[94,241,243],{"class":96,"line":242},9,[94,244,245],{"class":108},"    ]\n",[94,247,249],{"class":96,"line":248},10,[94,250,251],{"class":108},"];\n",[27,253,254],{},"Define only the one you need – a missing formatter leaves that line untouched.",[63,256,258],{"id":257},"what-a-formatter-sees","What a Formatter Sees",[27,260,261,262,265],{},"The value arrives already resolved: the content field if one is named and filled, otherwise the fallback, otherwise the composed page and site title. Queries in the blueprint have run by then, so a formatter never sees a ",[71,263,264],{},"{{ ... }}"," placeholder.",[27,267,268],{},"The page is the one being edited, which is what makes a formatter page-specific. On the site view it is the home page; on a page's file view the formatter does not run and the snippet draws the value as it is, while a site or user file is formatted against the home page:",[84,270,272],{"className":86,"code":271,"filename":88,"language":89,"meta":90,"style":90},"'title' => function (string $value, \\Kirby\\Cms\\Page $page) {\n    \u002F\u002F Articles carry the section in front of the title, other pages do not.\n    if ($page->intendedTemplate()->name() === 'article') {\n        return $page->parent()->title() . ': ' . $value;\n    }\n\n    return $value;\n}\n",[71,273,274,295,301,335,367,372,376,383],{"__ignoreMap":90},[94,275,276,279,281,284,286,288,290,292],{"class":96,"line":97},[94,277,278],{"class":131},"'title'",[94,280,135],{"class":100},[94,282,283],{"class":100}," function",[94,285,161],{"class":108},[94,287,164],{"class":100},[94,289,167],{"class":108},[94,291,170],{"class":104},[94,293,294],{"class":108}," $page) {\n",[94,296,297],{"class":96,"line":112},[94,298,300],{"class":299},"sCsY4","    \u002F\u002F Articles carry the section in front of the title, other pages do not.\n",[94,302,303,306,309,312,315,318,320,323,326,329,332],{"class":96,"line":119},[94,304,305],{"class":100},"    if",[94,307,308],{"class":108}," ($page",[94,310,311],{"class":100},"->",[94,313,314],{"class":182},"intendedTemplate",[94,316,317],{"class":108},"()",[94,319,311],{"class":100},[94,321,322],{"class":182},"name",[94,324,325],{"class":108},"() ",[94,327,328],{"class":100},"===",[94,330,331],{"class":131}," 'article'",[94,333,334],{"class":108},") {\n",[94,336,337,340,343,345,348,350,352,354,356,358,361,364],{"class":96,"line":128},[94,338,339],{"class":100},"        return",[94,341,342],{"class":108}," $page",[94,344,311],{"class":100},[94,346,347],{"class":182},"parent",[94,349,317],{"class":108},[94,351,311],{"class":100},[94,353,77],{"class":182},[94,355,325],{"class":108},[94,357,52],{"class":100},[94,359,360],{"class":131}," ': '",[94,362,363],{"class":100}," .",[94,365,366],{"class":108}," $value;\n",[94,368,369],{"class":96,"line":140},[94,370,371],{"class":108},"    }\n",[94,373,374],{"class":96,"line":150},[94,375,116],{"emptyLinePlaceholder":115},[94,377,378,381],{"class":96,"line":195},[94,379,380],{"class":100},"    return",[94,382,366],{"class":108},[94,384,385],{"class":96,"line":236},[94,386,387],{"class":108},"}\n",[389,390,391],"note",{},[27,392,393],{},"A formatter runs on the server, so it can reach anything Kirby can – other pages, files, the site. The closure itself never leaves your config; the Panel only learns that one exists.",[63,395,397],{"id":396},"where-it-stops","Where It Stops",[27,399,400,401,404],{},"A formatter shapes what the snippet shows and nothing else. It does not touch the content, the saved page, or the markup your frontend renders – if you want the same truncation in your ",[71,402,403],{},"\u003Ctitle>"," tag, call the same helper there.",[27,406,407],{},"Each change posts the current value to your site, at most once per 250 ms, title and description sharing that window. The reply replaces the drawn text; a failed reply keeps the previous one, so a formatter that throws shows no error in the Panel and the failed request is only in the browser's network tab. A formatter that fetches a remote URL delays the snippet, not the typing.",[409,410,411],"style",{},"html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":90,"searchDepth":112,"depth":112,"links":413},[414,415,416],{"id":65,"depth":112,"text":66},{"id":257,"depth":112,"text":258},{"id":396,"depth":112,"text":397},"Shorten or clean the title and description before the snippet draws them, with a closure in your config.","md",{},{"title":16,"description":417},"5Y8y9oLwj3nk0yAfsgEvRUeBC4q-rad8K0Hmrn_UdMM",[423,53],{"title":12,"path":13,"stem":14,"description":424,"children":-1},"Every blueprint property the SERP Preview section takes, and the order in which it resolves the title and the description.",1788798195413]