Webiny is customizable open-source content platform for enterprises. It features a drag&drop page builder, a scalable headless CMS, digital asset manager, publishing workflows and more.

© 2026 Webiny, Inc. All rights reserved.
Webiny is an open-source, developer-friendly CMS that is supported by cutting-edge tools and technologies like Node, React, and a GraphQL API. You have the option to integrate with tools like Gatsby, Nextjs, and Astro through the Content Delivery API. Webiny supports developers in the design, development, and deployment of applications on top of the serverless infrastructure.
Developers can create and run applications without having to worry about managing servers thanks to Webiny's integrated page builder, form builder, file manager, admin area, and headless CMS, which runs on top of AWS Lambda, DynamoDB, and S3. Webiny provides all the tools you need, all of which are optimised to work together seamlessly and performantly, allowing users to build websites and web applications in both large and small projects without the hassle.
React Native is a mobile app development framework. It allows you to create apps that run on Android and iOS using the React framework. With React Native you write code that can be shared across Android and iOS platforms.
In this article, we will create a notes app using React Native and Webiny Headless CMS.
To follow along with this tutorial, we’re going to assume you have already done the following:
To create a Webiny project, you need an AWS account and Node.js installed on your system.
Go to the directory where you want to set up your project, open the terminal, and type in the command:
npx create-webiny-project [your project name]
And run the following command to deploy the project:
yarn webiny deploy
After the deployment is complete, you will be presented with the URL for your Webiny project, where you can enter the Admin Dashboard and begin developing the project's backend.
You can find out all the relevant URLs at any time by running the following command inside the Webiny project directory:
yarn webiny info
Also, the in-depth guide on Webiny installtion can be found here.
Once installation and deployment are done, you can access Admin and start managing and modeling your CMS content models immediately.

Click on NEW CONTENT MODEL on dashboard to create your models. Or navigate NEW CONTENT MODEL > CONTENT MODELS > Models in side menu.

A New Content Model page will be launched. Click NEW MODAL.

The Note model will have following fiedls:
Text typeLong Text typeTo add these fields to the model, drag and drop them from your panel, ensuring you select them based on the data type each field will hold.

Here is an example of adding the title field in Note model:

Once the content model is created, go ahead and add a few record entries by clicking NEW ENTRY:

Ensure you add several new entries using the above case and an example.
To securely access content from Webiny Headless CMS, we will create an API Access Token. With this token, you can consume your content from any frontend tools of your choice.
Navigate to Settings and create a NEW API KEY as follows:

Provide API key details, ensuring:


Once you SAVE API KEY, a token will be generated, and copy its content. This key will be used alongside React Native to access the CMS.
Finally, you need an API endpoint to consume your data. Webiny provides that for you. Navigate to API Playground

Click Headless CMS Read API and copy the URL endpoint for reading Webiny CMS data.

Click Headless CMS Manage API and copy the URL endpoint for changing Webiny CMS data.

