Användningsområden · 10 min läsning · Uppdaterad 16 augusti 2026
Bygg ett CRM med AI: kundregister, anteckningar och en tratt du äger
Ett CRM i NorthernGo är ett kundregister, anteckningar och några steg i säljtratten, sparade med window.NorthernGoDB i en Supabase-databas i PostgreSQL i EU. Beskriv appen med text, röst eller en skiss. Tre agenter skriver vanilla JavaScript och Tailwind som du äger. Kräv inloggning, en radera-konto-knapp enligt GDPR och en export. Modellen hittar på dubbletter och behörigheter — skriv ut båda själv.
Ett CRM är en lista med minne, inte en säljplattform
De flesta som skriver "bygg ett CRM" till en AI-appbyggare ser ett förminskat Salesforce framför sig. Det de faktiskt behöver är mindre och ärligare: en namngiven lista med personer, en plats att skriva vad som sades, och några steg som säger var varje person befinner sig. Det är en CRUD-app med säljord. Det är också formen NorthernGo är bra på, för varje genererad app har redan en Supabase-databas i PostgreSQL i EU, inloggning med e-post och lösenord, och ett PWA-skal.
Du pratar, skriver eller ritar. Arkitekten planerar vyerna, Byggaren skriver ett HTML-dokument i vanilla JavaScript och Tailwind, och på Premium och Pro granskar Kritikern resultatet innan du ser det. Koden är din. ZIP-export finns på Premium och Pro. Det här är inte en gästad CRM-tjänst med licenser, flöden och en butik av tillägg.
Behöver du territorier, prognoser eller en behörighetsmatris, då handlar du ett CRM-system. Den här sidan gäller en VVS-firma i Lycksele, en konsult med åttio namn, eller en verkstad som vill att whiteboarden överlever kaffet.
Vad modellen gör fel innan du har skrivit en prompt
Två fel syns i nästan varje första generation, och de är inte kosmetiska.
Dubbletter. Be om "ett CRM" och du får en spara-knapp, inte en uppslagning. Två sparningar av samma e-post blir två kontakter; då ljuger tavlan. Skriv regeln i prompten: matcha på e-post, uppdatera den befintliga posten, lägg inte in en andra.
Behörigheter som inte finns. Inloggningen är e-post och lösenord. Det finns inga roller om du inte hittar på dem. Modellen ritar gärna en bricka med "Admin"; brickan är färg. get('contacts') returnerar den inloggades poster. Två kollegor delar en tratt genom att dela en inloggning.
Ett tredje, tystare fel: modellen lagrar gärna anteckningar, telefonnummer och affärsvärden utan en väg ut. Varje app med inloggning måste ha en fungerande radering av kontot. Det är GDPR artikel 17, inte en trevlig extra, och plattformen lägger tillbaka knappen om en senare ändring tar bort den. Bygg den i första versionen.
Steg 1: Beskriv posterna, inte varumärket
Prompta inte "bygg ett modernt CRM med en snygg översikt". Prompta datan. Ett första meddelande som fungerar ser ut så här:
Bygg ett CRM för en enmansfirma i hantverk. Vyer: inloggning, kontaktlista, kontaktdetalj, säljtratt. En kontakt har id, namn, epost, telefon, bolag, steg, vardeSek, senastRord. Stegen är exakt lead, offert, bokad, klar, forlorad. Anteckningar är en egen kollektion knuten med contactId. Innan en kontakt sparas: hämta kontakterna och om e-posten redan finns, uppdatera den posten i stället för att lägga in en ny. Inloggning med e-post och lösenord. En synlig knapp Radera mitt konto som anropar window.NorthernGoDB.deleteAccount() efter bekräftelse. En knapp Ladda ner min data som exporterar kontakter och anteckningar som CSV.
Det stycket gör mer jobb än en känslokarta. Det namnger kollektionerna, stegen, dubblettregeln, inloggningen och de två utgångarna varje app med personuppgifter behöver. Arkitekten kan planera från det. Byggaren får färre tillfällen att hitta på ett sjunde steg som heter "Bearbeta".
Molngenerering körs på Gemini och räknas mot taket. Lokal WebGPU (Qwen 3.5 4B, eller större Pro-modeller) eller Ollama är obegränsad på alla planer. För ett CRM räcker det lokala till andra varvet.
Steg 2: Kontakter och anteckningar som två kollektioner
Håll personer och samtal isär. Ett tjockt kontaktobjekt där anteckningssträngen växer vid varje samtal kommer att slåss med dig första gången du vill visa en tidslinje eller radera en enda mening.
async function saveContact(contact) {
const all = (await window.NorthernGoDB.get('contacts')) || [];
const email = String(contact.email || '').trim().toLowerCase();
const existing = all.find(row => String(row.email || '').trim().toLowerCase() === email);
const record = {
id: existing ? existing.id : crypto.randomUUID(),
name: contact.name,
email,
phone: contact.phone || '',
company: contact.company || '',
stage: contact.stage || 'lead',
valueSek: Number(contact.valueSek) || 0,
lastTouchedAt: new Date().toISOString()
};
await window.NorthernGoDB.save('contacts', record);
return record;
}
async function addNote(contactId, text) {
await window.NorthernGoDB.save('notes', {
id: crypto.randomUUID(),
contactId,
text,
createdAt: new Date().toISOString()
});
}
save() skriver in formen du skickar. Det finns inget schema att migrera och ingen tabell att skapa. Databas-API:t är save, get, delete och inloggningsanropen — det är hela baksidan. Tomma kollektioner kan slå ut som något annat än en array, så || [] är inte pynt; en första laddning utan kontakter målar annars en tom skärm.
Anteckningar hör till en kontakt via contactId, inte via att du klistrar in personens namn i texten. Byter du namn på kontakten hänger tidslinjen kvar. Raderar du kontakten har du en loop som kan radera matchande anteckningar. Modellen hoppar över den loopen om prompten inte nämner den.
Steg 3: Stegen i tratten som data, inte som målade kolumner
En tavla där korten inte går att flytta är en affisch. Steg är ett fält på kontakten, och att flytta ett kort är en uppdatering av det fältet plus en tidsstämpel.
const STAGES = ['lead', 'offert', 'bokad', 'klar', 'forlorad'];
async function moveToStage(contactId, stage) {
if (!STAGES.includes(stage)) return;
const all = (await window.NorthernGoDB.get('contacts')) || [];
const contact = all.find(row => row.id === contactId);
if (!contact) return;
contact.stage = stage;
contact.lastTouchedAt = new Date().toISOString();
await window.NorthernGoDB.save('contacts', contact);
}
Två regler värda att lägga i prompten och sedan kontrollera i koden.
Steglistan är stängd. Får modellen "lägga till ett steg" får du Pågående, Väntar, Uppföljning och en tavla ingen kan rapportera på. Fem namn räcker för en enmans-tratt. Att byta listan senare är ett dataproblem: varje befintlig kontakt med det gamla namnet måste flyttas.
Siffrorna kommer från posterna, inte från en egen mätkollektion. Summera vardeSek per steg i JavaScript när du ritar. En andra kollektion med totaler släpar efter första gången en sparning misslyckas. Översikten är en vy av get('contacts'), inte en plats som förvarar sin egen sanning.
Det är också här dubbletter gör verklig skada. Två rader för samma person, en i offert och en i bokad, får tavlan att se friskare ut än firman. E-postmatchningen i steg 2 är botemedlet. Be inte modellen om "smart sammanslagning" eller luddig namnmatchning i första versionen. Exakt e-post är tråkigt och det fungerar. Luddig matchning är så du limmar ihop två olika kunder för att båda heter Erik.
Steg 4: Inloggning innan någon annan kan se listan
Utan session skriver varje besökare till samma kollektioner. Det duger för en privat demo på din egen dator. Det duger inte för ett CRM. Namn, telefonnummer och anteckningar om obetalda fakturor är personuppgifter i samma stund de pekar ut någon.
async function ensureSession() {
if (window.NorthernGoDB.getToken()) return true;
showLoginForm();
return false;
}
async function handleLogin(email, password) {
try {
await window.NorthernGoDB.login(email, password);
location.reload();
} catch (err) {
showMessage(err.message);
}
}
Lås listan, tavlan och varje sparning bakom getToken(). Inloggningen är e-post och lösenord. Två kollegor kan inte var och en registrera sig och se samma kontakter — posterna hör till den inloggade. Dela en inloggning, eller acceptera ett personligt CRM. Det finns inget inbjudnings-API.
Steg 5: Radera kontot och en fil du kan ta med dig
Att bygga GDPR-säkert är inte en senare fas. Raderingsvägen ska vara synlig, bekräftad och gå via den injicerade metoden:
async function deleteMyAccount() {
const ok = confirm('Detta raderar kontot och all sparad data för alltid. Fortsätta?');
if (!ok) return;
try {
await window.NorthernGoDB.deleteAccount();
showMessage('Kontot och all din data har raderats.');
location.reload();
} catch (err) {
showMessage(err.message);
}
}
Loopa inte delete() över kollektionerna och kalla det färdigt. Då blir inloggningsposten kvar. Göm inte knappen på en sida modellen glömde länka. Lägg den intill logga ut, styla den som farlig, säg att återställning är omöjlig.
Export är raderingens syskon. get() returnerar redan den inloggades rader. Gör en fil i webbläsaren:
function toCsv(rows, columns) {
const escape = value => '"' + String(value ?? '').replace(/"/g, '""') + '"';
const header = columns.join(',');
const body = rows.map(row => columns.map(col => escape(row[col])).join(',')).join('\n');
return header + '\n' + body;
}
async function downloadMyData() {
const contacts = (await window.NorthernGoDB.get('contacts')) || [];
const notes = (await window.NorthernGoDB.get('notes')) || [];
const blob = new Blob(
[toCsv(contacts, ['id', 'name', 'email', 'phone', 'company', 'stage', 'valueSek', 'lastTouchedAt']), '\n', toCsv(notes, ['id', 'contactId', 'text', 'createdAt'])],
{ type: 'text/csv;charset=utf-8' }
);
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'crm-export.csv';
a.click();
}
En grundare som kan ladda ner listan på en fredag är en grundare som kan gå. Det är poängen med att inte låsa in sig: appen är vanilla JS, ZIP finns på Premium och Pro, och datan är en CSV du redan kan öppna.
Steg 6: Skicka tillbaka första versionen med en punktlista
Första generationen blir nära och fel. Svara med fakta: e-post ska uppdatera inte lägga in; stegen är en stängd lista; radera-konto ska anropa window.NorthernGoDB.deleteAccount() efter bekräftelse; exportera kontakter och anteckningar. Bränn de varven lokalt. Om HTML:en är rätt och logiken inte är det, exportera ZIP:en på Premium eller Pro och redigera index.html själv — det finns inget dolt ramverk.
Vad det här inte blir
- Inte Salesforce eller HubSpot. Inga automatiserade utskick, ingen spårning, ingen rapportsvit.
window.NorthernGo.sendEmailskickar ett meddelande du formulerar. Det är inte en säljplattform. - Inga roller eller delning per post. En användare, e-post och lösenord. Delade trattar kräver delad inloggning.
- Ingen sammanslagning och ingen färdig CSV-import. Hindra insättningar på matchande e-post. Mata testdata för hand, eller skriv en import i den exporterade koden.
Vad det kostar
Gratis är 0 SEK: fem molngenereringar i månaden och obegränsat lokalt. Ingen ZIP. Premium är 179 SEK / $19: 25 molngenereringar och ZIP-export. Pro är 299 SEK / $29: 50 molngenereringar, egna domäner, Shopify, SEO-granskningar, webhooks och Fortnox — inget krävs för ett CRM. Databas, inloggning och PWA följer med på alla planer.
Felsökning
Varje sparning skapar en andra Anna. Dubblettkollen kördes aldrig, eller den jämförde namn, eller den jämförde e-post med olika versaler. Normalisera med trim().toLowerCase() och spara samma adress två gånger.
En kollega ser en tom tavla. Förväntat. Kollektionerna hör till den inloggade. Dela en inloggning.
Radera mitt konto saknas efter en ändring. Be igen vid namn. Godta inte en egen fetch-rutt. Extra steg i tratten betyder att modellen fyllde på — håll en STAGES-array som flyttfunktionen lyssnar på.
Vanliga frågor
Kan jag bygga ett riktigt CRM med en AI-appbyggare?
Du kan bygga ett kundregister, anteckningar och några steg i säljtratten som sparas i en databas du styr, med inloggning och en väg att radera kontot. Det är ett fungerande CRM för en person eller en delad inloggning. Du kan inte generera Salesforce: inga roller, inga automatiserade utskick, inga teambehörigheter. Om den mindre formen var det du behövde räcker byggaren.
Stoppar AI:n dubbletter av sig själv?
Nej. En prompt som bara säger "CRM" ger en spara-knapp och ingen uppslagning. Säg åt byggaren att hämta kontaktkollektionen innan den sparar och att uppdatera raden när e-posten redan finns. Testa sedan: spara samma adress två gånger och kontrollera att du fortfarande har en post. Exakt e-post är den pålitliga första versionen; luddig namnmatchning limmar ihop fel personer.
Måste ett CRM med inloggning låta användare radera kontot?
Ja. Varje NorthernGo-app med inloggning måste ha en synlig radera-konto-knapp som bekräftar och sedan anropar window.NorthernGoDB.deleteAccount(). Det är GDPR artikel 17. En hemmagjord loop med delete() lämnar inloggningsposten kvar. Om en senare ändring tar bort knappen lägger genereringen tillbaka den.
Kan två kollegor dela samma säljtratt?
Inte som två separata konton. Posterna hör till den inloggade, och det finns inget API för inbjudningar eller roller. De praktiska vägarna är en delad inloggning, eller att behandla CRM:et som personligt. Lita inte på en administratörsbricka modellen målat; det är inte ett behörighetssystem.
Hur exporterar jag kontakterna ur ett AI-byggt CRM?
Anropa window.NorthernGoDB.get på varje kollektion och gör om arrayerna till en CSV i webbläsaren med en Blob och en nedladdningslänk. Det är bara vanilla JavaScript. ZIP-export av själva appen finns på Premium och Pro. Tillsammans är de två utgångarna det som gör att du inte fastnar i byggaren.
Bygg det själv
NorthernGo förvandlar en beskrivning i ren text till en fungerande webbapp med databas, inloggning och en live-adress. Lokal AI-generering körs på ditt eget grafikkort, är obegränsad och ingår gratis i alla prisplaner.