Skip to content
Skip to main contentWhere does your team stand on AI adoption?
CONTACT SALESSTART BUILDING

Serve Builder data across apps

Create a data model in Builder and serve the same data to both Next.js and Flutter applications. Builder data models deliver JSON through the Content API, providing data across multiple platforms and frameworks.

This tutorial shows how to create a Data model to store location data and serve the information across web and mobile apps.

Prerequisites

To get the most out of this tutorial, you need:

  • A Builder account and public API key.
  • A running Next.js and Flutter apps.
  • Basic understanding of REST APIs and JSON.
  • Familiarity with Builder Data models and the Data tab.

Final product

The video below shows the entire process of creating a Data model in Builder, creating Content entries, and serving the same data across both Next.js and Flutter apps:

Create and integrate data with Next.js

Step 1: Create a Data model

Create a Data model to store location entries:

  1. Go to Models and click + Create Model.
  2. Choose Data from the model type options.
  3. In the Name field, add a name for the model.
  4. In the Description field, add a short description of the model.
  5. Click Create.

Step 2: Add Fields to the Data model

Add fields to structure the location data:

  1. Go to the Fields tab within your Data model.
  2. Add a name field with type Text.
  3. Click Save.

Repeat the above steps to create additional fields according to the use case and select the relevant Type from the list.

Step 3: Create content entries

Use the new Data model to create Content entries:

  1. Go to Content and click + New Entry.
  2. Select your model from the available options.
  3. Give your entry a descriptive name. For example, New York.
  4. Fill in the form fields and click Publish.

Repeat the process for as many entries as needed.

Step 4: Integrate Builder in Next.js

Install the Builder React SDK:

npm install @builder.io/sdk-react

Add your API key to .env.local:

NEXT_PUBLIC_BUILDER_API_KEY=your_builder_api_key_here

Configure Next.js for Builder

To load images from Builder's CDN in your application, configure Next.js to allow images from Builder's domain.

Update your next.config.ts to access Builder images:

import type { NextConfig } from "next"; const nextConfig: NextConfig = { images: { remotePatterns: [ { protocol: "https", hostname: "cdn.builder.io", }, ], }, }; export default nextConfig;

Create a route for displaying content

The example below shows how to combine static content with dynamic data from Builder. You fetch model data and make the data available to content entries in the Visual Editor.

Create app/stores/page.tsx:

