Four Things Your React App Actually Needs to Do
Fetch content, take a form submission, answer a visitor's question, and respect their cookie choice. All four are plain HTTP calls except consent, which gets a real React hook. Every example on this page is copy-pasteable.
CONTENT LAYER
No SDK to Learn for Content
Some headless CMS platforms ship a client library you have to install, version, and learn a query syntax for. Zunoy's content layer is REST returning JSON, so it works with fetch, Axios, or React Query exactly as any other API would — nothing proprietary sitting between your components and your content.
SDK-dependent CMS
Learn a client library's query syntax and generated types
SDK version has to track platform releases
Locked into whatever the SDK's author chose to support
One SDK trying to cover every use case adequately
Plain fetch, or the data layer you already use
Nothing to version — it's an HTTP endpoint
The full REST API, every parameter available directly
One real React package for the one thing worth wrapping — consent
CONTENT
Fetch a Content Type in Four Lines
An API key and a GET request return typed JSON matching the fields you defined in the admin. No client library, no code generation step, and no defensive parsing — the schema is enforced before content is ever stored.
One fetch call per content type, standard REST semantics
Response fields match exactly what you defined in the content type
Works identically in a client component or a data-loading hook
Reading Content
Two ways to call the same endpoint — a raw fetch you can drop in anywhere, or a React Query hook if you already cache requests that way.
BlogList.jsx
function BlogList() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch("https://api.zunoy.cms/v1/post", {
headers: { "X-Zunoy-Key": import.meta.env.VITE_ZUNOY_KEY }
})
.then((res) => res.json())
.then((data) => setPosts(data.entries));
}, []);
return posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
));
}FORMS
A Contact Form, Submitted With No Third-Party Tool
Every form lives on a branch, so the endpoint takes both in its path. Post JSON or let a plain HTML form submit itself — either way, the submission lands in the same admin as your content, with no separate form-tool account to create.
Branch-scoped endpoint
URL shape: POST /v1/forms/:branch/:slug
Two ways in
Accepts JSON from a controlled component, or a bare form post
Isolated per branch
Submissions are isolated per branch, never merged or shared
Submitting a Form
Post JSON from a controlled component, or skip JavaScript entirely and let the browser submit a plain HTML form straight to the endpoint.
ContactForm.jsx
function ContactForm() {
const [status, setStatus] = useState("idle");
async function handleSubmit(e) {
e.preventDefault();
setStatus("sending");
const data = Object.fromEntries(new FormData(e.target));
const res = await fetch(
"https://api.zunoy.cms/v1/forms/main/contact-us",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Zunoy-Key": import.meta.env.VITE_ZUNOY_KEY
},
body: JSON.stringify(data)
}
);
setStatus(res.ok ? "sent" : "error");
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<textarea name="message" required />
<button disabled={status === "sending"}>
{status === "sending" ? "Sending…" : "Send"}
</button>
</form>
);
}AI SEARCH
A Search Bar That Answers, Not Just Links
The public ask endpoint returns a generated answer plus the source entries it drew from, so a search box can behave like a real answer rather than a results list. It runs entirely on published content — nothing you haven't shipped can be surfaced.
GET or POST /v1/_ask
Meters one AI credit per question
Cited sources
Every response includes cited sources, not just an answer
/v1/_search available
Raw semantic matches if you want them instead
Adding an Ask Bar
One hook wires a question in and a sourced answer out — no separate results-list UI to build.
AskBar.jsx
function AskBar() {
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState(null);
async function ask(e) {
e.preventDefault();
const res = await fetch(
`https://api.zunoy.cms/v1/_ask?q=${encodeURIComponent(question)}`,
{ headers: { "X-Zunoy-Key": import.meta.env.VITE_ZUNOY_KEY } }
);
setAnswer(await res.json());
}
return (
<>
<form onSubmit={ask}>
<input
value={question}
onChange={(e) => setQuestion(e.target.value)}
placeholder="Ask about our product…"
/>
</form>
{answer && (
<div>
<p>{answer.answer}</p>
<ul>
{answer.sources.map((s) => (
<li key={s.id}><a href={s.url}>{s.title}</a></li>
))}
</ul>
</div>
)}
</>
);
}A Real React Hook, Not a Third Wrapper Around a Script Tag
ConsentProvider
Wraps your app once, at the root
useConsent()
Gives you needsConsent, isGranted, acceptAll, rejectAll
Region resolution
Region resolution and script gating are handled inside the package
Wiring Up Consent
Wrap the app once with the provider, then build whatever banner UI you want on top of the hook's state.
App.jsx
import { ConsentProvider } from "@zunoy/consent";
function App({ children }) {
return (
<ConsentProvider
options={{
baseUrl: "https://api.zunoy.cms/v1/cms",
apiKey: import.meta.env.VITE_ZUNOY_KEY
}}
>
{children}
</ConsentProvider>
);
}AT A GLANCE
What You're Actually Wiring Up
Four integration points, four different shapes. Three are plain HTTP; one gets a real package because state management is worth not reinventing.
Content
GET requests, typed JSON, no SDK required.
Forms
POST to a branch-scoped endpoint, JSON or plain HTML.
AI Ask
A question in, an answer with sources out.
Consent
A provider and a hook — the one real package on this page.
Common Questions
Do I need to install a Zunoy SDK to fetch content in React?
No. Content is served as plain REST JSON, so a standard fetch call or your existing data-fetching library works without installing anything Zunoy-specific.
Can I build a custom AI search or ask experience?
Yes. The public _ask endpoint returns a generated answer with cited sources, and _search returns raw semantic matches if you want to build your own results list instead of an answer format.
Does the consent package block scripts automatically?
Yes, when used with the vendor script helpers the package exports (injectVendorScript, syncVendorScripts). Configured vendor scripts stay out until the visitor grants that category.
How do I submit a form from React without a third-party form tool?
POST JSON or a browser-native form submission to /v1/forms/:branch/:slug with your API key. The submission is stored in the Zunoy admin alongside your content, no separate service required.
Is there a real package for cookie consent, or do I build it myself?
There's a real package, @zunoy/consent, with a ConsentProvider and a useConsent hook. It handles fetching your region-aware banner config and tracking grants — you render the banner markup yourself.
Are form submission webhooks or spam filtering available yet?
Not yet. Both are in active development. Submissions currently raise an in-app notification instead of a webhook, and abuse is bounded by a monthly quota rather than content-level spam filtering.
Wire Up All Four in an Afternoon
Content, forms, search, and consent — copy the examples above and swap in your own workspace key.