To create a React Native, we will use Expo. It provides tools and services that make it easy to develop React Native apps. This means you don't need to install and configure native build tools such as Xcode or Android Studio.
With Expo, you can use a single codebase to build mobile apps for iOS, Android, and the web. Proceed to your preferred working directory. Run the following command to bootstrap the React Native Expo application:
npx create-expo-app notes_app
Once the installation process is complete, we will need to install the following packages for navigation.
npx expo install @react-navigation/native @react-navigation/stack react-native-gesture-handler react-native-safe-area-context
# On ios device
npx expo run ios
# On ios android
npm run android
We have created variables we need to use in the React Native app. This includes the API Access Key. To use these constants in React Native, we need to create environmental variables so that the application can access them where necessary.
Expo provides a few different ways to handle environmental variables in your React Native app. They are:
.env file with the expo-dotenv packageExpo.Constants.manifest.extra object..json file with the expo-constants packageFor loading environmental variables, we will use the expo-constants package. Go ahead and install the package using the following command:
npx expo install expo-constants
On the project root folder, create an app.config.js file. Edit the file as follows:
module.exports = {
name: 'NotesApp',
version: '1.0.0',
extra: {
manageUrl: "your_manage_url",
readUrl:"your_read_url",
apiKey:"your_api_key"
},
};
Update the above dynamic variables as per your Webiny credentials.
Using the above Webiny environmental variables, let’s connect the CMS to React Native to perform mutations and queries based on the Webiny generated GraphQL endpoints.
On the project root directory, create a store directory. Inside it, create a store.js file. The file will host the application's context. Edit the file as follows, step by step:
import React,{createContext, useState} from 'react';
import Constants from 'expo-constants';
const AppContext = createContext();
AppContextProvider function:const AppContextProvider = ({children}) => {
}
In React, the createContext function is used to create a ReactContext object. This object is used to pass data through the component tree without having to pass props down manually at every level. In his case, we are creating the AppContextProvider function to handle this instance to get data from Webiny and to pass them down through the component tree.
Inside the AppContextProvider, configure the createContext as follows:
The state can be managed within the component itself and updated using the setState method. In this example, we will define the application as follows:
const [notes,setNotes] = useState([]);
const [loading,setLoading] = useState(false);
const [error,setError] = useState('');
const apiKey = Constants.manifest.extra.apiKey;
In the above example, we create states to hold the notes data. To get notes from Webiny, we will create the getNotes method to fetch the notes data as follows:
const getNotes = async() => {
setLoading(true);
setError('');
try{
let url = Constants.manifest.extra.readUrl;
let headers = {
"content-type":"application/json",
"Authorization": `Bearer ${apiKey}`};
let graphqlQuery = {
"operationName" : "fetchNotes",
"query": `query fetchNotes{
listNotes{
data{
id
title
content
published
}
}
}`,
"variables":{}
};
const options = {
"method":"POST",
"headers":headers,
"body":JSON.stringify(graphqlQuery)
};
let response = await fetch(url,options);
let data = await response.json();
setNotes(data.data.listNotes.data);
setLoading(false);
setError('');
}catch(e){
setLoading(false);
setError(new Error(e).message);
}
}
Just as getting notes Webiny, we need to create notes and save them to the CMS. The below addNote() method will help add data to Webiny based on the data we have created on the CMS dashboard:
const addNote = async (title,content) => {
setLoading(true);
setError('');
try{
let url = Constants.manifest.extra.manageUrl;
let headers = {
"content-type":"application/json",
"Authorization": `Bearer ${apiKey}`};
let graphqlQuery = {
"operationName":"addNote",
"query":`mutation addNote($title:String!,$content:String!){
createNote(data:{title:$title,content:$content}){
data{
id
title
content
}
}
}`,
"variables":{
"title":title,
"content":content
}
};
const options = {
"method":"POST",
"headers":headers,
"body":JSON.stringify(graphqlQuery)
};
let response = await fetch(url,options);
let data = await response.json();
setLoading(false);
setError('');
return data.data.createNote.data;
}catch(error){
setLoading(false);
setError(new Error(error).message);
}
}
Any note published on Webiny CMS can be deleted on the front-end. Using React Native, below is how we can send a request to delete a note based on the specific note id:
const deleteNote = async (id) => {
setLoading(true);
setError('');
try{
let url = Constants.manifest.extra.manageUrl;
let headers = {
"content-type":"application/json",
"Authorization": `Bearer ${apiKey}`};
let graphqlQuery = {
"operationName":"deleteNote",
"query":`mutation deleteNote($id:ID!){
deleteNote(revision:$id){
data
}
}`,
"variables":{
"id":id,
}
};
const options = {
"method":"POST",
"headers":headers,
"body":JSON.stringify(graphqlQuery)
};
let response = await fetch(url,options);
let data = await response.json();
setLoading(false);
setError('');
if(data.data.deleteNote.data){
setNotes(notes.filter(note => note.id !== id));
}
return data.data.deleteNote.data;
}catch(error){
setLoading(false);
setError(new Error(error).message);
}
}
return (
<AppContext.Provider value={{notes,setNotes,loading,setLoading,error,setError,getNotes,addNote,deleteNote}}>
{children}
</AppContext.Provider>
)
AppContext, and AppContextProvider so that the rest of the application can be able to access all the methods we have created:export {
AppContext,
AppContextProvider
}
To allow React Native to use these changes, import the AppContextProvider in App.js:
import {AppContextProvider} from './store/store';
Wrap each component rendered from the App() with the provider:
<AppContextProvider>
// rest of the components
</AppContextProvider>
In React Native, a screen is typically implemented as a component rendered to the device's screen. This will allow us to create the components that will execute our methods, such as getting the notes and displaying them, a component for adding a new note based on addNote etc.
Inside the project folder, create a screens directory. Inside the directory, create two files:
Home.js - This screen will display the notes.AddNote.js - We will use this screen to create a component to add new notes to Webiny CMS.To execute these screens, use React Native navigation to control the screen interactions. To do so, edit App.js as follows:
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Home from './screens/Home';
import AddNote from './screens/AddNote';
import {AppContextProvider} from './store/store';
const Stack = createStackNavigator();
return (
<AppContextProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name="Home" component={Home} options={{title:"Home"}}/>
<Stack.Screen name="AddNote" component={AddNote} options={{title:"AddNote"}} />
</Stack.Navigator>
</NavigationContainer>
</AppContextProvider>
);
Using the screens/Home.js we have created above, let’s fetch the notes from Webiny:
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View,Button } from 'react-native';
import { useContext, useEffect } from 'react';
import { AppContext } from '../store/store';
export default function App({navigation}) {
}
const {notes,getNotes,loading,error} = useContext(AppContext);
useEffect( () => {
getNotes();
},[]);
return (
<View style={styles.container}>
{
loading && <Text>Loading...</Text>
}
{
error && <Text>{error}</Text>
}
{
notes && notes.length > 0 && notes.map((note,index) => (
<View key={index} style={styles.notesCard}>
<Text style={styles.notesTitle}>{note.title}</Text>
<Text style={styles.notesSubTitle}>{note.content}</Text>
</View>
))
}
<Button title="Add Note" onPress={ () => navigation.navigate("AddNote") } />
<StatusBar style="auto" />
</View>
);
const styles = StyleSheet.create({
container: {
padding:20
},
notesCard:{
margin:10,
shadowColor:"#fff",
shadowOpacity:1
},
notesTitle:{
fontWeight:"bold"
},
notesSubTitle:{
fontWeight:"300"
}
});

