How to Find Who Created a Stored Procedure in SQL Server (and Who Modified It)

SQL Server has no creator column.

Quick answer: to find who created a stored procedure in SQL Server, read the default trace with sys.fn_trace_gettable and check LoginName on event 46 (Object:Created). The catch is how long the trace keeps it.

I measured that, then logged changes two ways you can copy.

Test Setup and the Sample Procedures

Test setup: SQL Server 2025 17.0.1000.7 Standard Developer Edition, sqlcmd in VS Code. A SQL login, demo_dev, creates two procedures and alters one two seconds later:

CREATE LOGIN demo_dev WITH PASSWORD = N'<strong password>';
GO
USE proc_audit_demo;
CREATE USER demo_dev FOR LOGIN demo_dev;
ALTER ROLE db_ddladmin ADD MEMBER demo_dev;
CREATE TABLE dbo.Orders (OrderID int PRIMARY KEY,
    Customer nvarchar(16), Amount decimal(10,2));
INSERT INTO dbo.Orders VALUES
(1001, N'Megan Bowen', 245.50), (1002, N'Alex Wilber', 89.99);
GO
EXECUTE AS LOGIN = N'demo_dev';
EXEC (N'CREATE PROCEDURE dbo.usp_GetOrders AS
        SELECT OrderID, Amount FROM dbo.Orders;');
EXEC (N'CREATE PROCEDURE dbo.usp_CleanOrders AS
        DELETE dbo.Orders WHERE Amount < 0;');
WAITFOR DELAY '00:00:02';
EXEC (N'ALTER PROCEDURE dbo.usp_GetOrders AS
        SELECT OrderID, Customer, Amount FROM dbo.Orders;');
REVERT;

New? See how to create a stored procedure in SQL Server.

Stored Procedure Modified Date: create_date and modify_date

sys.procedures (or sys.objects WHERE type = 'P') stores when. Never who.

SET NOCOUNT ON;
SELECT CAST(name AS nvarchar(18)) AS ProcName,
       create_date, modify_date
FROM sys.procedures
ORDER BY modify_date DESC;

Output:

ProcName           create_date             modify_date            
------------------ ----------------------- -----------------------
usp_GetOrders      2026-10-28 13:26:52.390 2026-10-28 13:26:54.407
usp_CleanOrders    2026-10-28 13:26:52.393 2026-10-28 13:26:52.393
SQL Server stored procedure modified date with create_date and modify_date from sys.procedures
Only the altered procedure moved

What this tells me: the ALTER moved modify_date two seconds. One timestamp, no history.

How Do You Find Who Created or Modified a Stored Procedure?

The default trace logs Object:Created (46), Object:Altered (164) and Object:Deleted (47) with the login; ObjectType 8272 is a procedure:

SET NOCOUNT ON;
DECLARE @path nvarchar(260) =
    (SELECT path FROM sys.traces WHERE is_default = 1);
SELECT CAST(ObjectName AS nvarchar(18)) AS ProcName,
       IIF(EventClass = 46, 'Created', 'Altered') AS Action,
       CAST(LoginName AS nvarchar(9)) AS LoginName,
       StartTime
FROM sys.fn_trace_gettable(@path, DEFAULT)
WHERE EventClass IN (46, 164)
  AND EventSubClass = 1
  AND ObjectType = 8272
  AND DatabaseName = DB_NAME()
  AND StartTime >= (SELECT create_date FROM sys.databases
                    WHERE name = DB_NAME())
ORDER BY StartTime;

Output:

ProcName           Action  LoginName StartTime              
------------------ ------- --------- -----------------------
usp_GetOrders      Created demo_dev  2026-10-28 13:26:52.393
usp_CleanOrders    Created demo_dev  2026-10-28 13:26:52.393
usp_GetOrders      Altered demo_dev  2026-10-28 13:26:54.410
Find who created a stored procedure in SQL Server with sys.fn_trace_gettable reading the default trace
demo_dev created and altered them

There it is: demo_dev. Without EventSubClass = 1 (commit), every change showed up twice. The date filter hides my earlier runs.

In SSMS, the database’s Reports > Standard Reports > Schema Changes History shows the same data.

Why the Default Trace Forgets Old Procedures

sys.traces on my instance: five rollover files, 20 MB each. On a busy server that’s days, so last year’s procedure is gone. Microsoft also plans to remove the default trace.

Under EXECUTE AS, SessionLoginName shows the real login behind LoginName.

Log Who Modified a Procedure With a DDL Trigger

A database DDL trigger reads EVENTDATA() into a table that never rolls over:

SET QUOTED_IDENTIFIER ON;
GO
CREATE TABLE dbo.ProcChangeLog (
    EventTime datetime2(0) DEFAULT SYSDATETIME(),
    EventType sysname, ProcName nvarchar(260), LoginName sysname);
GRANT INSERT ON dbo.ProcChangeLog TO demo_dev;
GO
CREATE TRIGGER trg_LogProcChanges ON DATABASE
FOR CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @e xml = EVENTDATA();
    INSERT INTO dbo.ProcChangeLog (EventType, ProcName, LoginName)
    SELECT @e.value('(/EVENT_INSTANCE/EventType)[1]', 'sysname'),
           @e.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname') + N'.'
         + @e.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname'),
           @e.value('(/EVENT_INSTANCE/LoginName)[1]', 'sysname');
END;

demo_dev alters one procedure, creates a second and drops it:

SET NOCOUNT ON;
EXECUTE AS LOGIN = N'demo_dev';
EXEC (N'ALTER PROCEDURE dbo.usp_CleanOrders AS
        DELETE dbo.Orders WHERE Amount <= 0;');
EXEC (N'CREATE PROCEDURE dbo.usp_Temp AS SELECT 1 AS One;');
EXEC (N'DROP PROCEDURE dbo.usp_Temp;');
REVERT;
SELECT CONVERT(char(19), EventTime, 120) AS EventTime,
       CAST(EventType AS nvarchar(16)) AS EventType,
       CAST(ProcName AS nvarchar(21)) AS ProcName,
       CAST(LoginName AS nvarchar(9)) AS LoginName
FROM dbo.ProcChangeLog;

Output:

EventTime           EventType        ProcName              LoginName
------------------- ---------------- --------------------- ---------
2026-10-28 13:28:09 ALTER_PROCEDURE  dbo.usp_CleanOrders   demo_dev 
2026-10-28 13:28:09 CREATE_PROCEDURE dbo.usp_Temp          demo_dev 
2026-10-28 13:28:09 DROP_PROCEDURE   dbo.usp_Temp          demo_dev
SQL Server DDL trigger logging who modified a stored procedure with EVENTDATA LoginName
CREATE, ALTER and DROP logged

Three events, one login, kept for good.

Or Use SQL Server Audit (SCHEMA_OBJECT_CHANGE_GROUP)

Audit writes to a file outside the database, here the Log folder that also holds the SQL Server error log:

USE master;
CREATE SERVER AUDIT ProcAudit TO FILE (FILEPATH = N'C:\Program Files\Microsoft SQL Server\MSSQL17.MSSQLSERVER\MSSQL\Log\');
ALTER SERVER AUDIT ProcAudit WITH (STATE = ON);
GO
USE proc_audit_demo;
CREATE DATABASE AUDIT SPECIFICATION ProcChanges FOR SERVER AUDIT ProcAudit
    ADD (SCHEMA_OBJECT_CHANGE_GROUP) WITH (STATE = ON);
GO
EXECUTE AS LOGIN = N'demo_dev';
EXEC (N'ALTER PROCEDURE dbo.usp_GetOrders AS SELECT OrderID FROM dbo.Orders;');
REVERT;
WAITFOR DELAY '00:00:02';
SET NOCOUNT ON;
SELECT action_id, CAST(server_principal_name AS nvarchar(9)) AS LoginName
FROM sys.fn_get_audit_file((SELECT audit_file_path FROM
       sys.dm_server_audit_status WHERE name = N'ProcAudit'), DEFAULT, DEFAULT)
WHERE object_name = N'usp_GetOrders';

Output:

action_id LoginName
--------- ---------
AL        demo_dev

AL is ALTER. If you add event_time, it’s UTC.

Who Owns a Stored Procedure?

Owner isn’t creator. If you see NULL in principal_id means the schema owner owns it:

SET NOCOUNT ON;
SELECT CAST(name AS nvarchar(18)) AS ProcName, principal_id,
       CAST(USER_NAME(OBJECTPROPERTY(object_id, 'OwnerId'))
            AS nvarchar(9)) AS Owner
FROM sys.procedures;

Output:

ProcName           principal_id Owner    
------------------ ------------ ---------
usp_GetOrders              NULL dbo      
usp_CleanOrders            NULL dbo

demo_dev created both; dbo owns both. See SQL Server user permissions.

Last Execution Time of a Stored Procedure

sys.dm_exec_procedure_stats has one row per cached plan:

SET NOCOUNT ON;
EXEC dbo.usp_CleanOrders;
EXEC dbo.usp_CleanOrders;
SELECT CAST(OBJECT_NAME(object_id) AS nvarchar(18)) AS ProcName,
       last_execution_time, execution_count
FROM sys.dm_exec_procedure_stats
WHERE database_id = DB_ID();

EXEC sys.sp_recompile N'dbo.usp_CleanOrders';
SELECT COUNT(*) AS RowsLeft FROM sys.dm_exec_procedure_stats
WHERE database_id = DB_ID();

Output:

ProcName           last_execution_time     execution_count     
------------------ ----------------------- --------------------
usp_CleanOrders    2026-10-28 13:28:48.910                    2
Object 'dbo.usp_CleanOrders' was successfully marked for recompilation.
RowsLeft   
-----------
          0
SQL Server stored procedure last execution time from sys.dm_exec_procedure_stats, reset by sp_recompile
sp_recompile wiped the row

After sp_recompile the row was gone; an ALTER or restart does the same. No row doesn’t mean never ran, so log execution history if you need it.

DDL Trigger Errors That Tripped Me Up

My first trigger broke every ALTER with Msg 1934: sqlcmd sets QUOTED_IDENTIFIER OFF unless you pass -I, and xml methods need ON. Then, without the GRANT:

SET NOCOUNT ON;
EXECUTE AS LOGIN = N'demo_dev';
BEGIN TRY
    EXEC (N'ALTER PROCEDURE dbo.usp_GetOrders AS SELECT 1 AS Probe;');
END TRY
BEGIN CATCH
    PRINT CONCAT(N'Msg ', ERROR_NUMBER(), N': ', ERROR_MESSAGE());
END CATCH;
REVERT;

Output:

Msg 229: The INSERT permission was denied on the object 'ProcChangeLog', database 'proc_audit_demo', schema 'dbo'.

The trigger runs with the caller’s permissions, and its failure rolled back the ALTER PROCEDURE.

My Takeaways on Stored Procedure Auditing

  1. The default trace is the only built-in “who”, and it forgets.
  2. modify_date is when, not who.
  3. last_execution_time lives only while the plan is cached.

Azure SQL Database has no default trace; audit there.

Next stops for procedure cleanup:

Frequently Asked Questions

How do I list procedures modified in the last 7 days?

Add WHERE modify_date >= DATEADD(DAY, -7, GETDATE()).

Can I find who dropped a stored procedure?

Yes, while the trace has it: EventClass 47. See the sys.fn_trace_gettable reference.

What permission does the last execution time query need?

VIEW SERVER PERFORMANCE STATE (SQL Server 2022+), per the docs.