Convex Invite-links
Membership and invite management for Convex — shareable invite links with expiration, email locking, and group access control.
git clone https://github.com/TimpiaAI/convex-invite-links.git
cd convex-invite-linksThis upstream package has not been adapted or qualified for Bijection. Read the original documentation before adopting it.
Description
Membership and invite management for Convex — shareable invite links with expiration, email locking, and group access control.
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: TimpiaAI/convex-invite-links. License: See repository. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
Convex component for membership and invite management — create shareable invite links with expiration, email locking, group/resource access control, and full lifecycle tracking
Explore the demo ↗Upstream overview
From the upstream package documentation.
A Convex component that provides membership tracking and invite-based access control through shareable links. It handles group-level and resource-level permissions, invite lifecycle management with expiration and email locking, and includes HTTP endpoints for social preview generation. The component offers both direct membership management and invite-flow onboarding with comprehensive validation and cleanup utilities.
Use cases
From the upstream package documentation.
- Team onboarding workflows where you need invite links that grant group membership with role-based access and email validation
- Document or resource sharing that requires time-limited access control with revocation capabilities
- Multi-tenant applications managing user permissions across different groups or organizations with invite tracking
- Collaborative platforms needing automated membership management with social preview links for better user experience
- Applications with approval flows where administrators create invites for specific users with predefined access levels
How it works
From the upstream package documentation.
The component registers with your Convex app and exposes membership APIs like isMember(), hasAccess(), and getAllGroups() for querying permissions, plus mutation functions like addMember() and grantAccess() for direct access management. Invite creation happens through makeInvite() which generates tokens with optional email locks, expiration dates, and metadata, while claimInvite() validates and applies the invite to grant appropriate access.
HTTP integration is handled by handleInviteWebhook() which processes GET requests to invite URLs and returns HTML with Open Graph metadata for social previews. The component includes utility functions like makeInviteUrl() for generating shareable links and inviteFromUrl() for parsing tokens from requests.
Invite management includes revokeInvite() for immediate invalidation, listInvites() with filtering options, and deleteOldInvites() for scheduled cleanup through Convex cron jobs. The system tracks invite status (pending, claimed, expired, revoked) and provides detailed validation results during the claiming process.
Upstream package FAQ
Can I restrict invites to specific email addresses?
Yes, convex-invite-links supports email-locked invites. When creating an invite with makeInvite(), pass an email parameter to restrict claiming to that specific email address. The claimInvite() function will validate the email match before granting access.
How do I check if a user has access to a resource?
Use the hasAccess() query function from convex-invite-links to check resource-level permissions, or isMember() to verify group membership. Both functions take a userId and return boolean results for authorization checks in your Convex queries and mutations.
Does this component handle invite URL generation automatically?
The convex-invite-links component provides makeInviteUrl() to generate shareable URLs from invite tokens, and inviteFromUrl() to parse tokens from incoming requests. You configure the base URL during initialization and the component handles token generation and parsing.
Can I add users to groups without sending invites?
Yes, convex-invite-links includes direct membership management with addMember() and grantAccess() functions. These bypass the invite process and immediately add users to groups or grant resource access, useful for admin actions or programmatic membership changes.
How do I clean up old expired invites?
Use the deleteOldInvites() mutation from convex-invite-links in a scheduled Convex cron job. Pass an olderThanMs parameter to specify the age threshold, and the component will remove expired, claimed, and revoked invites older than the specified time period.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Convex Invite-links with Bijection.
Read: https://bijection.com/components/convex-invite-links/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from TimpiaAI/convex-invite-links, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
convex-invite-links
A Convex component for membership and invite management — create shareable invite links with expiration, email locking, group/resource access control, and full lifecycle tracking.
Built for apps that need invite-based onboarding, team management, resource sharing, and access control.
Convex Components Challenge — Invite Links: Membership Tracking, Invite Creation, Claiming, and HTTP Integration
Features
- Membership Tracking —
isMember(),hasAccess(),getAllGroups(),getAllMembers(),addMember(),grantAccess() - Generic Access Control — Group-level and resource-level access, configurable per invite
- Invite Creation —
makeInvite()with optional email/phone lock, expiration, destination URL, and metadata - Configurable Defaults — Set default expiration at component initialization
- Invite Revocation —
revokeInvite()to immediately invalidate by ID or token - Invite Listing —
listInvites()with filters for group, resource, user, and status - Invite Cleanup —
deleteOldInvites()for scheduled garbage collection - Invite Claiming —
claimInvite()validates token, checks recipient lock, auto-grants access - Pending Invite Check —
hasInvite()to check for unclaimed invites - HTTP Integration —
handleInviteWebhookfor direct invite URL visits with social previews - URL Helpers —
makeInviteUrl()andinviteFromUrl()for link generation and parsing - Social Previews —
og:title,og:descriptionmetadata on invite pages - TypeScript — Fully typed with exported interfaces for all result types
Installation
npm install convex-invite-links
Setup
1. Register the component
// convex/convex.config.ts
import { defineApp } from "convex/server";
import inviteLinks from "convex-invite-links/convex.config";
const app = defineApp();
app.use(inviteLinks);
export default app;
2. Initialize the client
// convex/invites.ts
import { InviteLinks } from "convex-invite-links";
import { components } from "./_generated/api.js";
const invites = new InviteLinks(components.inviteLinks, {
defaultExpiryMs: 7 * 24 * 60 * 60 * 1000, // 7 days
baseUrl: "https://myapp.com/invite",
});
Usage
Create an invite
import { mutation } from "./_generated/server.js";
import { v } from "convex/values";
export const createInvite = mutation({
args: { email: v.optional(v.string()), groupId: v.string() },
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
const result = await invites.makeInvite(ctx, {
email: args.email,
groupId: args.groupId,
createdBy: userId,
destinationUrl: "https://myapp.com/dashboard",
metadata: { role: "member" },
ogTitle: "Join My Team",
ogDescription: "You've been invited to collaborate!",
});
// Generate a shareable URL
const url = invites.makeInviteUrl(result.token);
return { ...result, url };
},
});
Claim an invite
export const acceptInvite = mutation({
args: { token: v.string() },
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
const userEmail = await getUserEmail(ctx);
const result = await invites.claimInvite(ctx, {
token: args.token,
userId,
email: userEmail,
});
if (!result.ok) {
throw new Error(`Invite invalid: ${result.reason}`);
// reason: "invalid" | "expired" | "revoked" | "already_claimed"
// | "email_mismatch" | "phone_mismatch"
}
return {
destinationUrl: result.destinationUrl,
groupId: result.groupId,
};
},
});
Check membership
export const checkAccess = query({
args: { groupId: v.string() },
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
const isMember = await invites.isMember(ctx, {
userId,
groupId: args.groupId,
});
return { isMember };
},
});
Direct membership management
// Add a user to a group directly (without invite)
await invites.addMember(ctx, {
userId: targetUserId,
groupId: "team_123",
addedBy: adminUserId,
});
// Grant resource access directly
await invites.grantAccess(ctx, {
userId: targetUserId,
resourceId: "doc_456",
grantedBy: adminUserId,
});
// Remove membership or access
await invites.removeMember(ctx, { userId, groupId: "team_123" });
await invites.revokeAccess(ctx, { userId, resourceId: "doc_456" });
Revoke and manage invites
// Revoke by ID
await invites.revokeInvite(ctx, { inviteId: "invite_id_here" });
// Revoke by token
await invites.revokeInviteByToken(ctx, { token: "inv_abc123..." });
// List pending invites for a group
const pending = await invites.listInvites(ctx, {
groupId: "team_123",
});
// List all invites including claimed/expired
const all = await invites.listInvites(ctx, {
groupId: "team_123",
includeClaimed: true,
includeRevoked: true,
includeExpired: true,
});
Schedule automatic cleanup
// In a cron job (convex/crons.ts)
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api.js";
const crons = cronJobs();
crons.daily("cleanup invites", { hourUTC: 3, minuteUTC: 0 }, internal.invites.cleanupOld);
export default crons;
// In your invites file
export const cleanupOld = internalMutation({
handler: async (ctx) => {
const deleted = await invites.deleteOldInvites(ctx, {
olderThanMs: 30 * 24 * 60 * 60 * 1000,
});
console.log(`Cleaned up ${deleted} old invites`);
},
});
HTTP integration with social previews
import { handleInviteWebhook } from "convex-invite-links";
import { httpRouter } from "convex/server";
const http = httpRouter();
const inviteHandler = handleInviteWebhook(components.inviteLinks, {
appName: "My App",
redirectUrl: "https://myapp.com/accept-invite",
});
http.route({
path: "/invite",
method: "GET",
handler: httpAction(async (ctx, request) => {
return await inviteHandler(ctx, request);
}),
});
export default http;
Parse invite tokens from URLs
// In an HTTP action or server function
const token = invites.inviteFromUrl(request);
if (token) {
const invite = await invites.getInviteByToken(ctx, { token });
if (invite?.isValid) {
// Show invite acceptance page
}
}
API Reference
Membership
| Method | Context | Description |
|---|---|---|
isMember(ctx, args) | query | Check if user is in a group |
hasAccess(ctx, args) | query | Check if user has resource access |
getAllGroups(ctx, args) | query | List groups for a user |
getAllMembers(ctx, args) | query | List members of a group |
addMember(ctx, args) | mutation | Add user to group |
removeMember(ctx, args) | mutation | Remove user from group |
grantAccess(ctx, args) | mutation | Grant resource access |
revokeAccess(ctx, args) | mutation | Revoke resource access |
Invite Management
| Method | Context | Description |
|---|---|---|
makeInvite(ctx, args) | mutation | Create invite with optional locks |
revokeInvite(ctx, args) | mutation | Revoke invite by ID |
revokeInviteByToken(ctx, args) | mutation | Revoke invite by token |
listInvites(ctx, args?) | query | List invites with filters |
deleteOldInvites(ctx, args?) | mutation | Cleanup old invites |
Invite Claiming
| Method | Context | Description |
|---|---|---|
claimInvite(ctx, args) | mutation | Validate and apply invite |
hasInvite(ctx, args) | query | Check for pending invites |
getInviteByToken(ctx, args) | query | Get invite details by token |
HTTP Integration
| Function | Description |
|---|---|
handleInviteWebhook(component, options) | HTTP handler with social preview metadata |
invites.makeInviteUrl(token) | Generate shareable invite URL |
invites.inviteFromUrl(request) | Parse token from URL/Request |
Group Management
| Method | Context | Description |
|---|---|---|
upsertGroup(ctx, args) | mutation | Create or update group |
getGroup(ctx, args) | query | Get group details |
Live Demo
Check out the live demo for a working example.
Author
Built and maintained by TimpiaAI.