# How to add JSON-LD structured data to your website

JSON-LD (JavaScript Object Notation for Linked Data) is the format Google, Bing, and other search engines use to understand your page content. agentmarkup injects schema.org JSON-LD into your HTML pages at build time for Vite, Astro, Next.js, and Nuxt (and via the CLI for any other static build) with XSS-safe serialization and type-safe presets.

In simple terms

**Beginner to intermediate Website owners and people who build websites**  
**About 10 minutes**

Structured data is a clear label attached to a page: this is a product, an article, a company, or a frequently asked question. It helps search and AI systems interpret the page without guessing.

[Check your structured information](/checker/)

## What is JSON-LD structured data?

Structured data tells search engines exactly what your page is about. Instead of guessing from HTML content, search engines read your JSON-LD and understand that a page is a Product with a price, an Article with a publish date, or an Organization with a logo.

This powers rich results in Google Search, including star ratings, FAQ dropdowns, product cards, and knowledge panels. Without structured data, search engines can only guess at your page content.

## Built-in schema.org presets

agentmarkup includes 6 type-safe presets for common structured data types. Each preset validates required fields at build time and generates spec-compliant JSON-LD.

| Preset  | Schema type  | Use case  |
|---------|--------------|-----------|
| webSite | WebSite      | Site-level schema with optional search action |
| organization| Organization | Company or brand identity |
| article | Article      | Blog posts, news, content pages |
| faqPage | FAQPage      | Question and answer pages |
| product  | Product      | E-commerce product pages |
| offer    | Offer        | Pricing and availability |

## Using presets

Apply schemas globally (every page) or per-page. Global schemas like `webSite` and `organization` go in `globalSchemas`. Page-specific schemas go in `pages`. The schema config is adapter-agnostic, so the same object works with `@agentmarkup/vite`, `@agentmarkup/astro`, and `@agentmarkup/next`.

```
const agentmarkupConfig = {
  site: 'https://myshop.com',
  name: 'My Shop',
  globalSchemas: [
    { preset: 'webSite', name: 'My Shop', url: 'https://myshop.com' },
    { preset: 'organization', name: 'My Shop', url: 'https://myshop.com', logo: '/logo.png' },
  ],
  pages: [
    {
      path: '/faq',
      schemas: [{
        preset: 'faqPage',
        url: 'https://myshop.com/faq',
        questions: [
          { question: 'Do you ship internationally?', answer: 'Yes, to 50+ countries.' },
        ],
      }],
    },
  ],
}
```

## Framework wrappers

After defining the shared schema config, pass it into the adapter for the framework that owns your final output:

```javascript
// Vite
import { defineConfig } from 'vite'
import { agentmarkup } from '@agentmarkup/vite'

export default defineConfig({
  plugins: [agentmarkup(agentmarkupConfig)],
})

// Astro
import { defineConfig } from 'astro/config'
import { agentmarkup } from '@agentmarkup/astro'

export default defineConfig({
  integrations: [agentmarkup(agentmarkupConfig)],
})

// Next.js
import type { NextConfig } from 'next'
import { withAgentmarkup } from '@agentmarkup/next'

const nextConfig: NextConfig = {
  output: 'export',
}

export default withAgentmarkup(agentmarkupConfig, nextConfig)

// Nuxt
export default defineNuxtConfig({
  modules: ['@agentmarkup/nuxt'],
  agentmarkup: agentmarkupConfig,
})

// CLI (any other built static site)
// agentmarkup generate ./dist
```

## Custom schemas

You can use any schema.org type by passing an object with an `@type` field. agentmarkup automatically adds the `@context` and handles serialization.

```
pages: [
  {
    path: '/products/wallets',
    schemas: [{
      '@type': 'Product',
      name: 'Classic Leather Wallet',
      description: 'Full-grain leather bifold wallet.',
      image: 'https://myshop.com/images/wallet.jpg',
      sku: 'WALLET-001',
      offers: {
        '@type': 'Offer',
        price: '89',
        priceCurrency: 'USD',
        availability: 'https://schema.org/InStock',
      },
    }],
  },
]
```

## XSS-safe output

agentmarkup escapes dangerous characters (`<`, `>`, `&`, `'`) to unicode escapes before injecting JSON-LD into HTML. This prevents XSS attacks through structured data injection.

```
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"WebSite","name":"My Shop","url":"https://myshop.com"}
</script>
```

## Build-time validation

Every schema is validated during build. Missing required fields produce errors. Missing recommended fields produce warnings. You see exactly what needs fixing in your terminal before deploying.

- **Required field errors**: Product without `name`, Article without `headline`
- **Recommended field warnings**: Organization without `logo`, Product without `sku`
- **Custom schema checks**: Every custom schema must have an `@type` field

Combined with [llms.txt generation](/docs/llms-txt/), [markdown mirrors](/blog/markdown-mirrors/), and [AI crawler management](/docs/ai-crawlers/), this gives your website a fuller machine-readable surface instead of relying on structured data alone.

## Frequently asked questions

**Do I need JSON-LD if I already have meta tags?**  
Yes. Meta tags (title, description) help search engines understand a single page. JSON-LD tells them what kind of thing the page represents (a product, an article, an FAQ) with structured fields they can use for rich results.

**Can I add multiple schemas to one page?**  
Yes. Use the `pages` config to add multiple schemas per path. Each schema generates its own `<script type="application/ld+json">` tag. Global schemas are also added to every page.

**What if I need a schema type that is not a preset?**  
Pass any object with an `@type` field. agentmarkup will add `@context` automatically and serialize it safely. Presets just save you from remembering required fields.
