Settings — Technical Reference
Where this lives
Backend
apps/backend/graphql/types/extended-privacy-settings.type.js+resolvers/extended-privacy-settings.resolver.js— extended privacy toggles, story-hidden-usersapps/backend/graphql/types/extended-notification-settings.type.js+ resolver — extended notification togglesapps/backend/graphql/types/media-preferences.type.js,sharing-settings.type.js,hidden-words.type.js,followers-invites.type.js,account-standing.type.js,reels-translation.type.js,time-management.type.js,content-archive.type.js,activity-log.type.js,content-preference.type.js,search-history.type.js,link-history.type.js,watch-history.type.js,tag-review.type.js— each a dedicated, brand-new type/resolver pair (see GraphQL Architecture for why this codebase avoids extending shared type names)apps/backend/managers/user-managers/privacy-settings.manager.js— backs nearly all of the extended privacy/media/sharing/hidden-words/followers-invites fields via one JSONBsettings.privacyblobapps/backend/managers/user-managers/notification-settings.manager.js,usage-tracking.manager.js,reels-translation.manager.js,content-preference.manager.js,activity-log.manager.js,search-history.manager.js,link-tracking.manager.js;managers/post-managers/content-archive.manager.js,watch-history.manager.jsapps/backend/database/models/PostMention.js— carriesstatus(approved/pending/rejected) for the Tags/Pending-review flow
Frontend
apps/frontend-nextjs/src/page-components/SettingsPage.tsx— settings shell, sidebarmenuItemsapps/frontend-nextjs/src/page-components/settings/— every settings screen, one component per file (full list in Frontend components below)apps/frontend-nextjs/src/app/settings/— one thin route wrapper per screen, mirroring the routes table belowapps/frontend-nextjs/src/components/UsageHeartbeat.tsx— mounted insideNavigation.tsx, powers Time management usage stats
Technical implementation checklist
-
privacySettings/updatePrivacySettings/resetPrivacySettings+extendedPrivacySettings/updateExtendedPrivacySettings— both resolver pairs wired;AccountPrivacyPage.tsxfully wired to both -
storyHiddenUsers/updateStoryHiddenUsers— wired; UI insideStoryLivePage.tsx -
mediaPreferences/updateMediaPreferences— wired; replaces the old singlemediaQualitystring enum with 3 booleans -
sharingSettings,hiddenWords,followersInvitesSettings+ mutations — each wired independently;FollowersAndInvitesPage.tsx's invite channels beyond "share link" are UI-only "coming soon" rows -
accountStandingStatus— wired, reads realuser.isRestricted/user_warnings/user_moderation_logs -
notificationSettings/updateNotificationSettings+extendedNotificationSettings/updateExtendedNotificationSettings— both wired -
reelsTranslationSettings/updateReelsTranslationSettings— wired; separate JSONB key (settings.reels_translation) from the UI-languagei18nextsetting -
timeManagementSettings/usageStats/updateTimeManagementSettings/recordUsageHeartbeat— wired end-to-end including the heartbeat client -
archivedPosts/recentlyDeletedPosts/archivePost/unarchivePost/restorePost/permanentlyDeletePost— wired; relies onPost.isArchived+ Sequelizeparanoidsoft-delete -
myLikedPosts/myComments/myReposts/watchHistory/searchHistory/linkHistory/notInterestedContent+ mutations — wired; "Sticker responses"/"Reviews" intentionally unexposed (no backing model) -
taggedPosts/pendingTagReviews/approvePostTag/rejectPostTag— wired; named distinctly from an older, unrelated, unfinishedapproveTag/rejectTagpair on the sameMutationtype to avoid a field-name collision - Crossposting — fully wired (
crosspost.type.js/crosspost.resolver.js):myCrosspostConnections,crosspostAuthorizeUrl,connectCrosspostAccount,disconnectCrosspostAccount,setCrosspostEnabled. OAuth to each provider is gated on server env (X_CLIENT_ID/FACEBOOK_APP_ID/TIKTOK_CLIENT_KEY…);CrosspostConnection.configuredreflects whether the provider's app is set up. Auto-share on publish. - Restrict — fully wired (
restrict.type.js/restrict.resolver.js/restrict.manager.js):restrictedAccounts,isAccountRestricted,restrictAccount,unrestrictAccount; persisted via theUserRestrictionmodel (user_restrictiontable,restrictor_id/restricted_id). Enforced inpost-comment.manager.jsalongside Limit interactions. - Limit interactions — fully wired (
interaction-limit.type.js/interaction-limit.resolver.js/interaction-limit.manager.js):myInteractionLimit,setInteractionLimit; persisted onuser.limit_interactions_enabled/limit_interactions_scope/limit_interactions_until(migration20260719030000-add-interaction-limits-to-user.js). - Insights & tools —
InsightsAndToolsPage.tsxis wired, but to creator monetization analytics rather than the supervised-account tooling the name implies:myPostPurchaseEarnings,subscriberCount,subscriberRetention(pre-existing fields onpost-purchase.resolver.js/user-subscription.resolver.js). - Family Center — frontend-only stub, no resolver/type/manager exists
Crossposting API
Lets a user link external social accounts (X, Facebook, TikTok) and auto-share new posts to them. OAuth apps are configured server-side; a provider only appears connectable when its credentials are present (configured: true).
type CrosspostConnection {
provider: String! # x | facebook | tiktok
connected: Boolean! # a token is stored (account linked)
enabled: Boolean! # auto-share to this provider is on
providerUsername: String
configured: Boolean! # the server has this provider's OAuth app set up
}
extend type Query {
myCrosspostConnections: [CrosspostConnection!]!
crosspostAuthorizeUrl(provider: String!, redirectUri: String!): String # null if not configured
}
extend type Mutation {
connectCrosspostAccount(provider: String!, code: String!, redirectUri: String!): Boolean! # finish OAuth
disconnectCrosspostAccount(provider: String!): Boolean!
setCrosspostEnabled(provider: String!, enabled: Boolean!): Boolean! # toggle auto-share
}
Flow: call crosspostAuthorizeUrl → send the user to the provider's OAuth consent → the callback returns a code → connectCrosspostAccount(provider, code, redirectUri) stores the token → setCrosspostEnabled toggles auto-share. Env vars: X_CLIENT_ID/X_CLIENT_SECRET, FACEBOOK_APP_ID/FACEBOOK_APP_SECRET, TIKTOK_CLIENT_KEY/TIKTOK_CLIENT_SECRET (see environment setup).
Restrict & Limit interactions
Two distinct, user-controlled "soft moderation" tools, both enforced in post-comment.manager.js (a comment is hidden from the post owner if the commenter is either restricted or limited by the owner). Neither is related to block/mute or to admin moderation.
# Restrict — persisted via the UserRestriction model (user_restriction table)
type RestrictedAccount { user: User! restrictedAt: DateTime! }
query RestrictedAccounts($limit: Int, $offset: Int) { restrictedAccounts(limit: $limit, offset: $offset) { user { id username } restrictedAt } }
query IsAccountRestricted($userId: ID!) { isAccountRestricted(userId: $userId) }
mutation RestrictAccount($userId: ID!) { restrictAccount(userId: $userId) }
mutation UnrestrictAccount($userId: ID!) { unrestrictAccount(userId: $userId) }
# Limit interactions — persisted on user.limit_interactions_enabled/_scope/_until
type InteractionLimit { enabled: Boolean! scope: String! until: DateTime active: Boolean! }
query MyInteractionLimit { myInteractionLimit { enabled scope until active } }
mutation SetInteractionLimit($enabled: Boolean!, $scope: String, $durationDays: Int) {
setInteractionLimit(enabled: $enabled, scope: $scope, durationDays: $durationDays) { enabled until active }
}
durationDays sets until to now + N days (omit for indefinite); myInteractionLimit.active is true only while enabled and not yet expired. The only enforced scope today is non_followers.
Insights & tools
InsightsAndToolsPage.tsx (route /settings/insights-tools) is not supervised-account tooling despite its name — it's creator monetization analytics, reusing pre-existing fields rather than adding new ones:
query MyPostPurchaseEarnings { myPostPurchaseEarnings { totalSales totalRevenue totalEarnings platformFees } }
query SubscriberCount($creatorId: ID!) { subscriberCount(creatorId: $creatorId) }
query SubscriberRetention($months: Int) {
subscriberRetention(months: $months) { month newSubscribers churned activeAtEnd churnRate retentionRate }
}
myPostPurchaseEarnings comes from post-purchase.resolver.js; subscriberCount/subscriberRetention come from user-subscription.resolver.js. The page renders exclusive-post sales/earnings stat tiles, an active-subscriber count, and a monthly retention/churn bar chart, plus a CSV export button.
Routes
Account & privacy
| Route | Component | Description |
|---|---|---|
/settings/profile | EditProfilePage | Name, username, bio, avatar, cover photo |
/settings/account-privacy | AccountPrivacyPage | Private account, visibility, mentions, activity, extended privacy toggles |
/settings/tags-and-mentions | TagsAndMentionsPage | Who can mention/tag you, tag-approval requirement |
/settings/tags-and-mentions/review | TagReviewPage | Approve/reject tags awaiting your review |
/settings/tags | TagsScreenPage | Tagged grid (posts you're tagged in) + Pending tags tab |
/settings/friends-activity | FriendsActivityPage | Visibility of your activity in the Friends feed |
/settings/followers-and-invites | FollowersAndInvitesPage | Auto-confirm followers, invite friends |
/settings/close-friends | CloseFriendsPage | Close Friends list management |
/settings/favorites | FavoritesPage | Favorite accounts — see Favorites |
/settings/blocked | BlockedPage | Blocked accounts |
/settings/restricted-accounts | RestrictedAccountsPage | Restricted accounts list with per-row unrestrict — wired to restrictedAccounts/unrestrictAccount (restrict.resolver.js) |
/settings/limit-interactions | LimitInteractionsPage | Turn interaction limiting on/off with optional auto-expiry — wired to myInteractionLimit/setInteractionLimit (interaction-limit.resolver.js) |
/settings/muted | MutedPage | Muted accounts |
/settings/hidden-words | HiddenWordsPage | Words/phrases auto-hidden from comments and message requests |
/settings/story-live | StoryLivePage | Story/Live audience defaults, location sharing, "Hide story from" list |
/settings/account-status | AccountStatusPage | Moderation standing summary (distinct from account lifecycle status) |
/settings/account | AccountManagementPage | Deactivate/delete account — see Account Management |
/profile?tab=saved | (ProfilePage, saved tab) | Saved posts — the Settings hub links here rather than duplicating the feature |
Messages & activity
| Route | Component | Description |
|---|---|---|
/settings/messages-and-story-replies | MessagesAndStoryRepliesPage | Hub linking to the four sub-pages below |
/settings/messages-and-story-replies/message-requests | MessageRequestsPage | Who can message you, group invites, hide unwanted requests |
/settings/messages-and-story-replies/story-replies | StoryRepliesPage | Who can reply to your stories |
/settings/messages-and-story-replies/read-receipts | ReadReceiptsPage | Show read receipts toggle |
/settings/messages-and-story-replies/nudity-protection | NudityProtectionPage | Nudity protection in DMs toggle |
/settings/notifications | NotificationsSettingsPage | Notifications hub — pause all, sleep mode, per-category list — see Notifications |
/settings/notifications/[category] | NotificationCategoryPage | Granular toggles for one notification category |
/settings/notifications/sleep-mode | SleepModePage | Scheduled do-not-disturb window |
/settings/time-management | TimeManagementPage | Daily usage chart, daily limit, sleep mode shortcut |
/settings/archive | ArchivePage | Archived posts/stories/instants |
/settings/recently-deleted | RecentlyDeletedPage | Soft-deleted posts, recoverable for 30 days |
/settings/activity | YourActivityPage | "Your activity" hub |
/settings/activity/interactions | InteractionsPage | Likes, comments, reposts |
/settings/activity/watch-history | WatchHistoryPage | Recently viewed posts/clips |
/settings/activity/recent-searches | RecentSearchesPage | Search history |
/settings/activity/link-history | LinkHistoryPage | Recently clicked external links |
/settings/activity/suggested-content | SuggestedContentPage | "Not interested" content preferences |
Discovery, content, and support
| Route | Component | Description |
|---|---|---|
/settings/language | LanguagePage | App language + Reels translation preferences |
/settings/content-preferences | ContentPreferencesPage | Hub linking to Suggested content, Accessibility, Media quality, Language |
/settings/accessibility | AccessibilityPage | Reduce motion / autoplay videos (device-local, localStorage) |
/settings/comments | CommentsPage | Allow comments toggle (mirrors the field also on Account privacy) |
/settings/sharing | ShareSettingsPage | Allow resharing your posts to others' Stories |
/settings/crossposting | CrosspostingPage | Connect/disconnect/toggle X, Facebook, TikTok — wired to crosspost.resolver.js (gated per provider on server OAuth env) |
/settings/download-data | DownloadDataPage | GDPR data export — see Data Export |
/settings/device-permissions | DevicePermissionsPage | Camera/Mic/Location/Notifications via Permissions API; Contacts/Photos informational only |
/settings/app-website-permissions | AppAndWebsitePermissionsPage | Connected apps/websites |
/settings/family-center | FamilyCenterPage | ⚠️ UI stub |
/settings/insights-tools | InsightsAndToolsPage | Creator earnings + subscriber analytics (exclusive-post sales, subscriber count, retention/churn) — despite the route name, this is not supervised-account tooling |
/settings/sessions | SessionsSettingsPage | Active sessions — see Sessions |
/settings/security | SecuritySettingsPage | 2FA, password — see Security |
/settings/verification | VerificationRequestPage | Request a verified badge — see Verification |
/settings/subscriptions | SubscriptionsSettingsPage | Manage subscribers/subscriptions |
/payments | (PaymentsPage) | Linked from the hub as "Your orders and fundraisers" |
/settings/more-info-support | MoreInfoSupportPage | Help, Privacy Center, Terms, links to About and Account status |
/settings/about | AboutPage | App version, legal links |
Privacy settings
The base PrivacySettings type (declared in user.type.js) covers the original privacy fields:
type PrivacySettings {
isPrivate: Boolean!
showActivityStatus: Boolean!
allowMessagesFrom: String! # "everyone" | "followers" | "nobody"
allowTagsFrom: String!
showFollowers: Boolean!
showFollowing: Boolean!
allowComments: Boolean!
showInExplore: Boolean!
showInSearch: Boolean!
allowStorySharing: Boolean!
saveStoryToArchive: Boolean!
shareLocation: Boolean!
allowGroupInvitesFrom: String!
}
query PrivacySettings {
privacySettings {
isPrivate showActivityStatus allowMessagesFrom allowTagsFrom
showFollowers showFollowing allowComments showInExplore
}
}
mutation UpdatePrivacySettings($input: PrivacySettingsInput!) {
updatePrivacySettings(input: $input) { isPrivate showActivityStatus }
}
mutation ResetPrivacySettings { resetPrivacySettings { settings { settingName value } } }
Every newer Instagram-parity toggle lives in a separate ExtendedPrivacySettings type rather than being merged into PrivacySettings — two same-named GraphQL types previously got silently field-unioned by @graphql-tools/merge when a duplicate PrivacySettingsInput existed, corrupting the schema (see GraphQL Architecture). Every new type introduced follows the "own brand-new type name" rule to avoid repeating that bug.
type ExtendedPrivacySettings {
whoCanMention: String!
requireApprovalForTags: Boolean!
friendsActivityVisibility: String! # "followers_you_follow_back" | "no_one"
hideLikeAndShareCounts: Boolean!
suggestPostsToNonFollowers: Boolean! # renamed from a mislabeled "previewsEnabled" — see note below
locationHistory: Boolean!
allowPersonalization: Boolean!
hideNotificationPreviews: Boolean!
autoDeleteMessages: Boolean!
messageDeletionDays: Int!
hideUnwantedMessageRequests: Boolean!
storyRepliesFrom: String! # "followers_you_follow_back" | "no_one"
readReceiptsEnabled: Boolean!
nudityProtectionEnabled: Boolean!
}
query ExtendedPrivacySettings { extendedPrivacySettings { whoCanMention requireApprovalForTags suggestPostsToNonFollowers storyRepliesFrom readReceiptsEnabled nudityProtectionEnabled } }
mutation UpdateExtendedPrivacySettings($input: ExtendedPrivacySettingsInput!) { updateExtendedPrivacySettings(input: $input) { suggestPostsToNonFollowers } }
"Previews" naming fix: an earlier pass added a
previewsEnabledfield meaning "suggest your posts to non-followers" and labeled it "Previews" — that's not what Instagram's real "Previews" setting means. It's been renamed tosuggestPostsToNonFollowers. The real "Previews" toggle (show message-content previews in notifications) isExtendedNotificationSettings.previewsEnabled, backed by anotification_previewfield that already existed in the notification manager but was never exposed until now.
Story privacy — hidden users
query StoryHiddenUsers { storyHiddenUsers { id username profilePicture } }
mutation UpdateStoryHiddenUsers($userIds: [ID!]!) { updateStoryHiddenUsers(userIds: $userIds) { id username } }
Media quality
Replaces the old single mediaQuality string enum with three independent booleans:
type MediaPreferences {
dataSaverCellular: Boolean! # reduce quality on cellular
uploadQualityHigh: Boolean! # upload in high vs. standard quality
hdrEnabled: Boolean! # upload/display HDR where supported
}
query MediaPreferences { mediaPreferences { dataSaverCellular uploadQualityHigh hdrEnabled } }
mutation UpdateMediaPreferences($input: MediaPreferencesInput!) { updateMediaPreferences(input: $input) { dataSaverCellular } }
Sharing, Hidden Words, Followers & invites
# Sharing — resharing this account's *posts* into other users' Stories
# (distinct from PrivacySettings.allowStorySharing, which governs resharing this account's *Stories*)
query SharingSettings { sharingSettings { allowResharingToStories } }
mutation UpdateSharingSettings($input: SharingSettingsInput!) { updateSharingSettings(input: $input) { allowResharingToStories } }
# Hidden Words — free-form word/phrase list, filters comments and message requests
query HiddenWords { hiddenWords }
mutation UpdateHiddenWords($words: [String!]!) { updateHiddenWords(words: $words) }
# Followers and invites
query FollowersInvitesSettings { followersInvitesSettings { autoConfirmFollowers } }
mutation UpdateFollowersInvitesSettings($input: FollowersInvitesSettingsInput!) { updateFollowersInvitesSettings(input: $input) { autoConfirmFollowers } }
Account standing
A moderation-standing summary, deliberately a different query from the pre-existing accountStatus (which reports isActive/isDeactivated/isDeleted lifecycle state, used by Account Management). restrictionLevel is derived from two sources: suspended/banned come from the real user.accountStatus field (set by suspendUser/banUser), while limited comes from the separate, mostly-dead user.isRestricted/restrictedUntil columns — this doc previously attributed the whole query to isRestricted/restrictedUntil alone; corrected. reason and suspendedUntil were added this pass (only populated while actually suspended/banned) to power the restriction-appeal flow — see Verification → Restriction appeals.
type AccountStandingStatus {
hasViolations: Boolean!
restrictionLevel: String! # "none" | "limited" | "suspended" | "banned"
isEligibleForMonetization: Boolean! # moderation-standing signal only, not full payout eligibility
recentStrikes: Int!
lastActionAt: DateTime
reason: String # suspension/ban reason - only populated while suspended/banned
suspendedUntil: DateTime # only populated while suspended/banned
}
query AccountStandingStatus {
accountStandingStatus { hasViolations restrictionLevel recentStrikes lastActionAt reason suspendedUntil }
}
AccountStatusPage.tsx also queries myLatestAppeal alongside accountStandingStatus in the same request, so it can render the right appeal state (form / pending / denied) without a second round-trip — see Verification → Restriction appeals for that query and the admin review side.
Notifications
See Notifications for the base notificationSettings API. The Settings hub also exposes a much larger ExtendedNotificationSettings surface covering per-category granularity (stories, live, follows, mentions, tags, messages, groups, reactions, friend activity, birthdays, suggestions, trending, post/story interactions), quiet hours, a one-tap "Pause all" / "Messages only" mode, and the real "Previews" toggle:
query ExtendedNotificationSettings {
extendedNotificationSettings {
pauseAll messagesOnlyMode
quietHoursEnabled quietHoursStart quietHoursEnd
notifyFollows notifyMentions notifyTags notifyMessages notifyGroupMessages
notifyStoryViews notifyStoryLikes notifyFriendActivity notifyLiveVideos
likesNotificationScope taggedPhotoNotificationScope
previewsEnabled
}
}
mutation UpdateExtendedNotificationSettings($input: ExtendedNotificationSettingsInput!) {
updateExtendedNotificationSettings(input: $input) { pauseAll previewsEnabled }
}
Language & Reels translations
The language selector (i18next, EN/ES) is unchanged. Reels translation preferences are new and stored separately (JSONB settings.reels_translation, not tied to the app UI language):
type ReelsTranslationSettings {
reelsAutoTranslateText: Boolean!
reelsAutoTranslateVoice: Boolean!
reelsPreferredLanguages: [String!]!
}
query ReelsTranslationSettings { reelsTranslationSettings { reelsAutoTranslateText reelsAutoTranslateVoice reelsPreferredLanguages } }
mutation UpdateReelsTranslationSettings($input: ReelsTranslationSettingsInput!) { updateReelsTranslationSettings(input: $input) { reelsAutoTranslateText } }
The language selector persists the app-UI language choice to localStorage and applies instantly. Supported: English (en), Spanish (es).
Time management
type TimeManagementSettings { dailyLimitMinutes: Int sleepModeEnabled: Boolean! sleepModeStart: String! sleepModeEnd: String! }
type UsageStats { days: Int! dailyAverageSeconds: Int! todaySeconds: Int! series: [UsageDayEntry!]! }
query TimeManagementSettings { timeManagementSettings { dailyLimitMinutes sleepModeEnabled sleepModeStart sleepModeEnd } }
query UsageStats($days: Int) { usageStats(days: $days) { dailyAverageSeconds todaySeconds series { date totalSeconds } } }
mutation UpdateTimeManagementSettings($input: TimeManagementSettingsInput!) { updateTimeManagementSettings(input: $input) { dailyLimitMinutes } }
mutation RecordUsageHeartbeat($seconds: Int!) { recordUsageHeartbeat(seconds: $seconds) }
recordUsageHeartbeat is called every 30 seconds by components/UsageHeartbeat.tsx, mounted inside Navigation.tsx while the tab is visible and the user is authenticated — this is what powers the daily usage chart (rendered with Recharts).
Archive & Recently Deleted
Reuses the existing Post GraphQL type — Stories and Instants are just Post rows with type: "story" / type: "clip", and archiving is a boolean flag (isArchived) rather than a separate model.
query ArchivedPosts($type: String, $limit: Int, $offset: Int) { archivedPosts(type: $type, limit: $limit, offset: $offset) { id type createdAt } }
query RecentlyDeletedPosts($limit: Int, $offset: Int) { recentlyDeletedPosts(limit: $limit, offset: $offset) { id deletedAt } }
mutation ArchivePost($postId: ID!) { archivePost(postId: $postId) { id isArchived } }
mutation UnarchivePost($postId: ID!) { unarchivePost(postId: $postId) { id isArchived } }
mutation RestorePost($postId: ID!) { restorePost(postId: $postId) { id deletedAt } }
mutation PermanentlyDeletePost($postId: ID!){ permanentlyDeletePost(postId: $postId) }
recentlyDeletedPosts relies on Post's Sequelize paranoid: true soft-delete — posts stay recoverable for 30 days before permanent deletion. Post.deletedAt is exposed on the GraphQL type specifically to power the "N days left" countdown on this screen.
Your activity
query MyLikedPosts($limit: Int, $offset: Int) { myLikedPosts(limit: $limit, offset: $offset) { id } }
query MyComments($limit: Int, $offset: Int) { myComments(limit: $limit, offset: $offset) { id text } }
query MyReposts($limit: Int, $offset: Int) { myReposts(limit: $limit, offset: $offset) { id } }
query WatchHistory($limit: Int, $offset: Int) { watchHistory(limit: $limit, offset: $offset) { id post { id } viewedAt } }
mutation RecordPostView($postId: ID!) { recordPostView(postId: $postId) }
mutation ClearWatchHistory { clearWatchHistory }
query SearchHistory($limit: Int) { searchHistory(limit: $limit) { id query createdAt } }
mutation RecordSearch($query: String!, $targetType: String, $targetId: ID) { recordSearch(query: $query, targetType: $targetType, targetId: $targetId) { id } }
mutation DeleteSearchHistoryItem($id: ID!) { deleteSearchHistoryItem(id: $id) }
mutation ClearSearchHistory { clearSearchHistory }
query LinkHistory($limit: Int) { linkHistory(limit: $limit) { id url createdAt } }
mutation RecordLinkClick($url: String!, $sourceType: String, $sourceId: ID) { recordLinkClick(url: $url, sourceType: $sourceType, sourceId: $sourceId) { id } }
mutation ClearLinkHistory { clearLinkHistory }
query NotInterestedContent($limit: Int, $offset: Int) { notInterestedContent(limit: $limit, offset: $offset) { id targetType targetId } }
mutation SetContentPreference($targetType: String!, $targetId: ID!, $preference: String!) { setContentPreference(targetType: $targetType, targetId: $targetId, preference: $preference) { id } }
mutation RemoveContentPreference($targetType: String!, $targetId: ID!) { removeContentPreference(targetType: $targetType, targetId: $targetId) }
"Sticker responses" and "Reviews" (two rows Instagram shows under Interactions) have no backing model anywhere in the codebase and are intentionally not exposed — the frontend shows an honest "not available" state instead of faking data.
Tags
Distinct from Tags & mentions (privacy controls for who can tag you). This screen shows the actual tagged-content grid:
query PendingTagReviews { pendingTagReviews { id user { username } media { url } positionX positionY } }
mutation ApprovePostTag($mentionId: ID!) { approvePostTag(mentionId: $mentionId) }
mutation RejectPostTag($mentionId: ID!) { rejectPostTag(mentionId: $mentionId) }
query TaggedPosts($userId: ID!, $limit: Int, $offset: Int) { taggedPosts(userId: $userId, limit: $limit, offset: $offset) { id } }
approvePostTag/rejectPostTag are deliberately not named approveTag/rejectTag — those names are already taken by an older, unrelated, never-finished "generic tag permissions" mutation pair on the same Mutation type. PostMention rows carry a status (approved | pending | rejected); only approved mentions appear on taggedPosts.
Stub screens
These screens are UI-only — no GraphQL type, resolver, or manager exists for them anywhere in the backend. They render honestly (visible but disabled, or clearly labeled "coming soon") rather than faking working state:
| Screen | Route |
|---|---|
| Family Center | /settings/family-center |
Restricted accounts, Limit interactions, Crossposting, and Insights & tools were previously stubs but are now fully wired — see the checklist above.
Frontend components
All components live under page-components/settings/ unless noted.
| Component | Backend | Notes |
|---|---|---|
EditProfilePage | ✅ | |
AccountPrivacyPage | ✅ | Base + Extended privacy settings |
TagsAndMentionsPage | ✅ | |
TagReviewPage | ✅ | |
TagsScreenPage | ✅ | Tagged grid + Pending tags tabs |
FriendsActivityPage | ✅ | |
FollowersAndInvitesPage | ✅ (partial) | Auto-confirm is real; invite channels beyond "share link" are "coming soon" |
CloseFriendsPage | ✅ | |
FavoritesPage | ✅ | Wired to favoriteUsers/addToFavorites/etc., backed by the real UserFavorite model (user_favorites table) — getFavoritesStats's mutualFavorites/recentAdditions/mostFavoritedUser are still hardcoded placeholders, see Favorites |
BlockedPage | ✅ | |
RestrictedAccountsPage | ✅ | Wired to restrictedAccounts/unrestrictAccount |
LimitInteractionsPage | ✅ | Wired to myInteractionLimit/setInteractionLimit |
MutedPage | ✅ | |
HiddenWordsPage | ✅ | |
StoryLivePage | ✅ | Includes "Hide story from" list (storyHiddenUsers) |
AccountStatusPage | ✅ | |
AccountManagementPage | ✅ | |
MessagesAndStoryRepliesPage | — | Hub, no query of its own |
MessageRequestsPage | ✅ | |
StoryRepliesPage | ✅ | |
ReadReceiptsPage | ✅ | |
NudityProtectionPage | ✅ | |
NotificationsSettingsPage | ✅ | |
NotificationCategoryPage | ✅ | |
SleepModePage | ✅ | |
TimeManagementPage | ✅ | |
ArchivePage | ✅ | |
RecentlyDeletedPage | ✅ | |
YourActivityPage | — | Hub |
InteractionsPage | ✅ | |
WatchHistoryPage | ✅ | |
RecentSearchesPage | ✅ | |
LinkHistoryPage | ✅ | |
SuggestedContentPage | ✅ | |
LanguagePage | ✅ | Includes Reels translations |
ContentPreferencesPage | — | Hub, links to other pages |
AccessibilityPage | device-local | localStorage, not backend-synced |
CommentsPage | ✅ | |
ShareSettingsPage | ✅ | |
CrosspostingPage | ✅ | Wired to crossposting API; per-provider connect gated on server OAuth env |
DownloadDataPage | ✅ | |
DevicePermissionsPage | browser APIs | Camera/Mic/Location/Notifications via Permissions API; Contacts/Photos informational only |
AppAndWebsitePermissionsPage | ✅ | |
FamilyCenterPage | ❌ stub | |
InsightsAndToolsPage | ✅ | Creator earnings/subscriber analytics, not supervised-account tooling — wired to myPostPurchaseEarnings/subscriberCount/subscriberRetention |
SessionsSettingsPage | ✅ | |
SecuritySettingsPage | ✅ | |
VerificationRequestPage | ✅ | |
SubscriptionsSettingsPage | ✅ | |
MoreInfoSupportPage | — | Static links |
AboutPage | — | Static |
components/LanguageSelector.tsx | ✅ | Language switcher |
components/ThemeToggle.tsx | device-local | Dark / Light mode |
components/UsageHeartbeat.tsx | ✅ | Powers Time management usage stats |
Discovery restrictions
hideFromSuggestions removes the user from "People you may know" / "Suggested users" lists.
mutation HideFromSuggestions($enabled: Boolean!) { hideFromSuggestions(enabled: $enabled) }
Privacy recommendations
getPrivacyRecommendations returns a ranked list of suggested improvements (e.g. "Enable 2FA," "Set account to private"), each with an isApplied flag.
query PrivacyRecommendations {
getPrivacyRecommendations { id recommendationType title description priority isApplied }
}