Build a complete pet adoption marketplace in FlutterFlow connecting shelters with potential adopters. You will create Firestore collections for pets, shelters, and adoption applications, a browseable pet listing with species, breed, size, and age filters, a pet detail page with a photo gallery and shelter info, an adoption application questionnaire, a shelter admin panel for reviewing applications and managing pet listings, and user profiles with a favorites watchlist. The platform uses application-based matching rather than direct purchase.
Connect shelter pets with loving homes through a browse, apply, and adopt workflow
This tutorial builds a pet adoption platform where shelters list available pets and potential adopters browse, save favorites, and submit adoption applications. Unlike a marketplace where users buy products, adoption is application-based — adopters fill out a questionnaire about their living situation, experience, and preferences, and shelters review and approve applications. The platform includes a browseable pet listing with rich filters, individual pet detail pages with photo galleries, an adoption application form, shelter admin tools for managing listings and reviewing applications, and user profiles with a favorites watchlist.
Prerequisites
- A FlutterFlow project with Firebase/Firestore connected and Authentication enabled
- Firebase Storage enabled for pet photo uploads
- Basic understanding of Firestore collections, Backend Queries, and Action Flows
- Familiarity with FlutterFlow's ListView and GridView widgets
Step-by-step guide
Design the Firestore data model for pets, shelters, and applications
Design the Firestore data model for pets, shelters, and applications
Create three Firestore collections. First, pets with fields: shelterId (String, references shelter doc), name (String), species (String: Dog, Cat, Rabbit, Bird, Other), breed (String), age (String: Puppy/Kitten, Young, Adult, Senior), gender (String: Male, Female), size (String: Small, Medium, Large), description (String, 200-500 chars), photoUrls (Array of Strings, storage URLs), status (String: available, pending, adopted), medicalHistory (String), isSpayed (Boolean), location (String, city/state). Second, shelters with fields: name (String), address (String), phone (String), email (String), websiteUrl (String), logoUrl (String), adminUserIds (Array of Strings). Third, adoption_applications with fields: petId (String), petName (String, denormalized for display), applicantUserId (String), applicantName (String), status (String: pending, approved, rejected), housingType (String), hasYard (Boolean), hasPets (Boolean), experience (String), reason (String), submittedAt (Timestamp). Add sample data: 2 shelters with 8-10 pets across species, with 3-4 photos each uploaded to Firebase Storage. Set most pets to status available.
Expected result: Three Firestore collections exist with sample data. Pets have multiple photos in Firebase Storage. Composite indexes are created for pet queries with species + status filters.
Build the pet browse page with multi-filter ChoiceChips
Build the pet browse page with multi-filter ChoiceChips
Create a page called BrowsePets. At the top, add a Column with three rows of ChoiceChips for filtering. First row: Species ChoiceChips with options All, Dog, Cat, Rabbit, Bird, bound to Page State variable selectedSpecies (String, default 'All'). Second row: Size ChoiceChips with All, Small, Medium, Large, bound to selectedSize. Third row: Age ChoiceChips with All, Puppy/Kitten, Young, Adult, Senior, bound to selectedAge. Below the filters, add a Backend Query that fetches from the pets collection where status equals 'available'. Apply additional where clauses conditionally: if selectedSpecies is not 'All', add where species equals selectedSpecies (and similarly for size and age). Order by name ascending. Display results in a GridView with crossAxisCount 2 and spacing 12. Each grid child is a Container with rounded corners and a Column: an Image widget (first item from photoUrls array, height 150, fit cover, borderRadius top 12), a Padding with a Column containing name in titleSmall bold, breed in bodySmall, and a Row with age and size in caption style with secondary color. Important: the default query MUST filter status equals 'available' so adopted pets never appear in browse results.
Expected result: The browse page shows a grid of available pets with photos, names, breeds, and age/size info. ChoiceChips filter the results by species, size, and age in real-time.
Create the pet detail page with photo gallery and shelter info
Create the pet detail page with photo gallery and shelter info
Create a page called PetDetail with a Page Parameter petDocRef (DocumentReference to the pets collection). Add a Backend Query for the pet document using petDocRef. At the top, add a PageView widget bound to the photoUrls array, with height 300 and borderRadius 0. Enable autoplay false and show dots indicator below. Below the gallery, add a Padding (16px) wrapping a Column. First child: a Row with the pet name in headlineSmall bold and an IconButton (heart icon) for favoriting. On tap, toggle the pet in the user's favorites list (a subcollection under the user document or an array field). Use Conditional Styles: filled red heart if already favorited, outlined grey if not. Second child: a Row of Chip widgets showing species, breed, age, size, and gender — each in a Container with rounded corners and a light background. Third child: a Divider, then the description Text in bodyMedium. Fourth child: a section for medical info — isSpayed shown as a Chip (Spayed/Neutered or Not Yet), and medicalHistory text. Fifth child: query the shelters collection using the pet's shelterId to display shelter name, address, phone, and a 'Visit Website' button (Launch URL). At the bottom, add a prominent ElevatedButton 'Apply to Adopt' that navigates to the AdoptionApplication page, passing petDocRef and petName as parameters. Hide this button if the pet status is not 'available'.
Expected result: The detail page shows a swipeable photo gallery, pet info chips, description, medical history, shelter details, and a prominent Apply to Adopt button. Users can favorite the pet.
Build the adoption application questionnaire form
Build the adoption application questionnaire form
Create a page called AdoptionApplication with Page Parameters petDocRef (DocumentReference) and petName (String). Display the petName at the top so applicants know which pet they are applying for. Build a form Column with the following fields: a TextField for full name (validate non-empty), a DropDown for housing type (House, Apartment, Condo, Townhouse, Other), a ToggleButton or SwitchListTile for 'Do you have a yard?', a ToggleButton for 'Do you currently have other pets?', a TextField (multiline, maxLines 4) for 'Describe your experience with pets', and a TextField (multiline, maxLines 4) for 'Why do you want to adopt this pet?'. Add a Form Validation action on the Submit button's On Tap: check that name, experience, and reason are non-empty and at least 20 characters. On validation pass, create a document in adoption_applications with all form values, the petId from petDocRef, petName, applicantUserId from currentUserUid, applicantName from the name field, status set to 'pending', and submittedAt set to server timestamp. Also update the pet document's status to 'pending'. Show a SnackBar confirming submission and navigate back to BrowsePets.
Expected result: The application form validates all required fields, creates an application document in Firestore, updates the pet status to pending, and confirms submission with a SnackBar.
Implement the shelter admin panel for reviewing applications
Implement the shelter admin panel for reviewing applications
Create a page called ShelterAdmin. On Page Load, query the shelters collection where adminUserIds array contains currentUserUid. If no result, show an 'Access Denied' message and a back button. If authorized, display the shelter name in the AppBar. Add a TabBar with two tabs: Pets and Applications. The Pets tab shows a ListView of all pets for this shelter (query pets where shelterId equals the shelter doc ID). Each list item shows the pet photo, name, status with a color-coded Chip (green for available, amber for pending, grey for adopted), and an Edit IconButton that navigates to a PetEditPage for updating pet details. Add a FloatingActionButton on this tab to add a new pet listing. The Applications tab queries adoption_applications where petId is in the shelter's pet IDs and status equals 'pending'. Each application shows the petName, applicantName, submittedAt, and two action buttons: Approve (green) and Reject (red). Approve action: update the application status to 'approved', update the pet status to 'adopted', and reject all other pending applications for the same pet. Reject action: update the application status to 'rejected' and set the pet status back to 'available' if no other pending applications exist.
Expected result: Shelter admins see their pet listings with status indicators and a tab for reviewing pending applications. Approving an application marks the pet as adopted and rejects competing applications.
Add user profiles with favorites watchlist and application history
Add user profiles with favorites watchlist and application history
Create a page called UserProfile. At the top, display the current user's name and email from the Firebase Auth profile. Add a profile photo using a CircleImage bound to the user's photoURL, with an Edit IconButton that triggers the FlutterFlow image upload flow to update the photo. Below the profile header, add a TabBar with two tabs: Favorites and My Applications. The Favorites tab queries a user_favorites subcollection under the current user document (each doc has petId and addedAt). For each favorite, run a secondary query to fetch the pet document and display it as a compact card with photo, name, breed, and status. If status is 'adopted', show a greyed-out card with an 'Adopted' overlay. Add a remove button (X icon) that deletes the favorite document. The My Applications tab queries adoption_applications where applicantUserId equals currentUserUid, ordered by submittedAt descending. Each item shows petName, submittedAt formatted date, and a status Chip (amber for pending, green for approved, red for rejected). Tapping an application navigates to the PetDetail page for that pet.
Expected result: Users can view their profile photo and info, browse their saved favorite pets, and track the status of all their adoption applications from a single profile page.
Complete working example
1Firestore Data Model:2├── pets/{auto-id}3│ ├── shelterId: String (ref to shelters doc)4│ ├── name: String (Buddy)5│ ├── species: String (Dog | Cat | Rabbit | Bird | Other)6│ ├── breed: String (Golden Retriever)7│ ├── age: String (Puppy/Kitten | Young | Adult | Senior)8│ ├── gender: String (Male | Female)9│ ├── size: String (Small | Medium | Large)10│ ├── description: String (200-500 chars)11│ ├── photoUrls: Array of Strings (Firebase Storage URLs)12│ ├── status: String (available | pending | adopted)13│ ├── medicalHistory: String14│ ├── isSpayed: Boolean15│ └── location: String (City, State)16├── shelters/{auto-id}17│ ├── name: String (Happy Paws Rescue)18│ ├── address: String19│ ├── phone: String20│ ├── email: String21│ ├── websiteUrl: String22│ ├── logoUrl: String23│ └── adminUserIds: Array of Strings24├── adoption_applications/{auto-id}25│ ├── petId: String26│ ├── petName: String (denormalized)27│ ├── applicantUserId: String28│ ├── applicantName: String29│ ├── status: String (pending | approved | rejected)30│ ├── housingType: String31│ ├── hasYard: Boolean32│ ├── hasPets: Boolean33│ ├── experience: String34│ ├── reason: String35│ └── submittedAt: Timestamp36└── users/{uid}37 └── user_favorites/{auto-id}38 ├── petId: String39 └── addedAt: Timestamp4041Page: BrowsePets42├── ChoiceChips: Species [All|Dog|Cat|Rabbit|Bird]43├── ChoiceChips: Size [All|Small|Medium|Large]44├── ChoiceChips: Age [All|Puppy|Young|Adult|Senior]45├── GridView (crossAxisCount: 2)46│ └── Pet Card: Image + name + breed + age/size47│ └── On Tap → Navigate PetDetail(petDocRef)48└── Backend Query: pets, status==available + filters4950Page: PetDetail (param: petDocRef)51├── PageView (photo gallery, dots indicator)52├── Row: name + favorite IconButton53├── Chips: species, breed, age, size, gender54├── Text: description55├── Medical: isSpayed Chip + medicalHistory56├── Shelter info: name + address + phone + website57└── Button: Apply to Adopt → AdoptionApplication5859Page: AdoptionApplication (params: petDocRef, petName)60├── Text: Applying for {petName}61├── TextField: full name62├── DropDown: housing type63├── Toggle: has yard, has pets64├── TextField: experience (multiline)65├── TextField: reason (multiline)66└── Button: Submit → Create application + update pet status6768Page: ShelterAdmin (authorized by adminUserIds)69├── TabBar: Pets | Applications70├── Pets Tab: ListView of shelter pets + status Chips + edit71├── Applications Tab: pending applications + Approve/Reject72└── FAB: Add new pet7374Page: UserProfile75├── Profile: photo + name + email76├── TabBar: Favorites | My Applications77├── Favorites: pet cards from user_favorites subcollection78└── My Applications: status tracking with ChipsCommon mistakes
Why it's a problem: Showing adopted pets in the default browse results
How to avoid: Always include where status equals 'available' as the default filter on the BrowsePets Backend Query. Never show adopted or pending pets in browse results unless the user explicitly toggles an 'Include adopted' filter.
Why it's a problem: Not updating the pet status when an application is submitted
How to avoid: When an application is submitted, immediately update the pet's status to 'pending'. This removes it from the available browse results and signals to other users that the pet is being reviewed. If the application is rejected, the Cloud Function or admin action sets the status back to 'available'.
Why it's a problem: Storing all pet photos in a single Firestore String field instead of an Array
How to avoid: Use an Array of Strings (photoUrls) field. Upload 3-5 photos per pet to Firebase Storage and store all URLs in the array. Bind the array to a PageView widget for a swipeable gallery on the detail page.
Best practices
- Default the browse query to status equals 'available' so adopted and pending pets are hidden from new users
- Use an Array field for photoUrls and display them in a PageView gallery — multiple photos significantly increase adoption interest
- Denormalize petName into the adoption_applications document so the applications list does not require a secondary pet query
- Use ChoiceChips instead of DropDowns for browse filters — they show all options at once and allow faster filtering on mobile
- Add Conditional Visibility on the Apply to Adopt button to hide it when the pet status is not 'available'
- Implement a favorites subcollection per user rather than an array field to avoid Firestore document size limits for power users
- Send push notifications to applicants when their status changes from pending to approved or rejected
- Add an empty state for the favorites and applications tabs showing a helpful message and a link to browse pets
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Design a Firestore data model for a pet adoption platform. I need collections for pets (with species, breed, age, size, status, photos), shelters (with admin user IDs), and adoption_applications (with applicant info, questionnaire answers, and approval status). Include the relationships between collections and suggest composite indexes for filtered queries by species, size, and status.
Create a pet adoption browse page with a 2-column GridView of pet cards. Each card shows a photo, pet name, breed, and age. Add three rows of ChoiceChips above the grid for filtering by species (Dog, Cat, Rabbit, Bird), size (Small, Medium, Large), and age (Puppy, Young, Adult, Senior). Tapping a card navigates to a detail page with a swipeable photo gallery, pet information, shelter contact details, and an Apply to Adopt button.
Frequently asked questions
How do I handle multiple applications for the same pet?
When the first application is submitted, change the pet status to 'pending'. The shelter admin reviews all pending applications for that pet in the Applications tab. When they approve one, the Cloud Function automatically rejects all other pending applications for that pet and sets the pet status to 'adopted'. If all applications are rejected, the pet goes back to 'available'.
Can shelters upload new pet listings from their phone?
Yes. The ShelterAdmin page includes a FloatingActionButton that navigates to a PetEditPage with an image upload flow. Shelter admins use the FlutterFlow upload button to take photos directly from the camera or select from the gallery. Photos upload to Firebase Storage and the URLs are stored in the photoUrls array. The form works fully on mobile.
How do I add location-based search to find nearby pets?
Add a GeoPoint field to the pets collection with the shelter's latitude and longitude. Use a Firestore geohash query or the GeoFlutterFire package to filter pets within a radius of the user's current location. Display results on a map using FlutterFlow's Google Maps widget with custom markers for each pet.
Should I use a subcollection or a top-level collection for adoption applications?
Use a top-level adoption_applications collection. A subcollection under each pet would work for viewing applications per pet, but it makes it harder to query all applications by a single user (for the My Applications tab on the profile). Top-level collections with petId and applicantUserId fields support both query patterns with composite indexes.
How do I prevent spam or fake adoption applications?
Require Firebase Authentication before allowing applications. Add rate limiting in Firestore Security Rules (e.g., max 3 applications per user per day using a counter document). Require minimum character counts on the experience and reason text fields. Optionally, add email verification as a prerequisite before the Apply button becomes active.
Can RapidDev help build a production pet adoption platform?
Yes. A production adoption platform often needs location-based search, real-time chat between shelters and applicants, background check integrations, automated email workflows, analytics dashboards for shelter metrics, and mobile push notifications for application updates. RapidDev can architect and build the full platform beyond what the visual builder handles alone.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation