Utilities for injecting search engine optimization (SEO), Open Graph, Twitter, site verification, and schema.org metadata into 'Shiny' applications from YAML files or named lists.
shinyseo is a small helper package for Shiny apps that need social, search, and PWA metadata.
It builds one shiny::tags$head() fragment containing:
The package accepts either a YAML file path or a named list.
Six exported functions:
init_meta(path) — interactively answer a few questions at the console and
write the result to a YAML file, so you can get started without reading the
field reference belowsocial_meta(meta) — inject metadata into the UI at startupupdate_meta(session, ...) — update title, description, url, or image reactively from the serverwrite_manifest(meta, ...) — generate www/manifest.json for PWA supportgenerate_assets(meta, generator, ...) — fill in a missing favicon, Apple
touch icon, or share image by calling your own LLM/image-generation
function, or one of the built-in generators belowopenai_image_generator(api_key, model) — a ready-made generator for
generate_assets() that calls OpenAI's image APIRun shinyseo::init_meta() in the console. It will ask for your title,
description, URL, image, and a handful of optional fields (favicon, theme
colour, home screen shortcut support, and so on), then write the answers to
meta.yml. Pass that file straight to social_meta():
shinyseo::social_meta("meta.yml")
When you call social_meta(), the package:
update_meta() can update tags at runtime.social_meta(meta):
meta may be a YAML path or a named listtitle, description, url, and image are requiredmeta is a character string, it is read as a UTF-8 YAML file pathschema = FALSE disables JSON-LD outputschema keeps JSON-LD enabledupdate_meta(session, title, description, url, image):
social_meta() to be present in the UIwrite_manifest(meta, path, display, start_url, background_color):
www/manifest.json for PWA supportglobal.R before the app startsmanifest = "/manifest.json" in social_meta()generate_assets(meta, generator, path, assets):
favicon, apple_touch_icon, or image by calling
a generator function — either one you write yourself, or a built-in
one such as openai_image_generator()generator is function(prompt, kind); it talks to whatever LLM/image
API it's written against, and returns either raw image bytes or a path
to a file on diskmeta with yaml::write_yaml()meta <- shinyseo::generate_assets(meta,
generator = shinyseo::openai_image_generator()
)
yaml::write_yaml(meta, "meta.yml")
Or supply your own, against whatever service you already have access to:
meta <- shinyseo::generate_assets(meta, generator = function(prompt, kind) {
# your own call to Adobe Firefly, a local model, ...
})
yaml::write_yaml(meta, "meta.yml")
openai_image_generator(api_key, model):
generator for generate_assets() that calls
OpenAI's image API (gpt-image-1 by default) and returns the generated
image bytesapi_key defaults to the OPENAI_API_KEY environment variablehttr package, but only loads it (via requireNamespace())
when you call this constructor — it costs nothing if you never use itgenerator — see
LLM.md
for the shape a contribution should takemeta <- shinyseo::generate_assets(meta,
generator = shinyseo::openai_image_generator(api_key = "sk-...")
)
Minimal configuration:
title: "Example app"
description: "A short app description."
url: "https://example.no"
image: "https://example.no/share.png"
Common extras:
| Field | What it does |
|---|---|
locale |
Sets Open Graph locale and schema language default |
robots |
Controls the robots meta tag |
twitter_card |
Sets the Twitter card type |
site_name |
Sets og:site_name |
twitter_site |
Sets twitter:site |
twitter_creator |
Sets twitter:creator |
image_alt |
Sets og:image:alt |
twitter_image_alt |
Sets twitter:image:alt |
favicon |
Sets <link rel="icon"> with auto-detected MIME type |
favicon_type |
Overrides the MIME type inferred from favicon |
favicon_png |
Adds a PNG <link rel="icon"> fallback — set this when favicon is an SVG; Chromium address bars don't render SVG favicons and show a generic globe icon without a PNG fallback |
favicon_png_sizes |
Overrides the sizes attribute on favicon_png (defaults to "32x32") |
apple_touch_icon |
Sets <link rel="apple-touch-icon"> |
theme_color |
Sets <meta name="theme-color"> |
manifest |
Sets <link rel="manifest"> |
short_name |
Used by write_manifest() for the manifest short name |
apple_mobile_web_app_capable |
Set to TRUE to run standalone when added to a phone's home screen (apple-mobile-web-app-capable and mobile-web-app-capable) |
apple_mobile_web_app_title |
Sets the name shown under the home screen icon (apple-mobile-web-app-title) |
apple_mobile_web_app_status_bar_style |
Sets the iOS status bar style (apple-mobile-web-app-status-bar-style) |
custom |
List of lists — each becomes an arbitrary <meta> tag |
bing_site_verification |
Sets Bing verification |
google_site_verification |
Sets Google Search Console verification |
yandex_site_verification |
Sets Yandex Webmaster verification |
baidu_site_verification |
Sets Baidu Webmaster verification |
naver_site_verification |
Sets Naver Webmaster verification |
facebook_domain_verification |
Sets Facebook domain verification |
pinterest_domain_verification |
Sets Pinterest domain verification |
schema |
Set to FALSE to disable JSON-LD |
If you want shared defaults across several apps, you can set these in
.Renviron and let app-level YAML override them when needed:
SHINYSEO_BING_SITE_VERIFICATIONSHINYSEO_TWITTER_SITESHINYSEO_TWITTER_CREATORUse .Renviron for values that are shared across many apps on the same
machine or deployment:
SHINYSEO_BING_SITE_VERIFICATIONSHINYSEO_TWITTER_SITESHINYSEO_TWITTER_CREATORKeep per-app values in each app's meta.yml:
titledescriptionurlimagelocale if one app differs from the shared defaultsite_name if one app needs a different display nametwitter_site or twitter_creator if one app should override the shared defaultbing_site_verification if one app needs a different Bing tokenSHINYSEO_GOOGLE_ANALYTICS_MEASUREMENT_ID is not used by shinyseo itself.
If you use GA4, keep that in your server or deployment config instead of in
meta.yml.
# global.R — generate manifest once at startup
shinyseo::write_manifest("meta.yml")
# ui.R
ui <- fluidPage(
shinyseo::social_meta("meta.yml"),
h1("My app")
)
# server.R — update metadata when the user navigates
server <- function(input, output, session) {
observeEvent(input$tabs, {
shinyseo::update_meta(session, title = paste(input$tabs, "– My App"))
})
}
You can also pass a list directly instead of a YAML file:
shinyseo::social_meta(list(
title = "Example app",
description = "A short app description.",
url = "https://example.no",
image = "https://example.no/share.png",
favicon = "/favicon.png",
theme_color = "#1a73e8"
))
The long-form package docs live in vignettes:
If the package is installed, you can also open them with browseVignettes("shinyseo").
For LLM use, see LLM.md.