AI companion chat app like Character.ai, Replika and Talkie
Chat with AI characters who remember you, with replies streaming from a real model, and create your own in one sentence. Seeded with 21 characters and real conversations.
- Free template
- Expo and Supabase
- iOS, Android, web
- Your own copy of the code








Screens built and filled with data




Workflows end to end
- Discover and start a chat
- Chat
- Memory
- Continue a conversation
Tables already designed
Access rules on every table
Conversations, messages, memories and likes belong to their owner alone, and messages can only go into the user's own conversations (owns_conversation); characters are readable when public or your own and writable only by their creator (can_see_character); counts and featured flags change only through triggers; reports are insert-and-read-your-own.
Supabase row-level security
Sample rows so no screen starts empty
- messages93
- characters21
- memories14
- character_likes9
- conversations9
Features
Everything the app does, on day one
Streaming AI chat
Replies arrive word by word from a real model through the platform's AI service, with a typing indicator, stop, regenerate, copy and thumbs up or down on any reply.

Characters that remember you
Chats add memories automatically ("Kai will remember this"), and every character reads your persona and its memories before it answers.
Memory you control
See what each character knows about you, edit or delete any memory, add one by hand, or share one with every character.
Discover characters
Featured characters, categories (companions, learn, wellness, play, create, career, life), search and a grid of portraits with chat counts.
Character profiles
Portrait, tagline, creator, chats and likes, conversation starters you can tap to begin, and like, share or report.
Create a character with AI
One sentence becomes a name, tagline, category, personality, first message and starters, and a generated portrait in the app's art style.
Suggested replies
"Need an idea?" offers three things you could say next.
Chats inbox
Pinned and recent conversations with previews, search, rename, pin and delete.
Safety built in
Every character follows fixed rules (says it is an AI when sincerely asked, no sexual content, steps out of character and points to help when someone is in danger), a note in every chat, and reporting.
Customize with prompts
Make it yours in one sentence
Pick what to change first. Your copy opens in the editor and the AI starts right away, changing this app in place.

Make it yours
You get your own copy of AI companion chat app. Tell the AI what to change first.
Themes and icons
Your look, before the first prompt
Pick a theme and an app icon. Your copy is made in them: no prompt, no credits.


Theme
App icon
Your copy starts in this look. It is one theme file, so every screen changes together, and you can keep adjusting it with prompts.
One prompt later
“Call it Muse, make the brand colour deep teal, and add a daily mood check-in card at the top of Discover: five mood emojis, and the one I pick is saved for today”
A real run on this template. One turn renamed the app, recoloured every screen and added the field to the database.


Workflows
What people can already do in it
Pick one to walk through its steps on the screen it happens on.






