Merge branch 'main' into availability-sla

This commit is contained in:
Evan
2025-06-17 20:24:50 +00:00
124 changed files with 3381 additions and 698 deletions

View File

@@ -1,4 +1,4 @@
FROM node:20-bullseye as base
FROM node:20-bullseye AS base
ARG PUBLIC_APPWRITE_ENDPOINT
ENV PUBLIC_APPWRITE_ENDPOINT ${PUBLIC_APPWRITE_ENDPOINT}
@@ -60,13 +60,13 @@ COPY pnpm-lock.yaml pnpm-lock.yaml
RUN npm i -g corepack@latest
RUN corepack enable
FROM base as build
FROM base AS build
COPY . .
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN NODE_OPTIONS=--max_old_space_size=16384 pnpm run build
FROM base as final
FROM base AS final
# Install fontconfig
COPY ./local-fonts /usr/share/fonts

View File

@@ -115,6 +115,7 @@
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"@tailwindcss/oxide",
"core-js",
"esbuild",
"sharp",

View File

@@ -59,7 +59,7 @@ export type TrackEventArgs = { name: string; data?: object };
export const trackEvent = (eventArgs?: string | TrackEventArgs): void => {
if (!eventArgs || ENV.TEST) return;
const path = page.route.id ?? '';
const path = page.route.id?.replace(/\(([^()]*)\)/g, '') ?? '';
const name = typeof eventArgs === 'string' ? eventArgs : eventArgs.name;
const data = typeof eventArgs === 'string' ? { path } : { ...eventArgs.data, path };

View File

@@ -3,7 +3,7 @@
import { getCodeHtml, type Language } from '$lib/utils/code';
import { copy } from '$lib/utils/copy';
import { platformMap } from '$lib/utils/references';
import { writable } from 'svelte/store';
import { SvelteSet } from 'svelte/reactivity';
interface Props {
selected?: Language;
@@ -14,18 +14,26 @@
let { selected = $bindable('js'), data = [], width = null, height = null }: Props = $props();
let snippets = $derived(writable(new Set(data.map((d) => d.language))));
const getSnippets = () => {
return snippets;
};
const snippets = $derived(new SvelteSet(data.map((d) => d.language)));
const content = $derived(data.find((d) => d.language === selected)?.content ?? '');
const platform = $derived(data.find((d) => d.language === selected)?.platform ?? '');
const result = $derived(
getCodeHtml({
content,
language: selected ?? 'sh',
withLineNumbers: true
})
);
const options = $derived(
Array.from(snippets).map((language) => ({
value: language,
label: platformMap[language]
}))
);
let content = $derived(data.find((d) => d.language === selected)?.content ?? '');
let platform = $derived(data.find((d) => d.language === selected)?.platform ?? '');
getSnippets().subscribe((n) => {
if (selected === null && n.size > 0) {
selected = Array.from(n)[0] as Language;
$effect(() => {
if (selected === null && snippets.size > 0) {
selected = Array.from(snippets)[0] as Language;
}
});
@@ -35,9 +43,7 @@
} as const;
type CopyStatusType = keyof typeof CopyStatus;
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
let copyText = $state<CopyStatusValue>(CopyStatus.Copy);
let copyText: CopyStatusValue = $state(CopyStatus.Copy);
async function handleCopy() {
await copy(content);
@@ -46,28 +52,13 @@
copyText = CopyStatus.Copy;
}, 1000);
}
let result = $derived(
getCodeHtml({
content,
language: selected ?? 'sh',
withLineNumbers: true
})
);
let options = $derived(
Array.from($snippets).map((language) => ({
value: language,
label: platformMap[language]
}))
);
</script>
<section
class="dark web-code-snippet mx-auto w-full lg:!max-w-[90vw]"
aria-label="code-snippet panel"
style={`width: ${width ? width / 16 + 'rem' : 'inherit'}; height: ${
height ? height / 16 + 'rem' : 'inherit'
}`}
style:width={width ? width / 16 + 'rem' : 'inherit'}
style:height={height ? height / 16 + 'rem' : 'inherit'}
>
<header class="web-code-snippet-header">
<div class="web-code-snippet-header-start">
@@ -79,9 +70,9 @@
</div>
<div class="web-code-snippet-header-end">
<ul class="buttons-list flex gap-3">
{#if $snippets.entries.length}
{#if snippets.size}
<li class="buttons-list-item flex self-center">
<Select bind:value={selected} bind:options />
<Select bind:value={selected} {options} />
</li>
{/if}
<li class="buttons-list-item" style="padding-inline-start: 13px">
@@ -104,7 +95,7 @@
</header>
<div
class="web-code-snippet-content overflow-auto"
style={`height: ${height ? height / 16 + 'rem' : 'inherit'}`}
style:height={height ? height / 16 + 'rem' : 'inherit'}
>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html result}

View File

@@ -116,6 +116,7 @@
{plan.description}
</p>
<Button
href={plan.buttonLink}
event={plan.eventName}
variant={plan.buttonVariant}
class="w-full! flex-3 self-end md:w-fit"
@@ -135,29 +136,16 @@
flex-basis: 5rem !important;
}
.web-strip-plans-item-wrapper {
gap: 2.65rem;
}
@media (min-width: 1024px) and (max-width: 1224px) {
.web-strip-plans-info {
flex-basis: 1rem !important;
}
.web-strip-plans-item-wrapper {
gap: 1.25rem !important;
inline-size: 100% !important;
}
}
@media (max-width: 1024px) {
.web-strip-plans-info {
flex-basis: 3rem !important;
}
.web-strip-plans-item-wrapper {
gap: 1.25rem !important;
}
}
.web-pre-footer-bg {

View File

@@ -2,7 +2,7 @@
import { classNames } from '$lib/utils/classnames';
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
import { cva, type VariantProps } from 'cva';
import InlineTag from '../ui/InlineTag.svelte';
import InlineTag from '../ui/inline-tag.svelte';
const button = cva(
[

View File

@@ -2,7 +2,6 @@
import { fade, scale } from 'svelte/transition';
import { createDialog, melt } from '@melt-ui/svelte';
import type { Snippet } from 'svelte';
import { browser } from '$app/environment';
type Props = {
url: string;
@@ -12,7 +11,7 @@
};
const {
elements: { portalled, trigger, content, overlay },
elements: { trigger, content, overlay },
states: { open }
} = createDialog({
forceVisible: true,
@@ -27,14 +26,9 @@
{@render children()}
</div>
{:else}
<button
class="contents cursor-pointer"
onclick={() => {
if (browser && window) window.open(url, '_blank');
}}
>
<a href={url} target="_blank" rel="noopener noreferrer" class="contents cursor-pointer">
{@render children()}
</button>
</a>
{/if}
{#if $open}

View File

@@ -1,13 +1,11 @@
<script lang="ts">
import { classNames } from '$lib/utils/classnames';
import type { SvelteHTMLElements } from 'svelte/elements';
import type { SVGAttributes } from 'svelte/elements';
import type { IconType } from './types';
type Props = SvelteHTMLElements['svg'] & {
type Props = SVGAttributes<SVGElement> & {
class?: string;
name: IconType;
name?: IconType;
};
const {
xmlns = 'http://www.w3.org/2000/svg',
viewBox = '0 0 24 24',

View File

@@ -59,13 +59,13 @@ export type SocialShareOption = {
type: 'link' | 'copy';
};
export type IntegrationCategory = {
export type SearchableCategory = {
slug: string;
heading: string;
description: string;
};
export const integrationCategoryDescriptions: IntegrationCategory[] = [
export const integrationCategoryDescriptions: SearchableCategory[] = [
{
slug: 'ai',
heading: 'AI',
@@ -118,6 +118,14 @@ export const integrationCategoryDescriptions: IntegrationCategory[] = [
}
];
export const partnerCategoryDescriptions: SearchableCategory[] = [
{
slug: 'agency',
heading: 'Agency',
description: 'Agencies that build software for their clients using Appwrite'
}
];
export const socialSharingOptions: Array<SocialShareOption> = [
{
icon: 'web-icon-x',

View File

@@ -1,5 +1,4 @@
<script lang="ts" module>
import { navigating } from '$app/stores';
import { writable } from 'svelte/store';
export type DocsLayoutVariant = 'default' | 'expanded' | 'two-side-navs';
@@ -40,8 +39,6 @@
</script>
<script lang="ts">
import { run } from 'svelte/legacy';
import { Search, IsLoggedIn } from '$lib/components';
import { isMac } from '$lib/utils/platform';
import { getContext, setContext } from 'svelte';
@@ -49,6 +46,7 @@
import { page } from '$app/state';
import { getAppwriteDashboardUrl } from '$lib/utils/dashboard';
import { Button, Icon, InlineTag } from '$lib/components/ui';
import { afterNavigate } from '$app/navigation';
interface Props {
variant?: DocsLayoutVariant;
@@ -70,7 +68,7 @@
$layoutState.currentVariant = variant;
});
navigating.subscribe(() => {
afterNavigate(() => {
layoutState.update((n) => ({
...n,
showReferences: false,

View File

@@ -138,7 +138,7 @@
rel="noopener noreferrer"
class="web-u-inline-width-100-percent-mobile"
>
<Icon class="star" aria-hidden="true"></Icon>
<Icon class="star" aria-hidden />
<span class="text">Star on GitHub</span>
<InlineTag>{SOCIAL_STATS.GITHUB.STAT}</InlineTag>
</Button>

View File

@@ -65,6 +65,7 @@ export const Platform = {
type PlatformType = typeof Platform;
export type Platform = (typeof Platform)[keyof typeof Platform];
export const VALID_PLATFORMS = new Set(Object.values(Platform));
export const Framework = {
NextJs: 'Next.js',
@@ -154,9 +155,17 @@ export const preferredVersion = writable<Version | null>(
globalThis?.localStorage?.getItem('preferredVersion') as Version
);
export const preferredPlatform = writable<Platform>(
(globalThis?.localStorage?.getItem('preferredPlatform') ?? 'client-web') as Platform
);
function getInitialPlatform(): Platform {
const stored = globalThis?.localStorage?.getItem('preferredPlatform') ?? Platform.ClientWeb;
// return if this platform is valid
if (VALID_PLATFORMS.has(stored as Platform)) {
return stored as Platform;
} else {
return Platform.ClientWeb;
}
}
export const preferredPlatform = writable<Platform>(getInitialPlatform());
if (browser) {
preferredVersion.subscribe((value) => {
@@ -164,6 +173,9 @@ if (browser) {
});
preferredPlatform.subscribe((value) => {
if (value) globalThis?.localStorage?.setItem('preferredPlatform', value);
// only save the ones for which we have api references.
if (value && VALID_PLATFORMS.has(value)) {
globalThis?.localStorage?.setItem('preferredPlatform', value);
}
});
}

View File

@@ -0,0 +1,172 @@
<script lang="ts">
import FooterNav from '$lib/components/FooterNav.svelte';
import MainFooter from '$lib/components/MainFooter.svelte';
import { Main } from '$lib/layouts';
import { DEFAULT_HOST } from '$lib/utils/metadata';
import { classNames } from '$lib/utils/classnames';
import type { Partner } from '$routes/partners/catalog/+page';
import ContactPartner from '$routes/partners/catalog/(components)/contact-partner.svelte';
export let title: Partner['title'];
export let partnerLevel: Partner['partnerLevel'];
export let category: Partner['category'];
export let description: Partner['description'];
export let cover: Partner['cover'];
export let capabilities: Partner['capabilities'];
export let frameworks: Partner['frameworks'];
export let regions: Partner['regions'];
export let languages: Partner['languages'];
export let website: Partner['website'];
const ogImage = DEFAULT_HOST + cover;
</script>
<svelte:head>
<!-- Titles -->
<title>{title}</title>
<meta property="og:title" content={title} />
<meta name="twitter:title" content={title} />
<!-- Description -->
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta name="twitter:description" content={description} />
<!-- Image -->
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
</svelte:head>
<Main>
<div
class={classNames(
'grid-bg border-smooth relative flex items-center border-b px-5 py-28 lg:px-8 xl:px-16',
'before:from-accent/20 before:absolute before:inset-0 before:-z-1 before:bg-linear-to-tr before:via-transparent before:via-40% before:to-transparent'
)}
>
<div class="relative container w-full pb-0">
<div class="flex flex-col gap-7">
<a href="/partners" class="text-caption text-primary group flex gap-2">
<span class="web-icon-arrow-left transition group-hover:-translate-x-1" />
Back to Partners Catalog
</a>
<h1 class="text-headline font-aeonik-pro text-primary">{title}</h1>
</div>
</div>
</div>
<div class="py-10">
<div class="container">
<article class="flex flex-col gap-10 md:gap-14">
<div class="grid grid-cols-1 gap-10 md:grid-cols-12 md:gap-x-14">
<div class="md:col-span-7">
<div class="web-article">
<div class="web-article-content">
<slot />
</div>
<ContactPartner />
</div>
</div>
<div class="md:col-span-5">
<h2 class="text-label text-primary font-aeonik-pro">About {title}</h2>
<dl class="divide-smooth sticky top-32 mt-10 flex flex-col gap-7 divide-y">
<div class="flex flex-col justify-between gap-7 pb-7">
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
Frameworks
</dt>
<dd class="flex flex-wrap gap-2">
{#each frameworks as framework}
<div
class="text-primary text-caption bg-smooth rounded-full px-3 py-1"
>
{framework}
</div>
{/each}
</dd>
</div>
<div class="flex flex-col justify-between gap-7 pb-7">
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
Capabilities
</dt>
<dd class="flex flex-wrap gap-2">
{#each capabilities as capability}
<div
class="text-primary text-caption bg-smooth rounded-full px-3 py-1"
>
{capability}
</div>
{/each}
</dd>
</div>
<div class="flex items-center justify-between gap-7 pb-7">
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
Category
</dt>
<dd class="text-primary text-caption">{category}</dd>
</div>
<div class="flex items-center justify-between gap-7 pb-7">
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
Website
</dt>
<dd
class="text-primary text-caption font-medium underline underline-offset-4"
>
{website}
</dd>
</div>
<div class="flex items-center justify-between gap-7 pb-7">
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
Partner Level
</dt>
<dd>
<div
class="text-primary text-caption rounded bg-white/24 px-2 py-0.5"
>
{partnerLevel}
</div>
</dd>
</div>
<div class="flex items-center justify-between gap-8 pb-7">
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
Regions
</dt>
<dd class="text-primary text-caption">
{regions.join(', ')}
</dd>
</div>
<div class="flex flex-col justify-between gap-7 pb-7">
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
Languages
</dt>
<dd class="flex flex-wrap gap-2">
{#each languages as language}
<div
class="text-primary text-caption bg-smooth rounded-full px-3 py-1"
>
{language}
</div>
{/each}
</dd>
</div>
</dl>
</div>
</div>
</article>
</div>
</div>
<div class="mt-12 overflow-hidden py-10">
<div class="container">
<FooterNav />
<MainFooter />
</div>
</div>
</Main>

View File

@@ -81,5 +81,5 @@
class:web-snap-location-references={id && inReferences}
class="{headingClass} text-primary scroll-m-32 font-medium"
>
<a href={`#${href}`} class="">{@render children()}</a>
<a href={`#${id ?? slugify(element?.innerText ?? '')}`} class="">{@render children()}</a>
</svelte:element>

View File

@@ -52,7 +52,7 @@
</li>
{/each}
</ul>
<div class="flex flex-col gap-2 md:flex-row">
<div class="z-[1] flex flex-col gap-2 md:flex-row">
<Button href={url} class="max-sm:w-full!">{cta}</Button>
</div>
</div>

View File

@@ -98,12 +98,12 @@
on:emblaInit={onEmblaInit}
>
<div class="embla__container flex">
{#each events as _}
{#each events as { poster, title }}
<div
class="embla__slide bg-card/90 mr-4 min-w-0 [flex:0_0_33%] items-center rounded-lg p-4"
>
<img src={_.poster} class="m-auto rounded-t" />
<h3 class="mt-0.5 text-base font-medium">{_.title}</h3>
<img alt={title} src={poster} class="m-auto rounded-t" />
<h3 class="mt-0.5 text-base font-medium">{title}</h3>
</div>
{/each}
</div>

View File

@@ -1,9 +1,8 @@
import { APPWRITE_DB_INIT_ID, APPWRITE_COL_INIT_ID } from '$env/static/private';
import { DOMParser, parseHTML } from 'linkedom';
import type { TicketData } from './tickets';
import { APPWRITE_COL_INIT_ID, APPWRITE_DB_INIT_ID } from '$env/static/private';
import { parseHTML } from 'linkedom';
import { z } from 'zod';
import { createInitServerClient } from './appwrite';
import type { TicketData } from './tickets';
const contributionsSchema = z.array(z.array(z.number()));
export type ContributionsMatrix = z.infer<typeof contributionsSchema>;

View File

@@ -1,8 +1,5 @@
import { redirect, type Action, type Actions } from '@sveltejs/kit';
import { redirect, type Actions } from '@sveltejs/kit';
import { getTicketByUser } from './(utils)/tickets';
import { OAuthProvider } from 'appwrite';
import { Account, Client } from 'node-appwrite';
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_INIT_ID } from '$env/static/public';
import { loginGithub } from './(utils)/auth';
export const prerender = false;

View File

@@ -1,9 +1,6 @@
import { getTicketDocByUsername } from '../../(utils)/tickets';
import { error, redirect, type Actions } from '@sveltejs/kit';
import { getTicketContributions } from '../../(utils)/contributions';
import { OAuthProvider } from 'appwrite';
import { Account, Client } from 'node-appwrite';
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_INIT_ID } from '$env/static/public';
import { loginGithub } from '../../(utils)/auth';
export const ssr = true;

View File

@@ -20,7 +20,7 @@
{
logo: LangX,
headline: 'LangX handled millions of requests using Appwrite',
blurb: 'With its comprehensive suite of services Appwrite emerged as an ideal choice for my needs.',
blurb: 'With its comprehensive suite of services, Appwrite emerged as an ideal choice for my needs.',
name: 'Xue',
title: 'Founder at LangX',
avatar: '/images/testimonials/xue.webp',
@@ -28,10 +28,10 @@
},
{
logo: KCollect,
headline: 'K-collect reduced infrastructure costs by 700%',
headline: 'K-Collect reduced infrastructure costs by 700%',
blurb: 'A major impact that Appwrite made was the amount of time and stress saved.',
name: "Ryan O'connor",
title: 'Founder at K-collect',
name: "Ryan O'Connor",
title: 'Founder at K-Collect',
avatar: '/images/testimonials/ryan.png',
url: '/blog/post/customer-stories-kcollect'
}

View File

@@ -38,7 +38,7 @@
<Map theme="light" />
<Scale
testimonial={{
name: 'Ryan OConner',
name: 'Ryan OConnor',
title: 'Founder',
company: 'K-Collect',
image: '/images/testimonials/ryan-oconner-testimonial.png'

View File

@@ -2,7 +2,7 @@
layout: author
name: Arman
slug: arman
role: Frontend Developer
role: Product Engineer
bio: In ♥ with Svelte and Vue, currently working at Appwrite
avatar: /images/avatars/arman.png
twitter: https://twitter.com/NikNotNik

View File

@@ -2,7 +2,7 @@
layout: author
name: Binyamin Yawitz
slug: binyamin-yawitz
role: Software Engineer
role: Platform Engineer
bio: Developer with multilingual skills and systems experience.
avatar: /images/avatars/binyamin.png
github: https://github.com/byawitz/

View File

@@ -2,7 +2,7 @@
layout: author
slug: bradley-schofield
name: Bradley Schofield
role: Software Engineer
role: Platform Engineer
bio: Integrating platforms and managing data in Appwrite.
avatar: /images/avatars/bradley.png
twitter: https://x.com/ionicisere

View File

@@ -2,7 +2,7 @@
layout: author
slug: damodar-lohani
name: Damodar Lohani
role: Engineer
role: Platform Engineer
bio: Author, mentor, trainer, and tech consultant.
avatar: /images/avatars/damodar.png
twitter: https://twitter.com/lohanidamodar

View File

@@ -2,7 +2,7 @@
layout: author
slug: luke-silver
name: Luke B. Silver
role: Software Engineer
role: Platform Engineer
bio: Building integrations with and on Appwrite
avatar: /images/avatars/luke.png
twitter: https://twitter.com/lukebsilver

View File

@@ -2,7 +2,7 @@
layout: author
slug: matej-baco
name: Matej Bačo
role: Software Engineer
role: Engineering Lead
bio: Always building something cool with Appwrite
avatar: /images/avatars/matej.png
twitter: https://twitter.com/_meldiron

View File

@@ -23,9 +23,15 @@ In simple words, open-source software is made available with a license that allo
Unlike traditional software, which is owned and controlled by a company, open-source products gives you full access to the code, so you can customize the software to meet your specific needs.
Its more than just access to the code. Weve seen too many cases where startups got burned by closed platforms.
A founder recently shared on [LinkedIn](https://www.linkedin.com/feed/update/urn:li:activity:7331362060750032896/) how their client lost $65,000 and their entire EdTech platform overnight. Just because the platform they were using filed for bankruptcy without warning. Fired all 1,500 employees. Locked every customer out. No access to code. No way to export data. No one to even reach out to.
Thats what happens when your infrastructure is not in your control. This is why open-source matters. Because with open-source, youre not dependent on a single companys fate. You can self-host. You can migrate. You can fork the code and keep building.
# Benefits of open-source software
While the huge benefit of an open-source product is the ability to customize it according to your needs, there are many other benefits to consider, like:
While the huge benefit of an [open-source](https://opensource.org/) product is the ability to customize it according to your needs, there are many other benefits to consider, like:
## Cost savings
Open-source products generally offer budget-friendly options for businesses of all sizes. Many open-source projects are free to get started and you can pay as you scale or need additional support, which could be a great option for budget-tight teams.
@@ -44,34 +50,64 @@ Most open-source projects have self-hostable versions that allow you to host the
# 10 open-source product alternatives
## 1. [n8n](https://n8n.io/)
![n8n](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/n8n.png)
**n8n** is an open-source alternative to **Zapier**. It allows users to automate workflows by connecting various apps and services, similar to Zapier, but with the added advantage of being fully customizable.
## 2. [Appsmith](https://www.appsmith.com/)
![Appsmith](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/appsmith.png)
Appsmith is an open-source alternative to Retool. It enables you to build custom internal tools quickly by connecting to databases, APIs, and other services. It provides a drag-and-drop interface to design apps, making it easy to create dashboards and workflows.
## 3. [Documenso](https://documenso.com/)
![Documenso](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/documenso.png)
Documenso is an open-source alternative to Docusign. It is a document management platform designed to help businesses organize, store, and collaborate on documents. It offers features like document indexing, searching, and version control, allowing teams to manage their files efficiently.
## 4. [Dub.co](https://dub.co/)
![Dub.co](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/dub.co.png)
Dub.co is an open-source alternative to Bitly. It is a link management platform designed for modern marketing teams. It allows users to create branded short links using custom domains, track clicks with detailed analytics, and manage marketing campaigns efficiently.
## 5. [AppFlowy](https://appflowy.com/)
![AppFlowy](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/AppFlowy.png)
AppFlowy is an open-source alternative to Notion. It provides a flexible workspace for note-taking, project management, and collaboration with features like customizable templates, task tracking, and databases.
## 6. [WebStudio](https://webstudio.is/)
WebStudio is an open-source alternative to WebFlow. An open-source platform for building and managing websites with a focus on simplicity and flexibility. It provides an intuitive drag-and-drop interface, allowing users to design websites without coding.
## 6. [Webstudio](https://webstudio.is/)
![Webstudio](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/Webstudio.png)
Webstudio is an open-source alternative to Webflow. An open-source platform for building and managing websites with a focus on simplicity and flexibility. It provides an intuitive drag-and-drop interface, allowing users to design websites without coding.
## 7. [Typesense](https://typesense.org/)
![Typesense](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/Typesense.png)
Typesense is an open-source alternative to Algolia. A fast and relevant search engine built for developers. It allows you to easily integrate full-text search functionality into your applications with features like typo tolerance, filtering, faceting, and real-time indexing.
## 8. [PostHog](https://posthog.com/)
![PostHog](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/PostHog.png)
PostHog is an open-source alternative to Mixpanel. A product analytics platform designed to help businesses track user behavior and make data-driven decisions. It provides teams with tools that allow them to gain insights into how users interact with their products.
## 9. [Sentry](https://sentry.io/welcome/)
![Sentry](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/Sentry.png)
Sentry is an open-source alternative to Raygun. An error tracking and monitoring platform that helps developers identify and fix issues in their applications in real-time. It provides detailed error reports making it easier to diagnose and resolve bugs quickly.
## 10. [Appwrite](https://appwrite.io/)
Appwrite is an open-source alternative to Firebase. A backend-as-a-service (BaaS) platform that simplifies the development of web and mobile applications. It provides developers with tools for user authentication, databases, file storage, and real-time capabilities, all through easy-to-use APIs.
![Appwrite](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/Appwrite.png)
Appwrite is an open-source alternative to Firebase, Vercel and Auth0. An all-in-one development platform that provides you with built-in backend infrastructure and hosting. It includes everything from auth, databases, and storage to serverless functions and real-time APIs, so you can focus on building, not wiring things together.
# Conclusion
In conclusion, open-source products gives your startup the flexibility, control, and cost savings you need to scale and innovate.

View File

@@ -9,6 +9,7 @@ author: veeresh-mulge
callToAction: true
unlisted: true
category: tutorial
call_to_action: true
---
This has to be the best time in history to start a business. With so many advancements in AI tools and agents, it has never been this easy to build and scale your startup. The next breed of billion-dollar companies will be tiny teams backed by AI agents and workflows.
@@ -39,7 +40,7 @@ MCP is just the standardized way of making this happen. Instead of every AI need
MCP was developed by Anthropic and released as an open standard in November 2024, making it a relatively new but rapidly growing solution.
We've covered everything you need to know about MCP in a recent blog, but let's dive into 5 MCP or AI agents startup opportunities you can start building today.
We've covered everything you need to know about MCP in a recent [blog](https://appwrite.io/blog/post/what-is-mcp), but let's dive into 5 MCP or AI agents startup opportunities you can start building today.
# 1. MCP App Store
Create an MCP APP Store, where users can browse and deploy various MCP servers. Think of it like an app store, but for MCP servers. Developers could look through different MCP servers, find the ones they need, and deploy them with just a few clicks.
@@ -49,6 +50,8 @@ The idea is to simplify the process of integrating different services with LLMs,
# 2. Incident Tracer
When your app breaks, whether it's a bug or an outage, an MCP agent traces every log, commit, and Slack message to give you a full incident report in seconds. So, instead of manually digging through logs or trying to piece together what went wrong, the MCP agent automatically collects all the relevant information, creating a detailed report that helps you quickly understand the issue.
{% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%}
# 3. Task Assistant
An MCP-powered agent that shadows founders, reading emails, meetings, tasks, and documents. It provides daily summaries and suggests the next best actions, acting like your personal Chief of Staff. This allows founders to stay focused on what matters while it keeps track of everything and ensures nothing slips through the cracks.
@@ -63,3 +66,8 @@ AIChangelog tracks everything every AI agent does and why, creating a detailed,
MCP changes everything. Before, AI agents were isolated. Now, they connect, collaborate, and scale. This is a huge opportunity to build AI agents and create connected, intelligent solutions that automate tasks, streamline processes, and solve real-world problems.
The Appwrite's Startups Program can help you do that with the tools and support you need to create these AI-driven products. [Apply today](appwrite.io/startups) and start building.
# Further reading
- [Appwrite MCP documentation](https://appwrite.io/docs/tooling/mcp?doFollow=true)
- [What exactly is MCP, and why is it trending?](https://appwrite.io/blog/post/what-is-mcp)
- [Anthropic MCP documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp)

View File

@@ -133,6 +133,16 @@ Self-hosting support essentially means a developer can deploy the application on
- Appwrite offers one-click setups for DigitalOcean, Gitpod, and Akamai Compute. Supabase offers the same for DigitalOcean and AWS.
- Supabase features community-maintained helm charts to self-host using Kubernetes.
## Hosting
Appwrite offers native hosting as part of its all-in-one platform. That means you can develop, deploy, and scale your application all from a single platform. Supabase, while it provides backend services, does not include frontend hosting.
*Differences:*
- Appwrite includes built-in hosting for static and dynamic sites via Appwrite Sites. Supabase does not offer frontend hosting. You must be dependent on third-party tools like Vercel or Netlify to host your application.
- Appwrite Sites supports server-side rendering out of the box.
- With Appwrite, developers can manage everything from frontend and backend to environments, deployments, and monitoring from a single control panel, while Supabase requires using multiple services and dashboards, increasing complexity.
## Support
Any developer-first product, regardless of how good and simple it may be, will need a system for support in case its consumers face any issues. Both Appwrite and Supabase have developed support offerings to ensure their consumers can share feedback and get the necessary help.

View File

@@ -0,0 +1,73 @@
---
layout: post
title: Redesigning our homepage to reflect Appwrites new positioning
description: A behind-the-scenes look at how we redesigned the Appwrite homepage to reflect our evolution from a BaaS to an all-in-one open-source development platform.
date: 2025-06-11
cover: /images/blog/appwrite-homepage-redesign/cover-image.png
timeToRead: 08
author: sara-kaandorp
category: design
featured: false
callToAction: true
---
At Appwrite, weve grown from a Backend-as-a-Service into an open-source, all-in-one development platform. With the introduction of our newest product, Sites, we expanded our offering beyond backend services to include hosting. This shift meant our homepage needed to do more, it had to communicate the full breadth of what Appwrite now offers.
# Phase 1: Setting the baseline
Before jumping into design changes, we wanted to understand how the homepage was performing. That meant:
- Recording baseline metrics of how our homepage was performing
- Getting fresh insight into what kind of first impression our homepage was giving to developers who were new to Appwrite
We ran a qualitative survey with 50 respondents who closely matched our target audience, but who were new to Appwrite. Instead of asking existing community members who already have a mental model of what Appwrite is, we wanted to get fresh insight into the first impression of a developer with no bias. The results were insightful.
## What we heard:
- Positive: People liked the clean layout and visual design. Many said the structure was easy to follow.
- Confusing: Our messaging wasnt clear enough. Some thought Appwrite was a DevOps tool, others a CMS, or just another Firebase clone.
This validated what we suspected: while the design was polished, the story we told wasnt fully landing.
![Our old homepage](/images/blog/appwrite-homepage-redesign/old-homepage.png)
# Phase 2: Iterating on the story
![Iterations](/images/blog/appwrite-homepage-redesign/iterations-top-part.png)
Based on those findings, we created a new homepage design in Figma and ran a second qualitative survey—this time on the proposed design with renewed messaging. The results were encouraging.
![New homepage design](/images/blog/appwrite-homepage-redesign/new-homepage.png)
## What we improved:
- **Backend setup clarity**: More users grasped what Appwrite offers, though some still wanted more detail on how the setup works.
- **Scalability**: Surfaced more often in feedback, indicating stronger visibility.
- **Feature awareness**: Users showed deeper engagement with our capabilities.
# Phase 3: Final design
We simplified our messaging further and focused on showing—not just telling—what Appwrite offers. We:
- Added components that give a quick visual and conceptual overview of our offering, without requiring visitors to read everything. We honed in on different user behaviors: some people want a fast impression; others want to scroll deep. Our content now supports both. (This is backed by UX research—see Nielsen Norman Group's work on scanning vs. reading behaviors.)
![Summary and deep dive](/images/blog/appwrite-homepage-redesign/summary-vs-deepdive1.png)
- Repositioned Appwrite from just a BaaS to a complete platform with Sites, our hosting solution, clearly integrated into the story.
- Selected testimonials strategically—highlighting impact metrics like time saved, cost reduction, and scalability, to build credibility with both developers and founders.
![Summary and deep dive 2](/images/blog/appwrite-homepage-redesign/summary-vs-deepdive2.png)
While the main aim was to improve the story that we were trying to convey, we also further improved our visual language. We did a few iterations to further simplify our visuals and worked on an animation style that feels more natural. In our old page, we had animations based on scroll behavior that slightly hijacked user control—something developers disliked. The new animations and microinteractions make the page feel alive without taking away from the main purpose: understanding the story of Appwrite.
![Scene](/images/blog/appwrite-homepage-redesign/scene.gif)
With all these changes, we moved into a final design iteration—the version thats now live.
# Whats next
We'll continue to refine our new homepage. There are still headlines we want to test, feature explanations to expand on, and new ways we can illustrate how Appwrite works.
Redesigning a homepage is about crafting the clearest possible introduction to your product. And that process doesn't end with this launch.

View File

@@ -0,0 +1,499 @@
---
layout: post
title: Build an offline AI chatbot with WebLLM and WebGPU
description: Learn how to build an offline AI chatbot with WebLLM and WebGPU.
date: 2025-06-10
cover: /images/blog/chatbot-with-webllm-and-webgpu/cover.png
timeToRead: 13
author: ebenezer-don
category: tutorial
featured: false
---
When you hear "LLM," you probably think of APIs, tokens, and cloud infrastructure. But what if we could remove the server completely? That your browser could download a model, run it on your device, and answer questions in real time, all using just JavaScript and GPU acceleration?
Local LLMs running inside the browser were nearly impossible just a year ago. But thanks to new technologies like WebLLM and WebGPU, you can now load a full language model into memory, run it on your device, and have a real-time conversation, all without a server.
In this guide, we'll build a local chatbot that runs entirely in the browser. No backend. No API keys. By the end, you should have a good understanding of [WebLLM](https://webllm.mlc.ai/) and [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API), and will have built an app that looks and functions like this:
![WebLLM and WebGPU chat app demo](/images/blog/chatbot-with-webllm-and-webgpu/webllm-webgpu-chat-app-demo.gif)
You can also try out the app using this [live URL](https://ebenezerdon.github.io/simple-webllm-chat/).
To build this, we need to first understand two important pieces: **WebLLM** and **WebGPU**.
## What is WebLLM?
WebLLM is an open-source project from the team at MLC (Machine Learning Compiler). It lets you run language models directly in your browser tab using GPU acceleration. The models are compiled into formats that your browser can understand and execute, no need to send data to a server.
Why is this important?
- It keeps user data private
- It reduces latency
- It works offline after the model download
- It removes the need for API costs or rate limits
Under the hood, WebLLM handles model loading, tokenization, execution, and streaming responses. It gives you a simple interface to load and chat with a model like LLaMA or Phi.
But WebLLM can't do it alone. It needs hardware access, and that's where WebGPU comes in.
## What is WebGPU?
WebGPU is a new browser API that gives JavaScript access to the system's GPU, not just for drawing graphics, but for running large-scale parallel computations like matrix operations and tensor math.
In our case, WebGPU lets the browser perform the heavy math required to generate text from an LLM.
Here's what WebGPU does for us:
- **Performance**: Runs faster than JavaScript or even WebAssembly for these workloads
- **GPU-first**: Designed from the ground up for compute, not just rendering
- **Accessibility**: Available across different browsers, though support varies by platform. As of June 2025:
- **Chrome/Edge**: Fully supported on Windows, Mac, and ChromeOS since version 113. On Linux, it requires enabling the `chrome://flags/#enable-unsafe-webgpu` flag
- **Firefox**: Available in Nightly builds by default, with stable release tentatively planned for Firefox 141
- **Safari**: Available in Safari Technology Preview, with support in iOS 18 and visionOS 2 betas via Feature Flags
- **Android**: Chrome 121+ supports WebGPU on Android
For production applications, you should include proper WebGPU feature detection and provide fallbacks for unsupported browsers.
Together, WebLLM and WebGPU allow us to do something powerful: load a quantized language model directly in the browser and have real-time chat without any backend server.
With this understanding of WebLLM and WebGPU, we can now start building!
## Setting up the HTML for our AI chat app
Before we write any JavaScript, we need a user interface. This will be the visible part of our app, the dropdown for selecting models, the chat area, and the input box.
Here's the plan:
- We'll create a container for everything
- Add a select box for choosing the model
- Include a progress bar for when the model is loading
- Display the chat history
- Create a form with a text input and a button to submit prompts
To begin, create an `index.html` file and paste the following code inside it:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Browser LLM Chat Demo</title>
<style>
/* We'll add styling here. But later! */
</style>
</head>
<body>
<div class="app-container">
<h1>Chat with LLM (In your browser)</h1>
<div class="controls">
<select id="model-select">
<option value="SmolLM2-360M-Instruct-q4f32_1-MLC">
SmolLM2 360M (Very Small)
</option>
<option value="Phi-3.5-mini-instruct-q4f32_1-MLC">
Phi 3.5 Mini (Medium)
</option>
<option value="Llama-3.1-8B-Instruct-q4f32_1-MLC">
Llama 3.1 8B (Large)
</option>
</select>
<button id="load-model">Load Model</button>
</div>
<div class="chat-container">
<div id="output">Select a model and click "Load Model" to begin</div>
<div
id="progress-container"
class="progress-container"
style="display: none"
>
<div class="progress-bar">
<div id="progress-fill" class="progress-fill"></div>
</div>
<div id="progress-text" class="progress-text">0%</div>
</div>
<form id="chat-form" class="form-group">
<input id="prompt" placeholder="Type your question..." disabled />
<button type="submit" disabled>Send</button>
</form>
</div>
</div>
</body>
</html>
```
In the HTML file, we've created a chat interface with controls for model selection and loading. The interface includes a chat output area, progress indicators, and an input form, which we need to let users interact with the AI model.
### Model selection
Notice that in the `div` with class `controls`, we have a `select` element for model selection and a `button` for loading the model. Here are the specifications for each model:
| Model | Parameters | Q4 file size | VRAM needed |
| ------------ | ------------ | ------------ | ----------- |
| SmolLM2-360M | 360 million | ~270 MB | ~380 MB |
| Phi-3.5-mini | 3.8 billion | ~2.4 GB | ~3.7 GB |
| Llama-3.1-8B | 8.03 billion | ~4.9 GB | ~5 GB |
When you're deciding which of these models to use in a browser environment with WebLLM, think first about what kind of work you want it to handle.
- **SmolLM2-360M** is the smallest by a wide margin, which means it loads quickly and puts the least strain on your device. If you're writing short notes, rewriting text, or making quick coding helpers that run in a browser, this might be all you need.
- **Phi-3.5-mini** brings more parameters and more capacity for reasoning, even though it still runs entirely in your browser. It's good for handling multi-step explanations, short document summarisation, or answering questions about moderately long prompts. If you're looking for a balance between size and capability, Phi-3.5-mini has a comfortable middle ground.
- **Llama-3.1-8B** is the largest of the three and carries more of the general knowledge and pattern recognition that bigger models can offer. It's more reliable if you're dealing with open-ended dialogue, creative writing, or complex coding tasks. But you'll need more memory.
Each of these models trades off size, memory use, and output quality in different ways. So choosing the right one depends on what your hardware can handle and what kind of prompts you plan to work with. All can run directly in modern browsers with WebGPU support.
There are more models available at the [WebLLM repository](https://github.com/mlc-ai/web-llm), ranging from smaller models for mobile devices to larger ones for more capable systems.
With the HTML in place, the next thing we'll do is work on the JavaScript implementation, then add some CSS to make it look nice. We're saving the CSS for the last step so we can focus on the core features.
## Using WebLLM and WebGPU to build the chatbot
Our JavaScript will do four main things:
1. Load the selected model into memory
2. Track the loading progress and show feedback
3. Enable the chat form once the model is ready
4. Stream the response back as the assistant types it out
We'll build this piece by piece.
### Step 1: Import WebLLM
We need to bring in WebLLM so we can access the model engine.
```js
import { CreateMLCEngine } from '<https://esm.run/@mlc-ai/web-llm@0.2.79>'
```
This gives us the function that will initialize the model.
### Step 2: Get references to the DOM elements
Let's wire up the interface. We'll grab the elements we need so we can update them later.
```js
const output = document.getElementById('output')
const form = document.getElementById('chat-form')
const promptInput = document.getElementById('prompt')
const submitButton = document.querySelector('button[type="submit"]')
const modelSelect = document.getElementById('model-select')
const loadModelButton = document.getElementById('load-model')
const progressContainer = document.getElementById('progress-container')
const progressFill = document.getElementById('progress-fill')
const progressText = document.getElementById('progress-text')
```
We'll use these to display messages, show progress, and control the form state.
### Step 3: Track the model engine
We need a variable to hold the model once it's loaded.
```js
let engine = null
```
We'll update this later when the user loads a model.
### Step 4: Show progress
When downloading and initializing the model, we want to keep the user informed.
```js
const updateProgress = (percent) => {
progressContainer.style.display = 'block'
progressFill.style.width = `${percent}%`
progressText.textContent = `${percent}%`
}
```
This sets the visual width of the progress bar and updates the percentage text.
### Step 5: Load the model
Here's the key function. We call this when the user clicks the "Load Model" button.
```js
const loadModel = async (modelId) => {
try {
output.textContent = 'Initializing...'
promptInput.disabled = true
submitButton.disabled = true
loadModelButton.disabled = true
progressContainer.style.display = 'none'
```
We first disable the interface to prevent interference while loading.
Next, we make sure the browser supports WebGPU:
```js
if (!navigator.gpu) {
throw new Error('WebGPU not supported in this browser...')
}
```
This check is crucial because WebGPU availability varies significantly across browsers and platforms. The code will gracefully fail if WebGPU isn't available, allowing you to show appropriate fallback content to users.
Then we download and initialize the model:
```js
engine = await CreateMLCEngine(modelId, {
initProgressCallback: (progress) => {
let percent =
typeof progress === 'number'
? Math.floor(progress * 100)
: Math.floor(progress.progress * 100)
updateProgress(percent)
output.textContent = `Loading model... ${percent}%`
},
useIndexedDBCache: true,
})
```
Once complete:
```js
output.textContent = 'Model ready! Ask me something!'
promptInput.disabled = false
submitButton.disabled = false
loadModelButton.disabled = false
} catch (error) {
loadModelButton.disabled = false
output.innerHTML += `<div class="error">Failed to load model: ${error.message}</div>`
}
}
```
Now the model is ready to use.
### Step 6: Handle chat form submission
When the user types a question and presses enter, this block sends it to the model:
```js
form.addEventListener('submit', async (e) => {
e.preventDefault()
if (!engine) {
output.innerHTML += `<div class="error">No model loaded...</div>`
return
}
const prompt = promptInput.value.trim()
if (!prompt) return
output.textContent = `You: ${prompt}\\n\\nAssistant: `
promptInput.value = ''
promptInput.disabled = true
submitButton.disabled = true
```
Then we stream the assistant's response:
```js
try {
const stream = await engine.chat.completions.create({
messages: [{ role: 'user', content: prompt }],
stream: true,
})
for await (const chunk of stream) {
const token = chunk.choices[0].delta.content || ''
output.textContent += token
output.scrollTop = output.scrollHeight
}
promptInput.disabled = false
submitButton.disabled = false
promptInput.focus()
} catch (error) {
output.innerHTML += `<div class="error">Error during chat: ${error.message}</div>`
promptInput.disabled = false
submitButton.disabled = false
}
})
```
### Step 7: Trigger the model load on button click
Finally, we hook up the "Load Model" button:
```js
loadModelButton.addEventListener('click', async () => {
await loadModel(modelSelect.value)
})
```
## Adding CSS styling
Now that we have the JavaScript in place, let's add some CSS to give the app a clean look. In the `<style>` tag in the `<head>` section of the HTML file, add the following CSS:
```css
body {
font-family: Inter, system-ui, -apple-system, sans-serif;
background-color: #f9fafb;
color: #111827;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.app-container {
background-color: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
padding: 2rem;
overflow: hidden;
margin-top: 5rem;
}
h1 {
font-size: 1.5rem;
font-weight: 600;
margin: 0 0 1.5rem;
color: #111827;
}
#output {
background: #f3f4f6;
padding: 1.25rem;
min-height: 220px;
border-radius: 12px;
white-space: pre-wrap;
overflow-y: auto;
max-height: 420px;
font-size: 0.95rem;
}
input,
button,
select {
font-family: inherit;
font-size: 0.95rem;
padding: 0.8rem 1rem;
width: 100%;
border-radius: 10px;
border: 1px solid #e5e7eb;
background-color: white;
}
button {
background-color: #fd366e;
color: white;
border: none;
font-weight: 500;
cursor: pointer;
transition: background-color 0.15s;
}
button:hover:not(:disabled) {
background-color: #e62e60;
}
button:disabled {
background-color: #ffa5c0;
cursor: not-allowed;
}
.error {
color: #dc2626;
background-color: #fee2e2;
padding: 0.8rem;
border-radius: 10px;
margin-top: 0.75rem;
font-size: 0.9rem;
}
.controls {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.chat-container {
margin-top: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.progress-container {
margin-top: 1rem;
}
.progress-bar {
width: 100%;
height: 8px;
background-color: #e5e7eb;
border-radius: 999px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: #fd366e;
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
font-size: 0.8rem;
text-align: center;
margin-top: 0.5rem;
color: #6b7280;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
input {
box-sizing: border-box;
margin: 0rem;
}
input:focus,
select:focus {
outline: none;
border-color: rgba(253, 54, 110, 0.5);
box-shadow: 0 0 0 1px rgba(253, 54, 110, 0.1);
}
```
Next, open the `index.html` file in your browser and you should see something like this:
![WebLLM and WebGPU chat app demo](/images/blog/chatbot-with-webllm-and-webgpu/webllm-webgpu-chat-app-demo.gif)
Try loading a model and chatting with the AI. To be sure that this all runs locally, you can also turn off your internet connection and see if the app still works after initial model download.
## Conclusion
We've built a local chatbot that runs entirely in the browser. You can now load a model, chat with it, and have a real-time conversation, all without a server.
This is the future of AI. Local, private, and fast. And what we've done is just the beginning. You can build on this foundation, add chat history, improve UX, support longer contexts, or experiment with your own compiled models.
You can also check out a more complex version of this app that includes chat history and a few other features [here](https://github.com/ebenezerdon/webllm-offline-ai).
The source code for the app we built in this guide is available on [GitHub](https://github.com/appwrite-community/simple-webllm-chat).
## Further reading
- [Building a chat app with Appwrite and Google Gemini](https://appwrite.io/blog/post/build-a-chat-app-with-appwrite-and-gemini)
- [Build a chatbot with GPT-4o and Appwrite Functions](https://appwrite.io/blog/post/personal-chatbot-gpt-4o)
- [Building a full-stack app with Svelte and Appwrite](https://appwrite.io/blog/post/build-fullstack-svelte-appwrite)

View File

@@ -0,0 +1,73 @@
---
layout: post
title: Everyone can do DevRel (but should they?)
description: Exploring the question if everyone should be rushing towards a job in Developer Relations?
date: 2024-01-07
cover: /images/blog/everyone-can-do-devrel-but-should-they/cover.png
timeToRead: 5
author: aditya-oberai
category: devrel
---
One thing that I can say, having spent five years in communities and over two years as a Developer Advocate at Appwrite, is that Developer Relations (or DevRel) isnt just a job but a philosophy. DevRel is something we can do by helping developers in our communities, sharing knowledge through our content, or writing code and building projects. That being said, should everyone be rushing towards a job in Developer Relations? Recently, I have observed a bubble developing around this space. I want to take this blog as an opportunity to share my opinion on whether everyone should chase DevRel as a career.
Before deciding whether everyone should become a DevRel professional, lets discuss what DevRel is.
# Understanding DevRel
> Developer Relations, or DevRel, is a domain that focuses on maintaining relationships with the folks building on an organization's technologies or products.
Essentially DevRel acts as the **bridge between the code and the community**. DevRel folks are often responsible for maintaining communication between organizations and developers to ensure a better information flow and feedback loop. Thus, both entities have a better experience and growth path.
![bridge](/images/blog/everyone-can-do-devrel-but-should-they/bridge.png)
DevRel teams came into inception because as we move towards a more technologically-enabled world, we see more people developing solutions to problems using programming and computer science and more companies catering to such folks. With more and more organizations catering their services to developers, it became apparent that these organizations need people who can **build** with their platforms, **communicate** with developers, **educate** newcomers, **grow** their audience, etc. DevRel aims to fit this gap precisely.
Now that weve discussed what DevRel is, lets talk briefly about what skills and experience DevRel practitioners need to succeed.
# What experience should DevRel practitioners possess
Considering that DevRel can be viewed as an amalgam of **code**, **content**, and **community**, most roles in this space need you to be a jack of all three trades while being a master of (at least) one. As a Developer Advocate at Appwrite, I provide technical assistance and contribute to our product aside from creating content for our blog and social media, developing new community initiatives, and so on.
My experience has taught me that, while there is always space to learn, as a DevRel practitioner (for example, Developer Advocate), you should gain experience on all three fronts (whether through dedicated jobs or by alternate means) before rushing into this career, and here is why:
## Writing code
The time I spent learning how to build projects and working on real-world solutions (through self-defined problems or my internship) has substantially helped me in my DevRel journey. Understanding the pros and cons of the product I advocate for from a technical standpoint allows me to build a better sense of developer empathy for our community members. If I couldnt build with our product, it would be impossible for me to relate to their problems and help solve them. Moreover, being able to code allows me to experience the Developer Experience of our product and offer feedback to our engineers, which enables better product development.
## Creating content
One key factor for any successful DevRel team is communicating with the brand and products audience; content enables that in the best way possible. Itll allow you to advocate for your product more accurately and increase your likability and relatability, which also benefits your personal and organizations brands. Therefore, being able to create and deliver content (written, audio, or video) is a skill that DevRel practitioners must develop.
## Building communities
My journey toward Developer Relations started by volunteering in communities. From leading a hackathon community (among others) to working as a Community Intern at a coding education company, I gained experience in planning new initiatives, managing their execution, collaborating with external stakeholders, building an audience, sustaining engagement, and maintaining brand growth. This experience gave me insights into how companies perceive communities and why they are necessary for user feedback and audience growth. Like it or not, being a DevRel professional, you are contributing to the business at the end of the day. Aside from learning how to work with larger groups of people, building communities helps to evaluate how you can impact the growth of an organization.
# Is DevRel just another fad?
While I firmly insist that DevRel isnt a fad, currently, a bubble is beginning to exist. Far too many people are prematurely rushing toward this domain for three reasons:
- DevRel might seem like an **easier, more accessible way into tech than software development** (in all honesty, this isnt true at all!)
- DevRel provides people with a **platform where their voice seems to matter more**.
- DevRel work can help **grow someones personal brand**.
Truthfully, anyone chasing a DevRel career in this manner often does not sustain here for long. This is because DevRel has so much beneath the tip of the iceberg. Aside from just speaking at stages and delivering pre-planned content or engaging with people in communities and social media, people in DevRel are often invested in various functions such as developer experience testing and feedback, sample application development, planning new community initiatives, technical and community thought leadership, quantifying the impact the community has on the products growth, and more depending on what their products need. Many of these arent always done publicly, so it is difficult to perceive them.
It is necessary to evaluate whether these are functions you want to be involved in. Pre-maturely deciding whether DevRel is the space for you can also cause you to **burn out** rather severely and impact your career direction!
# Taking this decision
Truthfully, the decision on whether you want to start a career in Developer Relations isnt one you need to rush. I spent two years volunteering in communities and worked for five months as a Community Intern, two months as a User Research Intern, and two months as a Software Developer Intern (aside from other contractual obligations) before I made up my mind to apply for my first Developer Relations role. These experiences, when combined, allowed me to understand my skills and experiences, which allowed me to develop my aspirations in this space.
The truth is that code, content, and community are vast spaces of their own; you can very well fall in love with any of these spaces individually and pursue your career there, or at least give yourself a chance to fail and move forward. As long as you spend time understanding the most suitable space for you to work in, youll be absolutely fine.
# A little about Appwrite
[Appwrite](http://appwrite.io/) is an open-source Backend-as-a-Service (BaaS) platform that lets you add Authentication, Databases, Functions, and Storage to your product and build any application at any scale, own your data, and use your preferred coding languages and tools.
Learn more about Appwrite:
- [Appwrite Docs](https://appwrite.io/docs)
- [Appwrite Discord](https://appwrite.io/discord)
- [Appwrite GitHub](https://github.com/appwrite)
- [Appwrite YouTube channel](https://youtube.com/@appwrite)

View File

@@ -9,6 +9,7 @@ author: veeresh-mulge
callToAction: true
unlisted: true
category: tutorial
call_to_action: true
---
Nearly [70% of startups fail](https://www.embroker.com/blog/startup-statistics/) within 2-5 years of starting operations, and while many factors contribute to this, one key factor is developing a product that doesn't align with customer needs.
@@ -67,6 +68,8 @@ Founders often fear that if users don't like the MVP, it will ruin their busines
Founders often build an MVP without considering future scalability, resulting in products that cannot scale as user demand grows. Even in the MVP phase, think about scalability. Build a flexible, scalable infrastructure that can handle future growth.
{% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%}
# Examples MVPs that turned into successful products
There's no right or wrong way to build an MVP. Many big companies kicked off with MVPs. Some got started with just a short demo video, while others with a lean MVP. However, in all cases, they began with very limited functionality that solved a real problem for a small group of early users.

View File

@@ -121,6 +121,22 @@ Messaging allows you to implement communications in your application. You can us
- Firebase offers the infrastructure to implement push notifications via Firebase Cloud Messaging (FCM). Appwrite doesnt have its own infrastructure but contains a provider to implement FCM.
- Firebase offers In-App Messaging to help you engage your app's active users through targeted, contextual messages inside the app. Appwrite does not offer this feature yet but aims to do so in the future.
## Hosting
Hosting refers to the service that allows developers to deploy and serve web applications to users over the internet. Appwrite offers native hosting as part of its all-in-one platform. That means you can develop, deploy, and scale your application all from a single platform. Firebase requires connecting separate services like Firestore and Cloud Functions.
*Similarities:*
- Both Appwrite and Firebase utilize global Content Delivery Networks (CDNs) to ensure low-latency access to hosted content worldwide.
- Each platform automatically provisions SSL certificates, ensuring that all content is served securely over HTTPS..
- Both services support popular web frameworks, enabling seamless deployment of applications built with technologies like React, Next.js, and Angular.
*Differences:*
- Appwrite Sites runs in containerized environments with full control over server-side rendering. Firebase Hosting is optimized for static content and uses Googles managed infrastructure.
- Appwrite is open-source and self-hostable. Firebase is fully managed by Google with no self-hosting support.
- Appwrite is an all-in-one solution that covers everything from hosting and authentication to databases and cloud functions in one platform. Firebase also offers these services but manages them across separate tools, requiring more integration effort.
# Conclusion
While both Appwrite and Firebase are great Backend-as-a-Service offerings that support numerous SDKs and integrations, they differ in terms of capabilities and pricing. The choice between Appwrite and Firebase hinges on the specific needs of a project. Appwrite stands out with its open-source nature, self-hosting capabilities, pricing affordability, and emphasis on privacy. The community is very welcoming and is praised for it. Firebase's strength lies in its comprehensive ecosystem, Google support, and maturity.

View File

@@ -9,6 +9,8 @@ author: veeresh-mulge
callToAction: true
unlisted: true
category: tutorial
call_to_action: true
---
B2B enterprise SaaS is about to undergo a major shift. While SaaS revolutionized how software is built and distributed, allowing businesses to streamline operations with cloud-based tools, it had one major problem: its still built on a one-size-fits-all approach, with general-purpose solutions that require constant customization and integration. This led to complexity and inefficiency, as businesses needed multiple tools and human resources to make everything work.
@@ -56,6 +58,8 @@ By default, vertical AI agents are designed to address the unique needs of speci
### Early-mover advantage
From healthcare to finance to retail, the application of vertical AI agents is virtually limitless. As vertical AI agents are still relatively new, theres a massive opportunity for early movers to establish themselves as leaders in their chosen niche.
{% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%}
# Conclusion
Vertical AI agents are set to revolutionize industries, offering companies greater efficiency and cost savings by automating workflows and replacing software and human labor. With advancements in AI technology, the right market conditions, and a shift in startup strategies, now is the perfect time to jump into this space.

View File

@@ -9,6 +9,7 @@ author: veeresh-mulge
callToAction: true
unlisted: true
category: tutorial
call_to_action: true
---
With AI advancing at a rapid pace, 2025 is shaping up to be the year of AI agents. We're moving beyond basic chatbots and entering a new era where AI can autonomously solve complex tasks. So, in the near future, fully autonomous AI agents can scope out a project and complete it with all the necessary tools they need and with no help from human partners.
@@ -76,6 +77,8 @@ When building agents, start with simple tasks or single-action models and gradua
In the beginning, keep it simple, measure performance, and adjust the agent's behavior as needed before scaling up.
{% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%}
## Ensure Feedback Mechanisms
Incorporating feedback loops is essential for refining an agent's performance. In particular, coding agents can greatly benefit from feedback through tests that verify the correctness of their output.

View File

@@ -0,0 +1,159 @@
---
layout: post
title: "Understanding OAuth2: The backbone of modern authorization"
description: A quick guide to OAuth2, its flows, and when to use each one.
date: 2025-06-12
cover: /images/blog/understand-oauth2/cover.png
timeToRead: 06
author: laura-du-ry
callToAction: true
unlisted: true
category: product
---
In todays interconnected app ecosystem, users expect seamless, secure access across services. OAuth2 has emerged as the industry standard for handling secure delegated access, making it a critical protocol for developers to understand.
This guide explains OAuth2, how it works, the different flows available, and when to use each one, helping you build secure, scalable authorization experiences.
# What is OAuth2?
OAuth2 is an open standard for authorization. It allows users to grant limited access to their resources on one service to another service without sharing credentials.
Rather than handing out a username and password, users authorize apps to act on their behalf using access tokens. OAuth2 ensures that:
- Apps never directly handle user credentials.
- Users retain control over what permissions they grant.
- Access can be easily revoked.
# Core components of OAuth2
Before diving into the flows, it's important to understand the key players:
- **Resource owner**: The user who authorizes access to their data.
- **Client**: The application requesting access.
- **Authorization server**: Issues access tokens after authenticating the user.
- **Resource server**: Hosts the protected resources.
These components work together to ensure secure authorization across systems.
Refer to the OAuth2 [documentation](/docs/product/auth/oauth2) for complete technical details.
# How OAuth2 works: A simple flow
1. **Authorization request**: The client asks the resource owner for permission.
2. **Authorization grant**: If the user consents, the server issues a grant (authorization code, token, etc.).
3. **Token request**: The client exchanges the grant for an access token.
4. **Resource access**: The client uses the token to access protected resources.
Tokens are typically short-lived and scoped, meaning they only allow the operations the user approved.
# Major OAuth2 flows
OAuth2 offers different "flows" to accommodate various scenarios. Here's a breakdown of the major ones:
## 1. Authorization code flow
**Best for**: Server-side applications
- User authenticates via browser.
- Client receives an authorization code.
- Server exchanges the code for an access token.
**Advantages**:
- Highly secure (authorization code exchanged server-side).
- Supports refresh tokens.
**Typical use cases**:
- Web apps with secure backend servers.
{% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="Multiple OAuth providers" point2="Built-in security" point3="Custom roles and permissions" point4="Integrates with your favourite SDK" cta="Contact sales" url="https://appwrite.io/contact-us/enterprise" /%}
## 2. Authorization code flow with PKCE (Proof Key for Code Exchange)
**Best for**: Mobile and SPA (Single Page Applications)
- Similar to Authorization Code Flow, but with an added security layer (PKCE).
- Prevents interception attacks.
**Advantages**:
- Stronger protection for public clients.
**Typical use cases**:
- Mobile apps, SPAs.
## 3. Client credentials flow
**Best for**: Machine-to-machine (M2M) communication
- No user interaction.
- Client authenticates itself to obtain an access token.
**Advantages**:
- Efficient for service-to-service communication.
**Typical use cases**:
- APIs accessed by backend services.
## 4. Implicit Flow (Legacy)
**Best for**: SPAs (historically)
- Tokens returned directly in browser URL.
- Faster but less secure.
**Note**: Now largely replaced by Authorization Code Flow with PKCE due to security risks.
## 5. Device authorization flow
**Best for**: Devices without browsers/keyboards
- User authenticates on a separate device.
- Device polls authorization server for approval.
**Typical use cases**:
- Smart TVs, IoT devices.
[Appwrite Auth](/products/auth) supports all major OAuth2 flows, making it easy to integrate secure authentication into any app
# OAuth2 Tokens: Access and refresh
OAuth2 commonly uses two types of tokens:
- **Access Token**: Grants access to protected resources.
- **Refresh Token**: Used to obtain new access tokens without re-authenticating the user.
Tokens are often JWTs (JSON Web Tokens) containing claims about the user and the permissions granted.
# When to Use OAuth2
- **Third-party integrations**: Allowing users to connect external services securely.
- **APIs**: Protecting APIs from unauthorized access.
- **Mobile and web Apps**: Enabling secure login and data access without managing credentials.
- **B2B applications**: Secure service-to-service communication.
# Common OAuth2 pitfalls
- **Over-scoped tokens**: Granting too many permissions.
- **Insecure storage**: Storing tokens in insecure locations (e.g., localStorage without encryption).
- **Ignoring token expiration**: Failing to handle token refresh flows.
- **Misusing Implicit Flow**: Using legacy flows where better options (PKCE) are available.
# OAuth2: A key enabler of modern security
OAuth2 powers secure, flexible authorization across the modern internet. Understanding its core flows and best practices helps developers build safer, more user-friendly apps.
Choosing the proper OAuth2 flow based on your application's architecture and user needs is critical to balancing security, usability, and scalability.
Ready to explore OAuth2 more deeply? Check
- [Appwrite Authentication docs](/docs/products/auth)
- [Overview of all the OAuth providers](/integrations#auth)
- [Appwrite Authentication overview](/products/auth)

View File

@@ -0,0 +1,110 @@
---
layout: post
title: "Understanding IdP vs SP-Initiated SSO"
description: A quick guide to IdP vs SP-initiated SSO and when to use each.
date: 2025-06-16
cover: /images/blog/understanding-idp-vs-sp-initiated-sso/cover.png
timeToRead: 06
author: laura-du-ry
callToAction: true
unlisted: true
category: product
---
Managing authentication across multiple applications is a growing challenge for developers, especially with users expecting more convenience and security. Single Sign-On (SSO) offers a practical solution to that problem, allowing users to access multiple services with one login. Although the experience is almost always seamless for users, developers have multiple options for implementing SSO in their applications.
This guide breaks down the differences between **Identity Provider (IdP)-initiated** and **Service Provider (SP)-initiated** SSO, their advantages and trade-offs, and how to choose the best fit for your setup.
# What is IdP-Initiated SSO?
First, a quick refresher: an **Identity Provider (IdP)** manages user identities, validating who a user is before granting access to different applications. Heres a quick [overview](/docs/products/auth/identities) of how Appwrite handles identity and access.
In an IdP-initiated SSO flow, the users journey starts at the IdP itself:
# How it works
1. User logs in to the IdP.
2. The IdP displays a dashboard of connected applications.
3. The user selects a service to access.
4. The IdP sends a secure authentication token (such as a SAML assertion) to the Service Provider (SP).
5. The SP grants access based on the [token](/docs/products/auth/tokens).
# Advantages
- **Streamlined access**: Launch multiple services from a single dashboard.
- **Reduced credential reuse**: Minimizes repeated logins, lowering the risk of compromised credentials.
- **Centralized control**: Simplifies user monitoring and access management.
# Trade-offs
- **Extra navigation step**: Users must first visit the IdP portal.
- **Single point of failure**: If the IdP is compromised, multiple services could be at risk.
- **Integration challenges**: Some services may not fully support IdP-initiated workflows.
{% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="GDPR, HIPAA and SOC 2 compliant" point2="Built-in security" point3="Multi-factor authentication" point4="Integrates with your favourite SDK" cta="Contact sales" url="/contact-us/enterprise" /%}
# What is SP-Initiated SSO?
**Service Providers (SPs)** are the applications or services users want to access.
In SP-initiated SSO, the process begins when a user attempts to log into an application directly:
# How it works
1. User tries to access the service.
2. The service detects no active session and redirects the user to the IdP.
3. The user authenticates at the IdP.
4. The IdP sends an authentication token back to the service.
5. The service grants access.
# Advantages
- **Direct access**: Users can go straight to the service they want.
- **Seamless integration**: Fits naturally into user-driven workflows.
- **Flexibility**: Useful for both internal and external users.
# Trade-offs
- **Redirect dependency**: Requires smooth coordination between service and IdP.
- **Increased setup complexity**: Proper configuration is critical to avoid login issues.
# IdP- vs SP-Initiated SSO: Quick Comparison
| Feature | IdP-Initiated SSO | SP-Initiated SSO |
| --- | --- | --- |
| **Starting Point** | Identity Provider portal | Service Provider login page |
| **User Flow** | Login at IdP, then select services | Attempt service access, then authenticate via IdP |
| **User Experience** | Best for environments with multiple services | Best for quick, direct service access |
| **Security Considerations** | Central control but single point of vulnerability | Stronger per-service session security |
| **Typical Use Cases** | Corporate portals, education hubs | SaaS apps, customer-facing platforms |
# When to choose IdP-Initiated SSO
- **Organizations with many internal services**: Ideal for centralized portals.
- **Formal environments**: Where users are accustomed to navigating through a unified dashboard.
- **Legacy system compatibility**: Easier integration with older systems.
# When to Choose SP-Initiated SSO
- **User-first services**: Where users need to quickly access a single app.
- **B2B and B2C platforms**: Especially when users might come in via bookmarks, emails, or direct links.
- **Dynamic environments**: Where new apps are frequently added or removed.
Pro tip: SP-initiated flows are often complemented by [adaptive MFA](/docs/products/auth/mfa) to enhance security without compromising the user experience.
# When to use both approaches
Many organizations implement both IdP- and SP-initiated SSO to serve different user needs:
- **Employee and partner ecosystems**: Employees might use IdP dashboards while partners or customers prefer direct access.
- **Hybrid cloud setups**: Supporting a mix of legacy and modern applications.
- **Adaptive security strategies**: Choosing the flow based on device, location, or user profile.
Choosing the right SSO initiation method,or blending both, can dramatically impact [security](/docs/products/auth/security), user satisfaction, and scalability. Evaluate your platform's user behavior, security posture, and integration needs to pick the best approach for your environment.
# Futher reading
- [Appwrite Authentication docs](/docs/products/auth)
- [Developer's guide to user authentication](/blog/post/guide-to-user-authentication)
- [Appwrite Authentication overview](/products/auth)

View File

@@ -153,6 +153,11 @@
label: 'Abuse',
href: '/docs/advanced/platform/abuse'
},
{
new: isNewUntil('31 July 2025'),
label: 'Support SLA',
href: '/docs/advanced/platform/support-sla'
},
{
new: isNewUntil('31 July 2025'),
label: 'Uptime SLA',

View File

@@ -0,0 +1,54 @@
---
layout: article
title: Support SLA
description: Learn about Appwrite's support service level agreement (SLA) including response times, severity levels, and support commitments for different subscription tiers.
---
This Support Service Level Agreement ("SLA") describes the support services provided by APPWRITE ("we," "us," or "our") to users of our products and services ("you" or "user"). By using our services, you agree to the terms of this SLA.
## Scope
This SLA outlines our commitments for providing support services via email, including response and resolution processes based on issue severity. The specific response times depend on the support tier associated with your subscription: **Pro**, **Scale**, or **Enterprise**.
## Severity levels
Support issues are categorized into the following severity levels:
- **Critical**: System is down or a critical component is non-functional, causing a complete stoppage of work or significant business impact.
- **High**: Major functionality is impaired, but a workaround is available, or a critical component is significantly degraded.
- **Medium**: Minor functionality is impaired without significant business impact.
- **Low**: Issue has minor impact on business operations; workaround is not necessary.
- **Question**: Requests for information, general guidance, or feature requests.
## Response time targets
| Severity | Pro | Scale | Enterprise |
| --- | --- | --- | --- |
| Critical | Unsupported | 1 hour (24/7/365) | 15 minutes (24/7/365) |
| High | Unsupported | 4 hours (24/7/365) | 1 hour (24/7/365) |
| Medium | 2 business days | 1 business day | 12 hours (24/7/365) |
| Low | 3 business days | 2 business days | 24 hours (24/7/365) |
| Question | 4 business days | 3 business days | 1 business day |
## Business hours and days
Our standard business hours are from **9:00 AM to 5:00 PM Pacific Time**, Monday through Friday, excluding public holidays. Enterprise and Scale customers receive extended support 24/7/365.
## User responsibilities
To ensure effective support, users are expected to:
- Provide detailed information about each issue, including screenshots, error messages, logs, and steps to reproduce the problem.
- Ensure relevant personnel are available to assist in diagnosing and resolving issues.
- Implement reasonable recommendations provided by our support team.
## Limitations and exclusions
- This SLA applies only to support requests submitted via the Appwrite Console.
- SLA obligations may be affected by factors outside our reasonable control, including but not limited to force majeure events, third-party dependencies, or actions taken by the user.
## Modifications
We reserve the right to modify this SLA at any time. Changes become effective upon posting to our website. Your continued use of our services after changes indicates your acceptance of the updated SLA.
For questions or concerns about this SLA, please contact us at our [contact page](https://appwrite.io/contact-us).

View File

@@ -109,8 +109,8 @@ If running in production, it might be easier to use a 3rd party SMTP server as i
| `_APP_STORAGE_S3_ACCESS_KEY` | **version >= 0.13.0** AWS S3 storage access key. Required when the storage adapter is set to S3. You can get your access key from your AWS console. |
| `_APP_STORAGE_S3_SECRET` | **version >= 0.13.0** AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console. |
| `_APP_STORAGE_S3_REGION` | **version >= 0.13.0** AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console. |
| `_APP_STORAGE_S3_BUCKET` | **version >= 0.13.0** AWS S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your AWS console. |
| `_APP_STORAGE_S3_ENDPOINT` | **version >= 1.7.0** Override the S3 endpoint to use an S3-compatible provider. This should just be the host (without 'https://'). |
| `_APP_STORAGE_S3_BUCKET` | **version >= 0.13.0** AWS S3 storage bucket. Required when storage adapter is set to S3 and using path-style requests (where the bucket is in the path). You can create buckets in your AWS console. If using virtual-hosted-style paths where the bucket is in the endpoint URL, this should be empty. |
| `_APP_STORAGE_S3_ENDPOINT` | **version >= 1.7.0** Override the S3 endpoint to use an S3-compatible provider. This should just be the host (without 'https://'). If using virtual-hosted-style paths where the bucket is included in the endpoint (e.g., `bucket-name.s3.amazonaws.com`), `_APP_STORAGE_S3_BUCKET` should be empty. For path-style requests, the endpoint should not include the bucket name and `_APP_STORAGE_S3_BUCKET` should be set. |
| `_APP_STORAGE_DO_SPACES_ACCESS_KEY` | **version >= 0.13.0** DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console. |
| `_APP_STORAGE_DO_SPACES_SECRET` | **version >= 0.13.0** DigitalOcean spaces secret key. Required when the storage adapter is set to DOSpaces. You can get your secret key from your DigitalOcean console. |
| `_APP_STORAGE_DO_SPACES_REGION` | **version >= 0.13.0** DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console. |

View File

@@ -97,6 +97,10 @@
label: 'Multi-factor authentication',
href: '/docs/products/auth/mfa'
},
{
label: 'Auth status check',
href: '/docs/products/auth/checking-auth-status'
},
{
label: 'User verification',
href: '/docs/products/auth/verify-user'

View File

@@ -0,0 +1,146 @@
---
layout: article
title: Checking auth status
description: Learn how to check a user's authentication status in your Appwrite application and handle authentication flow appropriately.
---
One of the first things your application needs to do when starting up is to check if the user is authenticated. This is an important step in creating a great user experience, as it determines whether to show login screens or protected content.
# Check auth with `account.get()`
The recommended approach for checking authentication status is to use the `account.get()` method when your application starts:
{% multicode %}
```client-web
import { Client, Account } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
const account = new Account(client);
// Check if user is logged in
async function checkAuthStatus() {
try {
// If successful, user is authenticated
const user = await account.get();
console.log("User is authenticated:", user);
// Proceed with your authenticated app flow
return user;
} catch (error) {
console.error("User is not authenticated:", error);
// Redirect to login page or show login UI
// window.location.href = '/login';
return null;
}
}
// Call this function when your app initializes
checkAuthStatus();
```
```client-flutter
import 'package:appwrite/appwrite.dart';
void checkAuthStatus() async {
final client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
final account = Account(client);
try {
// If successful, user is authenticated
final user = await account.get();
print('User is authenticated: ${user.name}');
// Proceed with your authenticated app flow
} catch (e) {
print('User is not authenticated: $e');
// Redirect to login page or show login UI
}
}
// Call this function when your app initializes
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account
import io.appwrite.exceptions.AppwriteException
class AuthManager {
private val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")
private val account = Account(client)
suspend fun checkAuthStatus(): Boolean {
return try {
val user = account.get()
Log.d("Auth", "User is authenticated: ${user.name}")
// Proceed with your authenticated app flow
true
} catch (e: AppwriteException) {
Log.e("Auth", "User is not authenticated: ${e.message}")
// Redirect to login page or show login UI
false
}
}
}
// Call this function when your app initializes
```
```client-apple
import Appwrite
func checkAuthStatus() {
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")
let account = Account(client)
Task {
do {
// If successful, user is authenticated
let user = try await account.get()
print("User is authenticated: \(user.name)")
// Proceed with your authenticated app flow
} catch {
print("User is not authenticated: \(error)")
// Redirect to login page or show login UI
}
}
}
// Call this function when your app initializes
```
{% /multicode %}
# Missing scope error
When a user is not authenticated and you call `account.get()`, you might see an error message like:
```
User (role: guests) missing scope (account)
```
This error is telling you that:
1. The current user has the role of "guest" (unauthenticated visitor)
2. This guest user does not have the required permission scope to access account information
3. This is the expected behavior when a user is not logged in
{% info title="Authentication flow" %}
In a typical application flow:
1. Call `account.get()` when your app starts
2. If successful → User is authenticated → Show the main app UI
3. If error → User is not authenticated → Redirect to login screen
{% /info %}
# Best practices
- Call `account.get()` early in your application lifecycle
- Handle both authenticated and unauthenticated states gracefully
- Show appropriate loading states while checking authentication
- Implement proper error handling to avoid showing error messages to users

View File

@@ -72,6 +72,17 @@ $adminClient = (new Client())
->setKey('<YOUR_API_KEY>'); // Your secret API key
```
```python
from appwrite.client import Client
admin_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint \
.set_project('<PROJECT_ID>') # Your project ID
.set_key('<YOUR_API_KEY>') # Your secret API key
)
```
{% /multicode %}
@@ -105,6 +116,22 @@ if ($session) {
$sessionClient->setSession($session);
}
```
```python
from flask import request
from appwrite.client import Client
session_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
.set_project('<PROJECT_ID>') # Your project ID
)
# Get the session cookie from the request
session = request.cookies.get('session')
if session:
session_client.set_session(session)
```
{% /multicode %}
# Creating email/password sessions {% #creating-sessions %}
@@ -178,6 +205,39 @@ try {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
```
```python
from flask import Flask, request, jsonify, make_response
# Initialize admin client here
# ...
@app.post('/login')
def login():
body = request.json
# Get email and password from request
email = body['email']
password = body['password']
try:
account = Account(admin_client)
# Create the session using the Appwrite client
session = account.create_email_password_session(email, password)
resp = make_response(jsonify({'success': True}))
# Set the session cookie
resp.set_cookie('session',
session['secret'],
httponly=True,
secure=True,
samesite='Strict',
expires=session['expire'],
path='/'
)
return resp
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 400
```
{% /multicode %}
We also recommend using the `httpOnly`, `secure`, and `sameSite` cookie options to ensure that the cookie is only sent over HTTPS,
@@ -242,6 +302,30 @@ try {
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
```
```python
# Initialize the session client here
@app.get('/user')
def get_user():
# First, read the session cookie from the request
session = request.cookies.get('session')
# If the session cookie is not present, return an error
if not session:
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
# pass the session cookie to the Appwrite client
session_client.set_session(session)
account = Account(session_client)
# Now, you can make authenticated requests to the Appwrite API
try:
user = account.get()
return jsonify({'success': True, 'user': user})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 400
```
{% /multicode %}
@@ -319,6 +403,19 @@ $account = new Account($client);
$result = $account->createAnonymousSession();
```
```python
from appwrite.client import Client
from appwrite.services.account import Account
client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
.set_project('<PROJECT_ID>') # Your project ID
)
account = Account(client)
result = account.create_anonymous_session()
```
{% /multicode %}
# Forwarding user agent {% #forwarding-user-agent %}
@@ -333,6 +430,9 @@ client.setForwardedUserAgent(req.headers['user-agent']);
<?php
$client->setForwardedUserAgent($_SERVER['HTTP_USER_AGENT']);
```
```python
client.set_forwarded_user_agent(request.headers.get('user-agent'))
```
{% /multicode %}
# OAuth2 {% #oauth2 %}
@@ -383,6 +483,29 @@ $redirectUrl = $account->createOAuth2Token(
header('Location' . $redirectUrl);
```
```python
from appwrite.client import Client
from appwrite.services.account import Account, OAuthProvider
from flask import Flask, request ,redirect, make_response, jsonify
admin_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('<PROJECT_ID>')
.set_key('<API_KEY>')
)
@app.get('/oauth')
def oauth():
account = Account(admin_client)
redirect_url = account.create_o_auth2_token(
OAuthProvider.Github, # Provider
'https://example.com/oauth/success', # Success URL
'https://example.com/oauth/failure', # Failure URL
)
return redirect(redirect_url)
```
{% /multicode %}
Next, create a success callback endpoint that receives the `userId` and `secret` URL parameters, and then calls `createSession` on the server side. This endpoint returns a session object, which you can store in a cookie.
@@ -448,6 +571,38 @@ try {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
```
```python
@app.get('/oauth/success')
def oauth_success():
account = Account(admin_client)
# Get the userId and secret from the URL parameters
user_id = request.args.get('userId')
secret = request.args.get('secret')
try:
# Create the session using the Appwrite client
session = account.create_session(user_id, secret)
# Set the session cookie
res = make_response(jsonify({'success': True}))
# Set session cookie
res.set_cookie(
'session',
session['secret'],
httponly=True,
secure=True,
samesite='Strict',
max_age=session['expire'],
path='/'
)
return res
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 400
```
{% /multicode %}
Now the cookie is set, it will be passed to the server with subsequent requests, and you can use it to make authenticated requests to the Appwrite API on behalf of the end-user.

View File

@@ -3,21 +3,25 @@
import Docs from '$lib/layouts/Docs.svelte';
import Sidebar, { type NavParent, type NavTree } from '$lib/layouts/Sidebar.svelte';
import { preferredPlatform, preferredVersion } from '$lib/utils/references';
import type { Snippet } from 'svelte';
$: expandable = !!page.url.pathname.match(
/\/docs\/references\/.*?\/(client|server).*?\/.*?\/?/
const { children }: { children: Snippet } = $props();
const expandable = $derived(
!!page.url.pathname.match(/\/docs\/references\/.*?\/(client|server).*?\/.*?\/?/)
);
$: platform = $preferredPlatform ?? page.params?.platform ?? 'client-web';
const platform = $derived($preferredPlatform ?? page.params?.platform ?? 'client-web');
/* correct platform prefix for references page */
$: resolvedPlatformPrefix = /^server-|^client-/.test(platform)
? platform
: `server-${platform}`;
const resolvedPlatformPrefix = $derived(
/^server-|^client-/.test(platform) ? platform : `server-${platform}`
);
$: prefix = `/docs/references/${$preferredVersion ?? page.params?.version ?? 'cloud'}/${resolvedPlatformPrefix}`;
const prefix = $derived(
`/docs/references/${$preferredVersion ?? page.params?.version ?? 'cloud'}/${resolvedPlatformPrefix}`
);
$: navigation = [
const navigation: NavTree = $derived([
{
label: 'Getting started',
items: [
@@ -93,22 +97,7 @@
}
]
}
// {
// label: 'Debugging',
// items: [
// {
// icon: 'icon-document-search',
// label: 'Response codes',
// href: '/docs/advanced/platform/response-codes'
// },
// {
// icon: 'icon-document-report',
// label: 'Rate-limits',
// href: '/docs/advanced/platform/rate-limits'
// }
// ]
// }
] as NavTree;
]);
const parent: NavParent = {
href: '/docs',
@@ -118,5 +107,5 @@
<Docs variant={expandable ? 'expanded' : 'two-side-navs'} isReferences={expandable}>
<Sidebar {navigation} {expandable} {parent} />
<slot />
{@render children()}
</Docs>

View File

@@ -15,7 +15,7 @@
<span class="text-micro text-primary uppercase">
{response.code}
</span>
<span class="text-caption">application/json</span>
<span class="text-caption">{response.contentType ?? 'no content'}</span>
</header>
{#if response.models.length > 0}
<ul class="text-sub-body mt-4">

View File

@@ -26,6 +26,7 @@
import Request from './(components)/Request.svelte';
import Response from './(components)/Response.svelte';
import RateLimits from './(components)/RateLimits.svelte';
import type { SDKMethod } from '$lib/utils/specs';
let { data } = $props();
@@ -34,7 +35,6 @@
const headings = getContext<LayoutContext>('headings');
let selected: string | undefined = $state(undefined);
let selectedMenuItem: HTMLElement;
headings.subscribe((n) => {
const noVisible = Object.values(n).every((n) => !n.visible);
@@ -61,7 +61,9 @@
correctPlatform = platform.replaceAll(`server-`, ``) as Platform;
}
preferredPlatform.set(correctPlatform as Platform);
if ($preferredPlatform === correctPlatform) return;
preferredPlatform.set(correctPlatform);
goto(`/docs/references/${version}/${platform}/${service}`, {
noScroll: true
@@ -71,6 +73,8 @@
function selectVersion(event: CustomEvent<unknown>) {
const { platform, service } = page.params;
const version = event.detail as Version;
if ($preferredVersion === version) return;
preferredVersion.set(version);
goto(`/docs/references/${version}/${platform}/${service}`, {
noScroll: true
@@ -110,8 +114,7 @@
: `server-${$preferredPlatform}`;
goto(`/docs/references/${$preferredVersion}/${platformMode}/${page.params.service}`, {
noScroll: true,
replaceState: false
noScroll: true
});
}
@@ -145,7 +148,7 @@
/**
* Sorts methods by their operation order and title
*/
function sortMethods(methods: any[]) {
function sortMethods(methods: SDKMethod[]) {
return methods.sort((a, b) => {
const orderA = getOperationOrder(a.title);
const orderB = getOperationOrder(b.title);
@@ -156,11 +159,8 @@
});
}
/**
* Groups methods by their group attribute, null group goes to '' for ordering
*/
function groupMethodsByGroup(methods: any[]) {
return methods.reduce((acc, method) => {
function groupMethodsByGroup(methods: SDKMethod[]) {
return methods.reduce<Record<string, SDKMethod[]>>((acc, method) => {
const groupKey = method.group || '';
if (!acc[groupKey]) {
acc[groupKey] = [];
@@ -170,20 +170,6 @@
}, {});
}
function bindSelectedRef(node: HTMLElement, isSelected: boolean) {
if (isSelected) {
selectedMenuItem = node;
}
return {
update(newIsSelected: boolean) {
if (newIsSelected) {
selectedMenuItem = node;
}
}
};
}
let platformBindingForSelect = $derived(page.params.platform as Platform);
let platform = $derived(/**$preferredPlatform ?? */ page.params.platform as Platform);
let platformType = $derived(platform.startsWith('client-') ? 'CLIENT' : 'SERVER');
@@ -400,7 +386,6 @@
href={`#${method.id}`}
class="web-references-menu-link text-caption"
class:is-selected={method.id === selected}
use:bindSelectedRef={method.id === selected}
>
{method.title}
</a>

View File

@@ -41,7 +41,7 @@
<Td>{property.name}</Td>
<Td>{property.type}</Td>
<Td>
{property.description}
{@html parse(property.description)}
{#if property.relatedModels}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
Can be one of: {@html parse(property.relatedModels)}

View File

@@ -1,7 +1,7 @@
import { base } from '$app/paths';
import { groupBy } from 'remeda';
import type { IntegrationCategory } from '$lib/constants';
import { integrationCategoryDescriptions as categoryDescriptions } from '$lib/constants';
import type { SearchableCategory } from '$lib/constants';
import { partnerCategoryDescriptions as categoryDescriptions } from '$lib/constants';
export type Integration = {
title: string;
@@ -26,7 +26,7 @@ export const load = () => {
eager: true
});
const categories: IntegrationCategory[] = [];
const categories: SearchableCategory[] = [];
const platforms: string[] = [];
const integrations = Object.entries(integrationsGlob).map(([filepath, integrationList]) => {
@@ -40,7 +40,7 @@ export const load = () => {
frontmatter.platform.map((platform) => platforms.push(platform));
categories.push(
categoryDescriptions.find((i) => i.slug === frontmatter.category) ??
({} as IntegrationCategory)
({} as SearchableCategory)
);
return {

View File

@@ -50,10 +50,22 @@ Visit the `.env` file created for your Appwrite instance and update the followin
```bash
_APP_STORAGE_DEVICE=s3
_APP_STORAGE_S3_BUCKET=[BUCKET_NAME]
_APP_STORAGE_S3_REGION=[AWS_REGION]
_APP_STORAGE_S3_ACCESS_KEY=[ACCESS_KEY_ID]
_APP_STORAGE_S3_SECRET=[SECRET_ACCESS_KEY]
_APP_STORAGE_S3_BUCKET=<BUCKET_NAME>
_APP_STORAGE_S3_REGION=<AWS_REGION>
_APP_STORAGE_S3_ACCESS_KEY=<ACCESS_KEY_ID>
_APP_STORAGE_S3_SECRET=<SECRET_ACCESS_KEY>
```
Starting from version 1.7.0, you can also use S3-compatible providers by configuring the endpoint:
```bash
# For path-style requests (bucket in the path)
_APP_STORAGE_S3_ENDPOINT=<PROVIDER_ENDPOINT> # e.g., s3.amazonaws.com (without https://)
_APP_STORAGE_S3_BUCKET=<BUCKET_NAME> # bucket name required here
# OR for virtual-hosted-style paths (bucket in the endpoint)
_APP_STORAGE_S3_ENDPOINT=<BUCKET_NAME>.<PROVIDER_ENDPOINT> # e.g., bucket-name.s3.amazonaws.com
_APP_STORAGE_S3_BUCKET= # leave this empty when bucket is in the endpoint
```
After that, run the following Docker Compose commands in your terminal to restart your Appwrite containers and verify if the changes have been successfully applied:

View File

@@ -20,7 +20,7 @@
{
title: 'Training',
description:
'We provide in depth training and workshops to help you master Appwrite for your clients.',
'We provide in-depth training and workshops to help you master Appwrite for your clients.',
icon: Training
},
{
@@ -36,9 +36,9 @@
icon: EarlyAccess
},
{
title: 'Revenue share',
title: 'Innovation',
description:
'For each client you sell Appwrite to, you will receive a part of the revenue for a whole year.',
"Empower your team and elevate your customers' experiences with the newest technology.",
icon: Revenue
},
{
@@ -64,7 +64,7 @@
},
{
title: 'All in one platform',
description: 'All the APIs a developer needs in one place. And more to come.',
description: 'Everything you need to develop, deploy, and scale your applications.',
icon: Expert
}
];

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import { classNames } from '$lib/utils/classnames';
</script>
<div
class={classNames(
'border-smooth relative -mb-24 border-t py-32',
'bg-[url("/images/bgs/building-blocks.webp")] [background-size:50%] bg-right-bottom bg-no-repeat',
'before:absolute before:top-0 before:left-0 before:z-0 before:block before:h-80 before:w-full before:bg-[radial-gradient(at_25%_0%,_hsla(343,_98%,_60%,_0.05)_0px,_transparent_73%,_transparent_100%)] md:before:w-1/2',
'after:absolute after:top-0 after:right-0 after:z-0 after:hidden after:h-80 after:w-1/2 after:bg-[radial-gradient(at_100%_0%,_hsla(177,_53%,_69%,_0.1)_0px,_transparent_73%,_transparent_100%)] after:md:block md:after:block'
)}
>
<div class="relative container grid grid-cols-1 place-items-center md:grid-cols-2">
<section class="flex flex-col gap-4">
<h2 class="text-display font-aeonik-pro text-primary max-w-[600px]">
Become a Technology Partner
</h2>
<p class="text-body font-medium">
Join our Technology Partners program to integrate your solutions with Appwrites
API, enhancing functionality and expanding your reach.
</p>
<a href="/integrations/technology-partner" class="web-button mt-4">
<span class="text">Get Started</span>
</a>
</section>
</div>
</div>

View File

@@ -0,0 +1,174 @@
<script context="module" lang="ts">
import { PUBLIC_GROWTH_ENDPOINT } from '$env/static/public';
export const subscribeToNewsletter = async (email: string) => {
const response = await fetch(`${PUBLIC_GROWTH_ENDPOINT}/partners/contact`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email
})
});
return response;
};
</script>
<script lang="ts">
let name = '';
let email = '';
let companyName = '';
let companyUrl = '';
let message = '';
let submitted = false;
let error: string | undefined;
let submitting = false;
const handleSubmit = async () => {
submitting = true;
error = undefined;
const response = await subscribeToNewsletter(email);
submitting = false;
if (response.status >= 400) {
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.';
return;
}
submitted = true;
};
</script>
<div class="relative max-h-[35vh]">
<div class="relative z-10 py-10">
<div class="mx-auto flex w-full max-w-4xl flex-col items-center gap-8 text-center">
{#if submitted}
<div class="flex items-center gap-2">
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="9"
cy="9"
r="8"
fill="#FD366E"
fill-opacity="0.08"
stroke="#FD366E"
stroke-opacity="0.32"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M5.25 10.5L7.75 12.5L12.75 6"
stroke="#E4E4E7"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<span class="text">
Thank you for subscribing! An email has been sent to your inbox.
</span>
</div>
{:else}
<form
method="post"
on:submit|preventDefault={handleSubmit}
class="mt-4 flex w-full flex-col gap-4 lg:mt-0"
>
<div class="flex w-full text-left">
<ul class="web-form-list grid w-full gap-4 md:grid-cols-2">
<li class="web-form-item">
<label class="text-caption block" for="name">Full name</label>
<input
required
class="web-input-text w-full"
type="text"
placeholder="Walter"
id="name"
bind:value={name}
/>
</li>
<li class="web-form-item">
<label class="text-caption block" for="workEmail">Email</label>
<input
required
class="web-input-text w-full"
type="email"
placeholder="walter@appwrite.io"
id="workEmail"
bind:value={email}
/>
</li>
<li class="web-form-item">
<label class="text-caption block" for="companyName"
>Company name</label
>
<input
required
class="web-input-text w-full"
type="text"
placeholder="Acme Corp"
id="companyName"
bind:value={companyName}
/>
</li>
<li class="web-form-item flex-col gap-1">
<label class="text-caption block" for="companyUrl"
>Company url</label
>
<input
required
class="web-input-text w-full"
type="url"
placeholder="https://appwrite.io"
id="companyUrl"
bind:value={companyUrl}
/>
</li>
<li class="web-form-item flex-col gap-1 sm:col-span-1 md:col-span-2">
<label class="text-caption block" for="message"
>Any other details youd like to share?</label
>
<textarea
required
class="web-input-text w-full"
id="message"
placeholder="Your message"
bind:value={message}
/>
</li>
</ul>
</div>
<div
class="flex flex-col items-center justify-between gap-4 sm:flex-col md:flex-col lg:flex-row"
>
<p class="text-caption max-w-sm self-start text-left lg:self-center">
{#if error}
{error}
{:else}
This form is protected by reCAPTCHA, and the Google Privacy Policy
and Terms of Service apply.
{/if}
</p>
<button
type="submit"
disabled={submitting}
class="web-button is-secondary cursor-pointer px-16!"
>
<span>Send</span>
</button>
</div>
</form>
{/if}
</div>
</div>
</div>

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import { classNames } from '$lib/utils/classnames';
</script>
<div
class={classNames(
'grid-bg border-smooth relative flex items-center border-b px-5 py-28 lg:px-8 xl:px-16',
'before:from-accent/20 before:absolute before:inset-0 before:-z-1 before:bg-linear-to-tr before:via-transparent before:via-40% before:to-transparent'
)}
>
<div class="relative container pb-0">
<div class="flex flex-col items-center">
<div class="flex flex-col items-center justify-center gap-5 text-center">
<div class="text-micro text-white uppercase">
Appwrite Partner Catalog<span class="web-u-color-text-accent">_</span>
</div>
<h1 class="text-headline font-aeonik-pro text-primary">Find a Partner</h1>
<p class="text-description max-w-xl">
Unlock the full potential of Appwrite by seamlessly integrating your favorite
apps with your projects.
</p>
</div>
</div>
</div>
</div>
<style>
.grid-bg {
--line-color: rgba(255, 255, 255, 0.02);
--size: calc(100vw / 16);
position: relative;
z-index: 1;
overflow: hidden;
background-image:
repeating-linear-gradient(
0deg,
var(--line-color),
var(--line-color) 1px,
transparent 1px,
transparent var(--size)
),
repeating-linear-gradient(
90deg,
var(--line-color),
var(--line-color) 1px,
transparent 1px,
transparent var(--size)
);
&::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background-image: radial-gradient(
circle at bottom right,
rgba(25, 25, 28, 0.5) 19%,
transparent 100%
);
z-index: -2;
}
}
</style>

View File

@@ -0,0 +1,541 @@
<script lang="ts">
import { Main } from '$lib/layouts';
import { DEFAULT_HOST } from '$lib/utils/metadata';
import { TITLE_SUFFIX } from '$routes/titles';
import FooterNav from '$lib/components/FooterNav.svelte';
import MainFooter from '$lib/components/MainFooter.svelte';
import { type ResultType, Fuse } from '$lib/integrations';
import { writable } from 'svelte/store';
import { autoHash } from '$lib/actions/autoHash';
import type { Partner } from './+page';
import { goto } from '$app/navigation';
import { onDestroy, onMount } from 'svelte';
import { browser } from '$app/environment';
import { classNames } from '$lib/utils/classnames';
import { page } from '$app/state';
import Hero from './(components)/hero.svelte';
import CallToAction from './(components)/call-to-action.svelte';
import Input from '$lib/components/ui/input.svelte';
import Icon from '$lib/components/ui/icon';
export let data;
const title = 'Partners' + TITLE_SUFFIX;
const description =
'Connect your favorite apps to Appwrite for one unified tech stack. Explore our catalog of integrations now.';
const ogImage = DEFAULT_HOST + '/images/open-graph/website.png';
// search functionality
let fuseOptions = {
keys: ['title'],
threshold: 0.2,
distance: 500
};
let result: ResultType<Partner> = [];
let hasQuery: boolean;
let query = writable(decodeURIComponent(page.url.searchParams.get('search') ?? ''));
$: query.subscribe((value) => {
hasQuery = value.length > 0;
});
// platform filters
const platforms = ['All', ...data.platforms];
let activePlatform = 'All';
// categories
let activeCategory: string | null = null;
const handleQuery = (e: Event) => {
const value = (e.currentTarget as HTMLInputElement).value;
query.set(value);
};
onMount(() => {
if (browser) document.documentElement.setAttribute('data-scroll-smooth', '');
});
onDestroy(() => {
if (browser) document.documentElement.removeAttribute('data-scroll-smooth');
});
</script>
<svelte:head>
<!-- Titles -->
<title>{title}</title>
<meta property="og:title" content={title} />
<meta name="twitter:title" content={title} />
<!-- Desscription -->
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta name="twitter:description" content={description} />
<!-- Image -->
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
</svelte:head>
<!-- binding for fuse -->
<Fuse list={data.list} options={fuseOptions} bind:query={$query} bind:result />
<Main>
<Hero />
<div class="py-10">
<div>
<div class="container">
<div class="l-integrations-grid">
<aside class="sidebar flex flex-col gap-8" id="integrations-side">
<section>
<Input
label="Search"
name="search"
placeholder="Search"
bind:value={$query}
autocomplete="off"
oninput={handleQuery}
>
{#snippet icon()}
<Icon name="search" aria-hidden="true" />
{/snippet}
</Input>
</section>
<section class="flex flex-col">
<section class="flex flex-col gap-4">
<h2
class="web-side-nav-header text-micro whitespace-nowrap uppercase"
>
Platform
</h2>
<ul class="flex flex-wrap gap-2" class:disabled={hasQuery}>
{#each platforms as platform}
<li>
<button
class={classNames(
'tag bg-greyscale-800 border-greyscale-700 h-8 cursor-pointer rounded-full border px-3 text-sm font-light',
{
'bg-white text-black':
activePlatform === platform
}
)}
class:active-tag={activePlatform === platform}
on:click={() => (activePlatform = platform)}
>{platform}</button
>
</li>
{/each}
</ul>
</section>
<div class="web-u-sep-block-start my-6"></div>
<section class="flex flex-col gap-4">
<h2
class="web-side-nav-header text-micro whitespace-nowrap uppercase"
>
Categories
</h2>
<div class="relative block sm:hidden">
<select
class="web-input-text w-full appearance-none"
disabled={hasQuery}
on:change={(e) =>
goto(`#${e.currentTarget.value.toLowerCase()}`)}
>
{#each data.categories as category}
{@const integrations = data.partners.find(
(i) => i.category === category.slug
)}
{#if integrations && (activePlatform === 'All' || integrations.integrations.some( (i) => i.partnerLevel.includes(activePlatform) ))}
<option value={category.slug}>
{category.heading}
</option>
{/if}
{/each}
<option value={null}> Select category </option>
</select>
<Icon
name="chevron-down"
class="web-u-pointer-events-none absolute top-[11px] right-2"
aria-hidden="true"
/>
</div>
<ul class="hidden flex-col gap-4 sm:flex" class:disabled={hasQuery}>
{#each data.categories as category}
{@const integrations = data.partners.find(
(i) => i.category === category.slug
)}
{#if integrations && (activePlatform === 'All' || integrations.integrations.some( (i) => i.partnerLevel.includes(activePlatform) ))}
<li>
<a
href={`#${category.slug}`}
class="web-link"
class:is-pink={category.slug === activeCategory}
on:click={() =>
activeCategory === category.slug}
>{category.heading}</a
>
</li>
{/if}
{/each}
</ul>
</section>
</section>
</aside>
<section>
<div class="flex flex-col gap-16">
{#if hasQuery}
<section class="l-max-size-list-cards-section flex flex-col gap-8">
<header class="flex flex-col gap-1">
<h2 class="text-label text-primary">Search results</h2>
<p class="text-description">
{result.length > 0 ? result.length : 'No'} results found
for "{$query}"
</p>
</header>
<div class="l-max-size-list-cards flex flex-col gap-8">
<ul class="l-grid-1">
{#each result.map((d) => d.item) as item}
<li>
<a
href={item.href}
class="web-card is-normal h-full"
style="--card-padding:1.5rem; --card-padding-mobile:1.5rem;"
>
<div
class="mb-3 flex items-center justify-between"
>
<img
class="web-user-box-image is-32px"
src={item.product.avatar}
alt={item.product.vendor}
width="32"
height="32"
/>
<Icon
name="arrow-right"
class="ml-auto"
aria-hidden="true"
/>
</div>
<h4 class="text-primary">
{item.title}
</h4>
<p class="text-sub-body mt-1">
{item.description}
</p>
</a>
</li>
{/each}
</ul>
</div>
</section>
{:else}
<section class="flex flex-col gap-8">
<header class="flex flex-col gap-1">
<h2 class="text-label text-primary">Featured</h2>
<p class="text-description">Top recommended integrations</p>
</header>
<div>
<ul class="web-feature-grid grid gap-4 sm:grid-cols-2">
{#each data.featured as item}
<li
class="web-feature-grid-item is-two-columns-desktop-only relative"
>
<a
class="block overflow-hidden rounded-2xl before:absolute before:inset-x-0 before:bottom-0 before:block before:h-80 before:rounded-[inherit] before:bg-gradient-to-b before:from-transparent before:via-transparent before:to-black"
href={item.integration.href}
>
<img
src={item.integration.cover}
alt={item.integration.title}
class="web-u-media-cover block aspect-video"
/>
<div
class="web-user-box absolute bottom-4 left-4 z-10 gap-x-2"
>
<img
class="row-span-2 block size-12 rounded-full"
src={item.integration.product
.avatar}
alt={`Avatar for ${item.integration.product.vendor}`}
width="40"
height="40"
/>
<div
class="text-body gap-2 font-medium"
>
<span class="text-primary mt-3">
{item.integration.title}
</span>
</div>
<div
class="text-caption web-u-color-text-secondary"
>
{item.heading}
</div>
</div>
</a>
</li>
{/each}
</ul>
</div>
</section>
{#each data.partners as { category, heading, description, integrations }}
{#if integrations?.length > 0 && (activePlatform === 'All' || integrations.some( (i) => i.partnerLevel.includes(activePlatform) ))}
<section
class="l-max-size-list-cards-section flex flex-col gap-8"
id={category.toLowerCase()}
use:autoHash={(entries) => {
entries.forEach((entry) => {
if (
entry.isIntersecting &&
activeCategory !== category.toLowerCase()
) {
activeCategory = category.toLowerCase();
}
});
}}
>
<header class="flex flex-col gap-1">
<h2 class="text-label text-primary">
{heading}
</h2>
<p class="text-description">
{description}
</p>
</header>
<div class="l-max-size-list-cards flex flex-col gap-8">
<ul class="l-grid-1">
{#each integrations as integration, index (`${integration.title}-${index}`)}
{#if activePlatform === 'All' || integration.partnerLevel.includes(activePlatform)}
<li>
<a
href={integration.href}
class="web-card is-normal h-full"
style="--card-padding:1.5rem; --card-padding-mobile:1.5rem; --card-border-radius: 1.5rem"
>
<div
class="mb-3 flex items-center justify-between"
>
<img
class="web-user-box-image is-32px"
src={integration.product
.avatar}
alt={integration.product
.vendor}
width="32"
height="32"
/>
<Icon
name="arrow-right"
class="ml-auto"
aria-hidden="true"
/>
</div>
<h4 class="text-primary">
{integration.title}
</h4>
<p
class="text-sub-body mt-1 line-clamp-2"
>
{integration.description}
</p>
</a>
</li>
{/if}
{/each}
</ul>
<a
href={`#${category.toLowerCase()}`}
class="l-float-button web-button is-text"
>
<span>Show more</span>
</a>
</div>
</section>
{/if}
{/each}
{/if}
</div>
</section>
</div>
</div>
</div>
</div>
<div class="overflow-hidden pt-10">
<CallToAction />
<div class="container">
<FooterNav />
<MainFooter />
</div>
</div>
</Main>
<style lang="scss">
@use '$scss/abstract/functions' as f;
@use '$scss/abstract/variables/devices';
:global([data-scroll-smooth]) {
scroll-behavior: smooth;
}
.web-pre-footer-bg {
position: absolute;
top: clamp(300px, 50vw, 50%);
left: clamp(300px, 50vw, 50%);
transform: translate(-58%, -72%);
width: clamp(1200px, 200vw, 3000px);
height: auto;
max-inline-size: unset;
max-block-size: unset;
}
.l-float-button {
display: none;
}
/* more tha 9 items */
.l-max-size-list-cards-section {
scroll-snap-align: start;
scroll-margin-top: f.pxToRem(120);
}
.l-max-size-list-cards {
&:where(:has(> ul > li:nth-child(10))) {
position: relative;
&::before {
position: absolute;
bottom: 0;
height: 100%;
width: 100%;
max-height: f.pxToRem(350);
content: '';
display: block;
background: linear-gradient(
180deg,
rgba(25, 25, 28, 0) 0%,
rgba(25, 25, 28, 0.92) 90%,
#19191c 100%
);
transition: var(--transition);
}
.l-float-button {
position: absolute;
inset-inline: 0;
inset-block-end: f.pxToRem(20);
margin-inline: auto;
display: flex;
}
}
}
:where(:target) {
.l-max-size-list-cards {
overflow: visible;
max-block-size: none;
scroll-margin: 100px;
&::before {
opacity: 0;
pointer-events: none;
}
.l-float-button {
display: none;
}
}
}
.l-grid-1 {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
gap: 1rem;
@media #{devices.$break1} {
gap: 1.25rem;
}
}
.l-integrations-grid {
position: relative;
@media #{devices.$break1} {
gap: 0;
padding-block-start: f.pxToRem(80);
}
.disabled {
& > li {
pointer-events: none;
opacity: 0.4;
}
}
.sidebar {
margin-bottom: f.pxToRem(60);
@media #{devices.$break2open} {
position: sticky;
top: 90px;
height: 500px;
transition: top 0.3s ease;
&.menu-visible {
top: 122px;
}
}
.tag {
min-width: f.pxToRem(42) !important;
&.active-tag {
background-color: #fff;
color: #000;
}
}
}
@media #{devices.$break2open} {
display: grid;
gap: f.pxToRem(68);
grid-template-columns: f.pxToRem(240) 1fr;
padding-block-start: f.pxToRem(40);
}
}
.l-integrations-hero {
@media #{devices.$break1} {
}
@media #{devices.$break2open} {
}
}
.l-bg-1 {
max-inline-size: none;
max-block-size: none;
inset-block-end: -600px;
inset-inline-start: -600px;
opacity: 0.5;
}
.l-bg-2 {
inset-block-start: 0;
inset-inline-start: 0;
opacity: 0.5;
}
.web-feature-grid {
@media #{devices.$break1} {
gap: 1rem;
}
}
.web-feature-grid {
@media #{devices.$break1} {
gap: 1rem;
}
}
</style>

View File

@@ -0,0 +1,95 @@
import { base } from '$app/paths';
import { groupBy } from 'remeda';
import {
partnerCategoryDescriptions as categoryDescriptions,
type SearchableCategory
} from '$lib/constants';
export type Partner = {
title: string;
description: string;
featured?: boolean;
cover: string;
partnerLevel: 'Bronze' | 'Silver' | 'Gold' | 'Platinum';
category: string;
frameworks: Array<string>;
website: string;
href: string;
product: {
vendor: string;
avatar: string;
href: string;
};
capabilities: Array<string>;
regions: Array<string>;
languages: Array<string>;
};
export const load = () => {
const partnersGlob = import.meta.glob('./**/*.markdoc', {
eager: true
});
const categories: Array<SearchableCategory> = [];
const platforms: string[] = [];
const partners = Object.entries(partnersGlob).map(([filepath, integrationList]) => {
const { frontmatter } = integrationList as {
frontmatter: Partner;
};
const slug = filepath.replace('./', '').replace('/+page.markdoc', '');
const integrationName = slug.slice(slug.lastIndexOf('/') + 1);
categories.push(
categoryDescriptions.find((i) => i.slug === frontmatter.category) ??
({} as SearchableCategory)
);
return {
...frontmatter,
href: `${base}/partners/catalog/${integrationName}`
};
});
const groupedPartners = groupBy(partners, (i) => i.category);
const partnersWithDescriptions = Object.entries(groupedPartners).map(
([category, integrations]) => {
const integrationCategory = categoryDescriptions.find(
(key) => key.slug === category.toLowerCase()
);
return {
category,
heading: integrationCategory?.heading,
description: integrationCategory?.description,
integrations
};
}
);
const featuredIntegrations = partners.filter((i) => i.featured);
const featuredIntegrationsWithCategoryHeadings = Object.entries(featuredIntegrations).map(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
([_, integration]) => {
const integrationCategory = categoryDescriptions.find(
(key) => key.slug === integration.category.toLowerCase()
);
return {
heading: integrationCategory?.heading,
integration
};
}
);
return {
partners: partnersWithDescriptions,
list: partners,
categories: new Set(categories),
platforms: new Set(platforms),
featured: featuredIntegrationsWithCategoryHeadings
};
};
export const prerender = false;

View File

@@ -0,0 +1,56 @@
---
layout: partner
title: Fraqtory
description: Transform your startup vision into reality with custom software and Fractional CTO services, tailored to accelerate growth and success.
date: 2025-03-21
featured: false
partnerLevel: Silver
isNew: true
cover: /images/partners/covers/fraqtory.png
category: agency
website:
product:
avatar: '/images/partners/avatars/fraqtory.png'
vendor: Fraqtory
description: Transform your startup vision into reality with custom software and Fractional CTO services, tailored to accelerate growth and success.
frameworks:
- Javascript
- Typescript
- React
- Next.js
- Flutter
- React Native
- PHP,
- Java
- Kotlin
- Python
- C
- .NET
- Spring Boot
- Laravel
- MySQL
- PostgreSQL
capabilities:
- Fractional CTO
- Software House
- Mobile App Development
- Web App Development
regions:
- Africa
- Europe
- South America
- Mid America
- North America
languages:
- English
---
# Overview
Transform your startup vision into reality with custom software and Fractional CTO services, tailored to accelerate growth and success.
# Services
We use Appwrite to accelerate the development of new technology for our clients, primarily tech-based startups. It also enables us to rapidly build MVPs and event platforms, ensuring speed and efficiency in execution.
Our ideal projects involve collaborating with startups that need to develop innovative solutions from the ground up, mainly focusing on computer vision, full-stack and cloud computing areas.

View File

@@ -0,0 +1,43 @@
---
layout: partner
title: MakeShyft
description: MakeShyft is a software company currently transitioning from client work to B2C & B2B software products. Our goal is to build productivity, communication and collaboration tools. We have big goals and dreams and we're just getting started.
date: 2025-03-21
featured: true
partnerLevel: Silver
isNew: true
cover: /images/partners/covers/makeshyft.png
category: agency
website: http://www.makeshyft.com
product:
avatar: '/images/partners/avatars/makeshyft.png'
vendor: SimbaLabs
description: MakeShyft is a software company currently transitioning from client work to B2C & B2B software products. Our goal is to build productivity, communication and collaboration tools. We have big goals and dreams and we're just getting started.
frameworks:
- Livecode
- Javascript
- PHP
capabilities:
- Web
regions:
- Africa
- Asia
- Australia
- New Zealand
- Europe
- Latin America
- Middle East
- North America
languages:
- English
---
# Overview
MakeShyft is a software company currently transitioning from client work to B2C & B2B software products. Our goal is to build productivity, communication and collaboration tools. We have big goals and dreams and we are just getting started.
# Services
We develop software products, we do not currently accept external projects.
Useful desktop applications that run native and local first.

View File

@@ -0,0 +1,43 @@
---
layout: partner
title: Mohesu Enterprises
description: Mohesu specializes in creating bespoke digital solutions—from responsive websites and mobile applications to comprehensive managed cloud services, AI & machine learning solutions, and digital marketing strategies. Their approach emphasizes clean, user-friendly interfaces and scalable, customizable technology stacks utilizing frameworks like Next.js, Flutter, and Angular with Appwrite.
date: 2025-03-21
featured: false
partnerLevel: Silver
isNew: true
cover: /images/partners/covers/mohesu.png
category: agency
website: https://mohesu.com/
product:
avatar: '/images/partners/avatars/mohesu.png'
vendor: SimbaLabs
description: 'Mohesu specializes in creating bespoke digital solutions—from responsive websites and mobile applications to comprehensive managed cloud services, AI & machine learning solutions, and digital marketing strategies. Their approach emphasizes clean, user-friendly interfaces and scalable, customizable technology stacks utilizing frameworks like Next.js, Flutter, and Angular with Appwrite.'
frameworks:
- Flutter
- NextJS
- Angular
- Remix
- Express.js
- Dart
- Typescript
- Javascript
- Python
capabilities:
- AI
- Web
- Mobile
regions:
- India
languages:
- English
---
# Overview
Mohesu specializes in creating bespoke digital solutions—from responsive websites and mobile applications to comprehensive managed cloud services, AI & machine learning solutions, and digital marketing strategies. Their approach emphasizes clean, user-friendly interfaces and scalable, customizable technology stacks utilizing frameworks like Next.js, Flutter, and Angular with Appwrite.
# Services
We specialize in web and mobile app development, managed cloud services, as well as SEO and digital marketing to help you build, scale, and grow your digital presence.
We rely on Appwrite as the backend server for our web and mobile applications, ensuring secure, scalable, and efficient development.

View File

@@ -0,0 +1,42 @@
---
layout: partner
title: Nanornia
description: Nanornia is a digital agency that creates websites, apps, and visual designs. We help businesses of all sizes build professional and user-friendly digital solutions, using both custom development and low-code tools to deliver high-quality results.
date: 2025-03-21
featured: true
partnerLevel: Silver
isNew: true
cover: /images/partners/covers/nanornia.png
category: agency
website: https://nanornia.se
product:
avatar: '/images/partners/avatars/nanornia.png'
vendor: nanornia
description: 'Nanornia is a digital agency that creates websites, apps, and visual designs. We help businesses of all sizes build professional and user-friendly digital solutions, using both custom development and low-code tools to deliver high-quality results.'
frameworks:
- Astro
- Nuxt
- Vue
- Flutter
capabilities:
- AI
- Web
- Mobile
regions:
- Europe
languages:
- English
- Swedish
---
# Overview
Nanornia is a digital agency that creates websites, apps, and visual designs. We help businesses of all sizes build professional and user-friendly digital solutions, using both custom development and low-code tools to deliver high-quality results.
# Services
We provide end-to-end services in web development, app creation, and visual design, building everything from websites and digital platforms to fully custom applications tailored to your business needs.
We use Appwrite to power secure and scalable web and mobile apps, handling everything from user authentication and databases to file storage and server-side functions—accelerating development and boosting efficiency.
Our sweet spot is building SaaS platforms, web apps, and AI-powered solutions for businesses that need reliable, scalable, and high-performance digital tools.

View File

@@ -0,0 +1,48 @@
---
layout: partner
title: Panra Studio
description: Panara Studio started as a one-person agency in 2021 and has grown into a small team of experts. We've worked with clients from 10+ countries, including startups, early-stage ventures, angel investors, and creators. Collaborate directly with Kamal Panara, the founder and engineer, and our talented professionals.
date: 2025-03-21
featured: false
partnerLevel: Silver
isNew: true
cover: /images/partners/covers/panara.png
category: agency
website: https://panara.studio
product:
avatar: '/images/partners/avatars/panara.png'
vendor: Panara Studio
description: Panara Studio started as a one-person agency in 2021 and has grown into a small team of experts. We've worked with clients from 10+ countries, including startups, early-stage ventures, angel investors, and creators. Collaborate directly with Kamal Panara, the founder and engineer, and our talented professionals.
frameworks:
- Flutter
- ReactJS
- NextJS
- Typescript
- Dart
- Javascript
- NodeJS
capabilities:
- UI/UX Design
- Mobile App Development
- Web App Development
regions:
- Africa
- India
- Australia
- Europe
- South America
- Mid America
- North America
languages:
- English
---
# Overview
Panara Studio started as a one-person agency in 2021 and has grown into a small team of experts. We've worked with clients from 10+ countries, including startups, early-stage ventures, angel investors, and creators. Collaborate directly with Kamal Panara, the founder and engineer, and our talented professionals.
# Services
At Panera Studio, we specialize in UI/UX design, mobile app development, and web app development, creating high-quality digital products tailored to your needs. Whether its an e-commerce app, e-learning platform, CRM system, or a fully bespoke application, we bring ideas to life with a strong focus on performance, scalability, and user experience.
We use Appwrite as the backend foundation for our internal tools, SaaS products, and client projects, ensuring modern, secure, and efficient development every step of the way.

View File

@@ -0,0 +1,43 @@
---
layout: partner
title: SimbaLabs
description: We are a web engineering company focused on making scalable, efficient web and mobile experiences.
date: 2025-03-21
featured: false
partnerLevel: Silver
isNew: true
cover: /images/partners/covers/simbalabs.png
category: agency
website: www.simbalabs.co.za
product:
avatar: '/images/partners/avatars/simbalabs.png'
vendor: SimbaLabs
description: 'We are a web engineering company focused on making scalable, efficient web and mobile experiences.'
frameworks:
- Javascript
- Python
- Swift
capabilities:
- AI
- Web
- Mobile
regions:
- Africa
- Asia
- Australia
- New Zealand
- Europe
- Latin America
- Middle East
- North America
languages:
- English
---
# Overview
We are a web engineering company focused on making scalable, efficient web and mobile experiences.
# Services
We build AI, web, and mobile applications for clients. We use Appwrite Databases, Functions, Auth & Storage to build innovative, scalable AI, web, and mobile apps.

View File

@@ -0,0 +1,15 @@
import { Client, Account } from 'node-appwrite';
async function getLoggedInUser(context) {
const session = cookies().get('custom-session-cookie');
if (!session) return;
const client = new Client()
.setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT_ID);
client.setSession(session.value);
const account = new Account(client);
return account.get();
}

View File

@@ -0,0 +1,16 @@
import { Client, Account } from 'node-appwrite';
import { cookies } from 'next/headers';
async function getLoggedInUser() {
const session = cookies().get('custom-session-cookie');
if (!session) return;
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID);
client.setSession(session.value);
const account = new Account(client);
return account.get();
}

View File

@@ -0,0 +1,16 @@
import { Client, Account } from 'node-appwrite';
import { H3Event, getCookie } from 'h3';
async function getLoggedInUser(event) {
const session = getCookie(event, 'custom-session-cookie');
if (!session) return;
const client = new Client()
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID);
client.setSession(session.value);
const account = new Account(client);
return account.get();
}

View File

@@ -0,0 +1,21 @@
import { Client, Account } from 'node-appwrite';
import { createCookie } from '@remix-run/node';
export const customSessionCookie = createCookie('custom-session-cookie', {
maxAge: 604800,
});
async function getLoggedInUser(request) {
const cookies = request.headers.get('Cookie');
const session = await customSessionCookie.parse(cookies):
if (!session) return;
const client = new Client()
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID);
client.setSession(session.value);
const account = new Account(client);
return await account.get();
}

View File

@@ -0,0 +1,15 @@
import { Client, Account } from 'node-appwrite';
async function getLoggedInUser() {
const session = cookies().get('custom-session-cookie');
if (!session) return;
const client = new Client()
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID);
client.setSession(session.value);
const account = new Account(client);
return account.get();
}

View File

@@ -1,117 +1,40 @@
<script lang="ts">
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
import { trackEvent } from '$lib/actions/analytics';
import { Tooltip } from '$lib/components';
import { Button } from '$lib/components/ui';
import { Framework, Platform } from '$lib/utils/references';
import MultiFrameworkCode from './MultiFrameworkCode.svelte';
import SnippetNextJs from './(snippets)/nextjs.txt';
import SnippetSvelteKit from './(snippets)/sveltekit.txt';
import SnippetAstro from './(snippets)/astro.txt';
import SnippetNuxt from './(snippets)/nuxt.txt';
import SnippetRemix from './(snippets)/remix.txt';
const codeSnippets = [
{
language: Platform.ClientWeb,
platform: Framework.NextJs,
content: `import { Client, Account } from 'node-appwrite'
import { cookies } from 'next/headers'
async function getLoggedInUser() {
const session = cookies().get("custom-session-cookie");
if (!session) return
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID)
client.setSession(session.value)
const account = new Account(client);
return await account.get()
}
`
content: SnippetNextJs
},
{
language: Platform.ClientWeb,
platform: Framework.SvelteKit,
content: `import { Client, Account } from 'node-appwrite'
async function getLoggedInUser() {
const session = cookies().get("custom-session-cookie");
if (!session) return
const client = new Client()
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID)
client.setSession(session.value)
const account = new Account(client);
return await account.get()
}
`
content: SnippetSvelteKit
},
{
language: Platform.ClientWeb,
platform: Framework.Astro,
content: `import { Client, Account } from 'node-appwrite'
async function getLoggedInUser(context) {
const session = cookies().get("custom-session-cookie");
if (!session) return
const client = new Client()
.setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT_ID)
client.setSession(session.value)
const account = new Account(client);
return await account.get()
}
`
content: SnippetAstro
},
{
language: Platform.ClientWeb,
platform: Framework.Nuxt3,
content: `import { Client, Account } from 'node-appwrite'
import { H3Event, getCookie } from 'h3'
async function getLoggedInUser(event) {
const session = getCookie(event, "custom-session-cookie");
if (!session) return
const client = new Client()
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID)
client.setSession(session.value)
const account = new Account(client);
return await account.get()
}`
content: SnippetNuxt
},
{
language: Platform.ClientWeb,
platform: Framework.Remix,
content: `import { Client, Account } from 'node-appwrite'
import { createCookie } from '@remix-run/node'
export const customSessionCookie = createCookie("custom-session-cookie", {
maxAge: 604800,
})
async function getLoggedInUser(request) {
const cookies = request.headers.get("Cookie")
const session = await customSessionCookie.parse(cookies)
if (!session) return
const client = new Client()
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID)
client.setSession(session.value)
const account = new Account(client);
return await account.get()
}`
content: SnippetRemix
}
];

View File

@@ -0,0 +1,42 @@
namespace DotNetRuntime;
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;
public class Handler {
// This is your Appwrite function
// It"s executed each time we get a request
public async Task Main(RuntimeContext Context)
{
// Why not try the Appwrite SDK?
//
// Set project and set API key
// var client = new Client()
// .SetProject(Environment.GetEnvironmentVariable("APPWRITE_FUNCTION_PROJECT_ID"))
// .SetKey(Context.Req.Headers["x-appwrite-key"]);
// You can log messages to the console
Context.Log("Hello, Logs!");
// If something goes wrong, log an error
Context.Error("Hello, Errors!");
// The 'Context.Req' object contains the request data
if (Context.Req.Method == "GET") {
// Send a response with the res object helpers
// 'Context.Res.Text()' dispatches a string back to the client
return Context.Res.Text("Hello, World!");
}
// 'Context.Res.Json()' is a handy helper for sending JSON
return Context.Res.Json(new Dictionary()
{
{ "motto", "Build like a team of hundreds_" },
{ "learn", "https://appwrite.io/docs" },
{ "connect", "https://appwrite.io/discord" },
{ "getInspired", "https://builtwith.appwrite.io" },
});
}
}

View File

@@ -0,0 +1,35 @@
import 'dart:async';
import 'package:dart_appwrite/dart_appwrite.dart';
// This is your Appwrite function
// It's executed each time we get a request
Future main(final context) async {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// final client = Client()
// .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])
// .setKey(context.req.headers['x-appwrite-key']);
// You can log messages to the console
context.log('Hello, Logs!');
// If something goes wrong, log an error
context.error('Hello, Errors!');
// The 'req' object contains the request data
if (context.req.method == 'GET') {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return context.res.text('Hello, World!');
}
// 'res.json()' is a handy helper for sending JSON
return context.res.json({
'motto': 'Build like a team of hundreds_',
'learn': 'https://appwrite.io/docs',
'connect': 'https://appwrite.io/discord',
'getInspired': 'https://builtwith.appwrite.io',
});
}

View File

@@ -0,0 +1,33 @@
import { Client } from "https://deno.land/x/appwrite@7.0.0/mod.ts";
// This is your Appwrite function
// It's executed each time we get a request
export default ({ req, res, log, error }: any) => {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// const client = new Client()
// .setProject(Deno.env.get("APPWRITE_FUNCTION_PROJECT_ID") || "")
// .setKey(req.headers["x-appwrite-key"] || "");
// You can log messages to the console
log("Hello, Logs!");
// If something goes wrong, log an error
error("Hello, Errors!");
// The 'req' object contains the request data
if (req.method === "GET") {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return res.text("Hello, World!");
}
// 'res.json()' is a handy helper for sending JSON
return res.json({
motto: "Build like a team of hundreds_",
learn: "https://appwrite.io/docs",
connect: "https://appwrite.io/discord",
getInspired: "https://builtwith.appwrite.io",
});
};

View File

@@ -0,0 +1,45 @@
package handler
import (
"fmt"
"os"
"github.com/appwrite/sdk-for-go/appwrite"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
type Response struct {
Motto string `json:"motto"`
Learn string `json:"learn"`
Connect string `json:"connect"`
GetInspired string `json:"getInspired"`
}
func Main(Context openruntimes.Context) openruntimes.Response {
// This is your Appwrite function
// It's executed each time we get a request service
var _ = appwrite.NewClient(
appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")),
appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]),
)
// You can log messages to the console
fmt.Println("Hello, Logs!")
fmt.Fprintln(os.Stderr, "Error:", "Hello, Errors!")
// The 'Context.Req' object contains the request data
if Context.Req.Method == "GET" {
// Send a response with the Context.Res object helpers
// 'Context.Res.Text()' dispatches a string back to the client
return Context.Res.Text("Hello, World!")
}
// 'res.json()' is a handy helper for sending JSON
return Context.Res.Json(
Response{
Motto: "Build like a team of hundreds_",
Learn: "https://appwrite.io/docs",
Connect: "https://appwrite.io/discord",
GetInspired: "https://builtwith.appwrite.io",
})
}

View File

@@ -0,0 +1,42 @@
package io.openruntimes.java.src;
import io.openruntimes.java.RuntimeContext;
import io.openruntimes.java.RuntimeOutput;
import java.util.HashMap;
import io.appwrite.Client;
public class Main {
// This is your Appwrite function
// It's executed each time we get a request
public RuntimeOutput main(RuntimeContext context) throws Exception {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// Client client = new Client();
// .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID"))
// .setKey(context.getReq().getHeaders().get("x-appwrite-key"));
// You can log messages to the console
context.log("Hello, Logs!");
// If something goes wrong, log an error
context.error("Hello, Errors!");
// The 'context.getReq()' object contains the request data
if (context.getReq().getMethod().equals("GET")) {
// Send a response with the res object helpers
// 'context.getRes().text()' dispatches a string back to the client
return context.getRes().text("Hello, World!");
}
Map json = new HashMap<>();
json.put("motto", "Build like a team of hundreds_");
json.put("learn", "https://appwrite.io/docs");
json.put("connect", "https://appwrite.io/discord");
json.put("getInspired", "https://builtwith.appwrite.io");
// 'context.getRes().json()' is a handy helper for sending JSON
return context.getRes().json(json);
}
}

View File

@@ -0,0 +1,40 @@
package io.openruntimes.kotlin.src
import io.openruntimes.kotlin.RuntimeContext
import io.openruntimes.kotlin.RuntimeOutput
import io.appwrite.Client
import java.util.HashMap
class Main {
// This is your Appwrite function
// It's executed each time we get a request
fun main(context: RuntimeContext): RuntimeOutput {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// val client = Client()
// .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID"))
// .setKey(context.req.headers["x-appwrite-key"])
// You can log messages to the console
context.log("Hello, Logs!")
// If something goes wrong, log an error
context.error("Hello, Errors!")
// The 'context.req' object contains the request data
if (context.req.method == "GET") {
// Send a response with the res object helpers
// 'context.res.text()' dispatches a string back to the client
return context.res.text("Hello, World!")
}
// 'context.res.json()' is a handy helper for sending JSON
return context.res.json(mutableMapOf(
"motto" to "Build like a team of hundreds_",
"learn" to "https://appwrite.io/docs",
"connect" to "https://appwrite.io/discord",
"getInspired" to "https://builtwith.appwrite.io"
))
}
}

View File

@@ -0,0 +1,33 @@
import { Client } from 'node-appwrite';
// This is your Appwrite function
// It's executed each time we get a request
export default async ({ req, res, log, error }) => {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// const client = new Client()
// .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
// .setKey(req.headers['x-appwrite-key']);
// You can log messages to the console
log('Hello, Logs!');
// If something goes wrong, log an error
error('Hello, Errors!');
// The 'req' object contains the request data
if (req.method === 'GET') {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return res.text('Hello, World!');
}
// 'res.json()' is a handy helper for sending JSON
return res.json({
motto: 'Build like a team of hundreds_',
learn: 'https://appwrite.io/docs',
connect: 'https://appwrite.io/discord',
getInspired: 'https://builtwith.appwrite.io',
});
};

View File

@@ -0,0 +1,36 @@
require(__DIR__ . '/../vendor/autoload.php');
use Appwrite\Client;
use Appwrite\Exception;
// This is your Appwrite function
// It's executed each time we get a request
return function ($context) {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// $client = (new Client())
// ->setProject(getenv(APPWRITE_FUNCTION_PROJECT_ID))
// ->setKey($context->req->headers['x-appwrite-key']);
// You can log messages to the console
$context->log('Hello, Logs!');
// If something goes wrong, log an error
$context->error('Hello, Errors!');
// The 'req' object contains the request data
if ($context->req->method === 'GET') {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return $context->res->text('Hello, World!');
}
// 'res.json()' is a handy helper for sending JSON
return $context->res->json([
'motto' => 'Build like a team of hundreds_',
'learn' => 'https://appwrite.io/docs',
'connect' => 'https://appwrite.io/discord',
'getInspired' => 'https://builtwith.appwrite.io',
]);
};

View File

@@ -0,0 +1,34 @@
from appwrite.client import Client
import os
# This is your Appwrite function
# It's executed each time we get a request
def main(context):
# Why not try the Appwrite SDK?
#
# Set project and set API key
# client = (
# Client()
# .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"])
# .set_key(context.req.headers["x-appwrite-key"])
# )
# You can log messages to the console
context.log("Hello, Logs!")
# If something goes wrong, log an error
context.error("Hello, Errors!")
# The 'context.req' object contains the request data
if context.req.method == "GET":
# Send a response with the res object helpers
# 'context.res.text()' dispatches a string back to the client
return context.res.text("Hello, World!")
# 'context.res.json()' is a handy helper for sending JSON
return context.res.json({
"motto": "Build like a team of hundreds_",
"learn": "https://appwrite.io/docs",
"connect": "https://appwrite.io/discord",
"getInspired": "https://builtwith.appwrite.io",
})

View File

@@ -0,0 +1,33 @@
require "appwrite"
# This is your Appwrite function
# It's executed each time we get a request
def main(context)
# Why not try the Appwrite SDK?
#
# Set project and set API key
# client = Client.new
# .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])
# .set_key(context.req.headers['x-appwrite-key'])
# You can log messages to the console
context.log("Hello, Logs!")
# If something goes wrong, log an error
context.error("Hello, Errors!")
# The 'context.req' object contains the request data
if (context.req.method == "GET")
# Send a response with the res object helpers
# 'context.res.text()' dispatches a string back to the client
return context.res.text("Hello, World!")
end
# 'context.res.json()' is a handy helper for sending JSON
return context.res.json({
"motto": "Build like a team of hundreds_",
"learn": "https://appwrite.io/docs",
"connect": "https://appwrite.io/discord",
"getInspired": "https://builtwith.appwrite.io",
})
end

View File

@@ -0,0 +1,35 @@
import Appwrite
import AppwriteModels
import Foundation
// This is your Appwrite function
// It's executed each time we get a request
func main(context: RuntimeContext) async throws -> RuntimeOutput {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// let client = Client()
// .setProject(ProcessInfo.processInfo.environment["APPWRITE_FUNCTION_PROJECT_ID"])
// .setKey(context.req.headers["x-appwrite-key"] ?? "")
// You can log messages to the console
context.log("Hello, Logs!")
// If something goes wrong, log an error
context.error("Hello, Errors!")
// The 'context.req' object contains the request data
if context.req.method == "GET" {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return context.res.text("Hello, World!")
}
// 'context.res.json()' is a handy helper for sending JSON
return try context.res.json([
"motto": "Build like a team of hundreds_",
"learn": "https://appwrite.io/docs",
"connect": "https://appwrite.io/discord",
"getInspired": "https://builtwith.appwrite.io",
])
}

View File

@@ -2,463 +2,65 @@
import { Button } from '$lib/components/ui';
import { Platform } from '$lib/utils/references';
import MultiCodeContextless from '$routes/products/messaging/(components)/MultiCodeContextless.svelte';
import SnippetNodejs from './(snippets)/nodejs.txt';
import SnippetPhp from './(snippets)/php.txt';
import SnippetPython from './(snippets)/python.txt';
import SnippetRuby from './(snippets)/ruby.txt';
import SnippetDeno from './(snippets)/deno.txt';
import SnippetGo from './(snippets)/go.txt';
import SnippetDart from './(snippets)/dart.txt';
import SnippetKotlin from './(snippets)/kotlin.txt';
import SnippetJava from './(snippets)/java.txt';
import SnippetSwift from './(snippets)/swift.txt';
import SnippetCsharp from './(snippets)/csharp.txt';
const codeTopic = [
{
language: 'server-nodejs',
platform: 'Web',
content: `import { Client } from 'node-appwrite';
// This is your Appwrite function
// It's executed each time we get a request
export default async ({ req, res, log, error }) => {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// const client = new Client()
// .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
// .setKey(req.headers['x-appwrite-key']);
// You can log messages to the console
log('Hello, Logs!');
// If something goes wrong, log an error
error('Hello, Errors!');
// The 'req' object contains the request data
if (req.method === 'GET') {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return res.text('Hello, World!');
}
// 'res.json()' is a handy helper for sending JSON
return res.json({
motto: 'Build like a team of hundreds_',
learn: 'https://appwrite.io/docs',
connect: 'https://appwrite.io/discord',
getInspired: 'https://builtwith.appwrite.io',
});
};
`
content: SnippetNodejs
},
{
language: 'php',
platform: 'PHP',
content: `require(__DIR__ . '/../vendor/autoload.php');
use Appwrite\Client;
use Appwrite\Exception;
// This is your Appwrite function
// It's executed each time we get a request
return function ($context) {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// $client = (new Client())
// ->setProject(getenv(APPWRITE_FUNCTION_PROJECT_ID))
// ->setKey($context->req->headers['x-appwrite-key']);
// You can log messages to the console
$context->log('Hello, Logs!');
// If something goes wrong, log an error
$context->error('Hello, Errors!');
// The 'req' object contains the request data
if ($context->req->method === 'GET') {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return $context->res->text('Hello, World!');
}
// 'res.json()' is a handy helper for sending JSON
return $context->res->json([
'motto' => 'Build like a team of hundreds_',
'learn' => 'https://appwrite.io/docs',
'connect' => 'https://appwrite.io/discord',
'getInspired' => 'https://builtwith.appwrite.io',
]);
};
`
content: SnippetPhp
},
{
language: 'python',
platform: 'Python',
content: `from appwrite.client import Client
import os
# This is your Appwrite function
# It's executed each time we get a request
def main(context):
# Why not try the Appwrite SDK?
#
# Set project and set API key
# client = (
# Client()
# .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"])
# .set_key(context.req.headers["x-appwrite-key"])
# )
# You can log messages to the console
context.log("Hello, Logs!")
# If something goes wrong, log an error
context.error("Hello, Errors!")
# The 'context.req' object contains the request data
if context.req.method == "GET":
# Send a response with the res object helpers
# 'context.res.text()' dispatches a string back to the client
return context.res.text("Hello, World!")
# 'context.res.json()' is a handy helper for sending JSON
return context.res.json({
"motto": "Build like a team of hundreds_",
"learn": "https://appwrite.io/docs",
"connect": "https://appwrite.io/discord",
"getInspired": "https://builtwith.appwrite.io",
})
`
content: SnippetPython
},
{
language: 'ruby',
content: `require "appwrite"
# This is your Appwrite function
# It's executed each time we get a request
def main(context)
# Why not try the Appwrite SDK?
#
# Set project and set API key
# client = Client.new
# .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])
# .set_key(context.req.headers['x-appwrite-key'])
# You can log messages to the console
context.log("Hello, Logs!")
# If something goes wrong, log an error
context.error("Hello, Errors!")
# The 'context.req' object contains the request data
if (context.req.method == "GET")
# Send a response with the res object helpers
# 'context.res.text()' dispatches a string back to the client
return context.res.text("Hello, World!")
end
# 'context.res.json()' is a handy helper for sending JSON
return context.res.json({
"motto": "Build like a team of hundreds_",
"learn": "https://appwrite.io/docs",
"connect": "https://appwrite.io/discord",
"getInspired": "https://builtwith.appwrite.io",
})
end
`
content: SnippetRuby
},
{
language: 'deno',
content: `import { Client } from "https://deno.land/x/appwrite@7.0.0/mod.ts";
// This is your Appwrite function
// It's executed each time we get a request
export default ({ req, res, log, error }: any) => {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// const client = new Client()
// .setProject(Deno.env.get("APPWRITE_FUNCTION_PROJECT_ID") || "")
// .setKey(req.headers["x-appwrite-key"] || "");
// You can log messages to the console
log("Hello, Logs!");
// If something goes wrong, log an error
error("Hello, Errors!");
// The 'req' object contains the request data
if (req.method === "GET") {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return res.text("Hello, World!");
}
// 'res.json()' is a handy helper for sending JSON
return res.json({
motto: "Build like a team of hundreds_",
learn: "https://appwrite.io/docs",
connect: "https://appwrite.io/discord",
getInspired: "https://builtwith.appwrite.io",
});
};
`
content: SnippetDeno
},
{
language: 'go',
content: `package handler
import (
"fmt"
"os"
"github.com/appwrite/sdk-for-go/appwrite"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
type Response struct {
Motto string 'json:"motto"'
Learn string 'json:"learn"'
Connect string 'json:"connect"'
GetInspired string 'json:"getInspired"'
}
func Main(Context openruntimes.Context) openruntimes.Response {
// This is your Appwrite function
// It's executed each time we get a request service
var _ = appwrite.NewClient(
appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")),
appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]),
)
// You can log messages to the console
fmt.Println("Hello, Logs!")
fmt.Fprintln(os.Stderr, "Error:", "Hello, Errors!")
// The 'Context.Req' object contains the request data
if Context.Req.Method == "GET" {
// Send a response with the Context.Res object helpers
// 'Context.Res.Text()' dispatches a string back to the client
return Context.Res.Text("Hello, World!")
}
// 'res.json()' is a handy helper for sending JSON
return Context.Res.Json(
Response{
Motto: "Build like a team of hundreds_",
Learn: "https://appwrite.io/docs",
Connect: "https://appwrite.io/discord",
GetInspired: "https://builtwith.appwrite.io",
})
}
`
content: SnippetGo
},
{
language: 'dart',
content: `import 'dart:async';
import 'package:dart_appwrite/dart_appwrite.dart';
// This is your Appwrite function
// It's executed each time we get a request
Future main(final context) async {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// final client = Client()
// .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])
// .setKey(context.req.headers['x-appwrite-key']);
// You can log messages to the console
context.log('Hello, Logs!');
// If something goes wrong, log an error
context.error('Hello, Errors!');
// The 'req' object contains the request data
if (context.req.method == 'GET') {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return context.res.text('Hello, World!');
}
// 'res.json()' is a handy helper for sending JSON
return context.res.json({
'motto': 'Build like a team of hundreds_',
'learn': 'https://appwrite.io/docs',
'connect': 'https://appwrite.io/discord',
'getInspired': 'https://builtwith.appwrite.io',
});
}
`
content: SnippetDart
},
{
language: 'kotlin',
content: `package io.openruntimes.kotlin.src
import io.openruntimes.kotlin.RuntimeContext
import io.openruntimes.kotlin.RuntimeOutput
import io.appwrite.Client
import java.util.HashMap
class Main {
// This is your Appwrite function
// It's executed each time we get a request
fun main(context: RuntimeContext): RuntimeOutput {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// val client = Client()
// .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID"))
// .setKey(context.req.headers["x-appwrite-key"])
// You can log messages to the console
context.log("Hello, Logs!")
// If something goes wrong, log an error
context.error("Hello, Errors!")
// The 'context.req' object contains the request data
if (context.req.method == "GET") {
// Send a response with the res object helpers
// 'context.res.text()' dispatches a string back to the client
return context.res.text("Hello, World!")
}
// 'context.res.json()' is a handy helper for sending JSON
return context.res.json(mutableMapOf(
"motto" to "Build like a team of hundreds_",
"learn" to "https://appwrite.io/docs",
"connect" to "https://appwrite.io/discord",
"getInspired" to "https://builtwith.appwrite.io"
))
}
}
`
content: SnippetKotlin
},
{
language: 'java',
content: `package io.openruntimes.java.src;
import io.openruntimes.java.RuntimeContext;
import io.openruntimes.java.RuntimeOutput;
import java.util.HashMap;
import io.appwrite.Client;
public class Main {
// This is your Appwrite function
// It's executed each time we get a request
public RuntimeOutput main(RuntimeContext context) throws Exception {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// Client client = new Client();
// .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID"))
// .setKey(context.getReq().getHeaders().get("x-appwrite-key"));
// You can log messages to the console
context.log("Hello, Logs!");
// If something goes wrong, log an error
context.error("Hello, Errors!");
// The 'context.getReq()' object contains the request data
if (context.getReq().getMethod().equals("GET")) {
// Send a response with the res object helpers
// 'context.getRes().text()' dispatches a string back to the client
return context.getRes().text("Hello, World!");
}
Map json = new HashMap<>();
json.put("motto", "Build like a team of hundreds_");
json.put("learn", "https://appwrite.io/docs");
json.put("connect", "https://appwrite.io/discord");
json.put("getInspired", "https://builtwith.appwrite.io");
// 'context.getRes().json()' is a handy helper for sending JSON
return context.getRes().json(json);
}
}
`
content: SnippetJava
},
{
language: 'swift',
content: `import Appwrite
import AppwriteModels
import Foundation
// This is your Appwrite function
// It's executed each time we get a request
func main(context: RuntimeContext) async throws -> RuntimeOutput {
// Why not try the Appwrite SDK?
//
// Set project and set API key
// let client = Client()
// .setProject(ProcessInfo.processInfo.environment["APPWRITE_FUNCTION_PROJECT_ID"])
// .setKey(context.req.headers["x-appwrite-key"] ?? "")
// You can log messages to the console
context.log("Hello, Logs!")
// If something goes wrong, log an error
context.error("Hello, Errors!")
// The 'context.req' object contains the request data
if context.req.method == "GET" {
// Send a response with the res object helpers
// 'res.text()' dispatches a string back to the client
return context.res.text("Hello, World!")
}
// 'context.res.json()' is a handy helper for sending JSON
return try context.res.json([
"motto": "Build like a team of hundreds_",
"learn": "https://appwrite.io/docs",
"connect": "https://appwrite.io/discord",
"getInspired": "https://builtwith.appwrite.io",
])
}
`
content: SnippetSwift
},
{
language: 'csharp',
content: `namespace DotNetRuntime;
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;
public class Handler {
// This is your Appwrite function
// It"s executed each time we get a request
public async Task Main(RuntimeContext Context)
{
// Why not try the Appwrite SDK?
//
// Set project and set API key
// var client = new Client()
// .SetProject(Environment.GetEnvironmentVariable("APPWRITE_FUNCTION_PROJECT_ID"))
// .SetKey(Context.Req.Headers["x-appwrite-key"]);
// You can log messages to the console
Context.Log("Hello, Logs!");
// If something goes wrong, log an error
Context.Error("Hello, Errors!");
// The 'Context.Req' object contains the request data
if (Context.Req.Method == "GET") {
// Send a response with the res object helpers
// 'Context.Res.Text()' dispatches a string back to the client
return Context.Res.Text("Hello, World!");
}
// 'Context.Res.Json()' is a handy helper for sending JSON
return Context.Res.Json(new Dictionary()
{
{ "motto", "Build like a team of hundreds_" },
{ "learn", "https://appwrite.io/docs" },
{ "connect", "https://appwrite.io/discord" },
{ "getInspired", "https://builtwith.appwrite.io" },
});
}
}
`
content: SnippetCsharp
}
];
</script>

View File

@@ -1,6 +1,6 @@
<script>
import Main from '$lib/layouts/Main.svelte';
import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
import { DEFAULT_HOST } from '$lib/utils/metadata';
import { TITLE_SUFFIX } from '$routes/titles';
import Templates from './(components)/Templates.svelte';
@@ -14,7 +14,6 @@
import DevelopLocally from './(components)/DevelopLocally.svelte';
import DeploySeamlessly from './(components)/DeploySeamlessly.svelte';
import Testimonials from './(components)/Testimonials.svelte';
import RegionsMap from './(components)/RegionsMap.svelte';
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
import ProductCards from '$lib/components/product-pages/product-cards.svelte';

View File

@@ -1,7 +1,7 @@
<script>
import { FooterNav, MainFooter, PreFooter } from '$lib/components';
import Main from '$lib/layouts/Main.svelte';
import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
import { DEFAULT_HOST } from '$lib/utils/metadata';
import { TITLE_SUFFIX } from '$routes/titles';
import Draft from './(components)/Draft.svelte';
import Schedule from './(components)/Schedule.svelte';
@@ -719,9 +719,6 @@ messaging.create_email(
@media (max-width: 500px) {
flex-direction: column;
gap: 1rem;
& a {
width: 100%;
}
}
}

View File

@@ -238,10 +238,5 @@
color: hsl(var(--web-color-primary));
text-align: center;
}
button {
margin-block-start: 1.5rem;
margin-inline: auto;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 KiB

Some files were not shown because too many files have changed in this diff Show More