Moderate dating and adults-only apps
If every member of your app is an adult, start from the General built-in policy and confirm each member's age, then copy and change it if your members shouldn't see explicit images. Your app must check members' ages itself: CalmLens doesn't verify them.
Choose a policy
| Your app | Policy | Why |
|---|---|---|
| A community where members share explicit images and video on purpose | General, unchanged | Explicit images and video are age-restricted, so confirmed adults can see them. |
| A dating app | A copy of General with stricter sexual rules | Explicit images are blocked. Sexual text and people's own contact details are limited, so your app can show them in chat and keep them off profiles. |
| An adult forum or social app without explicit images | The same copy, with two changes | Explicit images are blocked, and sexual text and contact details are treated as ordinary content. |
Adult communities: General and age confirmation
General allows sexual text and age-restricts adult nudity, sexual activity and other adult images and video. An age-restricted asset isn't served from its plain URL, so a member sees it only when your app passes viewerClaims.ageConfirmed: true for them.
- Check each member's age when they join. Check age natively covers the checks built into iOS, Android and browsers, and Age-check responsibilities lists where a check must be reliable rather than self-declared.
- Keep age-restricted assets private and issue signed URLs to members who passed, as described in Age-restricted assets.
- Pass
viewerClaims.ageConfirmed: trueon those members' requests, and leave it out for everyone else.
Dating apps: a custom policy
Copy General and change its Sexual and Personal information categories. These are the outcomes that change:
| Content | General | This policy |
|---|---|---|
| Adult nudity, sexual activity and explicit sexual language in images and video | age-restrict | block |
| Explicit sexual text | allow | limit |
| Requests or offers for an unpaid sexual encounter, in text | allow | limit |
| Requests or offers for an unpaid sexual encounter, in images and video | age-restrict | block |
| Promotion of adult content, such as a subscription page | allow in text, age-restrict in images and video | block |
| Someone's own contact details and other private details | allow | limit |
Flirtation, innuendo, revealing clothing and non-erotic nudity stay allowed. Paid sexual services, non-consensual sexual content, sexual harassment and other people's private details stay blocked, as they are in General. A private detail that doesn't say whose it is counts as someone else's, so a message that says the number is the sender's own is limited, while a number on its own is blocked.
Create the policy
Duplicate General in Portal › Moderation policies and change the rules below, or copy it through the API:
const response = await client.policies.create({
"presetId": "general-community",
"name": "Dating"
});
Then send the policy's complete settings with PATCH /, replacing these two categories and keeping the other six, and choose it with PUT /. Manage policies in code and configuration files cover keeping the policy in your repository.
{
"sexual": {
"action": "block",
"allowances": [
{ "name": "Reporting or documentary", "when": ["sexual-documentary"], "action": "allow" },
{ "name": "Support and prevention", "when": ["sexual-support-prevention"], "action": "allow" },
{ "name": "Suggestive content", "when": ["sexual-suggestive"], "action": "allow" },
{ "name": "Revealing clothing", "when": ["sexual-revealing-clothing"], "action": "allow" },
{ "name": "Non-graphic discussion", "when": ["sexual-discussion"], "action": "allow" },
{ "name": "Non-erotic nudity", "when": ["sexual-nudity", "sexual-non-erotic-nudity"], "action": "allow" },
{ "name": "Text", "when": ["sexual-text"], "action": "limit" }
],
"restrictions": [
{ "name": "Paid sexual services", "when": ["sexual-solicitation", "sexual-paid"], "action": "block" },
{ "name": "Explicit sexual language", "when": ["sexual-explicit-language"], "unless": ["sexual-text"], "action": "block" },
{ "name": "Nudity", "when": ["sexual-nudity"], "unless": ["sexual-non-erotic-nudity", "sexual-text"], "action": "block" },
{ "name": "Sexual activity", "when": ["sexual-activity"], "unless": ["sexual-documentary", "sexual-support-prevention", "sexual-discussion", "sexual-text"], "action": "block" },
{ "name": "Non-consensual sexual content", "when": ["sexual-non-consensual"], "unless": ["sexual-documentary", "sexual-support-prevention", "sexual-discussion"], "action": "block" },
{ "name": "Sexual-assault images or video", "when": ["sexual-assault-media"], "action": "block" },
{ "name": "Adult content promotion", "when": ["sexual-adult-promotion"], "action": "block" }
]
},
"personal-information": {
"action": "limit",
"allowances": [],
"restrictions": [
{ "name": "Other people's details", "when": ["personal-information-third-party"], "unless": ["personal-information-documentary"], "action": "block" }
]
}
}A copy keeps these settings when General changes. Check the preset changelog on the policy's page from time to time; see Preset versions and updates. To block insults aimed at a person as well, remove both Insults or humiliation rules from Harassment.
To block links to adult sites too, set their link safety action to block; General age-restricts them. See Link safety.
Keep limited content off profiles
CalmLens can't tell a public profile from a private chat: it decides from the content you send. Your app knows where the content is going, so your app decides where limited content appears. classification.moderation.flaggedCategories says why it was limited, such as sexual or personal-information.
| Status | On a profile, bio or anything other members browse | In a chat between members |
|---|---|---|
| allowed | Show it. | Deliver it. |
| limited | Ask the member to change it, and don't publish it. | Deliver it, unless it opens the conversation; see Sexual openers. |
| age-restricted | Don't publish it. | Deliver it only if your app allows this content in chat, and pass age confirmation for members who passed your check. |
| blocked or purged | Don't publish it. Tell the member why. | Don't deliver it. Tell the sender why. |
Age-restricted is still possible in this policy, because the copy keeps General's rules for other categories, such as graphic violence in images. If your profile photos go through an upload preset, you can give that preset its own stricter policy, so they are decided there without any check in your app.
Sexual openers
An unwanted, humiliating or coercive sexual advance aimed at someone is Sexual harassment under Harassment, and is blocked. Consensual sexual dialogue and sexual content not aimed at anyone are not. CalmLens reads what you send, so it can't know whether this recipient wants a message.
Two things help:
- Send the conversation. Pass the sender and the earlier messages with each message, so it's read against what it answers, such as a repeated proposition after a refusal. See Threaded moderation.
- Decide first messages in your app. Your app knows when a message opens a conversation. Hold a first message that is limited with sexual in
flaggedCategories, and show it only if the recipient chooses to see it.
Adult communities without explicit images
Use the policy above with two changes, so explicit images stay out and text isn't treated differently by surface:
- In Sexual, set the Text allowance's action to allow.
- Keep Personal information as it is in General, with an action of allow and the restriction on other people's details.
Test the policy
Before assigning the policy to a live project, try representative profiles, messages and images on a test project, and check each row of the table above. See Testing.