The Terms Setup feature uses a single database table ip_terms to store IP licensing terms. The schema is designed to support multiple term types per account, soft deletion, audit trails, and version tracking.
The ip_terms table stores all IP licensing terms for accounts.
Location: packages/db/src/schema/ipTerms.ts
The table includes the following columns:
id (text, primary key): Unique identifier for the term record. Generated using nanoid() function.accountId (text, not null): References accountTable.id. Links the term to the account (seller) that owns it.ipTermType (enum, not null): Type of IP term. Must be one of:
"VoiceOver" - Voice recordings and audio content"Likeness" - Person's likeness or appearance"Image" - Images and photographscompensationType (varchar(255), nullable): Type of compensation model. Must be a valid value from the compensationTypes enum if provided.compensationValue (decimal(13, 2), nullable): Compensation amount. Stored as decimal string for precise currency handling. Must be non-negative if provided.approvalType (varchar(255), nullable): Type of approval required. Must be a valid value from the approvalTypes enum if provided.ipApprove (boolean, not null, default: false): Whether the term has been approved. Must be explicitly set to true when approved.permittedUsage (text, nullable): Free-text field describing permitted usage of the IP.additionalRestrictions (text, nullable): Free-text field for additional restrictions on IP usage.additionalShareData (text, nullable): Free-text field for additional data sharing requirements.termsVersion (integer, not null, default: 1): Version of the legal terms template used when creating the term. Tracks which version of the legal template was used for compliance purposes.createdBy (text, not null): User ID who created the term.updatedBy (text, nullable): User ID who last updated the term.createdAt (timestamp with timezone, not null, default: now): When the term was created.updatedAt (timestamp with timezone, not null, default: now): When the term was last updated. Explicitly set on every update (not auto-updated by Drizzle).deletedAt (timestamp with timezone, nullable): When the term was soft-deleted. NULL means the term is active. Non-NULL means the term is deleted but preserved for audit.Constraint Name: unique_active_account_ip_term_type
Definition: Unique index on [accountId, ipTermType] where deletedAt IS NULL
Purpose:
ipTermTypeBehavior:
Constraint: accountId references accountTable.id
Purpose: Ensures referential integrity. A term cannot exist without a valid account.
Behavior:
accountIdThe compensationValue field is stored as decimal(13, 2):
priceUSDollar field in formatted responseImportant: Always use priceUSDollar from the formatted response for numeric operations, not ipTerms.compensationValue directly (which is a string).
The ipTermType field uses a PostgreSQL enum type:
ip_terms_ip_term_type"VoiceOver", "Likeness", "Image"The schema implements soft deletion:
deletedAt IS NULL means the term is activedeletedAt IS NOT NULL means the term is soft-deletedAll database access functions automatically filter out soft-deleted records:
getIpTermsById() - Only returns records where deletedAt IS NULLgetIpTermsByAccountAndType() - Only returns records where deletedAt IS NULLlistIpTermsByAccountId() - Only returns records where deletedAt IS NULLWhen soft-deleting a term:
deletedAt to current timestampupdatedBy to user ID who performed deletionupdatedAt to current timestampAfter soft-deleting a term, you can create a new one of the same type because:
deletedAt IS NULLThe schema provides complete audit trail capabilities:
createdBy: Who created the termcreatedAt: When it was createdupdatedBy: Who last modified the term (set on every update)updatedAt: When it was last modified (explicitly set, not auto-updated)deletedAt: When it was soft-deletedupdatedBy: Who performed the deletion (set during soft delete)Consider creating an ip_terms_audit table for detailed change history:
Important: Never access the ip_terms table directly. Always use the access functions in packages/db/src/access/ipTerms.ts:
getIpTermsById(id: string): Get term by ID (excludes soft-deleted)getIpTermsByAccountAndType(accountId: string, ipTermType: IpTermType): Get term by account and type (excludes soft-deleted)listIpTermsByAccountId(accountId: string): List all terms for account (excludes soft-deleted, ordered by ipTermType)createIpTerms(data: NewIpTerms): Create new termupdateIpTerms(id: string, data: Partial<UpdateIpTerms>): Update existing term (always sets updatedAt)softDeleteIpTerms(id: string, updatedBy: string): Soft-delete term (sets deletedAt, updatedBy, updatedAt)@zooly/typesThe schema types are defined in @zooly/types:
The main type inferred from the schema:
interface IpTerms {
id: string;
accountId: string;
ipTermType: "VoiceOver" | "Likeness" | "Image";
compensationType: string | null;
compensationValue: string | null; // Decimal stored as string
approvalType: string | null;
additionalRestrictions: string | null;
additionalShareData: string | null;
permittedUsage: string | null;
ipApprove: boolean;
termsVersion: number;
createdBy: string;
updatedBy: string | null;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
Type for creating new terms (omits auto-generated fields):
type NewIpTerms = Omit<IpTerms, "id" | "createdAt" | "updatedAt" | "deletedAt">;
Type for updating terms (partial with explicit update fields):
type UpdateIpTerms = Partial<Omit<IpTerms, "id" | "accountId" | "createdAt" | "createdBy">> & {
updatedBy: string;
updatedAt: Date;
};
The schema is managed using Drizzle ORM migrations:
npm run db:generate in packages/dbpackages/db/drizzle/npm run db:migrate or npm run db:pushThe migration will create:
ip_terms_ip_term_type enum typeip_terms table with all columnsupdatedBy when updatingcompensationValue string to number using formatted responseOn This Page
OverviewTable: ip_termsSchema DefinitionPrimary KeyForeign KeysTerm TypeCompensation FieldsApproval FieldsUsage and RestrictionsVersioningAudit FieldsSoft DeleteConstraintsPartial Unique IndexForeign Key ConstraintData TypesDecimal StorageEnum TypesSoft Delete PatternHow It WorksQuery FilteringSoft Delete OperationRecreation After DeletionAudit TrailCreation TrackingModification TrackingDeletion TrackingFuture Enhancement: Audit TableAccess LayerAvailable FunctionsWhy Use Access Layer?Type DefinitionsIpTermsNewIpTermsUpdateIpTermsMigrationBest Practices