AI content detection sounds like it should be easy.
You take a piece of text, run it through a detector, and get an answer:
Human-written
or:
AI-generated
But real life is messier than that.
AI-generated text can be edited by humans. Human text can be polished by Grammarly or ChatGPT. Non-native English writing can look “too simple” to some detectors. Academic writing can look formulaic. SEO content can look predictable. Short text is especially hard to judge.
So if you are building AI content detection with JavaScript, the safer approach is this:
Return a probability, show evidence, add uncertainty, and avoid treating the score as proof.
In this guide, we’ll build a JavaScript AI content detection workflow. We’ll use simple text signals, a model-based detector with Transformers.js, an API-style detector option, and a review flow that makes the result usable in a real app.
Why we can write this guide
We’ve spent around 6 years working with AI APIs, text classification, NLP tools, content workflows, and developer automation. We also researched current AI detection research, JavaScript model tooling, and public guidance around AI provenance.
The main thing we’d tell any developer is this: AI detection should be treated as a risk score, not a final accusation.
A 2026 large-scale audit of AI-generated text detection systems found that state-of-the-art detectors still show considerable false positives in the wild. That matters because a false positive means human writing gets flagged as AI-written, which can be a serious problem in schools, publishing, hiring, compliance, and content moderation. Research link: A Large Scale Social Web Audit of AI Generated Text Detection Systems.
What are we actually detecting?
Before writing code, let’s define the goal.
An AI content detector usually tries to answer:
| Question | Better wording |
| “Was this written by AI?” | “How likely is it that this text has AI-generated patterns?” |
| “Can I prove this is AI?” | “Is there enough evidence to send this for review?” |
| “Should I reject this content?” | “Should a human check this before approval?” |
That wording matters.
A detector can look for signals, but it cannot always know the full writing process. Maybe the user wrote it manually. Maybe AI drafted it. Maybe AI only fixed grammar. Maybe several people edited it. Maybe it was translated.
So the output should look more like this:
{
"ai_risk_score": 0.74,
"label": "needs_review",
"confidence": "medium",
"signals": [
"Very low sentence variation",
"High repetition of transition phrases",
"Model classifier returned AI-like probability"
],
"warning": "This is a probabilistic result, not proof of authorship."
}
That is much safer and more useful than:
{
“verdict”: “AI”
}
Where AI detection fits in an app
AI detection is useful when it supports a review process.
For example:
| App type | How detection can help |
| CMS | Flag suspicious content before publishing |
| Marketplace | Review spammy product descriptions |
| School platform | Support academic integrity review |
| SEO tool | Check if drafts look generic or over-automated |
| Hiring platform | Flag suspicious application answers |
| Forum/community | Detect low-quality mass-generated posts |
| Legal/compliance workflow | Add review signal for sensitive content |
| Publishing tool | Ask for source notes or human verification |
The key phrase is “support review.”
AI detection is weaker when you use it as the only decision-maker.
Method 1: Start with simple text signals
Before adding ML, you can calculate basic writing signals.
These will not “detect AI” by themselves, but they help explain why a text may feel generated.
Useful signals:
| Signal | What it may show |
| Average sentence length | Very uniform writing can look machine-like |
| Sentence length variation | Low variation may feel templated |
| Repeated phrases | AI drafts can overuse patterns |
| Generic transitions | “In conclusion,” “Furthermore,” “Additionally” |
| Lexical diversity | Low diversity can suggest repetitive text |
| Paragraph structure | Very symmetrical paragraphs may be suspicious |
| Personal details | Human writing often includes specific context |
| Citations/source links | Source-backed writing is easier to verify |
Let’s build a simple analyzer.
function splitSentences(text) {
return text
.replace(/\s+/g, " ")
.split(/(?<=[.!?])\s+/)
.filter(Boolean);
}
function tokenize(text) {
return text
.toLowerCase()
.replace(/[^\w\s]/g, " ")
.split(/\s+/)
.filter(Boolean);
}
function getAverage(numbers) {
if (numbers.length === 0) return 0;
return numbers.reduce((sum, number) => sum + number, 0) / numbers.length;
}
function getStandardDeviation(numbers) {
if (numbers.length === 0) return 0;
const average = getAverage(numbers);
const variance = getAverage(
numbers.map((number) => Math.pow(number - average, 2))
);
return Math.sqrt(variance);
}
function analyzeTextSignals(text) {
const sentences = splitSentences(text);
const words = tokenize(text);
const sentenceLengths = sentences.map(
(sentence) => tokenize(sentence).length
);
const uniqueWords = new Set(words);
const lexicalDiversity =
words.length === 0 ? 0 : uniqueWords.size / words.length;
const avgSentenceLength = getAverage(sentenceLengths);
const sentenceVariation = getStandardDeviation(sentenceLengths);
return {
word_count: words.length,
sentence_count: sentences.length,
average_sentence_length: Number(avgSentenceLength.toFixed(2)),
sentence_length_variation: Number(sentenceVariation.toFixed(2)),
lexical_diversity: Number(lexicalDiversity.toFixed(2))
};
}
Test it:
const text = `
Artificial intelligence is changing content creation.
It helps teams write faster and organize ideas.
It also supports automation across many workflows.
`;
console.log(analyzeTextSignals(text));
Example output:
{
"word_count": 24,
"sentence_count": 3,
"average_sentence_length": 8,
"sentence_length_variation": 0.82,
"lexical_diversity": 0.88
}
Again, these signals are not enough alone. They are explanation features.
Method 2: Add repeated phrase detection
AI-generated text often repeats certain structures, especially in low-effort prompts.
Let’s detect repeated phrases.
function getNgrams(words, size) {
const ngrams = [];
for (let i = 0; i <= words.length - size; i++) {
ngrams.push(words.slice(i, i + size).join(" "));
}
return ngrams;
}
function findRepeatedPhrases(text, size = 3, minCount = 2) {
const words = tokenize(text);
const ngrams = getNgrams(words, size);
const counts = new Map();
for (const ngram of ngrams) {
counts.set(ngram, (counts.get(ngram) || 0) + 1);
}
return [...counts.entries()]
.filter(([, count]) => count >= minCount)
.map(([phrase, count]) => ({ phrase, count }))
.sort((a, b) => b.count - a.count);
}
Use it:
const repeated = findRepeatedPhrases(text, 3, 2);
console.log(repeated);
This is useful for spam detection, SEO content QA, and content review.
Some repeated phrases are normal. Legal writing, technical docs, and product descriptions can repeat terms for good reasons. So treat repetition as a signal, not a verdict.
Method 3: Look for generic AI-style transitions
You can also detect overused transition phrases.
const genericPhrases = [
"in conclusion",
"it is important to note",
"in today's digital landscape",
"delve into",
"unlock the power",
"seamless experience",
"revolutionize",
"furthermore",
"moreover",
"additionally",
"when it comes to"
];
function findGenericPhrases(text) {
const normalized = text.toLowerCase();
return genericPhrases.filter((phrase) =>
normalized.includes(phrase)
);
}
Use it:
console.log(findGenericPhrases(“In conclusion, it is important to note that AI can revolutionize workflows.”));
Output:
[
“in conclusion”,
“it is important to note”,
“revolutionize”
]
This is helpful for content quality. But be careful. A human can write generic phrases too. The goal is to flag weak or templated writing, not prove who wrote it.
Method 4: Create a simple risk score
Now let’s combine signals into a basic risk score.
This is a heuristic detector. It is not a real AI classifier.
function heuristicAiRisk(text) {
const signals = analyzeTextSignals(text);
const repeatedPhrases = findRepeatedPhrases(text, 3, 2);
const genericMatches = findGenericPhrases(text);
let score = 0;
const reasons = [];
if (signals.word_count < 80) {
reasons.push("Text is short, so detection is less reliable.");
}
if (signals.sentence_length_variation < 3 && signals.sentence_count >= 5) {
score += 0.2;
reasons.push("Sentence lengths are very uniform.");
}
if (signals.lexical_diversity < 0.45 && signals.word_count > 100) {
score += 0.2;
reasons.push("Lexical diversity is low.");
}
if (repeatedPhrases.length > 3) {
score += 0.2;
reasons.push("Several repeated phrases were found.");
}
if (genericMatches.length > 2) {
score += 0.2;
reasons.push("Several generic AI-style phrases were found.");
}
const finalScore = Math.min(score, 1);
let label = "likely_human_or_unclear";
if (finalScore >= 0.7) {
label = "needs_review";
} else if (finalScore >= 0.4) {
label = "some_ai_like_signals";
}
return {
ai_risk_score: Number(finalScore.toFixed(2)),
label,
signals,
repeated_phrases: repeatedPhrases.slice(0, 10),
generic_phrases: genericMatches,
reasons,
warning: "This heuristic score is not proof that AI wrote the text."
};
}
Use it:
const result = heuristicAiRisk(`
In today's digital landscape, businesses must unlock the power of AI.
Furthermore, AI can streamline workflows.
Moreover, AI can enhance productivity.
Additionally, AI can revolutionize how teams work.
In conclusion, it is important to note that AI is powerful.
`);
console.log(result);
This kind of detector can help with content QA. It is not enough for academic misconduct, hiring decisions, or anything high-stakes.
Method 5: Use Transformers.js for model-based detection
For a real model-based detector in JavaScript, you can use Transformers.js. Hugging Face says Transformers.js is designed to be functionally similar to the Python Transformers library and supports tasks like text classification directly in the browser or Node.js.
Install:
npm install @huggingface/transformers
Then use a text classification pipeline:
import { pipeline } from "@huggingface/transformers";
const detector = await pipeline(
"text-classification",
"your-ai-detection-model"
);
const text = `
Artificial intelligence can help teams automate content workflows
by generating drafts, summaries, and structured outputs.
`;
const result = await detector(text);
console.log(result);
You need to choose a model that is actually trained for AI-generated text detection. Model availability changes, so check the Hugging Face model card before using one in production. Look for:
- Training data.
- Supported languages.
- Intended use.
- Limitations.
- Evaluation results.
- False positive notes.
- License.
A 2026 paper called AI Generated Text Detection compared traditional ML and transformer-based methods using HC3 and DAIGT v2 datasets. It found that a TF-IDF logistic regression baseline reached 82.87% accuracy, while deep learning models such as BiLSTM and DistilBERT performed better, with DistilBERT reaching the highest ROC-AUC in the study. That fits this section because it shows why a transformer classifier can be stronger than simple lexical rules, but also why evaluation setup matters.
Method 6: Use a detection API
If you do not want to run a model in JavaScript, call a detection API.
The workflow is simple:
- User submits text.
- Your app sends text to a detector API.
- The API returns a score.
- Your app combines the score with your own checks.
- Low-risk text passes.
- High-risk text goes to review.
Example JavaScript request:
async function detectAiContent(text) {
const response = await fetch("https://example-detector-api.com/check", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.DETECTOR_API_KEY}`
},
body: JSON.stringify({
text
})
});
if (!response.ok) {
throw new Error(`Detector API failed: ${response.status}`);
}
return response.json();
}
Example response shape:
{
"ai_probability": 0.81,
"human_probability": 0.19,
"confidence": "medium",
"segments": [
{
"text": "AI can transform modern workflows...",
"ai_probability": 0.87
}
]
}
When choosing a detector API, ask:
| Question | Why it matters |
| What models was it tested against? | Newer LLMs may bypass older detectors |
| Does it support your language? | English-only detectors may fail elsewhere |
| Does it handle short text? | Short text is usually unreliable |
| Does it report false positives? | You need risk estimates |
| Does it show segment-level results? | Better for review |
| Does it store submitted text? | Privacy issue |
| Can it detect edited AI text? | Human-edited AI text is harder |
| Does it provide API limits/pricing? | Needed for scale |
Why false positives are the scary part
A false negative means AI text gets labeled as human.
A false positive means human text gets labeled as AI.
Both are bad, but false positives can hurt people directly.
For example:
| Setting | Why false positives matter |
| School | A student may be wrongly accused |
| Hiring | A real applicant may be unfairly filtered |
| Publishing | A writer may be wrongly flagged |
| Workplace | An employee may be accused of cheating |
| Moderation | Good user content may be removed |
This is why your UI should never say:
This was written by AI.
Say:
This text has AI-like signals and should be reviewed.
The large-scale social web audit is especially useful here because it focuses on detector behavior in the wild, not only polished benchmark settings. That fits product design because your users will submit messy, real text, not clean benchmark examples.
Add segment-level review
Instead of flagging the whole document, show risky sections.
Example:
{
"document_score": 0.68,
"label": "needs_review",
"segments": [
{
"paragraph": 2,
"score": 0.82,
"reason": "Very generic phrasing and low sentence variation"
},
{
"paragraph": 4,
"score": 0.31,
"reason": "More specific detail and varied structure"
}
]
}
Build a simple paragraph splitter:
function splitParagraphs(text) {
return text
.split(/\n\s*\n/)
.map((paragraph) => paragraph.trim())
.filter(Boolean);
}
function analyzeByParagraph(text) {
const paragraphs = splitParagraphs(text);
return paragraphs.map((paragraph, index) => ({
paragraph_number: index + 1,
...heuristicAiRisk(paragraph)
}));
}
Use it:
console.log(analyzeByParagraph(longText));
This makes review easier. A human can inspect the highest-risk sections instead of guessing why the whole document was flagged.
Build a safer detector output
Here is a practical combined format:
function buildDetectionReport(text, modelResult = null) {
const heuristic = heuristicAiRisk(text);
const paragraphs = analyzeByParagraph(text);
const modelScore = modelResult?.ai_probability ?? null;
let combinedScore = heuristic.ai_risk_score;
if (modelScore !== null) {
combinedScore = (heuristic.ai_risk_score * 0.4) + (modelScore * 0.6);
}
combinedScore = Number(combinedScore.toFixed(2));
let label = "low_risk";
if (combinedScore >= 0.75) {
label = "needs_review";
} else if (combinedScore >= 0.45) {
label = "unclear";
}
return {
ai_risk_score: combinedScore,
label,
heuristic_score: heuristic.ai_risk_score,
model_score: modelScore,
document_signals: heuristic,
paragraph_results: paragraphs,
recommendation:
label === "needs_review"
? "Send this text to human review."
: "Do not treat this result as proof of authorship.",
warning:
"AI detection is probabilistic and can produce false positives and false negatives."
};
}
This gives your frontend a much better result than a single score.
Build a simple Express API
Now let’s wrap it in an API.
Install:
npm install express
Create server.js:
import express from "express";
const app = express();
app.use(express.json({ limit: "1mb" }));
app.post("/detect-ai", async (req, res) => {
try {
const { text } = req.body;
if (!text || typeof text !== "string") {
return res.status(400).json({
error: "Text is required."
});
}
if (text.length < 300) {
return res.json({
label: "too_short",
warning: "Short text is hard to classify reliably.",
report: buildDetectionReport(text)
});
}
const report = buildDetectionReport(text);
return res.json(report);
} catch (error) {
return res.status(500).json({
error: "AI detection failed.",
details: error.message
});
}
});
app.listen(3000, () => {
console.log("AI detector API running on http://localhost:3000");
});
Run it:
node server.js
Test it:
curl -X POST http://localhost:3000/detect-ai \
-H "Content-Type: application/json" \
-d '{"text":"Paste your text here..."}'
What about DetectGPT-style methods?
DetectGPT is one of the best-known research methods for detecting machine-generated text.
The DetectGPT paper proposed detecting generated text by looking at probability curvature under a language model. The authors reported strong results on fake news articles generated by GPT-NeoX, improving AUROC compared with zero-shot baselines.
That research matters because it shows AI detection can use deeper model probability behavior, not only surface features like “too many generic words.”
But DetectGPT-style methods are heavier to implement in a normal JavaScript app. They usually require access to model log probabilities, perturbations, and repeated scoring. For most product teams, a practical setup is:
- Use a detector API or classifier model.
- Add simple explainable text signals.
- Add segment-level review.
- Avoid hard claims.
- Keep humans in the loop.
What about provenance and watermarking?
Text detection is hard because the content itself may not carry reliable proof.
Provenance is different. Provenance tries to verify where content came from.
OpenAI’s 2026 post on advancing content provenance discusses public verification for media generated by ChatGPT, the OpenAI API, or Codex by checking provenance signals such as Content Credentials and SynthID. That post focuses on media signals rather than normal text detection, but the idea matters: verification is stronger when content carries provenance metadata or watermark signals.
For text, you usually do not have that. So text detectors must infer patterns from writing, which is why uncertainty is unavoidable.
In a product, you can combine:
| Method | Use |
| Text detector | Risk score |
| Provenance metadata | Stronger source signal when available |
| User disclosure | Ask users whether AI was used |
| Revision history | See how text was created |
| Human review | Final judgment |
| Source/citation check | Verify factual claims |
That is a much stronger workflow than detector-only decisions.
Where LLMAPI fits
LLMAPI can fit around AI detection when detection is part of a larger content review workflow.
For example:
- JavaScript app receives text.
- Detector returns a risk score.
- If risk is low, content passes.
- If risk is unclear, LLMAPI routes the text to a model for content quality review.
- The model checks for generic wording, unsupported claims, missing citations, or weak sections.
- Your app sends the text to a human editor if needed.
Useful LLMAPI follow-up tasks:
| Task | Example |
| Content quality review | “Which sections feel generic?” |
| Citation check | “Which claims need sources?” |
| Rewrite suggestion | “Make this less generic but keep meaning.” |
| Policy review | “Does this violate content guidelines?” |
| Summary for reviewer | “Explain why this was flagged.” |
| Model routing | Use cheaper models for simple checks and stronger ones for hard review |
| Fallback | Retry with another model if one fails |
This is useful because detection alone is a weak product experience. Review guidance is much more helpful.
What should the UI say?
Please be careful with wording.
Use:
AI-like signals detected
or:
This text should be reviewed
Avoid:
This was written by AI
A good UI could show:
| UI field | Example |
| Risk label | Needs review |
| Risk score | 74% |
| Confidence | Medium |
| Why flagged | Repetition, generic phrases, classifier score |
| Segment highlights | Paragraphs 2 and 5 |
| Warning | This result is probabilistic |
| Action | Review, approve, request sources, ask for revision |
This makes the detector more fair and useful.
Common mistakes
| Mistake | Better approach |
| Treating detector score as proof | Treat it as a review signal |
| Checking very short text | Require enough text or mark as unreliable |
| Ignoring false positives | Show uncertainty and review options |
| Using only generic phrase rules | Add model-based detection if needed |
| Using only one detector | Compare tools on your own data |
| No segment view | Show which parts triggered risk |
| No human review | Add review for medium/high risk |
| No privacy policy | Explain what happens to submitted text |
| No language testing | Test every language you support |
| No appeals/recheck flow | Let users provide sources or revision history |
The biggest mistake is overconfidence. AI detection is useful, but it is not magic.
A better production workflow
If you are building this for a real app, use this flow:
- User submits text.
- App checks text length and language.
- App runs heuristic checks.
- App runs model/API detection.
- App combines scores into a risk label.
- App highlights risky sections.
- App shows a warning about uncertainty.
- Low-risk content passes.
- Medium-risk content asks for sources or revision notes.
- High-risk content goes to human review.
This creates a safer review system.
The practical takeaway
You can detect AI-like content patterns with JavaScript, but you should build the feature carefully.
Use simple signals for explainability: sentence variation, repeated phrases, lexical diversity, and generic wording. Add a model-based detector with Transformers.js or an external API if you need stronger classification. Return a risk score, not a hard verdict. Show segment-level evidence. Mark short text as unreliable. Add human review for medium and high-risk cases.
And most importantly: do not accuse people based only on a detector.
AI content detection works best as part of a content quality and review workflow. Use JavaScript for the detection pipeline, use LLMAPI for review assistance or model routing when needed, and keep a human decision point anywhere the result could affect someone seriously.