{"id":43874123,"url":"https://github.com/hygraph/preview-sdk","last_synced_at":"2026-02-06T14:39:13.977Z","repository":{"id":324038350,"uuid":"1093489009","full_name":"hygraph/preview-sdk","owner":"hygraph","description":null,"archived":false,"fork":false,"pushed_at":"2025-12-12T09:42:37.000Z","size":329,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-27T18:16:28.507Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hygraph.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"agents.md","dco":null,"cla":null}},"created_at":"2025-11-10T12:50:59.000Z","updated_at":"2025-12-12T07:53:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hygraph/preview-sdk","commit_stats":null,"previous_names":["hygraph/preview-sdk"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/hygraph/preview-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hygraph%2Fpreview-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hygraph%2Fpreview-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hygraph%2Fpreview-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hygraph%2Fpreview-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hygraph","download_url":"https://codeload.github.com/hygraph/preview-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hygraph%2Fpreview-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29164929,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-06T14:37:12.680Z","status":"ssl_error","status_checked_at":"2026-02-06T14:36:22.973Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-06T14:39:13.257Z","updated_at":"2026-02-06T14:39:13.968Z","avatar_url":"https://github.com/hygraph.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hygraph Preview SDK\n\nAdd clickable edit buttons to your Hygraph content preview. Click any content element to jump directly to it in the Hygraph editor.\n\n[![npm version](https://img.shields.io/npm/v/@hygraph/preview-sdk.svg)](https://www.npmjs.com/package/@hygraph/preview-sdk)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\n## What It Does\n\nThis SDK adds interactive edit buttons to your content preview that:\n- Show up when you hover over content elements\n- Open the exact field in Hygraph when clicked\n- Automatically refresh your preview when content is saved\n- Preserve scroll position and page state during updates\n\nWorks with React, Next.js, Remix, Vue, and vanilla JavaScript.\n\n## Installation\n\n```bash\nnpm install @hygraph/preview-sdk\n```\n\n## Quick Start\n\n### Next.js (App Router)\n\n```tsx\n// app/layout.tsx\nimport { PreviewWrapper } from '@/components/PreviewWrapper';\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003chtml\u003e\n      \u003cbody\u003e\n        \u003cPreviewWrapper\u003e{children}\u003c/PreviewWrapper\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  );\n}\n```\n\n```tsx\n// components/PreviewWrapper.tsx\n'use client';\n\nimport { useRouter } from 'next/navigation';\nimport dynamic from 'next/dynamic';\n\nconst HygraphPreview = dynamic(\n  () =\u003e import('@hygraph/preview-sdk/react').then(mod =\u003e ({ default: mod.HygraphPreview })),\n  { ssr: false }\n);\n\nexport function PreviewWrapper({ children }) {\n  const router = useRouter();\n\n  return (\n    \u003cHygraphPreview\n      endpoint={process.env.NEXT_PUBLIC_HYGRAPH_ENDPOINT}\n      onSave={() =\u003e router.refresh()}\n      debug={process.env.NODE_ENV === 'development'}\n    \u003e\n      {children}\n    \u003c/HygraphPreview\u003e\n  );\n}\n```\n\n### Mark Your Content\n\nAdd `data-hygraph-*` attributes to make content editable:\n\n```tsx\n\u003carticle data-hygraph-entry-id=\"entry-123\"\u003e\n  \u003ch1\n    data-hygraph-entry-id=\"entry-123\"\n    data-hygraph-field-api-id=\"title\"\n  \u003e\n    My Article Title\n  \u003c/h1\u003e\n\n  \u003cp\n    data-hygraph-entry-id=\"entry-123\"\n    data-hygraph-field-api-id=\"content\"\n  \u003e\n    Article content here...\n  \u003c/p\u003e\n\u003c/article\u003e\n```\n\nRequired attribute:\n- `data-hygraph-entry-id`: The entry ID from Hygraph. Every element you want to make editable must include this.\n\nCommon optional attributes:\n- `data-hygraph-field-api-id`: Identifies which field to open. Without it the edit button opens the entry without focusing a field.\n- `data-hygraph-rich-text-format`: Set to `html`, `markdown`, or `text` so the SDK knows which format to update on field sync.\n- `data-hygraph-component-chain`: JSON string describing the path to nested components (see below).\n\n```tsx\n\u003cdiv\n  data-hygraph-entry-id={post.id}\n  data-hygraph-field-api-id=\"content\"\n  data-hygraph-rich-text-format=\"html\"\n  dangerouslySetInnerHTML={{ __html: post.content.html }}\n/\u003e\n```\n\n### Tagging Component Fields\n\nUse the `data-hygraph-component-chain` attribute when a field lives inside a modular component, repeatable component list, or union component. The component chain tells Studio how to navigate from the root entry to the specific nested field.\n\n#### Key Concepts\n\n| Attribute | Value | Purpose |\n|-----------|-------|---------|\n| `data-hygraph-entry-id` | Always the **root page/entry ID** | Identifies which Hygraph entry contains this content |\n| `data-hygraph-field-api-id` | The component field name in your schema | Identifies which field to open in the editor |\n| `data-hygraph-component-chain` | JSON array of `{fieldApiId, instanceId}` | Describes the path from root entry to the nested field |\n\n**Important:** `data-hygraph-entry-id` is always the root entry ID (e.g., page or article), even for deeply nested components. The component chain handles the navigation to nested fields—you never use the component's ID as the entry ID.\n\n#### What is `instanceId`?\n\nThe `instanceId` is the unique identifier for each component instance, returned by Hygraph in your GraphQL response:\n\n```graphql\nquery {\n  page(where: { id: \"page_123\" }) {\n    id                    # Root entry ID → use for data-hygraph-entry-id\n    title\n    contentSections {     # Modular component field\n      ... on HeroSection {\n        id                # ← This is the instanceId for the component chain\n        headline\n        subheadline\n      }\n      ... on FeatureGrid {\n        id                # ← instanceId\n        features {\n          id              # ← instanceId for nested components\n          title\n          description\n        }\n      }\n    }\n  }\n}\n```\n\n#### Single-Level Components\n\nFor a field inside a component list (e.g., `Page.contentSections[]`):\n\n```tsx\n{page.contentSections.map((section) =\u003e {\n  const chain = [\n    createComponentChainLink('contentSections', section.id)\n  ];\n\n  return (\n    \u003ch2\n      {...createPreviewAttributes({\n        entryId: page.id,           // Always the root page ID\n        fieldApiId: 'headline',     // Field inside the component\n        componentChain: chain,\n      })}\n    \u003e\n      {section.headline}\n    \u003c/h2\u003e\n  );\n})}\n```\n\nThe resulting HTML:\n```html\n\u003ch2\n  data-hygraph-entry-id=\"page_123\"\n  data-hygraph-field-api-id=\"headline\"\n  data-hygraph-component-chain='[{\"fieldApiId\":\"contentSections\",\"instanceId\":\"section_abc\"}]'\n\u003e\n  Welcome to Our Site\n\u003c/h2\u003e\n```\n\n#### Deeply Nested Components\n\nFor components inside other components (e.g., `Page.contentSections[].features[]`):\n\n```tsx\n{page.contentSections.map((section, sectionIndex) =\u003e (\n  \u003cdiv key={section.id}\u003e\n    {section.features?.map((feature, featureIndex) =\u003e {\n      // Build the chain: page → contentSections → features\n      const chain = [\n        createComponentChainLink('contentSections', section.id),\n        createComponentChainLink('features', feature.id)\n      ];\n\n      return (\n        \u003cdiv key={feature.id}\u003e\n          \u003ch3\n            {...createPreviewAttributes({\n              entryId: page.id,         // Still the root page ID\n              fieldApiId: 'title',\n              componentChain: chain,\n            })}\n          \u003e\n            {feature.title}\n          \u003c/h3\u003e\n          \u003cp\n            {...createPreviewAttributes({\n              entryId: page.id,\n              fieldApiId: 'description',\n              componentChain: chain,\n            })}\n          \u003e\n            {feature.description}\n          \u003c/p\u003e\n        \u003c/div\u003e\n      );\n    })}\n  \u003c/div\u003e\n))}\n```\n\n#### Union/Modular Components (Multiple Component Types)\n\nWhen a field accepts different component types, handle each type in a switch statement:\n\n```tsx\n{page.contentSections.map((section) =\u003e {\n  const chain = [createComponentChainLink('contentSections', section.id)];\n\n  switch (section.__typename) {\n    case 'HeroSection':\n      return (\n        \u003csection key={section.id}\u003e\n          \u003ch1\n            {...createPreviewAttributes({\n              entryId: page.id,\n              fieldApiId: 'headline',\n              componentChain: chain,\n            })}\n          \u003e\n            {section.headline}\n          \u003c/h1\u003e\n        \u003c/section\u003e\n      );\n\n    case 'FeatureGrid':\n      return (\n        \u003csection key={section.id}\u003e\n          \u003ch2\n            {...createPreviewAttributes({\n              entryId: page.id,\n              fieldApiId: 'gridTitle',\n              componentChain: chain,\n            })}\n          \u003e\n            {section.gridTitle}\n          \u003c/h2\u003e\n          {/* Render nested features with extended chain */}\n        \u003c/section\u003e\n      );\n\n    default:\n      return null;\n  }\n})}\n```\n\n#### Complete Example: Page with Modular Content\n\nHere's a full example showing a page template with multiple component types and nesting:\n\n```tsx\nimport {\n  createPreviewAttributes,\n  createComponentChainLink,\n} from '@hygraph/preview-sdk/core';\n\ninterface Page {\n  id: string;\n  title: string;\n  contentSections: Array\u003cHeroSection | FeatureGrid | Testimonial\u003e;\n}\n\nexport function PageTemplate({ page }: { page: Page }) {\n  return (\n    \u003cmain\u003e\n      {/* Simple field - no component chain needed */}\n      \u003ch1\n        {...createPreviewAttributes({\n          entryId: page.id,\n          fieldApiId: 'title',\n        })}\n      \u003e\n        {page.title}\n      \u003c/h1\u003e\n\n      {/* Modular content sections */}\n      {page.contentSections.map((section, index) =\u003e {\n        const sectionChain = [\n          createComponentChainLink('contentSections', section.id)\n        ];\n\n        switch (section.__typename) {\n          case 'HeroSection':\n            return (\n              \u003csection key={section.id} className=\"hero\"\u003e\n                \u003ch2\n                  {...createPreviewAttributes({\n                    entryId: page.id,\n                    fieldApiId: 'headline',\n                    componentChain: sectionChain,\n                  })}\n                \u003e\n                  {section.headline}\n                \u003c/h2\u003e\n                \u003cdiv\n                  {...createPreviewAttributes({\n                    entryId: page.id,\n                    fieldApiId: 'content',\n                    componentChain: sectionChain,\n                  })}\n                  data-hygraph-rich-text-format=\"html\"\n                  dangerouslySetInnerHTML={{ __html: section.content.html }}\n                /\u003e\n              \u003c/section\u003e\n            );\n\n          case 'FeatureGrid':\n            return (\n              \u003csection key={section.id} className=\"features\"\u003e\n                {section.features.map((feature, featureIndex) =\u003e {\n                  // Extend the chain for nested components\n                  const featureChain = [\n                    ...sectionChain,\n                    createComponentChainLink('features', feature.id)\n                  ];\n\n                  return (\n                    \u003cdiv key={feature.id} className=\"feature-card\"\u003e\n                      \u003ch3\n                        {...createPreviewAttributes({\n                          entryId: page.id,\n                          fieldApiId: 'title',\n                          componentChain: featureChain,\n                        })}\n                      \u003e\n                        {feature.title}\n                      \u003c/h3\u003e\n                      \u003cp\n                        {...createPreviewAttributes({\n                          entryId: page.id,\n                          fieldApiId: 'description',\n                          componentChain: featureChain,\n                        })}\n                      \u003e\n                        {feature.description}\n                      \u003c/p\u003e\n                    \u003c/div\u003e\n                  );\n                })}\n              \u003c/section\u003e\n            );\n\n          case 'Testimonial':\n            return (\n              \u003cblockquote\n                key={section.id}\n                {...createPreviewAttributes({\n                  entryId: page.id,\n                  fieldApiId: 'quote',\n                  componentChain: sectionChain,\n                })}\n              \u003e\n                {section.quote}\n              \u003c/blockquote\u003e\n            );\n\n          default:\n            return null;\n        }\n      })}\n    \u003c/main\u003e\n  );\n}\n```\n\n#### Using Raw HTML Attributes (Without Helpers)\n\nIf you prefer not to use the helper functions, you can write the attributes directly:\n\n```html\n\u003c!-- Single-level component --\u003e\n\u003cspan\n  data-hygraph-entry-id=\"page_123\"\n  data-hygraph-field-api-id=\"headline\"\n  data-hygraph-component-chain='[{\"fieldApiId\":\"contentSections\",\"instanceId\":\"section_abc\"}]'\n\u003e\n  Welcome\n\u003c/span\u003e\n\n\u003c!-- Deeply nested component --\u003e\n\u003cp\n  data-hygraph-entry-id=\"page_123\"\n  data-hygraph-field-api-id=\"description\"\n  data-hygraph-component-chain='[{\"fieldApiId\":\"contentSections\",\"instanceId\":\"section_abc\"},{\"fieldApiId\":\"features\",\"instanceId\":\"feature_xyz\"}]'\n\u003e\n  Feature description here\n\u003c/p\u003e\n```\n\n\u003e **Note:** Always use double quotes inside the JSON string and single quotes for the HTML attribute value to ensure valid HTML.\n\n## Framework Guides\n\n- [Next.js App Router](docs/frameworks/nextjs-app-router.md)\n- [Next.js Pages Router](docs/frameworks/nextjs-pages-router.md)\n- [Remix](docs/frameworks/remix.md)\n- [Vue 3](docs/frameworks/vue.md)\n- [Vanilla JavaScript](docs/frameworks/vanilla.md)\n\n## Configuration\n\n```tsx\n\u003cHygraphPreview\n  endpoint=\"https://your-region.cdn.hygraph.com/content/your-project-id/master\"\n  studioUrl=\"https://app.hygraph.com\"  // Optional: custom Studio URL, required if running outside of the studio interface\n  debug={true}                          // Optional: enable console logging\n  onSave={(entryId) =\u003e {               // Optional: custom save handler\n    console.log('Content saved:', entryId);\n    router.refresh();\n  }}\n  overlay={{                            // Optional: customize overlay styling\n    style: {\n      borderColor: '#3b82f6',\n      borderWidth: '2px',\n    },\n    button: {\n      backgroundColor: '#3b82f6',\n      color: 'white',\n    },\n  }}\n  sync={{\n    fieldFocus: true,                   // Optional: enable field focus sync from Studio\n    fieldUpdate: false,                 // Optional: apply live field updates\n  }}\n/\u003e\n```\n\nKey props:\n- `endpoint` (required): Hygraph Content API endpoint (with stage)\n- `studioUrl` (optional): Custom Studio domain when running outside `app.hygraph.com`\n- `debug` (optional): Enables verbose console logs to help diagnose attribute issues\n- `onSave` (optional): Runs after Hygraph reports a save; receives the Hygraph entry ID so you can target revalidation logic\n- `overlay` (optional): Customize overlay border/button appearance\n- `sync.fieldFocus` (optional): Ask Studio to focus the field inside the editor when users click an overlay\n- `sync.fieldUpdate` (optional): Opt in to live field updates from Studio (Defaults to `false`)\n- `allowedOrigins` (optional): Extend the list of domains that can host your preview iframe\n\n## How It Works\n\nThe SDK operates in two modes:\n\n**Studio Mode**: When your preview loads inside Hygraph Studio, the SDK detects it is running inside an iframe (`window.self !== window.top`). Edit buttons talk to Studio via `postMessage`, focusing the exact input in the Studio content editor.\n\n**Standalone Mode**: When your preview loads outside Studio (`window.self === window.top`), the SDK opens Studio in a new tab pointing at the same entry. The `studioUrl` option controls which Studio instance to open (defaults to `https://app.hygraph.com`).\n\nYou can force a mode with the `mode` prop (`'iframe' | 'standalone' | 'auto'`), but auto detection covers the majority of cases. If you run Studio on a custom domain, pass that URL via `studioUrl` so the SDK knows where to send editors.\n\n## Setting Up Preview URLs in Hygraph Studio\n\nTo see the SDK inside the Studio sidebar:\n\n1. Open your project at [app.hygraph.com](https://app.hygraph.com)\n2. Go to **Schema → [Choose a model] → Sidebar**\n3. Add the **Preview** widget and paste the URL to your running preview (e.g. `http://localhost:4500/preview/{entryId}`)\n4. Save the model configuration, then open an entry of that model and click **Open Preview**\n5. The preview loads inside Studio and the SDK switches to iframe mode automatically\n\n\u003e Tip: For shared preview environments (QA, staging), add additional Preview URLs that point to those deployments and ensure their origins are included in `allowedOrigins`.\n\n## API Reference\n\n### React Components\n\n- `\u003cHygraphPreview\u003e` - Main wrapper component\n- `usePreview()` - Access preview instance\n- `usePreviewSave()` - Subscribe to save events\n- `usePreviewEvent()` - Subscribe to any event\n\n### Core API\n\n```javascript\nimport { Preview } from '@hygraph/preview-sdk/core';\n\nconst preview = new Preview({\n  endpoint: 'your-endpoint',\n  debug: true,\n});\n\n// Subscribe to save events\npreview.subscribe('save', {\n  callback: (entryId) =\u003e {\n    console.log('Content saved:', entryId);\n    window.location.reload();\n  }\n});\n\n// Clean up\npreview.destroy();\n```\n\nSee the TypeScript definitions in `src/react` and `src/core` for the complete surface area. All helpers and types are exported from the package entry points.\n\n## Examples\n\nWorking examples for each framework:\n\n- [Next.js App Router](examples/nextjs-example/)\n- [Remix](examples/remix-example/)\n- [Vue 3](examples/vue-example/)\n- [Vanilla HTML](examples/vanilla-html-example/)\n\nEach example includes a complete recipe application with schema setup instructions.\n\n## Troubleshooting\n\n**Edit buttons not appearing?**\n- Check that `data-hygraph-entry-id` is set on your elements\n- Enable `debug={true}` to see console logs\n- Verify your endpoint is correct\n\n**Preview not refreshing on save?**\n- Make sure `onSave` callback is set up\n- Check that your framework refresh method is working (e.g., `router.refresh()`)\n\n**Content not updating in real-time?**\n- Real-time field updates are optional and disabled by default\n- Enable with `sync={{ fieldUpdate: true }}` if needed\n- Note: Most users only need the save event for full page refresh\n\nNeed more help? Open an issue or start a discussion in the repository.\n\n## License\n\nMIT © [Hygraph](https://hygraph.com)\n\n## Support\n\n- [Documentation](docs/)\n- [GitHub Issues](https://github.com/hygraph/preview-sdk/issues)\n- [Hygraph Support](https://hygraph.com/support)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhygraph%2Fpreview-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhygraph%2Fpreview-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhygraph%2Fpreview-sdk/lists"}