These changes go to screens/AddNote.js as follows:
import { StatusBar } from 'expo-status-bar';
import { useState,useContext } from 'react';
import { StyleSheet, Text, View,TextInput,Button } from 'react-native';
import { AppContext } from '../store/store';
export default function App({navigation}) {
}
Inside the render function:
const [title,setTitle] = useState('');
const [content,setContent] = useState('');
const [formError,setFormError] = useState('');
const {addNote,publishNote,loading,error} = useContext(AppContext);
const handleAddNote = async () => {
setFormError('');
if(title && content){
try{
let response = await addNote(title,content); // add a note
let publishResponse = await publishNote(response.id); // publish the added note
if(publishResponse.id){
setTitle('');
setContent('');
navigation.navigate('Home'); // redirect to home
}
}catch(error){
setFormError(new Error(error).message);
}
}else{
setFormError("Title and Content are required");
}
}
return (
<View style={styles.container}>
{
formError && <Text>Form Error : {formError}</Text>
}
{
error && <Text>Error adding note : {error} </Text>
}
<TextInput
placeholder='Title of note'
value={title}
onChangeText={text => setTitle(text)}
/>
<TextInput
placeholder='Content of note'
value={content}
onChangeText={text => setContent(text)}
/>
<Button title={
loading ? "Loading" : "Submit"
} onPress={() => handleAddNote() } />
<StatusBar style="auto" />
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

Using the screens/Home.js we have created above, let’s delete a note from Webiny:
deleteNote function from context:const {notes,getNotes,loading,error,deleteNote} = useContext(AppContext);
const handleDeleteNote = async (id) => {
let res = await deleteNote(id);
if(res){
console.log("done");
}else{
console.log("an error occurred");
}
}
{
notes && notes.length > 0 && notes.map((note,index) => (
<View key={index} style={styles.notesCard}>
<Text style={styles.notesTitle}>{note.title}</Text>
<Text style={styles.notesSubTitle}>{note.content}</Text>
<Button title="Delete note" color="red" onPress={() => handleDeleteNote(note.id)} />
</View>
))
}
<p data-line="636" class="sync-line" style="margin:0;"></p>

We have successfully created a React Native notes app that is consuming data form Webiny Headless CMS 🚀. In this article we learned how to setup and use the Webiny CMS, configure content models and consuming data from React Native app.
Full source code: https://github.com/webiny/write-with-webiny/tree/main/tutorials/react-native-notes-app
This article was written by a contributor to the Write with Webiny program. Would you like to write a technical article like this and get paid to do so? Check out the Write with Webiny GitHub repo.