import { fetchEntries, fetchOneEntry } from "@builder.io/sdk-react"; import { Content } from "@builder.io/sdk-react"; import styles from "./stores.module.css"; export default async function Stores() { // Fetch store data from your Builder Data model const storeData = await fetchEntries({ apiKey: process.env.NEXT_PUBLIC_BUILDER_API_KEY!, model: /* YOUR BUILDER MODEL NAME */, }); // Fetch page content from Builder like sections and components const pageContent = await fetchOneEntry({ apiKey: process.env.NEXT_PUBLIC_BUILDER_API_KEY!, model: /* YOUR BUILDER MODEL NAME */ }); return ( <div className={styles.page}> {/* Static hero section - controlled by your code */} <section className={styles.hero}> <div className={styles.heroContent}> <div className={styles.heroOverlay}> <h1 className={styles.heroTitle}>Our Store Locations</h1> <p className={styles.heroSubtitle}> Visit us in person at any of our locations </p> </div> </div> </section> {/* Dynamic Builder content with store data */} <Content content={pageContent} apiKey={process.env.NEXT_PUBLIC_BUILDER_API_KEY!} model="ecom-store" data={{ stores: storeData }} // Makes store data available in Builder /> </div> ); }

The example above shows several important concepts:

  • fetchEntries(): retrieves all content entries from your store-locations data model.
  • fetchOneEntry(): fetches page content from Builder such as layouts and sections.
  • data prop: passes store data to Builder components, making the data available in the Visual Editor.
  • Hybrid approach: combines static React components with dynamic Builder content.

For more information, see Data Binding.

Add styling

Create app/stores/stores.module.css:

.page { min-height: 100vh; background: var(--background); } .hero { position: relative; width: 100%; height: 400px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; overflow: hidden; } .heroContent { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } .heroOverlay { text-align: center; color: white; z-index: 2; max-width: 600px; padding: 0 20px; } .heroTitle { font-size: 3.5rem; font-weight: 700; margin-bottom: 1rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .heroSubtitle { font-size: 1.25rem; font-weight: 300; opacity: 0.9; margin: 0; } @media (max-width: 768px) { .hero { height: 300px; } .heroTitle { font-size: 2.5rem; } .heroSubtitle { font-size: 1.1rem; } }

To run your Next.js application:

npm run dev

Set up Element data bindings

Your store data is now available through the data prop. Bind data to content in the Visual Editor:

  1. Go to Content in the Builder dashboard.
  2. Click + New Entry and select the Section model from the dropdown.
  3. Click the Text block to bind the data.
  4. In the Data tab, expand Element data bindings, select your store data from the FROM dropdown, and select Text from the GET dropdown.
  5. The element displays dynamic content from your store's Data model.

The data prop provides dynamic access to your Next.js data in the Visual Editor. The video below demonstrates this data binding workflow:

Fetch Builder data in Flutter

Install Flutter if not already installed, then create a new project. For more information, see the Flutter installation guide.

Step 1: Set up the Flutter project

Create a new Flutter project:

flutter create store_locations_app

Open the Flutter project in your IDE:

cd store_locations_app

Add the HTTP package to your pubspec.yaml:

dependencies: flutter: sdk: flutter ... http: ^1.1.0 #add this line to add HTTP package

Install dependencies:

flutter pub get

Step 2: Create API constants and data models

This step sets up the foundation for communicating with Builder's API. Create constants for the API endpoint and models to structure the data your app receives.

Create API constants

These constants centralize your Builder API configuration, making the configuration easy to manage and update your API endpoints.

Create lib/constants/api_constants.dart:

/// API constants for Builder integration class ApiConstants { // Private constructor to prevent instantiation ApiConstants._(); /// Builder API endpoint for store locations static const String storeLocationsUrl = 'https://cdn.builder.io/api/v3/content/store-locations?apiKey=YOUR_API_KEY_HERE'; }

Create data models

These models define the structure of your store location data and handle converting JSON responses from Builder's API into Dart objects that your Flutter app can work with.

Create lib/models/store_location.dart:

/// Model for store location data from Builder API class StoreLocation { final String name; final String image; final String address; const StoreLocation({ required this.name, required this.image, required this.address, }); /// Create StoreLocation from API JSON response factory StoreLocation.fromJson(Map<String, dynamic> json) { final data = json['data'] as Map<String, dynamic>; return StoreLocation( name: data['name'] as String, image: data['image'] as String, address: data['address'] as String, ); } }

Create lib/models/api_response.dart:

import 'store_location.dart'; /// Model for Builder API response class ApiResponse { final List<StoreLocation> results; const ApiResponse({required this.results}); /// Create ApiResponse from JSON factory ApiResponse.fromJson(Map<String, dynamic> json) { final resultsJson = json['results'] as List<dynamic>; final results = resultsJson .map((item) => StoreLocation.fromJson(item as Map<String, dynamic>)) .toList(); return ApiResponse(results: results); } }

Step 3: Create the service and UI components

This step builds the core functionality: a service to fetch data from Builder and UI components to display that data in your app.

Create a service

This service handles the HTTP communication with Builder's Content API, fetching your store location data and handling any network errors that might occur.

Create lib/services/store_service.dart:

import 'dart:convert'; import 'package:http/http.dart' as http; import '../models/api_response.dart'; import '../models/store_location.dart'; import '../constants/api_constants.dart'; /// Service to fetch store locations from Builder API class StoreService { /// Fetch store locations from the API Future<List<StoreLocation>> fetchStoreLocations() async { try { // Make HTTP request final response = await http.get( Uri.parse(ApiConstants.storeLocationsUrl), ); if (response.statusCode == 200) { // Parse JSON response final jsonData = json.decode(response.body) as Map<String, dynamic>; final apiResponse = ApiResponse.fromJson(jsonData); return apiResponse.results; } else { throw Exception('Failed to load store locations'); } } catch (e) { throw Exception('Error fetching data: $e'); } } }

Create the UI widget

This reusable widget displays individual store locations in a consistent card format throughout your app.

Create lib/widgets/store_location_card.dart:

import 'package:flutter/material.dart'; import '../models/store_location.dart'; /// Card widget to display store location information class StoreLocationCard extends StatelessWidget { final StoreLocation location; final VoidCallback? onTap; const StoreLocationCard({super.key, required this.location, this.onTap}); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.only(bottom: 12.0), child: ListTile( leading: const CircleAvatar( backgroundColor: Colors.blue, child: Icon(Icons.location_city, color: Colors.white), ), title: Text( location.name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), subtitle: Text(location.address), trailing: onTap != null ? const Icon(Icons.arrow_forward_ios) : null, onTap: onTap, ), ); } }

Step 4: Create the main screen

The screen uses the service to fetch data from the Builder Data model and displays data using your UI components with loading states and error handling.

Create lib/screens/store_list_screen.dart:

import 'package:flutter/material.dart'; import '../models/store_location.dart'; import '../services/store_service.dart'; import '../widgets/store_location_card.dart'; /// Main screen that displays store locations from Builder class StoreListScreen extends StatefulWidget { const StoreListScreen({super.key}); @override State<StoreListScreen> createState() => _StoreListScreenState(); } class _StoreListScreenState extends State<StoreListScreen> { final StoreService _storeService = StoreService(); List<StoreLocation> _storeLocations = []; bool _isLoading = true; String? _errorMessage; @override void initState() { super.initState(); _loadStoreLocations(); } /// Load store locations from Builder API Future<void> _loadStoreLocations() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final locations = await _storeService.fetchStoreLocations(); setState(() { _storeLocations = locations; _isLoading = false; }); } catch (e) { setState(() { _errorMessage = e.toString(); _isLoading = false; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Store Locations'), centerTitle: true, backgroundColor: Theme.of(context).colorScheme.inversePrimary, ), body: _buildBody(), floatingActionButton: FloatingActionButton( onPressed: _loadStoreLocations, tooltip: 'Refresh', child: const Icon(Icons.refresh), ), ); } /// Build the main content based on current state Widget _buildBody() { if (_isLoading) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('Loading store locations...'), ], ), ); } if (_errorMessage != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 64, color: Colors.red), const SizedBox(height: 16), Text(_errorMessage!), const SizedBox(height: 24), ElevatedButton( onPressed: _loadStoreLocations, child: const Text('Retry'), ), ], ), ); } if (_storeLocations.isEmpty) { return const Center( child: Text('No store locations found'), ); } return ListView.builder( padding: const EdgeInsets.all(16), itemCount: _storeLocations.length, itemBuilder: (context, index) { final location = _storeLocations[index]; return StoreLocationCard( location: location, onTap: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Selected: ${location.name}')), ); }, ); }, ); } }

Step 5: Update main.dart

This final step configures your app's entry point to display the store locations screen you just created.

Update your lib/main.dart:

import 'package:flutter/material.dart'; import 'screens/store_list_screen.dart'; void main() { runApp(const StoreLocationsApp()); } class StoreLocationsApp extends StatelessWidget { const StoreLocationsApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Store Locations Demo', debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), home: const StoreListScreen(), ); } }

Your Flutter application now displays store location data from the Data model with a native interface. To run your Flutter app:

For iOS:

flutter run -d ios

For Android:

flutter run -d android

For web:

flutter run -d web

Make sure you have the appropriate simulators or devices connected before running these commands.

Test the changes

Test how changes in Builder appear across both applications:

  1. Edit a store location in Builder and click Publish.
  2. Refresh your Next.js app and Flutter app to see the changes.

Both applications now display the same updated content from your central Builder data source.

What's next

You now have data serving across multiple applications with consistent content delivery. Consider these next steps:

For advanced patterns, see Custom Components and Content Models.

Was this article helpful?