Discover characters
Under the hood
A real full-stack app, not a mockup
For the technical side: how it is built, the database, the code itself and the stack.
The editor writes a real app. The app talks to its own Supabase backend, and the same code runs on phones and the web.
RapidNative editor
The AI writes the code, schema and sample data
Your app
Expo SDK 58, React Native, 13 screens
Supabase backend
Supabase: Postgres (7 tables, 28 access rules), auth, storage, API routes
Runs on
iOS, Android and the web
7 tables, linked the way the app works. Conversations, messages, memories and likes belong to their owner alone, and messages can only go into the user's own conversations (owns_conversation); characters are readable when public or your own and writable only by their creator (can_see_character); counts and featured flags change only through triggers; reports are insert-and-read-your-own.
profiles
2 sample rows
characters
21 sample rows
character_likes
9 sample rows
conversations
9 sample rows
messages
93 sample rows
memories
14 sample rows
reports
2 sample rows
Slices of this template's own files. Your copy is a standard Expo project: change it in the editor, by prompt or by hand, or, on a paid plan, download it and keep going in your own editor.
37export default function DiscoverScreen() {
38 const { user } = useAuth();
39 const { width } = useWindowDimensions();
40 const { data: profile } = useMyProfile();
41 const { data: featured } = useFeaturedCharacters();
42 const { data: recent } = useRecentConversations(6);
43 const [category, setCategory] = useState<string>('all');
44 const { data: characters, isLoading, error, refetch, isFetching } = useCharacters(category);
45
46 const muted = useThemeColor('--muted-foreground');
47 const cardBg = useThemeColor('--card');
48 const border = useThemeColor('--border');
49 const fg = useThemeColor('--foreground');
50 const chipBg = useThemeColor('--background');
51
52 const name = profile?.display_name ?? 'there';
53 const greetingText = `${greeting()}, ${firstName(name)}`;
54
55 const hasRecent = (recent?.length ?? 0) > 0;
56 const now = Date.now();
57
58 const hasNew = (lastMessageAt: string) => {
59 return now - new Date(lastMessageAt).getTime() < 3600_000;
60 };
61
62 const renderCharacterCard = (c: { id: string; name: string; tagline: string; avatar_url: string | null; chat_count: number; creator_id: string | null; creator?: { username?: string | null } | null }) => (
63 <Pressable
64 key={c.id}
65 onPress={() => router.push(`/character/${c.id}`)}
66 className="mb-4 w-[48%]"
67 >
68 <View className="overflow-hidden rounded-3xl" style={{ borderWidth: 1, borderColor: border, backgroundColor: cardBg }}>
69 <Image source={{ uri: c.avatar_url ?? undefined }} style={{ width: '100%', aspectRatio: 3 / 4 }} />
70 <View className="p-3">8export function useThemeColor(token: `--${string}`, { alpha = 1, shade = 0 } = {}): string {
9 const { colorScheme } = nativewind.useColorScheme();
10 const native = useNativeVariable(token);
11 const vars = (colorScheme === 'dark' ? theme.darkTheme : theme.lightTheme) as Record<string, any>;
12 const raw = String((Platform.OS === 'web' ? (vars.__cssVars ?? vars)[token] : native) ?? '');
13 const rgb = raw.trim().split(/\s+/).map(Number);
14 if (rgb.length !== 3 || rgb.some(Number.isNaN)) return `rgba(127,127,127,${alpha})`;
15 const to = shade < 0 ? 0 : 255;
16 const [r, g, b] = rgb.map((c) => Math.round(c + (to - c) * Math.abs(shade)));
17 return `rgba(${r},${g},${b},${alpha})`;
18}2create or replace function public.ensure_my_profile(p_name text)
3returns public.profiles
4language plpgsql
5security definer
6set search_path = public
7as $$
8declare
9 v_email text;
10 v_local text;
11 v_username text;
12 v_color text;
13 v_row public.profiles;
14begin
15 select email into v_email from auth.users where id = auth.uid();
16 v_local := coalesce(nullif(p_name, ''), split_part(v_email, '@', 1), 'friend');
17
18 -- unique username from the email local part + a short suffix if taken
19 v_username := regexp_replace(lower(v_local), '[^a-z0-9_]', '', 'g');
20 if v_username = '' then
21 v_username := 'friend';
22 end if;
23
24 loop
25 begin
26 insert into public.profiles (id, display_name, username, avatar_color)
27 values (auth.uid(), coalesce(nullif(p_name, ''), v_local), v_username,
28 (array['#DB553A','#6C5CE7','#26A69A','#F0A63C','#3D8BFF','#E5689A'])[1 + mod(hashtext(auth.uid()::text), 6)])
29 on conflict (id) do nothing;
30 exit;
31 exception when unique_violation then
32 -- username taken: append a short numeric suffix and retry
33 v_username := left(v_username, 18) || substr(md5(auth.uid()::text), 1, 4);
34 end;
35 end loop;25export const lightTheme = vars({
26 "--radius": "18",
27 "--background": "251 248 244",
28 "--foreground": "30 27 24",
29 "--card": "255 255 255",
30 "--card-foreground": "30 27 24",
31 "--popover": "255 255 255",
32 "--popover-foreground": "30 27 24",
33 "--primary": "219 85 58",
34 "--primary-foreground": "255 255 255",
35 "--secondary": "243 237 230",
36 "--secondary-foreground": "30 27 24",
37 "--muted": "240 235 228",
38 "--muted-foreground": "128 118 108",
39 "--accent": "108 92 231",
40 "--accent-foreground": "255 255 255",
41 "--destructive": "214 48 49",
42 "--destructive-foreground": "255 255 255",
43 "--border": "233 226 218",
44 "--input": "228 220 211",
45 "--ring": "219 85 58",
46 "--chart-1": "219 85 58",
47 "--chart-2": "108 92 231",
48 "--chart-3": "38 166 154",
49 "--chart-4": "240 166 60",
50 "--chart-5": "30 27 24",
51});
52
53export const darkTheme = vars({
54 "--radius": "18",
55 "--background": "20 17 16",
56 "--foreground": "245 240 234",
57 "--card": "31 27 25",
58 "--card-foreground": "245 240 234",Sample data: Demo accounts (password rapidnative-demo): demo@rapidnative.com (Maya Brooks, a product designer in Portland training for a 10K, with 7 conversations, 12 memories, 6 liked characters and 2 characters of her own, one private) and sam@rapidnative.com (Sam Carter, creator of 3 community characters, with 2 conversations). 21 characters in all (16 official, 5 community) with generated portraits, 9 conversations and about 100 messages. Good to know: The AI is real: chat, memories, suggested replies, character drafts and portraits run on the platform's AI service through api/index.js. Chat and like counts start from seeded popularity numbers. Reports are stored for review; there is no moderation dashboard yet. No payments or subscriptions (a paywall plugs in before the chat or at Settings), no push notifications and no voice.
- Framework
- Expo SDK 58, React Native 0.88.0
- Navigation
- Expo Router 58.0.26, file-based routes
- Data
- TanStack Query 5.90.16
- Styling
- NativeWind 4.2.1, one theme file
- Database
- Supabase (Postgres), row-level security on every table
- Auth
- Email and password
- Server code
- API routes in api/, deployed with the app
- Language
- TypeScript 6.0.3, strict
- Platforms
- iOS, Android, web
- Ownership
- A standard Expo project; download the full source on the Starter plan and up
Screens
Every screen, built and filled with data
13 routes: /chats, /create, /, /profile, /character-edit/[id], /character/[id], /chat/[id], /memories, /persona, /search, /settings, /login, /signup.






How it works
From template to the App Store
- Step 1
Use the template
You get your own copy in your account, running in the editor with its data.
- Step 2
Tell the AI what to change
Rename it, restyle it, add features. It edits this app; it does not start over.
- Step 3
Run it and ship it
Open it on your phone with a QR code, then publish to the App Store and Google Play.
Questions
Before you start
Is the template free?+
Do I own the code?+
Can I publish it to the App Store and Google Play?+
What is mocked?+
Can I use my own backend?+
Is this affiliated with Character.ai, Replika or Talkie?+
More templates
Or start from another app
Your app, already started
Free to start. Your copy, your name, your brand, and the AI ready to change any of it.
Not affiliated with or endorsed by Character.ai, Replika or Talkie. Each is a trademark of its owner.














